@triformine/nexis-sdk 0.1.0-next.6d96464
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/cjs/client.js +746 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/codec.js +57 -0
- package/dist/cjs/codec.js.map +1 -0
- package/dist/cjs/index.js +24 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/patch.js +110 -0
- package/dist/cjs/patch.js.map +1 -0
- package/dist/cjs/rpc.js +69 -0
- package/dist/cjs/rpc.js.map +1 -0
- package/dist/cjs/types.js +6 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/client.js +719 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/codec.js +36 -0
- package/dist/esm/codec.js.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/patch.js +86 -0
- package/dist/esm/patch.js.map +1 -0
- package/dist/esm/rpc.js +51 -0
- package/dist/esm/rpc.js.map +1 -0
- package/dist/esm/types.js +3 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/types/client.d.ts +78 -0
- package/dist/types/codec.d.ts +19 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/patch.d.ts +5 -0
- package/dist/types/rpc.d.ts +14 -0
- package/dist/types/types.d.ts +75 -0
- package/package.json +53 -0
- package/src/client.ts +949 -0
- package/src/codec.ts +48 -0
- package/src/index.ts +5 -0
- package/src/patch.ts +128 -0
- package/src/rpc.ts +54 -0
- package/src/types.ts +82 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/client.ts"],"sourcesContent":["import {\n applyPatch,\n computeStateChecksum,\n parsePatchPayload,\n parseSnapshotPayload,\n} from \"./patch\";\nimport { JsonCodec, MsgpackCodec, codecFor, type Codec } from \"./codec\";\nimport { RpcClient, UnknownRidError } from \"./rpc\";\nimport type {\n ConnectOptions,\n Envelope,\n HandshakeRequest,\n MatchFound,\n MatchmakingDequeueResponse,\n MatchmakingQueueResponse,\n RoomListResponse,\n RoomMessagePayload,\n RoomMessageType,\n} from \"./types\";\n\nconst DEFAULT_VERSION = 1;\nconst DEFAULT_RECONNECT_INITIAL_DELAY_MS = 250;\nconst DEFAULT_RECONNECT_MAX_DELAY_MS = 3_000;\nconst DEFAULT_RECONNECT_MAX_ATTEMPTS = 20;\n\ntype EventHandler = (message: Envelope) => void;\ntype StateHandler = (state: Record<string, unknown>) => void;\ntype MatchFoundHandler = (match: MatchFound, message: Envelope) => void;\ntype RoomMessageHandler = (data: unknown, envelope: Envelope) => void;\ntype SelectorHandler = (value: unknown, state: Record<string, unknown>) => void;\ntype SupportedCodec = \"json\" | \"msgpack\";\n\ntype SelectorRegistration = {\n path: string;\n callback: SelectorHandler;\n lastValue: unknown;\n};\n\ntype ResolvedConnectOptions = ConnectOptions & {\n codecs: Array<\"msgpack\" | \"json\">;\n autoJoinMatchedRoom: boolean;\n autoReconnect: boolean;\n reconnectInitialDelayMs: number;\n reconnectMaxDelayMs: number;\n reconnectMaxAttempts: number;\n};\n\nexport type RoomStateChangeSubscription = {\n (callback: StateHandler): () => void;\n once(callback: StateHandler): () => void;\n select(path: string, callback: SelectorHandler): () => void;\n};\n\nfunction normalizeConnectOptions(\n options: ConnectOptions,\n): ResolvedConnectOptions {\n const reconnectInitialDelayMs = Math.max(\n 50,\n options.reconnectInitialDelayMs ?? DEFAULT_RECONNECT_INITIAL_DELAY_MS,\n );\n const reconnectMaxDelayMs = Math.max(\n reconnectInitialDelayMs,\n options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS,\n );\n const reconnectMaxAttempts = Math.max(\n 1,\n options.reconnectMaxAttempts ?? DEFAULT_RECONNECT_MAX_ATTEMPTS,\n );\n return {\n ...options,\n codecs: options.codecs ?? [\"msgpack\", \"json\"],\n autoJoinMatchedRoom: options.autoJoinMatchedRoom ?? false,\n autoReconnect: options.autoReconnect ?? true,\n reconnectInitialDelayMs,\n reconnectMaxDelayMs,\n reconnectMaxAttempts,\n };\n}\n\nfunction readCodecName(message: Envelope): SupportedCodec {\n const payload = message.p;\n if (\n payload &&\n typeof payload === \"object\" &&\n \"codec\" in payload &&\n (payload as { codec?: unknown }).codec === \"msgpack\"\n ) {\n return \"msgpack\";\n }\n return \"json\";\n}\n\nfunction readSessionId(message: Envelope): string | undefined {\n const payload = message.p;\n if (\n payload &&\n typeof payload === \"object\" &&\n \"session_id\" in payload &&\n typeof (payload as { session_id?: unknown }).session_id === \"string\"\n ) {\n return (payload as { session_id: string }).session_id;\n }\n return undefined;\n}\n\nfunction readErrorReason(message: Envelope): string {\n const payload = message.p;\n if (\n payload &&\n typeof payload === \"object\" &&\n \"reason\" in payload &&\n typeof (payload as { reason?: unknown }).reason === \"string\"\n ) {\n return (payload as { reason: string }).reason;\n }\n return \"server returned error\";\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return (\n Array.isArray(value) && value.every((item) => typeof item === \"string\")\n );\n}\n\nfunction deepEqual(left: unknown, right: unknown): boolean {\n return JSON.stringify(left) === JSON.stringify(right);\n}\n\nfunction toJsonValue(bytes: Uint8Array): string {\n let binary = \"\";\n for (const value of bytes) {\n binary += String.fromCharCode(value);\n }\n return btoa(binary);\n}\n\nfunction fromJsonValue(value: unknown): Uint8Array | null {\n if (typeof value !== \"string\") {\n return null;\n }\n\n try {\n const decoded = atob(value);\n const bytes = new Uint8Array(decoded.length);\n for (let i = 0; i < decoded.length; i += 1) {\n bytes[i] = decoded.charCodeAt(i);\n }\n return bytes;\n } catch {\n return null;\n }\n}\n\nfunction readRoomMessagePayload(payload: unknown): RoomMessagePayload | null {\n if (!payload || typeof payload !== \"object\") {\n return null;\n }\n const type = (payload as { type?: unknown }).type;\n if (typeof type !== \"string\" && typeof type !== \"number\") {\n return null;\n }\n const data = (payload as { data?: unknown }).data;\n return { type, data };\n}\n\nexport function parseMatchFoundPayload(payload: unknown): MatchFound | null {\n if (!payload || typeof payload !== \"object\") {\n return null;\n }\n\n const room = (payload as { room?: unknown }).room;\n const roomType = (payload as { room_type?: unknown }).room_type;\n const size = (payload as { size?: unknown }).size;\n const participants = (payload as { participants?: unknown }).participants;\n if (\n typeof room !== \"string\" ||\n typeof roomType !== \"string\" ||\n typeof size !== \"number\" ||\n !Number.isFinite(size) ||\n !isStringArray(participants)\n ) {\n return null;\n }\n\n return {\n room,\n roomType,\n size,\n participants,\n };\n}\n\nexport class NexisRoom {\n readonly id: string;\n readonly onStateChange: RoomStateChangeSubscription;\n\n constructor(\n private readonly client: NexisClient,\n roomId: string,\n ) {\n this.id = roomId;\n this.onStateChange = this.buildStateChangeSubscription();\n }\n\n get state(): Record<string, unknown> {\n return this.client.getRoomState(this.id);\n }\n\n send(type: RoomMessageType, message: unknown): void {\n this.client.sendRoomMessage(this.id, type, message);\n }\n\n sendBytes(type: RoomMessageType, bytes: Uint8Array | number[]): void {\n const normalized =\n bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes);\n this.client.sendRoomMessageBytes(this.id, type, normalized);\n }\n\n onMessage(type: RoomMessageType, callback: RoomMessageHandler): () => void {\n return this.client.onRoomMessage(this.id, type, callback);\n }\n\n private buildStateChangeSubscription(): RoomStateChangeSubscription {\n const subscribe = (callback: StateHandler): (() => void) =>\n this.client.onRoomState(this.id, callback);\n\n subscribe.once = (callback: StateHandler): (() => void) =>\n this.client.onRoomStateOnce(this.id, callback);\n\n subscribe.select = (\n path: string,\n callback: SelectorHandler,\n ): (() => void) => this.client.onRoomStateSelect(this.id, path, callback);\n\n return subscribe;\n }\n}\n\nexport class NexisClient {\n private socket: WebSocket;\n private readonly url: string;\n private readonly connectOptions: ResolvedConnectOptions;\n private readonly rpc = new RpcClient();\n private codec: Codec;\n private readonly eventHandlers = new Map<string, Set<EventHandler>>();\n private readonly stateHandlers = new Set<StateHandler>();\n private readonly roomStateHandlers = new Map<string, Set<StateHandler>>();\n private readonly roomStateSelectors = new Map<\n string,\n Set<SelectorRegistration>\n >();\n private readonly roomMessageHandlers = new Map<\n string,\n Map<string, Set<RoomMessageHandler>>\n >();\n private readonly roomStates = new Map<string, Record<string, unknown>>();\n private readonly roomSeq = new Map<string, number>();\n private readonly roomChecksum = new Map<string, string>();\n private sessionId: string | undefined;\n private readonly autoJoinMatchedRoom: boolean;\n private reconnecting = false;\n private disposed = false;\n\n private constructor(\n url: string,\n socket: WebSocket,\n codec: Codec,\n sessionId: string | undefined,\n options: ResolvedConnectOptions,\n ) {\n this.url = url;\n this.socket = socket;\n this.codec = codec;\n this.sessionId = sessionId;\n this.connectOptions = options;\n this.autoJoinMatchedRoom = options.autoJoinMatchedRoom;\n this.attachSocket(socket);\n }\n\n static connect(url: string, options: ConnectOptions): Promise<NexisClient> {\n const resolved = normalizeConnectOptions(options);\n return NexisClient.openSocketAndHandshake(\n url,\n resolved,\n resolved.sessionId,\n ).then(\n ({ socket, codec, sessionId }) =>\n new NexisClient(url, socket, codec, sessionId, resolved),\n );\n }\n\n close(): void {\n this.disposed = true;\n this.socket.close();\n }\n\n private attachSocket(socket: WebSocket): void {\n this.socket = socket;\n this.socket.addEventListener(\"message\", (event) => {\n void this.onRawMessage(event.data);\n });\n this.socket.addEventListener(\"close\", () => {\n this.rpc.rejectAll(new Error(\"socket closed\"));\n if (!this.disposed && this.connectOptions.autoReconnect) {\n void this.tryReconnect();\n }\n });\n }\n\n private async tryReconnect(): Promise<void> {\n if (this.reconnecting || this.disposed) {\n return;\n }\n this.reconnecting = true;\n this.dispatchEvent({\n v: DEFAULT_VERSION,\n t: \"reconnect.start\",\n p: { session_id: this.sessionId },\n });\n\n let attempt = 0;\n let delayMs = this.connectOptions.reconnectInitialDelayMs;\n while (\n !this.disposed &&\n attempt < this.connectOptions.reconnectMaxAttempts\n ) {\n attempt += 1;\n await NexisClient.wait(delayMs);\n try {\n const reconnect = await NexisClient.openSocketAndHandshake(\n this.url,\n this.connectOptions,\n this.sessionId,\n );\n this.codec = reconnect.codec;\n this.sessionId = reconnect.sessionId ?? this.sessionId;\n this.attachSocket(reconnect.socket);\n this.dispatchEvent({\n v: DEFAULT_VERSION,\n t: \"reconnect.ok\",\n p: { attempt, session_id: this.sessionId },\n });\n this.reconnecting = false;\n return;\n } catch {\n this.dispatchEvent({\n v: DEFAULT_VERSION,\n t: \"reconnect.retry\",\n p: { attempt },\n });\n }\n delayMs = Math.min(delayMs * 2, this.connectOptions.reconnectMaxDelayMs);\n }\n\n this.reconnecting = false;\n this.dispatchEvent({\n v: DEFAULT_VERSION,\n t: \"reconnect.failed\",\n p: { session_id: this.sessionId },\n });\n }\n\n getSessionId(): string | undefined {\n return this.sessionId;\n }\n\n room(roomId: string): NexisRoom {\n return new NexisRoom(this, roomId);\n }\n\n async joinOrCreate(\n roomType: string,\n options?: { roomId?: string } & Record<string, unknown>,\n ): Promise<NexisRoom> {\n const roomId =\n typeof options?.roomId === \"string\" ? options.roomId : undefined;\n const response = await this.sendRPC(\n \"room.join_or_create\",\n {\n roomType,\n roomId,\n options,\n },\n roomId,\n );\n if (\n response &&\n typeof response === \"object\" &&\n typeof (response as { room?: unknown }).room === \"string\"\n ) {\n return this.room((response as { room: string }).room);\n }\n if (roomId) {\n return this.room(roomId);\n }\n return this.room(`${roomType}:default`);\n }\n\n listRooms(roomType?: string): Promise<RoomListResponse> {\n return this.sendRPC(\n \"room.list\",\n roomType ? { roomType } : {},\n ) as Promise<RoomListResponse>;\n }\n\n enqueueMatchmaking(\n roomType: string,\n size = 2,\n ): Promise<MatchmakingQueueResponse> {\n return this.sendRPC(\"matchmaking.enqueue\", {\n roomType,\n size,\n }) as Promise<MatchmakingQueueResponse>;\n }\n\n dequeueMatchmaking(): Promise<MatchmakingDequeueResponse> {\n return this.sendRPC(\n \"matchmaking.dequeue\",\n {},\n ) as Promise<MatchmakingDequeueResponse>;\n }\n\n onStateChange(callback: StateHandler): () => void {\n this.stateHandlers.add(callback);\n return () => this.stateHandlers.delete(callback);\n }\n\n onEvent(type: string, callback: EventHandler): () => void {\n const handlers = this.eventHandlers.get(type) ?? new Set<EventHandler>();\n handlers.add(callback);\n this.eventHandlers.set(type, handlers);\n return () => {\n const current = this.eventHandlers.get(type);\n if (!current) {\n return;\n }\n current.delete(callback);\n if (current.size === 0) {\n this.eventHandlers.delete(type);\n }\n };\n }\n\n onMatchFound(callback: MatchFoundHandler): () => void {\n return this.onEvent(\"match.found\", (message) => {\n const parsed = parseMatchFoundPayload(message.p);\n if (!parsed) {\n return;\n }\n callback(parsed, message);\n });\n }\n\n sendRPC(type: string, payload: unknown, room?: string): Promise<unknown> {\n const { message, promise } = this.rpc.createRequest(type, payload, room);\n this.sendEnvelope(message);\n return promise;\n }\n\n getRoomState(roomId: string): Record<string, unknown> {\n return this.roomStates.get(roomId) ?? {};\n }\n\n sendRoomMessage(roomId: string, type: RoomMessageType, data: unknown): void {\n this.sendEnvelope({\n v: DEFAULT_VERSION,\n t: \"room.message\",\n room: roomId,\n p: { type: String(type), data },\n });\n }\n\n sendRoomMessageBytes(\n roomId: string,\n type: RoomMessageType,\n data: Uint8Array,\n ): void {\n this.sendEnvelope({\n v: DEFAULT_VERSION,\n t: \"room.message.bytes\",\n room: roomId,\n p: { type: String(type), data_b64: toJsonValue(data) },\n });\n }\n\n onRoomMessage(\n roomId: string,\n type: RoomMessageType,\n callback: RoomMessageHandler,\n ): () => void {\n const key = String(type);\n const byType = this.roomMessageHandlers.get(roomId) ?? new Map();\n const handlers = byType.get(key) ?? new Set<RoomMessageHandler>();\n handlers.add(callback);\n byType.set(key, handlers);\n this.roomMessageHandlers.set(roomId, byType);\n\n return () => {\n const roomHandlers = this.roomMessageHandlers.get(roomId);\n if (!roomHandlers) {\n return;\n }\n const typeHandlers = roomHandlers.get(key);\n if (!typeHandlers) {\n return;\n }\n typeHandlers.delete(callback);\n if (typeHandlers.size === 0) {\n roomHandlers.delete(key);\n }\n if (roomHandlers.size === 0) {\n this.roomMessageHandlers.delete(roomId);\n }\n };\n }\n\n onRoomState(roomId: string, callback: StateHandler): () => void {\n const handlers =\n this.roomStateHandlers.get(roomId) ?? new Set<StateHandler>();\n handlers.add(callback);\n this.roomStateHandlers.set(roomId, handlers);\n if (this.roomStates.has(roomId)) {\n callback(this.roomStates.get(roomId) ?? {});\n }\n return () => {\n const current = this.roomStateHandlers.get(roomId);\n if (!current) {\n return;\n }\n current.delete(callback);\n if (current.size === 0) {\n this.roomStateHandlers.delete(roomId);\n }\n };\n }\n\n onRoomStateOnce(roomId: string, callback: StateHandler): () => void {\n let disposed = false;\n const off = this.onRoomState(roomId, (state) => {\n if (disposed) {\n return;\n }\n disposed = true;\n off();\n callback(state);\n });\n return () => {\n disposed = true;\n off();\n };\n }\n\n onRoomStateSelect(\n roomId: string,\n path: string,\n callback: SelectorHandler,\n ): () => void {\n const normalizedPath = path.startsWith(\"/\") ? path.slice(1) : path;\n const currentState = this.getRoomState(roomId);\n const registration: SelectorRegistration = {\n path: normalizedPath,\n callback,\n lastValue: currentState[normalizedPath],\n };\n const selectors = this.roomStateSelectors.get(roomId) ?? new Set();\n selectors.add(registration);\n this.roomStateSelectors.set(roomId, selectors);\n\n return () => {\n const current = this.roomStateSelectors.get(roomId);\n if (!current) {\n return;\n }\n current.delete(registration);\n if (current.size === 0) {\n this.roomStateSelectors.delete(roomId);\n }\n };\n }\n\n private sendEnvelope(message: Envelope): void {\n const bytes = this.codec.encode(message);\n this.socket.send(bytes);\n }\n\n private dispatchEvent(message: Envelope): void {\n const handlers = this.eventHandlers.get(message.t);\n if (!handlers) {\n return;\n }\n\n for (const handler of handlers) {\n handler(message);\n }\n }\n\n private dispatchState(\n roomId: string,\n nextState: Record<string, unknown>,\n prevState: Record<string, unknown>,\n ): void {\n for (const handler of this.stateHandlers) {\n handler(nextState);\n }\n\n const roomHandlers = this.roomStateHandlers.get(roomId);\n if (roomHandlers) {\n for (const handler of roomHandlers) {\n handler(nextState);\n }\n }\n\n const selectors = this.roomStateSelectors.get(roomId);\n if (selectors) {\n for (const registration of selectors) {\n const nextValue = nextState[registration.path];\n const prevValue = prevState[registration.path];\n if (!deepEqual(nextValue, prevValue)) {\n registration.lastValue = nextValue;\n registration.callback(nextValue, nextState);\n }\n }\n }\n }\n\n private dispatchRoomMessage(message: Envelope): void {\n if (!message.room) {\n return;\n }\n\n const payload = readRoomMessagePayload(message.p);\n if (!payload) {\n return;\n }\n\n const byType = this.roomMessageHandlers.get(message.room);\n if (!byType) {\n return;\n }\n\n const handlers = byType.get(String(payload.type));\n if (!handlers) {\n return;\n }\n for (const handler of handlers) {\n handler(payload.data, message);\n }\n }\n\n private async onRawMessage(raw: unknown): Promise<void> {\n const bytes = await NexisClient.toBytes(raw);\n if (!bytes) {\n return;\n }\n\n let message: Envelope;\n try {\n message = this.codec.decode(bytes);\n } catch {\n return;\n }\n\n if (message.t === \"rpc.response\") {\n try {\n this.rpc.resolveResponse(message);\n } catch (error) {\n if (error instanceof UnknownRidError) {\n this.dispatchEvent({\n v: DEFAULT_VERSION,\n t: \"error\",\n p: { reason: error.message },\n });\n return;\n }\n throw error;\n }\n return;\n }\n\n if (message.t === \"state.snapshot\") {\n if (!message.room) {\n return;\n }\n const snapshot = parseSnapshotPayload(message.p);\n if (!snapshot) {\n return;\n }\n const computedChecksum = await computeStateChecksum(snapshot.state);\n if (snapshot.checksum && snapshot.checksum !== computedChecksum) {\n this.sendEnvelope({\n v: DEFAULT_VERSION,\n t: \"state.resync\",\n room: message.room,\n p: { since: this.roomSeq.get(message.room) ?? 0 },\n });\n return;\n }\n const checksum = snapshot.checksum ?? computedChecksum;\n\n const prevState = this.roomStates.get(message.room) ?? {};\n this.roomStates.set(message.room, snapshot.state);\n this.roomSeq.set(message.room, snapshot.seq);\n this.roomChecksum.set(message.room, checksum);\n this.dispatchState(message.room, snapshot.state, prevState);\n this.sendEnvelope({\n v: DEFAULT_VERSION,\n t: \"state.ack\",\n room: message.room,\n p: { seq: snapshot.seq, checksum },\n });\n return;\n }\n\n if (message.t === \"state.patch\") {\n if (!message.room) {\n return;\n }\n const parsedPatch = parsePatchPayload(message.p);\n if (!parsedPatch) {\n return;\n }\n\n const currentSeq = this.roomSeq.get(message.room) ?? 0;\n const patchSeq = parsedPatch.seq > 0 ? parsedPatch.seq : currentSeq + 1;\n\n if (patchSeq <= currentSeq) {\n return;\n }\n\n if (patchSeq > currentSeq + 1) {\n this.sendEnvelope({\n v: DEFAULT_VERSION,\n t: \"state.resync\",\n room: message.room,\n p: { since: currentSeq },\n });\n return;\n }\n\n const currentState = this.roomStates.get(message.room) ?? {};\n const nextState = applyPatch(currentState, parsedPatch.ops);\n let localChecksum: string | undefined;\n if (parsedPatch.checksum) {\n localChecksum = await computeStateChecksum(nextState);\n if (parsedPatch.checksum !== localChecksum) {\n this.sendEnvelope({\n v: DEFAULT_VERSION,\n t: \"state.resync\",\n room: message.room,\n p: {\n since: currentSeq,\n checksum: this.roomChecksum.get(message.room),\n },\n });\n return;\n }\n }\n\n this.roomStates.set(message.room, nextState);\n this.roomSeq.set(message.room, patchSeq);\n if (parsedPatch.checksum) {\n this.roomChecksum.set(message.room, parsedPatch.checksum);\n } else if (localChecksum) {\n this.roomChecksum.set(message.room, localChecksum);\n }\n this.dispatchState(message.room, nextState, currentState);\n this.sendEnvelope({\n v: DEFAULT_VERSION,\n t: \"state.ack\",\n room: message.room,\n p: parsedPatch.checksum\n ? { seq: patchSeq, checksum: this.roomChecksum.get(message.room) }\n : { seq: patchSeq },\n });\n return;\n }\n\n if (this.autoJoinMatchedRoom && message.t === \"match.found\") {\n const parsed = parseMatchFoundPayload(message.p);\n if (parsed) {\n void this.joinOrCreate(parsed.roomType, { roomId: parsed.room }).catch(\n () => undefined,\n );\n }\n }\n\n if (message.t === \"room.message\" && message.room) {\n this.dispatchRoomMessage(message);\n }\n\n this.dispatchEvent(message);\n }\n\n private static openSocketAndHandshake(\n url: string,\n options: ResolvedConnectOptions,\n sessionIdOverride?: string,\n ): Promise<{\n socket: WebSocket;\n codec: Codec;\n sessionId: string | undefined;\n }> {\n const socket = new WebSocket(url);\n const jsonCodec = new JsonCodec();\n const msgpackCodec = new MsgpackCodec();\n\n return new Promise((resolve, reject) => {\n let settled = false;\n const handshakeSessionId = sessionIdOverride ?? options.sessionId;\n\n const onOpen = () => {\n const handshake: HandshakeRequest = {\n v: DEFAULT_VERSION,\n codecs: options.codecs,\n project_id: options.projectId?.trim() || \"anonymous\",\n token: options.token?.trim() || \"\",\n session_id: handshakeSessionId,\n };\n socket.send(JSON.stringify(handshake));\n };\n\n const onError = () => {\n finishReject(new Error(\"socket connection failed\"));\n };\n\n const onClose = () => {\n finishReject(new Error(\"socket closed before handshake\"));\n };\n\n const onMessage = async (event: MessageEvent) => {\n try {\n const message = await NexisClient.decodeHandshakeMessage(\n event.data,\n jsonCodec,\n msgpackCodec,\n );\n if (!message) {\n return;\n }\n\n if (message.t === \"error\") {\n finishReject(new Error(readErrorReason(message)));\n return;\n }\n\n if (message.t !== \"handshake.ok\") {\n return;\n }\n\n const negotiatedCodec = readCodecName(message);\n const sessionId = readSessionId(message) ?? handshakeSessionId;\n finishResolve({\n socket,\n codec: codecFor(negotiatedCodec),\n sessionId,\n });\n } catch (error) {\n finishReject(new Error(`handshake decode failed: ${String(error)}`));\n }\n };\n\n const cleanup = () => {\n socket.removeEventListener(\"open\", onOpen);\n socket.removeEventListener(\"error\", onError);\n socket.removeEventListener(\"close\", onClose);\n socket.removeEventListener(\"message\", onMessage);\n };\n\n const finishResolve = (result: {\n socket: WebSocket;\n codec: Codec;\n sessionId: string | undefined;\n }) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n resolve(result);\n };\n\n const finishReject = (error: Error) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n reject(error);\n };\n\n socket.addEventListener(\"open\", onOpen);\n socket.addEventListener(\"error\", onError);\n socket.addEventListener(\"close\", onClose);\n socket.addEventListener(\"message\", onMessage);\n });\n }\n\n private static wait(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n private static async decodeHandshakeMessage(\n raw: unknown,\n jsonCodec: JsonCodec,\n msgpackCodec: MsgpackCodec,\n ): Promise<Envelope | null> {\n if (typeof raw === \"string\") {\n return JSON.parse(raw) as Envelope;\n }\n\n const bytes = await NexisClient.toBytes(raw);\n if (!bytes) {\n return null;\n }\n\n try {\n return msgpackCodec.decode(bytes);\n } catch {\n return jsonCodec.decode(bytes);\n }\n }\n\n private static async toBytes(raw: unknown): Promise<Uint8Array | null> {\n if (raw instanceof Uint8Array) {\n return raw;\n }\n if (raw instanceof ArrayBuffer) {\n return new Uint8Array(raw);\n }\n if (raw instanceof Blob) {\n return new Uint8Array(await raw.arrayBuffer());\n }\n if (typeof raw === \"string\") {\n return new TextEncoder().encode(raw);\n }\n return null;\n }\n}\n\nexport async function connect(\n url: string,\n options: ConnectOptions,\n): Promise<NexisClient> {\n return NexisClient.connect(url, options);\n}\n\nexport function decodeRoomBytes(payload: unknown): Uint8Array | null {\n return fromJsonValue(payload);\n}\n"],"names":["applyPatch","computeStateChecksum","parsePatchPayload","parseSnapshotPayload","JsonCodec","MsgpackCodec","codecFor","RpcClient","UnknownRidError","DEFAULT_VERSION","DEFAULT_RECONNECT_INITIAL_DELAY_MS","DEFAULT_RECONNECT_MAX_DELAY_MS","DEFAULT_RECONNECT_MAX_ATTEMPTS","normalizeConnectOptions","options","reconnectInitialDelayMs","Math","max","reconnectMaxDelayMs","reconnectMaxAttempts","codecs","autoJoinMatchedRoom","autoReconnect","readCodecName","message","payload","p","codec","readSessionId","session_id","undefined","readErrorReason","reason","isStringArray","value","Array","isArray","every","item","deepEqual","left","right","JSON","stringify","toJsonValue","bytes","binary","String","fromCharCode","btoa","fromJsonValue","decoded","atob","Uint8Array","length","i","charCodeAt","readRoomMessagePayload","type","data","parseMatchFoundPayload","room","roomType","room_type","size","participants","Number","isFinite","NexisRoom","id","onStateChange","constructor","client","roomId","buildStateChangeSubscription","state","getRoomState","send","sendRoomMessage","sendBytes","normalized","from","sendRoomMessageBytes","onMessage","callback","onRoomMessage","subscribe","onRoomState","once","onRoomStateOnce","select","path","onRoomStateSelect","NexisClient","socket","url","connectOptions","rpc","eventHandlers","Map","stateHandlers","Set","roomStateHandlers","roomStateSelectors","roomMessageHandlers","roomStates","roomSeq","roomChecksum","sessionId","reconnecting","disposed","attachSocket","connect","resolved","openSocketAndHandshake","then","close","addEventListener","event","onRawMessage","rejectAll","Error","tryReconnect","dispatchEvent","v","t","attempt","delayMs","wait","reconnect","min","getSessionId","joinOrCreate","response","sendRPC","listRooms","enqueueMatchmaking","dequeueMatchmaking","add","delete","onEvent","handlers","get","set","current","onMatchFound","parsed","promise","createRequest","sendEnvelope","data_b64","key","byType","roomHandlers","typeHandlers","has","off","normalizedPath","startsWith","slice","currentState","registration","lastValue","selectors","encode","handler","dispatchState","nextState","prevState","nextValue","prevValue","dispatchRoomMessage","raw","toBytes","decode","resolveResponse","error","snapshot","computedChecksum","checksum","since","seq","parsedPatch","currentSeq","patchSeq","ops","localChecksum","catch","sessionIdOverride","WebSocket","jsonCodec","msgpackCodec","Promise","resolve","reject","settled","handshakeSessionId","onOpen","handshake","project_id","projectId","trim","token","onError","finishReject","onClose","decodeHandshakeMessage","negotiatedCodec","finishResolve","cleanup","removeEventListener","result","ms","setTimeout","parse","ArrayBuffer","Blob","arrayBuffer","TextEncoder","decodeRoomBytes"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Packr, Unpackr } from "msgpackr";
|
|
2
|
+
export class JsonCodec {
|
|
3
|
+
name = "json";
|
|
4
|
+
encode(message) {
|
|
5
|
+
return new TextEncoder().encode(JSON.stringify(message));
|
|
6
|
+
}
|
|
7
|
+
decode(bytes) {
|
|
8
|
+
const text = new TextDecoder().decode(bytes);
|
|
9
|
+
const parsed = JSON.parse(text);
|
|
10
|
+
return parsed;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class MsgpackCodec {
|
|
14
|
+
name = "msgpack";
|
|
15
|
+
packr = new Packr({
|
|
16
|
+
useRecords: false,
|
|
17
|
+
structuredClone: false,
|
|
18
|
+
bundleStrings: false,
|
|
19
|
+
maxSharedStructures: 0
|
|
20
|
+
});
|
|
21
|
+
unpackr = new Unpackr({
|
|
22
|
+
useRecords: false
|
|
23
|
+
});
|
|
24
|
+
encode(message) {
|
|
25
|
+
return this.packr.pack(message);
|
|
26
|
+
}
|
|
27
|
+
decode(bytes) {
|
|
28
|
+
const decoded = this.unpackr.unpack(bytes);
|
|
29
|
+
return decoded;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function codecFor(name) {
|
|
33
|
+
return name === "msgpack" ? new MsgpackCodec() : new JsonCodec();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/codec.ts"],"sourcesContent":["import { Packr, Unpackr } from \"msgpackr\";\nimport type { Envelope } from \"./types\";\n\nexport interface Codec {\n readonly name: \"json\" | \"msgpack\";\n encode(message: Envelope): Uint8Array;\n decode(bytes: Uint8Array): Envelope;\n}\n\nexport class JsonCodec implements Codec {\n readonly name = \"json\" as const;\n\n encode(message: Envelope): Uint8Array {\n return new TextEncoder().encode(JSON.stringify(message));\n }\n\n decode(bytes: Uint8Array): Envelope {\n const text = new TextDecoder().decode(bytes);\n const parsed = JSON.parse(text);\n return parsed as Envelope;\n }\n}\n\nexport class MsgpackCodec implements Codec {\n readonly name = \"msgpack\" as const;\n private readonly packr = new Packr({\n useRecords: false,\n structuredClone: false,\n bundleStrings: false,\n maxSharedStructures: 0,\n });\n private readonly unpackr = new Unpackr({\n useRecords: false,\n });\n\n encode(message: Envelope): Uint8Array {\n return this.packr.pack(message);\n }\n\n decode(bytes: Uint8Array): Envelope {\n const decoded = this.unpackr.unpack(bytes);\n return decoded as Envelope;\n }\n}\n\nexport function codecFor(name: \"json\" | \"msgpack\"): Codec {\n return name === \"msgpack\" ? new MsgpackCodec() : new JsonCodec();\n}\n"],"names":["Packr","Unpackr","JsonCodec","name","encode","message","TextEncoder","JSON","stringify","decode","bytes","text","TextDecoder","parsed","parse","MsgpackCodec","packr","useRecords","structuredClone","bundleStrings","maxSharedStructures","unpackr","pack","decoded","unpack","codecFor"],"mappings":"AAAA,SAASA,KAAK,EAAEC,OAAO,QAAQ,WAAW;AAS1C,OAAO,MAAMC;IACFC,OAAO,OAAgB;IAEhCC,OAAOC,OAAiB,EAAc;QACpC,OAAO,IAAIC,cAAcF,MAAM,CAACG,KAAKC,SAAS,CAACH;IACjD;IAEAI,OAAOC,KAAiB,EAAY;QAClC,MAAMC,OAAO,IAAIC,cAAcH,MAAM,CAACC;QACtC,MAAMG,SAASN,KAAKO,KAAK,CAACH;QAC1B,OAAOE;IACT;AACF;AAEA,OAAO,MAAME;IACFZ,OAAO,UAAmB;IAClBa,QAAQ,IAAIhB,MAAM;QACjCiB,YAAY;QACZC,iBAAiB;QACjBC,eAAe;QACfC,qBAAqB;IACvB,GAAG;IACcC,UAAU,IAAIpB,QAAQ;QACrCgB,YAAY;IACd,GAAG;IAEHb,OAAOC,OAAiB,EAAc;QACpC,OAAO,IAAI,CAACW,KAAK,CAACM,IAAI,CAACjB;IACzB;IAEAI,OAAOC,KAAiB,EAAY;QAClC,MAAMa,UAAU,IAAI,CAACF,OAAO,CAACG,MAAM,CAACd;QACpC,OAAOa;IACT;AACF;AAEA,OAAO,SAASE,SAAStB,IAAwB;IAC/C,OAAOA,SAAS,YAAY,IAAIY,iBAAiB,IAAIb;AACvD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts"],"sourcesContent":["export * from \"./types\";\nexport * from \"./codec\";\nexport * from \"./patch\";\nexport * from \"./rpc\";\nexport * from \"./client\";\n"],"names":[],"mappings":"AAAA,cAAc,UAAU;AACxB,cAAc,UAAU;AACxB,cAAc,UAAU;AACxB,cAAc,QAAQ;AACtB,cAAc,WAAW"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
function keyFromPath(path) {
|
|
2
|
+
if (!path.startsWith("/")) {
|
|
3
|
+
throw new Error(`Invalid patch path: ${path}`);
|
|
4
|
+
}
|
|
5
|
+
const key = path.slice(1);
|
|
6
|
+
if (!key || key.includes("/")) {
|
|
7
|
+
throw new Error(`Invalid patch path: ${path}`);
|
|
8
|
+
}
|
|
9
|
+
return key.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
10
|
+
}
|
|
11
|
+
export function applyPatch(state, patch) {
|
|
12
|
+
const next = {
|
|
13
|
+
...state
|
|
14
|
+
};
|
|
15
|
+
for (const op of patch){
|
|
16
|
+
const key = keyFromPath(op.path);
|
|
17
|
+
if (op.op === "set") {
|
|
18
|
+
next[key] = op.value;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
delete next[key];
|
|
22
|
+
}
|
|
23
|
+
return next;
|
|
24
|
+
}
|
|
25
|
+
function canonicalize(value) {
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
return value.map((item)=>canonicalize(item));
|
|
28
|
+
}
|
|
29
|
+
if (value && typeof value === "object") {
|
|
30
|
+
const sortedEntries = Object.entries(value).sort(([left], [right])=>left.localeCompare(right));
|
|
31
|
+
const normalized = {};
|
|
32
|
+
for (const [key, item] of sortedEntries){
|
|
33
|
+
normalized[key] = canonicalize(item);
|
|
34
|
+
}
|
|
35
|
+
return normalized;
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
export async function computeStateChecksum(state) {
|
|
40
|
+
const cryptoApi = globalThis.crypto?.subtle;
|
|
41
|
+
if (!cryptoApi) {
|
|
42
|
+
throw new Error("crypto.subtle is unavailable");
|
|
43
|
+
}
|
|
44
|
+
const canonicalJson = JSON.stringify(canonicalize(state));
|
|
45
|
+
const bytes = new TextEncoder().encode(canonicalJson);
|
|
46
|
+
const digest = await cryptoApi.digest("SHA-256", bytes);
|
|
47
|
+
const hashBytes = new Uint8Array(digest);
|
|
48
|
+
return Array.from(hashBytes).map((value)=>value.toString(16).padStart(2, "0")).join("");
|
|
49
|
+
}
|
|
50
|
+
export function parsePatchPayload(payload) {
|
|
51
|
+
if (Array.isArray(payload)) {
|
|
52
|
+
return {
|
|
53
|
+
seq: 0,
|
|
54
|
+
checksum: undefined,
|
|
55
|
+
ops: payload
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (!payload || typeof payload !== "object") {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const candidate = payload;
|
|
62
|
+
if (typeof candidate.seq !== "number" || !Array.isArray(candidate.ops) || candidate.checksum !== undefined && typeof candidate.checksum !== "string") {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
seq: candidate.seq,
|
|
67
|
+
checksum: candidate.checksum,
|
|
68
|
+
ops: candidate.ops
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function parseSnapshotPayload(payload) {
|
|
72
|
+
if (!payload || typeof payload !== "object") {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const candidate = payload;
|
|
76
|
+
if (typeof candidate.seq !== "number" || candidate.checksum !== undefined && typeof candidate.checksum !== "string" || !candidate.state || typeof candidate.state !== "object" || Array.isArray(candidate.state)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
seq: candidate.seq,
|
|
81
|
+
checksum: candidate.checksum,
|
|
82
|
+
state: candidate.state
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=patch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/patch.ts"],"sourcesContent":["import type { PatchOp, StatePatchPayload, StateSnapshotPayload } from \"./types\";\n\nfunction keyFromPath(path: string): string {\n if (!path.startsWith(\"/\")) {\n throw new Error(`Invalid patch path: ${path}`);\n }\n const key = path.slice(1);\n if (!key || key.includes(\"/\")) {\n throw new Error(`Invalid patch path: ${path}`);\n }\n return key.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n}\n\nexport function applyPatch<T extends Record<string, unknown>>(\n state: T,\n patch: PatchOp[],\n): T {\n const next: Record<string, unknown> = { ...state };\n\n for (const op of patch) {\n const key = keyFromPath(op.path);\n if (op.op === \"set\") {\n next[key] = op.value;\n continue;\n }\n delete next[key];\n }\n\n return next as T;\n}\n\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => canonicalize(item));\n }\n\n if (value && typeof value === \"object\") {\n const sortedEntries = Object.entries(value as Record<string, unknown>).sort(\n ([left], [right]) => left.localeCompare(right),\n );\n const normalized: Record<string, unknown> = {};\n for (const [key, item] of sortedEntries) {\n normalized[key] = canonicalize(item);\n }\n return normalized;\n }\n\n return value;\n}\n\nexport async function computeStateChecksum(state: unknown): Promise<string> {\n const cryptoApi = globalThis.crypto?.subtle;\n if (!cryptoApi) {\n throw new Error(\"crypto.subtle is unavailable\");\n }\n\n const canonicalJson = JSON.stringify(canonicalize(state));\n const bytes = new TextEncoder().encode(canonicalJson);\n const digest = await cryptoApi.digest(\"SHA-256\", bytes);\n const hashBytes = new Uint8Array(digest);\n\n return Array.from(hashBytes)\n .map((value) => value.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\nexport function parsePatchPayload(payload: unknown): StatePatchPayload | null {\n if (Array.isArray(payload)) {\n return {\n seq: 0,\n checksum: undefined,\n ops: payload as PatchOp[],\n };\n }\n\n if (!payload || typeof payload !== \"object\") {\n return null;\n }\n\n const candidate = payload as {\n seq?: unknown;\n checksum?: unknown;\n ops?: unknown;\n };\n if (\n typeof candidate.seq !== \"number\" ||\n !Array.isArray(candidate.ops) ||\n (candidate.checksum !== undefined && typeof candidate.checksum !== \"string\")\n ) {\n return null;\n }\n\n return {\n seq: candidate.seq,\n checksum: candidate.checksum as string | undefined,\n ops: candidate.ops as PatchOp[],\n };\n}\n\nexport function parseSnapshotPayload(\n payload: unknown,\n): StateSnapshotPayload | null {\n if (!payload || typeof payload !== \"object\") {\n return null;\n }\n\n const candidate = payload as {\n seq?: unknown;\n checksum?: unknown;\n state?: unknown;\n };\n if (\n typeof candidate.seq !== \"number\" ||\n (candidate.checksum !== undefined &&\n typeof candidate.checksum !== \"string\") ||\n !candidate.state ||\n typeof candidate.state !== \"object\" ||\n Array.isArray(candidate.state)\n ) {\n return null;\n }\n\n return {\n seq: candidate.seq,\n checksum: candidate.checksum as string | undefined,\n state: candidate.state as Record<string, unknown>,\n };\n}\n"],"names":["keyFromPath","path","startsWith","Error","key","slice","includes","replace","applyPatch","state","patch","next","op","value","canonicalize","Array","isArray","map","item","sortedEntries","Object","entries","sort","left","right","localeCompare","normalized","computeStateChecksum","cryptoApi","globalThis","crypto","subtle","canonicalJson","JSON","stringify","bytes","TextEncoder","encode","digest","hashBytes","Uint8Array","from","toString","padStart","join","parsePatchPayload","payload","seq","checksum","undefined","ops","candidate","parseSnapshotPayload"],"mappings":"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"}
|
package/dist/esm/rpc.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export class UnknownRidError extends Error {
|
|
2
|
+
constructor(rid){
|
|
3
|
+
super(`Unknown RPC rid: ${rid}`);
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export class RpcClient {
|
|
7
|
+
nextId = 1;
|
|
8
|
+
pending = new Map();
|
|
9
|
+
createRequest(type, payload, room) {
|
|
10
|
+
const rid = `rpc-${this.nextId++}`;
|
|
11
|
+
const message = {
|
|
12
|
+
v: 1,
|
|
13
|
+
t: type,
|
|
14
|
+
rid,
|
|
15
|
+
p: payload
|
|
16
|
+
};
|
|
17
|
+
if (room !== undefined) {
|
|
18
|
+
message.room = room;
|
|
19
|
+
}
|
|
20
|
+
const promise = new Promise((resolve, reject)=>{
|
|
21
|
+
this.pending.set(rid, {
|
|
22
|
+
resolve,
|
|
23
|
+
reject
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
message,
|
|
28
|
+
promise
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
resolveResponse(response) {
|
|
32
|
+
const rid = response.rid;
|
|
33
|
+
if (!rid) {
|
|
34
|
+
throw new UnknownRidError("missing");
|
|
35
|
+
}
|
|
36
|
+
const pending = this.pending.get(rid);
|
|
37
|
+
if (!pending) {
|
|
38
|
+
throw new UnknownRidError(rid);
|
|
39
|
+
}
|
|
40
|
+
this.pending.delete(rid);
|
|
41
|
+
pending.resolve(response.p);
|
|
42
|
+
}
|
|
43
|
+
rejectAll(error) {
|
|
44
|
+
for (const pending of this.pending.values()){
|
|
45
|
+
pending.reject(error);
|
|
46
|
+
}
|
|
47
|
+
this.pending.clear();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=rpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/rpc.ts"],"sourcesContent":["import type { Envelope } from \"./types\";\n\nexport class UnknownRidError extends Error {\n constructor(rid: string) {\n super(`Unknown RPC rid: ${rid}`);\n }\n}\n\nexport class RpcClient {\n private nextId = 1;\n private pending = new Map<\n string,\n { resolve: (payload: unknown) => void; reject: (error: Error) => void }\n >();\n\n createRequest(\n type: string,\n payload: unknown,\n room?: string,\n ): { message: Envelope; promise: Promise<unknown> } {\n const rid = `rpc-${this.nextId++}`;\n const message: Envelope = { v: 1, t: type, rid, p: payload };\n if (room !== undefined) {\n message.room = room;\n }\n const promise = new Promise<unknown>((resolve, reject) => {\n this.pending.set(rid, { resolve, reject });\n });\n\n return { message, promise };\n }\n\n resolveResponse(response: Envelope): void {\n const rid = response.rid;\n if (!rid) {\n throw new UnknownRidError(\"missing\");\n }\n\n const pending = this.pending.get(rid);\n if (!pending) {\n throw new UnknownRidError(rid);\n }\n\n this.pending.delete(rid);\n pending.resolve(response.p);\n }\n\n rejectAll(error: Error): void {\n for (const pending of this.pending.values()) {\n pending.reject(error);\n }\n this.pending.clear();\n }\n}\n"],"names":["UnknownRidError","Error","constructor","rid","RpcClient","nextId","pending","Map","createRequest","type","payload","room","message","v","t","p","undefined","promise","Promise","resolve","reject","set","resolveResponse","response","get","delete","rejectAll","error","values","clear"],"mappings":"AAEA,OAAO,MAAMA,wBAAwBC;IACnCC,YAAYC,GAAW,CAAE;QACvB,KAAK,CAAC,CAAC,iBAAiB,EAAEA,KAAK;IACjC;AACF;AAEA,OAAO,MAAMC;IACHC,SAAS,EAAE;IACXC,UAAU,IAAIC,MAGlB;IAEJC,cACEC,IAAY,EACZC,OAAgB,EAChBC,IAAa,EACqC;QAClD,MAAMR,MAAM,CAAC,IAAI,EAAE,IAAI,CAACE,MAAM,IAAI;QAClC,MAAMO,UAAoB;YAAEC,GAAG;YAAGC,GAAGL;YAAMN;YAAKY,GAAGL;QAAQ;QAC3D,IAAIC,SAASK,WAAW;YACtBJ,QAAQD,IAAI,GAAGA;QACjB;QACA,MAAMM,UAAU,IAAIC,QAAiB,CAACC,SAASC;YAC7C,IAAI,CAACd,OAAO,CAACe,GAAG,CAAClB,KAAK;gBAAEgB;gBAASC;YAAO;QAC1C;QAEA,OAAO;YAAER;YAASK;QAAQ;IAC5B;IAEAK,gBAAgBC,QAAkB,EAAQ;QACxC,MAAMpB,MAAMoB,SAASpB,GAAG;QACxB,IAAI,CAACA,KAAK;YACR,MAAM,IAAIH,gBAAgB;QAC5B;QAEA,MAAMM,UAAU,IAAI,CAACA,OAAO,CAACkB,GAAG,CAACrB;QACjC,IAAI,CAACG,SAAS;YACZ,MAAM,IAAIN,gBAAgBG;QAC5B;QAEA,IAAI,CAACG,OAAO,CAACmB,MAAM,CAACtB;QACpBG,QAAQa,OAAO,CAACI,SAASR,CAAC;IAC5B;IAEAW,UAAUC,KAAY,EAAQ;QAC5B,KAAK,MAAMrB,WAAW,IAAI,CAACA,OAAO,CAACsB,MAAM,GAAI;YAC3CtB,QAAQc,MAAM,CAACO;QACjB;QACA,IAAI,CAACrB,OAAO,CAACuB,KAAK;IACpB;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/types.ts"],"sourcesContent":["export type Envelope = {\n v: number;\n t: string;\n rid?: string;\n room?: string;\n p?: unknown;\n};\n\nexport type HandshakeRequest = {\n v: number;\n codecs: string[];\n project_id: string;\n token: string;\n session_id?: string;\n};\n\nexport type ConnectOptions = {\n projectId?: string;\n token?: string;\n codecs?: Array<\"msgpack\" | \"json\">;\n sessionId?: string;\n autoJoinMatchedRoom?: boolean;\n autoReconnect?: boolean;\n reconnectInitialDelayMs?: number;\n reconnectMaxDelayMs?: number;\n reconnectMaxAttempts?: number;\n};\n\nexport type RoomSummary = {\n id: string;\n room_type: string;\n members: number;\n};\n\nexport type RoomListResponse = {\n ok: boolean;\n rooms: RoomSummary[];\n};\n\nexport type MatchFound = {\n room: string;\n roomType: string;\n size: number;\n participants: string[];\n};\n\nexport type MatchmakingQueueResponse = {\n ok: boolean;\n queued?: boolean;\n matched?: boolean;\n room_type?: string;\n size?: number;\n position?: number;\n};\n\nexport type MatchmakingDequeueResponse = {\n ok: boolean;\n removed: boolean;\n};\n\nexport type PatchOp =\n | { op: \"set\"; path: string; value: unknown }\n | { op: \"del\"; path: string };\n\nexport type StatePatchPayload = {\n seq: number;\n checksum?: string;\n ops: PatchOp[];\n};\n\nexport type StateSnapshotPayload = {\n seq: number;\n checksum?: string;\n state: Record<string, unknown>;\n};\n\nexport type RoomMessageType = string | number;\n\nexport type RoomMessagePayload = {\n type: RoomMessageType;\n data: unknown;\n};\n"],"names":[],"mappings":"AA8EA,WAGE"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ConnectOptions, Envelope, MatchFound, MatchmakingDequeueResponse, MatchmakingQueueResponse, RoomListResponse, RoomMessageType } from "./types";
|
|
2
|
+
type EventHandler = (message: Envelope) => void;
|
|
3
|
+
type StateHandler = (state: Record<string, unknown>) => void;
|
|
4
|
+
type MatchFoundHandler = (match: MatchFound, message: Envelope) => void;
|
|
5
|
+
type RoomMessageHandler = (data: unknown, envelope: Envelope) => void;
|
|
6
|
+
type SelectorHandler = (value: unknown, state: Record<string, unknown>) => void;
|
|
7
|
+
export type RoomStateChangeSubscription = {
|
|
8
|
+
(callback: StateHandler): () => void;
|
|
9
|
+
once(callback: StateHandler): () => void;
|
|
10
|
+
select(path: string, callback: SelectorHandler): () => void;
|
|
11
|
+
};
|
|
12
|
+
export declare function parseMatchFoundPayload(payload: unknown): MatchFound | null;
|
|
13
|
+
export declare class NexisRoom {
|
|
14
|
+
private readonly client;
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly onStateChange: RoomStateChangeSubscription;
|
|
17
|
+
constructor(client: NexisClient, roomId: string);
|
|
18
|
+
get state(): Record<string, unknown>;
|
|
19
|
+
send(type: RoomMessageType, message: unknown): void;
|
|
20
|
+
sendBytes(type: RoomMessageType, bytes: Uint8Array | number[]): void;
|
|
21
|
+
onMessage(type: RoomMessageType, callback: RoomMessageHandler): () => void;
|
|
22
|
+
private buildStateChangeSubscription;
|
|
23
|
+
}
|
|
24
|
+
export declare class NexisClient {
|
|
25
|
+
private socket;
|
|
26
|
+
private readonly url;
|
|
27
|
+
private readonly connectOptions;
|
|
28
|
+
private readonly rpc;
|
|
29
|
+
private codec;
|
|
30
|
+
private readonly eventHandlers;
|
|
31
|
+
private readonly stateHandlers;
|
|
32
|
+
private readonly roomStateHandlers;
|
|
33
|
+
private readonly roomStateSelectors;
|
|
34
|
+
private readonly roomMessageHandlers;
|
|
35
|
+
private readonly roomStates;
|
|
36
|
+
private readonly roomSeq;
|
|
37
|
+
private readonly roomChecksum;
|
|
38
|
+
private sessionId;
|
|
39
|
+
private readonly autoJoinMatchedRoom;
|
|
40
|
+
private reconnecting;
|
|
41
|
+
private disposed;
|
|
42
|
+
private constructor();
|
|
43
|
+
static connect(url: string, options: ConnectOptions): Promise<NexisClient>;
|
|
44
|
+
close(): void;
|
|
45
|
+
private attachSocket;
|
|
46
|
+
private tryReconnect;
|
|
47
|
+
getSessionId(): string | undefined;
|
|
48
|
+
room(roomId: string): NexisRoom;
|
|
49
|
+
joinOrCreate(roomType: string, options?: {
|
|
50
|
+
roomId?: string;
|
|
51
|
+
} & Record<string, unknown>): Promise<NexisRoom>;
|
|
52
|
+
listRooms(roomType?: string): Promise<RoomListResponse>;
|
|
53
|
+
enqueueMatchmaking(roomType: string, size?: number): Promise<MatchmakingQueueResponse>;
|
|
54
|
+
dequeueMatchmaking(): Promise<MatchmakingDequeueResponse>;
|
|
55
|
+
onStateChange(callback: StateHandler): () => void;
|
|
56
|
+
onEvent(type: string, callback: EventHandler): () => void;
|
|
57
|
+
onMatchFound(callback: MatchFoundHandler): () => void;
|
|
58
|
+
sendRPC(type: string, payload: unknown, room?: string): Promise<unknown>;
|
|
59
|
+
getRoomState(roomId: string): Record<string, unknown>;
|
|
60
|
+
sendRoomMessage(roomId: string, type: RoomMessageType, data: unknown): void;
|
|
61
|
+
sendRoomMessageBytes(roomId: string, type: RoomMessageType, data: Uint8Array): void;
|
|
62
|
+
onRoomMessage(roomId: string, type: RoomMessageType, callback: RoomMessageHandler): () => void;
|
|
63
|
+
onRoomState(roomId: string, callback: StateHandler): () => void;
|
|
64
|
+
onRoomStateOnce(roomId: string, callback: StateHandler): () => void;
|
|
65
|
+
onRoomStateSelect(roomId: string, path: string, callback: SelectorHandler): () => void;
|
|
66
|
+
private sendEnvelope;
|
|
67
|
+
private dispatchEvent;
|
|
68
|
+
private dispatchState;
|
|
69
|
+
private dispatchRoomMessage;
|
|
70
|
+
private onRawMessage;
|
|
71
|
+
private static openSocketAndHandshake;
|
|
72
|
+
private static wait;
|
|
73
|
+
private static decodeHandshakeMessage;
|
|
74
|
+
private static toBytes;
|
|
75
|
+
}
|
|
76
|
+
export declare function connect(url: string, options: ConnectOptions): Promise<NexisClient>;
|
|
77
|
+
export declare function decodeRoomBytes(payload: unknown): Uint8Array | null;
|
|
78
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Envelope } from "./types";
|
|
2
|
+
export interface Codec {
|
|
3
|
+
readonly name: "json" | "msgpack";
|
|
4
|
+
encode(message: Envelope): Uint8Array;
|
|
5
|
+
decode(bytes: Uint8Array): Envelope;
|
|
6
|
+
}
|
|
7
|
+
export declare class JsonCodec implements Codec {
|
|
8
|
+
readonly name: "json";
|
|
9
|
+
encode(message: Envelope): Uint8Array;
|
|
10
|
+
decode(bytes: Uint8Array): Envelope;
|
|
11
|
+
}
|
|
12
|
+
export declare class MsgpackCodec implements Codec {
|
|
13
|
+
readonly name: "msgpack";
|
|
14
|
+
private readonly packr;
|
|
15
|
+
private readonly unpackr;
|
|
16
|
+
encode(message: Envelope): Uint8Array;
|
|
17
|
+
decode(bytes: Uint8Array): Envelope;
|
|
18
|
+
}
|
|
19
|
+
export declare function codecFor(name: "json" | "msgpack"): Codec;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PatchOp, StatePatchPayload, StateSnapshotPayload } from "./types";
|
|
2
|
+
export declare function applyPatch<T extends Record<string, unknown>>(state: T, patch: PatchOp[]): T;
|
|
3
|
+
export declare function computeStateChecksum(state: unknown): Promise<string>;
|
|
4
|
+
export declare function parsePatchPayload(payload: unknown): StatePatchPayload | null;
|
|
5
|
+
export declare function parseSnapshotPayload(payload: unknown): StateSnapshotPayload | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Envelope } from "./types";
|
|
2
|
+
export declare class UnknownRidError extends Error {
|
|
3
|
+
constructor(rid: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class RpcClient {
|
|
6
|
+
private nextId;
|
|
7
|
+
private pending;
|
|
8
|
+
createRequest(type: string, payload: unknown, room?: string): {
|
|
9
|
+
message: Envelope;
|
|
10
|
+
promise: Promise<unknown>;
|
|
11
|
+
};
|
|
12
|
+
resolveResponse(response: Envelope): void;
|
|
13
|
+
rejectAll(error: Error): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export type Envelope = {
|
|
2
|
+
v: number;
|
|
3
|
+
t: string;
|
|
4
|
+
rid?: string;
|
|
5
|
+
room?: string;
|
|
6
|
+
p?: unknown;
|
|
7
|
+
};
|
|
8
|
+
export type HandshakeRequest = {
|
|
9
|
+
v: number;
|
|
10
|
+
codecs: string[];
|
|
11
|
+
project_id: string;
|
|
12
|
+
token: string;
|
|
13
|
+
session_id?: string;
|
|
14
|
+
};
|
|
15
|
+
export type ConnectOptions = {
|
|
16
|
+
projectId?: string;
|
|
17
|
+
token?: string;
|
|
18
|
+
codecs?: Array<"msgpack" | "json">;
|
|
19
|
+
sessionId?: string;
|
|
20
|
+
autoJoinMatchedRoom?: boolean;
|
|
21
|
+
autoReconnect?: boolean;
|
|
22
|
+
reconnectInitialDelayMs?: number;
|
|
23
|
+
reconnectMaxDelayMs?: number;
|
|
24
|
+
reconnectMaxAttempts?: number;
|
|
25
|
+
};
|
|
26
|
+
export type RoomSummary = {
|
|
27
|
+
id: string;
|
|
28
|
+
room_type: string;
|
|
29
|
+
members: number;
|
|
30
|
+
};
|
|
31
|
+
export type RoomListResponse = {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
rooms: RoomSummary[];
|
|
34
|
+
};
|
|
35
|
+
export type MatchFound = {
|
|
36
|
+
room: string;
|
|
37
|
+
roomType: string;
|
|
38
|
+
size: number;
|
|
39
|
+
participants: string[];
|
|
40
|
+
};
|
|
41
|
+
export type MatchmakingQueueResponse = {
|
|
42
|
+
ok: boolean;
|
|
43
|
+
queued?: boolean;
|
|
44
|
+
matched?: boolean;
|
|
45
|
+
room_type?: string;
|
|
46
|
+
size?: number;
|
|
47
|
+
position?: number;
|
|
48
|
+
};
|
|
49
|
+
export type MatchmakingDequeueResponse = {
|
|
50
|
+
ok: boolean;
|
|
51
|
+
removed: boolean;
|
|
52
|
+
};
|
|
53
|
+
export type PatchOp = {
|
|
54
|
+
op: "set";
|
|
55
|
+
path: string;
|
|
56
|
+
value: unknown;
|
|
57
|
+
} | {
|
|
58
|
+
op: "del";
|
|
59
|
+
path: string;
|
|
60
|
+
};
|
|
61
|
+
export type StatePatchPayload = {
|
|
62
|
+
seq: number;
|
|
63
|
+
checksum?: string;
|
|
64
|
+
ops: PatchOp[];
|
|
65
|
+
};
|
|
66
|
+
export type StateSnapshotPayload = {
|
|
67
|
+
seq: number;
|
|
68
|
+
checksum?: string;
|
|
69
|
+
state: Record<string, unknown>;
|
|
70
|
+
};
|
|
71
|
+
export type RoomMessageType = string | number;
|
|
72
|
+
export type RoomMessagePayload = {
|
|
73
|
+
type: RoomMessageType;
|
|
74
|
+
data: unknown;
|
|
75
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@triformine/nexis-sdk",
|
|
3
|
+
"version": "0.1.0-next.6d96464",
|
|
4
|
+
"private": false,
|
|
5
|
+
"author": "TriForMine <quentin@triformine.dev> (https://github.com/TriForMine/)",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/TriForMine/nexis"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/cjs/index.js",
|
|
16
|
+
"module": "./dist/esm/index.js",
|
|
17
|
+
"types": "./dist/types/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"@source": "./src/index.ts",
|
|
27
|
+
"types": "./dist/types/index.d.ts",
|
|
28
|
+
"import": "./dist/esm/index.js",
|
|
29
|
+
"require": "./dist/cjs/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"packageManager": "bun@1.3.9",
|
|
34
|
+
"scripts": {
|
|
35
|
+
"test": "bun test",
|
|
36
|
+
"typecheck": "bunx tsc -p tsconfig.json --noEmit",
|
|
37
|
+
"clean": "rimraf dist",
|
|
38
|
+
"build:esm": "swc src --strip-leading-paths src --out-dir dist/esm --config-file .swcrc -C module.type=es6",
|
|
39
|
+
"build:cjs": "swc src --strip-leading-paths src --out-dir dist/cjs --config-file .swcrc -C module.type=commonjs",
|
|
40
|
+
"build:type": "bunx tsc -p tsconfig.json --declaration --emitDeclarationOnly --declarationDir dist/types",
|
|
41
|
+
"build": "bun run clean && bun run build:esm && bun run build:cjs && bun run build:type",
|
|
42
|
+
"prepublishOnly": "bun run test && bun run typecheck && bun run build"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"msgpackr": "1.11.8"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@swc/cli": "0.6.0",
|
|
49
|
+
"@swc/core": "1.7.36",
|
|
50
|
+
"rimraf": "6.0.1",
|
|
51
|
+
"typescript": "5.9.3"
|
|
52
|
+
}
|
|
53
|
+
}
|