Skip to content

Commit ac60d31

Browse files
authored
Merge branch 'main' into m2
2 parents baefb72 + 6a0a5fa commit ac60d31

6 files changed

Lines changed: 71 additions & 3 deletions

File tree

.changeset/vast-friends-march.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"nostream": minor
3+
---
4+
5+
feat: add NIP-42 types, schemas and constants

src/@types/messages.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ export enum MessageType {
1212
OK = 'OK',
1313
COUNT = 'COUNT',
1414
CLOSED = 'CLOSED',
15+
AUTH = 'AUTH',
1516
}
1617

17-
export type IncomingMessage = (SubscribeMessage | IncomingEventMessage | UnsubscribeMessage | CountMessage) & {
18+
export type IncomingMessage = (SubscribeMessage | IncomingEventMessage | UnsubscribeMessage | CountMessage | AuthMessage) & {
1819
[ContextMetadataKey]?: ContextMetadata
1920
}
2021

21-
export type OutgoingMessage = OutgoingEventMessage | EndOfStoredEventsNotice | NoticeMessage | CommandResult | CountResultMessage | ClosedMessage
22+
export type OutgoingMessage = OutgoingEventMessage | EndOfStoredEventsNotice | NoticeMessage | CommandResult | CountResultMessage | ClosedMessage | AuthChallengeMessage
2223

2324
export type SubscribeMessage = {
2425
[index in Range<2, 100>]: SubscriptionFilter
@@ -89,3 +90,14 @@ export interface ClosedMessage {
8990
1: SubscriptionId
9091
2: string
9192
}
93+
94+
// NIP-42
95+
export interface AuthMessage {
96+
0: MessageType.AUTH
97+
1: Event
98+
}
99+
100+
export interface AuthChallengeMessage {
101+
0: MessageType.AUTH
102+
1: string
103+
}

src/constants/base.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export enum EventKinds {
4545
REPLACEABLE_LAST = 19999,
4646
// Ephemeral events
4747
EPHEMERAL_FIRST = 20000,
48+
// NIP-42: Client Authentication
49+
AUTH = 22242,
4850
EPHEMERAL_LAST = 29999,
4951
// Parameterized replaceable events
5052
PARAMETERIZED_REPLACEABLE_FIRST = 30000,
@@ -72,6 +74,9 @@ export enum EventTags {
7274
Emoji = 'emoji',
7375
// NIP-12: geohash tag for location-based queries
7476
Geohash = 'g',
77+
// NIP-42: Authentication tags
78+
Challenge = 'challenge',
79+
AuthRelay = 'relay',
7580
// Marmot Protocol MIP-03: group ID for filtering kind:445 Group Events
7681
Group = 'h',
7782
}

src/schemas/message-schema.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,7 @@ export const countMessageSchema = z
5555

5656
export const closeMessageSchema = z.tuple([z.literal(MessageType.CLOSE), subscriptionSchema])
5757

58-
export const messageSchema = z.union([eventMessageSchema, reqMessageSchema, closeMessageSchema, countMessageSchema])
58+
// NIP-42
59+
export const authMessageSchema = z.tuple([z.literal(MessageType.AUTH), eventSchema])
60+
61+
export const messageSchema = z.union([eventMessageSchema, reqMessageSchema, closeMessageSchema, countMessageSchema, authMessageSchema])

src/utils/messages.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
AuthChallengeMessage,
23
ClosedMessage,
34
CountResultMessage,
45
CountResultPayload,
@@ -41,6 +42,11 @@ export const createClosedMessage = (queryId: SubscriptionId, reason: string): Cl
4142
return [MessageType.CLOSED, queryId, reason]
4243
}
4344

45+
// NIP-42
46+
export const createAuthChallengeMessage = (challenge: string): AuthChallengeMessage => {
47+
return [MessageType.AUTH, challenge]
48+
}
49+
4450
export const createSubscriptionMessage = (
4551
subscriptionId: SubscriptionId,
4652
filters: SubscriptionFilter[],

test/unit/schemas/message-schema.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,5 +167,42 @@ describe('NIP-01', () => {
167167
expect(result).to.have.property('error').that.is.not.undefined
168168
})
169169
})
170+
171+
describe('AUTH', () => {
172+
let events: Event[]
173+
beforeEach(() => {
174+
events = getEvents()
175+
})
176+
177+
it('returns same message if valid', () => {
178+
const event = events[0]
179+
message = ['AUTH', event] as any
180+
181+
const result = validateSchema(messageSchema)(message)
182+
expect(result.error).to.be.undefined
183+
expect(result).to.have.deep.property('value', message)
184+
})
185+
186+
it('returns error if event is missing', () => {
187+
message = ['AUTH'] as any
188+
189+
const result = validateSchema(messageSchema)(message)
190+
expect(result).to.have.property('error').that.is.not.undefined
191+
})
192+
193+
it('returns error if event is not an object', () => {
194+
message = ['AUTH', 'not-an-event'] as any
195+
196+
const result = validateSchema(messageSchema)(message)
197+
expect(result).to.have.property('error').that.is.not.undefined
198+
})
199+
200+
it('returns error if event is null', () => {
201+
message = ['AUTH', null] as any
202+
203+
const result = validateSchema(messageSchema)(message)
204+
expect(result).to.have.property('error').that.is.not.undefined
205+
})
206+
})
170207
})
171208
})

0 commit comments

Comments
 (0)