@triformine/nexis-sdk 0.1.0 → 0.1.1

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.
@@ -0,0 +1,719 @@
1
+ import { applyPatch, computeStateChecksum, parsePatchPayload, parseSnapshotPayload } from "./patch";
2
+ import { JsonCodec, MsgpackCodec, codecFor } from "./codec";
3
+ import { RpcClient, UnknownRidError } from "./rpc";
4
+ const DEFAULT_VERSION = 1;
5
+ const DEFAULT_RECONNECT_INITIAL_DELAY_MS = 250;
6
+ const DEFAULT_RECONNECT_MAX_DELAY_MS = 3_000;
7
+ const DEFAULT_RECONNECT_MAX_ATTEMPTS = 20;
8
+ function normalizeConnectOptions(options) {
9
+ const reconnectInitialDelayMs = Math.max(50, options.reconnectInitialDelayMs ?? DEFAULT_RECONNECT_INITIAL_DELAY_MS);
10
+ const reconnectMaxDelayMs = Math.max(reconnectInitialDelayMs, options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS);
11
+ const reconnectMaxAttempts = Math.max(1, options.reconnectMaxAttempts ?? DEFAULT_RECONNECT_MAX_ATTEMPTS);
12
+ return {
13
+ ...options,
14
+ codecs: options.codecs ?? [
15
+ "msgpack",
16
+ "json"
17
+ ],
18
+ autoJoinMatchedRoom: options.autoJoinMatchedRoom ?? false,
19
+ autoReconnect: options.autoReconnect ?? true,
20
+ reconnectInitialDelayMs,
21
+ reconnectMaxDelayMs,
22
+ reconnectMaxAttempts
23
+ };
24
+ }
25
+ function readCodecName(message) {
26
+ const payload = message.p;
27
+ if (payload && typeof payload === "object" && "codec" in payload && payload.codec === "msgpack") {
28
+ return "msgpack";
29
+ }
30
+ return "json";
31
+ }
32
+ function readSessionId(message) {
33
+ const payload = message.p;
34
+ if (payload && typeof payload === "object" && "session_id" in payload && typeof payload.session_id === "string") {
35
+ return payload.session_id;
36
+ }
37
+ return undefined;
38
+ }
39
+ function readErrorReason(message) {
40
+ const payload = message.p;
41
+ if (payload && typeof payload === "object" && "reason" in payload && typeof payload.reason === "string") {
42
+ return payload.reason;
43
+ }
44
+ return "server returned error";
45
+ }
46
+ function isStringArray(value) {
47
+ return Array.isArray(value) && value.every((item)=>typeof item === "string");
48
+ }
49
+ function deepEqual(left, right) {
50
+ return JSON.stringify(left) === JSON.stringify(right);
51
+ }
52
+ function toJsonValue(bytes) {
53
+ let binary = "";
54
+ for (const value of bytes){
55
+ binary += String.fromCharCode(value);
56
+ }
57
+ return btoa(binary);
58
+ }
59
+ function fromJsonValue(value) {
60
+ if (typeof value !== "string") {
61
+ return null;
62
+ }
63
+ try {
64
+ const decoded = atob(value);
65
+ const bytes = new Uint8Array(decoded.length);
66
+ for(let i = 0; i < decoded.length; i += 1){
67
+ bytes[i] = decoded.charCodeAt(i);
68
+ }
69
+ return bytes;
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+ function readRoomMessagePayload(payload) {
75
+ if (!payload || typeof payload !== "object") {
76
+ return null;
77
+ }
78
+ const type = payload.type;
79
+ if (typeof type !== "string" && typeof type !== "number") {
80
+ return null;
81
+ }
82
+ const data = payload.data;
83
+ return {
84
+ type,
85
+ data
86
+ };
87
+ }
88
+ export function parseMatchFoundPayload(payload) {
89
+ if (!payload || typeof payload !== "object") {
90
+ return null;
91
+ }
92
+ const room = payload.room;
93
+ const roomType = payload.room_type;
94
+ const size = payload.size;
95
+ const participants = payload.participants;
96
+ if (typeof room !== "string" || typeof roomType !== "string" || typeof size !== "number" || !Number.isFinite(size) || !isStringArray(participants)) {
97
+ return null;
98
+ }
99
+ return {
100
+ room,
101
+ roomType,
102
+ size,
103
+ participants
104
+ };
105
+ }
106
+ export class NexisRoom {
107
+ client;
108
+ id;
109
+ onStateChange;
110
+ constructor(client, roomId){
111
+ this.client = client;
112
+ this.id = roomId;
113
+ this.onStateChange = this.buildStateChangeSubscription();
114
+ }
115
+ get state() {
116
+ return this.client.getRoomState(this.id);
117
+ }
118
+ send(type, message) {
119
+ this.client.sendRoomMessage(this.id, type, message);
120
+ }
121
+ sendBytes(type, bytes) {
122
+ const normalized = bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes);
123
+ this.client.sendRoomMessageBytes(this.id, type, normalized);
124
+ }
125
+ onMessage(type, callback) {
126
+ return this.client.onRoomMessage(this.id, type, callback);
127
+ }
128
+ buildStateChangeSubscription() {
129
+ const subscribe = (callback)=>this.client.onRoomState(this.id, callback);
130
+ subscribe.once = (callback)=>this.client.onRoomStateOnce(this.id, callback);
131
+ subscribe.select = (path, callback)=>this.client.onRoomStateSelect(this.id, path, callback);
132
+ return subscribe;
133
+ }
134
+ }
135
+ export class NexisClient {
136
+ socket;
137
+ url;
138
+ connectOptions;
139
+ rpc = new RpcClient();
140
+ codec;
141
+ eventHandlers = new Map();
142
+ stateHandlers = new Set();
143
+ roomStateHandlers = new Map();
144
+ roomStateSelectors = new Map();
145
+ roomMessageHandlers = new Map();
146
+ roomStates = new Map();
147
+ roomSeq = new Map();
148
+ roomChecksum = new Map();
149
+ sessionId;
150
+ autoJoinMatchedRoom;
151
+ reconnecting = false;
152
+ disposed = false;
153
+ constructor(url, socket, codec, sessionId, options){
154
+ this.url = url;
155
+ this.socket = socket;
156
+ this.codec = codec;
157
+ this.sessionId = sessionId;
158
+ this.connectOptions = options;
159
+ this.autoJoinMatchedRoom = options.autoJoinMatchedRoom;
160
+ this.attachSocket(socket);
161
+ }
162
+ static connect(url, options) {
163
+ const resolved = normalizeConnectOptions(options);
164
+ return NexisClient.openSocketAndHandshake(url, resolved, resolved.sessionId).then(({ socket, codec, sessionId })=>new NexisClient(url, socket, codec, sessionId, resolved));
165
+ }
166
+ close() {
167
+ this.disposed = true;
168
+ this.socket.close();
169
+ }
170
+ attachSocket(socket) {
171
+ this.socket = socket;
172
+ this.socket.addEventListener("message", (event)=>{
173
+ void this.onRawMessage(event.data);
174
+ });
175
+ this.socket.addEventListener("close", ()=>{
176
+ this.rpc.rejectAll(new Error("socket closed"));
177
+ if (!this.disposed && this.connectOptions.autoReconnect) {
178
+ void this.tryReconnect();
179
+ }
180
+ });
181
+ }
182
+ async tryReconnect() {
183
+ if (this.reconnecting || this.disposed) {
184
+ return;
185
+ }
186
+ this.reconnecting = true;
187
+ this.dispatchEvent({
188
+ v: DEFAULT_VERSION,
189
+ t: "reconnect.start",
190
+ p: {
191
+ session_id: this.sessionId
192
+ }
193
+ });
194
+ let attempt = 0;
195
+ let delayMs = this.connectOptions.reconnectInitialDelayMs;
196
+ while(!this.disposed && attempt < this.connectOptions.reconnectMaxAttempts){
197
+ attempt += 1;
198
+ await NexisClient.wait(delayMs);
199
+ try {
200
+ const reconnect = await NexisClient.openSocketAndHandshake(this.url, this.connectOptions, this.sessionId);
201
+ this.codec = reconnect.codec;
202
+ this.sessionId = reconnect.sessionId ?? this.sessionId;
203
+ this.attachSocket(reconnect.socket);
204
+ this.dispatchEvent({
205
+ v: DEFAULT_VERSION,
206
+ t: "reconnect.ok",
207
+ p: {
208
+ attempt,
209
+ session_id: this.sessionId
210
+ }
211
+ });
212
+ this.reconnecting = false;
213
+ return;
214
+ } catch {
215
+ this.dispatchEvent({
216
+ v: DEFAULT_VERSION,
217
+ t: "reconnect.retry",
218
+ p: {
219
+ attempt
220
+ }
221
+ });
222
+ }
223
+ delayMs = Math.min(delayMs * 2, this.connectOptions.reconnectMaxDelayMs);
224
+ }
225
+ this.reconnecting = false;
226
+ this.dispatchEvent({
227
+ v: DEFAULT_VERSION,
228
+ t: "reconnect.failed",
229
+ p: {
230
+ session_id: this.sessionId
231
+ }
232
+ });
233
+ }
234
+ getSessionId() {
235
+ return this.sessionId;
236
+ }
237
+ room(roomId) {
238
+ return new NexisRoom(this, roomId);
239
+ }
240
+ async joinOrCreate(roomType, options) {
241
+ const roomId = typeof options?.roomId === "string" ? options.roomId : undefined;
242
+ const response = await this.sendRPC("room.join_or_create", {
243
+ roomType,
244
+ roomId,
245
+ options
246
+ }, roomId);
247
+ if (response && typeof response === "object" && typeof response.room === "string") {
248
+ return this.room(response.room);
249
+ }
250
+ if (roomId) {
251
+ return this.room(roomId);
252
+ }
253
+ return this.room(`${roomType}:default`);
254
+ }
255
+ listRooms(roomType) {
256
+ return this.sendRPC("room.list", roomType ? {
257
+ roomType
258
+ } : {});
259
+ }
260
+ enqueueMatchmaking(roomType, size = 2) {
261
+ return this.sendRPC("matchmaking.enqueue", {
262
+ roomType,
263
+ size
264
+ });
265
+ }
266
+ dequeueMatchmaking() {
267
+ return this.sendRPC("matchmaking.dequeue", {});
268
+ }
269
+ onStateChange(callback) {
270
+ this.stateHandlers.add(callback);
271
+ return ()=>this.stateHandlers.delete(callback);
272
+ }
273
+ onEvent(type, callback) {
274
+ const handlers = this.eventHandlers.get(type) ?? new Set();
275
+ handlers.add(callback);
276
+ this.eventHandlers.set(type, handlers);
277
+ return ()=>{
278
+ const current = this.eventHandlers.get(type);
279
+ if (!current) {
280
+ return;
281
+ }
282
+ current.delete(callback);
283
+ if (current.size === 0) {
284
+ this.eventHandlers.delete(type);
285
+ }
286
+ };
287
+ }
288
+ onMatchFound(callback) {
289
+ return this.onEvent("match.found", (message)=>{
290
+ const parsed = parseMatchFoundPayload(message.p);
291
+ if (!parsed) {
292
+ return;
293
+ }
294
+ callback(parsed, message);
295
+ });
296
+ }
297
+ sendRPC(type, payload, room) {
298
+ const { message, promise } = this.rpc.createRequest(type, payload, room);
299
+ this.sendEnvelope(message);
300
+ return promise;
301
+ }
302
+ getRoomState(roomId) {
303
+ return this.roomStates.get(roomId) ?? {};
304
+ }
305
+ sendRoomMessage(roomId, type, data) {
306
+ this.sendEnvelope({
307
+ v: DEFAULT_VERSION,
308
+ t: "room.message",
309
+ room: roomId,
310
+ p: {
311
+ type: String(type),
312
+ data
313
+ }
314
+ });
315
+ }
316
+ sendRoomMessageBytes(roomId, type, data) {
317
+ this.sendEnvelope({
318
+ v: DEFAULT_VERSION,
319
+ t: "room.message.bytes",
320
+ room: roomId,
321
+ p: {
322
+ type: String(type),
323
+ data_b64: toJsonValue(data)
324
+ }
325
+ });
326
+ }
327
+ onRoomMessage(roomId, type, callback) {
328
+ const key = String(type);
329
+ const byType = this.roomMessageHandlers.get(roomId) ?? new Map();
330
+ const handlers = byType.get(key) ?? new Set();
331
+ handlers.add(callback);
332
+ byType.set(key, handlers);
333
+ this.roomMessageHandlers.set(roomId, byType);
334
+ return ()=>{
335
+ const roomHandlers = this.roomMessageHandlers.get(roomId);
336
+ if (!roomHandlers) {
337
+ return;
338
+ }
339
+ const typeHandlers = roomHandlers.get(key);
340
+ if (!typeHandlers) {
341
+ return;
342
+ }
343
+ typeHandlers.delete(callback);
344
+ if (typeHandlers.size === 0) {
345
+ roomHandlers.delete(key);
346
+ }
347
+ if (roomHandlers.size === 0) {
348
+ this.roomMessageHandlers.delete(roomId);
349
+ }
350
+ };
351
+ }
352
+ onRoomState(roomId, callback) {
353
+ const handlers = this.roomStateHandlers.get(roomId) ?? new Set();
354
+ handlers.add(callback);
355
+ this.roomStateHandlers.set(roomId, handlers);
356
+ if (this.roomStates.has(roomId)) {
357
+ callback(this.roomStates.get(roomId) ?? {});
358
+ }
359
+ return ()=>{
360
+ const current = this.roomStateHandlers.get(roomId);
361
+ if (!current) {
362
+ return;
363
+ }
364
+ current.delete(callback);
365
+ if (current.size === 0) {
366
+ this.roomStateHandlers.delete(roomId);
367
+ }
368
+ };
369
+ }
370
+ onRoomStateOnce(roomId, callback) {
371
+ let disposed = false;
372
+ const off = this.onRoomState(roomId, (state)=>{
373
+ if (disposed) {
374
+ return;
375
+ }
376
+ disposed = true;
377
+ off();
378
+ callback(state);
379
+ });
380
+ return ()=>{
381
+ disposed = true;
382
+ off();
383
+ };
384
+ }
385
+ onRoomStateSelect(roomId, path, callback) {
386
+ const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
387
+ const currentState = this.getRoomState(roomId);
388
+ const registration = {
389
+ path: normalizedPath,
390
+ callback,
391
+ lastValue: currentState[normalizedPath]
392
+ };
393
+ const selectors = this.roomStateSelectors.get(roomId) ?? new Set();
394
+ selectors.add(registration);
395
+ this.roomStateSelectors.set(roomId, selectors);
396
+ return ()=>{
397
+ const current = this.roomStateSelectors.get(roomId);
398
+ if (!current) {
399
+ return;
400
+ }
401
+ current.delete(registration);
402
+ if (current.size === 0) {
403
+ this.roomStateSelectors.delete(roomId);
404
+ }
405
+ };
406
+ }
407
+ sendEnvelope(message) {
408
+ const bytes = this.codec.encode(message);
409
+ this.socket.send(bytes);
410
+ }
411
+ dispatchEvent(message) {
412
+ const handlers = this.eventHandlers.get(message.t);
413
+ if (!handlers) {
414
+ return;
415
+ }
416
+ for (const handler of handlers){
417
+ handler(message);
418
+ }
419
+ }
420
+ dispatchState(roomId, nextState, prevState) {
421
+ for (const handler of this.stateHandlers){
422
+ handler(nextState);
423
+ }
424
+ const roomHandlers = this.roomStateHandlers.get(roomId);
425
+ if (roomHandlers) {
426
+ for (const handler of roomHandlers){
427
+ handler(nextState);
428
+ }
429
+ }
430
+ const selectors = this.roomStateSelectors.get(roomId);
431
+ if (selectors) {
432
+ for (const registration of selectors){
433
+ const nextValue = nextState[registration.path];
434
+ const prevValue = prevState[registration.path];
435
+ if (!deepEqual(nextValue, prevValue)) {
436
+ registration.lastValue = nextValue;
437
+ registration.callback(nextValue, nextState);
438
+ }
439
+ }
440
+ }
441
+ }
442
+ dispatchRoomMessage(message) {
443
+ if (!message.room) {
444
+ return;
445
+ }
446
+ const payload = readRoomMessagePayload(message.p);
447
+ if (!payload) {
448
+ return;
449
+ }
450
+ const byType = this.roomMessageHandlers.get(message.room);
451
+ if (!byType) {
452
+ return;
453
+ }
454
+ const handlers = byType.get(String(payload.type));
455
+ if (!handlers) {
456
+ return;
457
+ }
458
+ for (const handler of handlers){
459
+ handler(payload.data, message);
460
+ }
461
+ }
462
+ async onRawMessage(raw) {
463
+ const bytes = await NexisClient.toBytes(raw);
464
+ if (!bytes) {
465
+ return;
466
+ }
467
+ let message;
468
+ try {
469
+ message = this.codec.decode(bytes);
470
+ } catch {
471
+ return;
472
+ }
473
+ if (message.t === "rpc.response") {
474
+ try {
475
+ this.rpc.resolveResponse(message);
476
+ } catch (error) {
477
+ if (error instanceof UnknownRidError) {
478
+ this.dispatchEvent({
479
+ v: DEFAULT_VERSION,
480
+ t: "error",
481
+ p: {
482
+ reason: error.message
483
+ }
484
+ });
485
+ return;
486
+ }
487
+ throw error;
488
+ }
489
+ return;
490
+ }
491
+ if (message.t === "state.snapshot") {
492
+ if (!message.room) {
493
+ return;
494
+ }
495
+ const snapshot = parseSnapshotPayload(message.p);
496
+ if (!snapshot) {
497
+ return;
498
+ }
499
+ const computedChecksum = await computeStateChecksum(snapshot.state);
500
+ if (snapshot.checksum && snapshot.checksum !== computedChecksum) {
501
+ this.sendEnvelope({
502
+ v: DEFAULT_VERSION,
503
+ t: "state.resync",
504
+ room: message.room,
505
+ p: {
506
+ since: this.roomSeq.get(message.room) ?? 0
507
+ }
508
+ });
509
+ return;
510
+ }
511
+ const checksum = snapshot.checksum ?? computedChecksum;
512
+ const prevState = this.roomStates.get(message.room) ?? {};
513
+ this.roomStates.set(message.room, snapshot.state);
514
+ this.roomSeq.set(message.room, snapshot.seq);
515
+ this.roomChecksum.set(message.room, checksum);
516
+ this.dispatchState(message.room, snapshot.state, prevState);
517
+ this.sendEnvelope({
518
+ v: DEFAULT_VERSION,
519
+ t: "state.ack",
520
+ room: message.room,
521
+ p: {
522
+ seq: snapshot.seq,
523
+ checksum
524
+ }
525
+ });
526
+ return;
527
+ }
528
+ if (message.t === "state.patch") {
529
+ if (!message.room) {
530
+ return;
531
+ }
532
+ const parsedPatch = parsePatchPayload(message.p);
533
+ if (!parsedPatch) {
534
+ return;
535
+ }
536
+ const currentSeq = this.roomSeq.get(message.room) ?? 0;
537
+ const patchSeq = parsedPatch.seq > 0 ? parsedPatch.seq : currentSeq + 1;
538
+ if (patchSeq <= currentSeq) {
539
+ return;
540
+ }
541
+ if (patchSeq > currentSeq + 1) {
542
+ this.sendEnvelope({
543
+ v: DEFAULT_VERSION,
544
+ t: "state.resync",
545
+ room: message.room,
546
+ p: {
547
+ since: currentSeq
548
+ }
549
+ });
550
+ return;
551
+ }
552
+ const currentState = this.roomStates.get(message.room) ?? {};
553
+ const nextState = applyPatch(currentState, parsedPatch.ops);
554
+ let localChecksum;
555
+ if (parsedPatch.checksum) {
556
+ localChecksum = await computeStateChecksum(nextState);
557
+ if (parsedPatch.checksum !== localChecksum) {
558
+ this.sendEnvelope({
559
+ v: DEFAULT_VERSION,
560
+ t: "state.resync",
561
+ room: message.room,
562
+ p: {
563
+ since: currentSeq,
564
+ checksum: this.roomChecksum.get(message.room)
565
+ }
566
+ });
567
+ return;
568
+ }
569
+ }
570
+ this.roomStates.set(message.room, nextState);
571
+ this.roomSeq.set(message.room, patchSeq);
572
+ if (parsedPatch.checksum) {
573
+ this.roomChecksum.set(message.room, parsedPatch.checksum);
574
+ } else if (localChecksum) {
575
+ this.roomChecksum.set(message.room, localChecksum);
576
+ }
577
+ this.dispatchState(message.room, nextState, currentState);
578
+ this.sendEnvelope({
579
+ v: DEFAULT_VERSION,
580
+ t: "state.ack",
581
+ room: message.room,
582
+ p: parsedPatch.checksum ? {
583
+ seq: patchSeq,
584
+ checksum: this.roomChecksum.get(message.room)
585
+ } : {
586
+ seq: patchSeq
587
+ }
588
+ });
589
+ return;
590
+ }
591
+ if (this.autoJoinMatchedRoom && message.t === "match.found") {
592
+ const parsed = parseMatchFoundPayload(message.p);
593
+ if (parsed) {
594
+ void this.joinOrCreate(parsed.roomType, {
595
+ roomId: parsed.room
596
+ }).catch(()=>undefined);
597
+ }
598
+ }
599
+ if (message.t === "room.message" && message.room) {
600
+ this.dispatchRoomMessage(message);
601
+ }
602
+ this.dispatchEvent(message);
603
+ }
604
+ static openSocketAndHandshake(url, options, sessionIdOverride) {
605
+ const socket = new WebSocket(url);
606
+ const jsonCodec = new JsonCodec();
607
+ const msgpackCodec = new MsgpackCodec();
608
+ return new Promise((resolve, reject)=>{
609
+ let settled = false;
610
+ const handshakeSessionId = sessionIdOverride ?? options.sessionId;
611
+ const onOpen = ()=>{
612
+ const handshake = {
613
+ v: DEFAULT_VERSION,
614
+ codecs: options.codecs,
615
+ project_id: options.projectId?.trim() || "anonymous",
616
+ token: options.token?.trim() || "",
617
+ session_id: handshakeSessionId
618
+ };
619
+ socket.send(JSON.stringify(handshake));
620
+ };
621
+ const onError = ()=>{
622
+ finishReject(new Error("socket connection failed"));
623
+ };
624
+ const onClose = ()=>{
625
+ finishReject(new Error("socket closed before handshake"));
626
+ };
627
+ const onMessage = async (event)=>{
628
+ try {
629
+ const message = await NexisClient.decodeHandshakeMessage(event.data, jsonCodec, msgpackCodec);
630
+ if (!message) {
631
+ return;
632
+ }
633
+ if (message.t === "error") {
634
+ finishReject(new Error(readErrorReason(message)));
635
+ return;
636
+ }
637
+ if (message.t !== "handshake.ok") {
638
+ return;
639
+ }
640
+ const negotiatedCodec = readCodecName(message);
641
+ const sessionId = readSessionId(message) ?? handshakeSessionId;
642
+ finishResolve({
643
+ socket,
644
+ codec: codecFor(negotiatedCodec),
645
+ sessionId
646
+ });
647
+ } catch (error) {
648
+ finishReject(new Error(`handshake decode failed: ${String(error)}`));
649
+ }
650
+ };
651
+ const cleanup = ()=>{
652
+ socket.removeEventListener("open", onOpen);
653
+ socket.removeEventListener("error", onError);
654
+ socket.removeEventListener("close", onClose);
655
+ socket.removeEventListener("message", onMessage);
656
+ };
657
+ const finishResolve = (result)=>{
658
+ if (settled) {
659
+ return;
660
+ }
661
+ settled = true;
662
+ cleanup();
663
+ resolve(result);
664
+ };
665
+ const finishReject = (error)=>{
666
+ if (settled) {
667
+ return;
668
+ }
669
+ settled = true;
670
+ cleanup();
671
+ reject(error);
672
+ };
673
+ socket.addEventListener("open", onOpen);
674
+ socket.addEventListener("error", onError);
675
+ socket.addEventListener("close", onClose);
676
+ socket.addEventListener("message", onMessage);
677
+ });
678
+ }
679
+ static wait(ms) {
680
+ return new Promise((resolve)=>setTimeout(resolve, ms));
681
+ }
682
+ static async decodeHandshakeMessage(raw, jsonCodec, msgpackCodec) {
683
+ if (typeof raw === "string") {
684
+ return JSON.parse(raw);
685
+ }
686
+ const bytes = await NexisClient.toBytes(raw);
687
+ if (!bytes) {
688
+ return null;
689
+ }
690
+ try {
691
+ return msgpackCodec.decode(bytes);
692
+ } catch {
693
+ return jsonCodec.decode(bytes);
694
+ }
695
+ }
696
+ static async toBytes(raw) {
697
+ if (raw instanceof Uint8Array) {
698
+ return raw;
699
+ }
700
+ if (raw instanceof ArrayBuffer) {
701
+ return new Uint8Array(raw);
702
+ }
703
+ if (raw instanceof Blob) {
704
+ return new Uint8Array(await raw.arrayBuffer());
705
+ }
706
+ if (typeof raw === "string") {
707
+ return new TextEncoder().encode(raw);
708
+ }
709
+ return null;
710
+ }
711
+ }
712
+ export async function connect(url, options) {
713
+ return NexisClient.connect(url, options);
714
+ }
715
+ export function decodeRoomBytes(payload) {
716
+ return fromJsonValue(payload);
717
+ }
718
+
719
+ //# sourceMappingURL=client.js.map