Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/vast-friends-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nostream": minor
---

feat: add NIP-42 types, schemas and constants
16 changes: 14 additions & 2 deletions src/@types/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ export enum MessageType {
OK = 'OK',
COUNT = 'COUNT',
CLOSED = 'CLOSED',
AUTH = 'AUTH',
}

export type IncomingMessage = (SubscribeMessage | IncomingEventMessage | UnsubscribeMessage | CountMessage) & {
export type IncomingMessage = (SubscribeMessage | IncomingEventMessage | UnsubscribeMessage | CountMessage | AuthMessage) & {
[ContextMetadataKey]?: ContextMetadata
}

export type OutgoingMessage = OutgoingEventMessage | EndOfStoredEventsNotice | NoticeMessage | CommandResult | CountResultMessage | ClosedMessage
export type OutgoingMessage = OutgoingEventMessage | EndOfStoredEventsNotice | NoticeMessage | CommandResult | CountResultMessage | ClosedMessage | AuthChallengeMessage

export type SubscribeMessage = {
[index in Range<2, 100>]: SubscriptionFilter
Expand Down Expand Up @@ -89,3 +90,14 @@ export interface ClosedMessage {
1: SubscriptionId
2: string
}

// NIP-42
export interface AuthMessage {
0: MessageType.AUTH
1: Event
}

export interface AuthChallengeMessage {
0: MessageType.AUTH
1: string
}
5 changes: 5 additions & 0 deletions src/constants/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export enum EventKinds {
REPLACEABLE_LAST = 19999,
// Ephemeral events
EPHEMERAL_FIRST = 20000,
// NIP-42: Client Authentication
AUTH = 22242,
EPHEMERAL_LAST = 29999,
// Parameterized replaceable events
PARAMETERIZED_REPLACEABLE_FIRST = 30000,
Expand Down Expand Up @@ -72,6 +74,9 @@ export enum EventTags {
Emoji = 'emoji',
// NIP-12: geohash tag for location-based queries
Geohash = 'g',
// NIP-42: Authentication tags
Challenge = 'challenge',
AuthRelay = 'relay',
// Marmot Protocol MIP-03: group ID for filtering kind:445 Group Events
Group = 'h',
}
Expand Down
5 changes: 4 additions & 1 deletion src/schemas/message-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,7 @@ export const countMessageSchema = z

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

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

export const messageSchema = z.union([eventMessageSchema, reqMessageSchema, closeMessageSchema, countMessageSchema, authMessageSchema])
6 changes: 6 additions & 0 deletions src/utils/messages.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AuthChallengeMessage,
ClosedMessage,
CountResultMessage,
CountResultPayload,
Expand Down Expand Up @@ -41,6 +42,11 @@ export const createClosedMessage = (queryId: SubscriptionId, reason: string): Cl
return [MessageType.CLOSED, queryId, reason]
}

// NIP-42
export const createAuthChallengeMessage = (challenge: string): AuthChallengeMessage => {
return [MessageType.AUTH, challenge]
}

export const createSubscriptionMessage = (
subscriptionId: SubscriptionId,
filters: SubscriptionFilter[],
Expand Down
37 changes: 37 additions & 0 deletions test/unit/schemas/message-schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,5 +167,42 @@ describe('NIP-01', () => {
expect(result).to.have.property('error').that.is.not.undefined
})
})

describe('AUTH', () => {
let events: Event[]
beforeEach(() => {
events = getEvents()
})

it('returns same message if valid', () => {
const event = events[0]
message = ['AUTH', event] as any

const result = validateSchema(messageSchema)(message)
expect(result.error).to.be.undefined
expect(result).to.have.deep.property('value', message)
})

it('returns error if event is missing', () => {
message = ['AUTH'] as any

const result = validateSchema(messageSchema)(message)
expect(result).to.have.property('error').that.is.not.undefined
})

it('returns error if event is not an object', () => {
message = ['AUTH', 'not-an-event'] as any

const result = validateSchema(messageSchema)(message)
expect(result).to.have.property('error').that.is.not.undefined
})

it('returns error if event is null', () => {
message = ['AUTH', null] as any

const result = validateSchema(messageSchema)(message)
expect(result).to.have.property('error').that.is.not.undefined
})
})
})
})
Loading