@colyseus/sdk 0.17.1 → 0.17.2

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.
Files changed (67) hide show
  1. package/build/cjs/3rd_party/discord.js +1 -1
  2. package/build/cjs/Auth.js +1 -1
  3. package/build/cjs/Client.js +1 -1
  4. package/build/cjs/Connection.js +1 -1
  5. package/build/cjs/HTTP.js +1 -1
  6. package/build/cjs/Protocol.js +1 -1
  7. package/build/cjs/Room.js +1 -1
  8. package/build/cjs/Storage.js +1 -1
  9. package/build/cjs/core/nanoevents.js +1 -1
  10. package/build/cjs/core/signal.js +1 -1
  11. package/build/cjs/core/utils.js +1 -1
  12. package/build/cjs/errors/Errors.js +1 -1
  13. package/build/cjs/index.js +1 -1
  14. package/build/cjs/legacy.js +1 -1
  15. package/build/cjs/serializer/NoneSerializer.js +1 -1
  16. package/build/cjs/serializer/SchemaSerializer.js +1 -1
  17. package/build/cjs/serializer/Serializer.js +1 -1
  18. package/build/cjs/transport/H3Transport.js +1 -1
  19. package/build/cjs/transport/WebSocketTransport.js +1 -1
  20. package/build/esm/3rd_party/discord.mjs +1 -1
  21. package/build/esm/Auth.mjs +1 -1
  22. package/build/esm/Client.mjs +1 -1
  23. package/build/esm/Connection.mjs +1 -1
  24. package/build/esm/HTTP.mjs +1 -1
  25. package/build/esm/Protocol.mjs +1 -1
  26. package/build/esm/Room.mjs +1 -1
  27. package/build/esm/Storage.mjs +1 -1
  28. package/build/esm/core/nanoevents.mjs +1 -1
  29. package/build/esm/core/signal.mjs +1 -1
  30. package/build/esm/core/utils.mjs +1 -1
  31. package/build/esm/errors/Errors.mjs +1 -1
  32. package/build/esm/index.mjs +1 -1
  33. package/build/esm/legacy.mjs +1 -1
  34. package/build/esm/serializer/NoneSerializer.mjs +1 -1
  35. package/build/esm/serializer/SchemaSerializer.mjs +1 -1
  36. package/build/esm/serializer/Serializer.mjs +1 -1
  37. package/build/esm/transport/H3Transport.mjs +1 -1
  38. package/build/esm/transport/WebSocketTransport.mjs +1 -1
  39. package/dist/colyseus-cocos-creator.js +1 -1
  40. package/dist/colyseus.js +1 -1
  41. package/dist/debug.js +1 -1
  42. package/lib/core/http_bkp.d.ts +10 -10
  43. package/package.json +7 -6
  44. package/src/3rd_party/discord.ts +48 -0
  45. package/src/Auth.ts +177 -0
  46. package/src/Client.ts +459 -0
  47. package/src/Connection.ts +51 -0
  48. package/src/HTTP.ts +545 -0
  49. package/src/HTTP_bkp.ts +67 -0
  50. package/src/Protocol.ts +25 -0
  51. package/src/Room.ts +505 -0
  52. package/src/Storage.ts +94 -0
  53. package/src/core/http_bkp.ts +358 -0
  54. package/src/core/nanoevents.ts +38 -0
  55. package/src/core/signal.ts +62 -0
  56. package/src/core/utils.ts +3 -0
  57. package/src/debug.ts +2743 -0
  58. package/src/errors/Errors.ts +29 -0
  59. package/src/index.ts +18 -0
  60. package/src/legacy.ts +29 -0
  61. package/src/serializer/FossilDeltaSerializer.ts +39 -0
  62. package/src/serializer/NoneSerializer.ts +9 -0
  63. package/src/serializer/SchemaSerializer.ts +61 -0
  64. package/src/serializer/Serializer.ts +23 -0
  65. package/src/transport/H3Transport.ts +199 -0
  66. package/src/transport/ITransport.ts +18 -0
  67. package/src/transport/WebSocketTransport.ts +53 -0
@@ -0,0 +1,29 @@
1
+ export enum CloseCode {
2
+ NORMAL_CLOSURE = 1000,
3
+ GOING_AWAY = 1001,
4
+ NO_STATUS_RECEIVED = 1005,
5
+ ABNORMAL_CLOSURE = 1006,
6
+
7
+ CONSENTED = 4000,
8
+ SERVER_SHUTDOWN = 4001,
9
+ WITH_ERROR = 4002,
10
+ DEVMODE_RESTART = 4010
11
+ }
12
+
13
+ export class ServerError extends Error {
14
+ public code: number;
15
+
16
+ constructor(code: number, message: string) {
17
+ super(message);
18
+
19
+ this.name = "ServerError";
20
+ this.code = code;
21
+ }
22
+ }
23
+
24
+ export class AbortError extends Error {
25
+ constructor(message: string) {
26
+ super(message);
27
+ this.name = "AbortError";
28
+ }
29
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ import './legacy';
2
+
3
+ export { ColyseusSDK, Client, MatchMakeError, type JoinOptions, type EndpointSettings, type ClientOptions, type ISeatReservation as SeatReservation } from './Client.ts';
4
+ export { Protocol, ErrorCode } from './Protocol.ts';
5
+ export { Room, type RoomAvailable } from './Room.ts';
6
+ export { Auth, type AuthSettings, type PopupSettings } from "./Auth.ts";
7
+ export { ServerError, CloseCode } from './errors/Errors.ts';
8
+
9
+ /*
10
+ * Serializers
11
+ */
12
+ import { SchemaSerializer, getStateCallbacks } from "./serializer/SchemaSerializer.ts";
13
+ import { NoneSerializer } from "./serializer/NoneSerializer.ts";
14
+ import { registerSerializer } from './serializer/Serializer.ts';
15
+
16
+ export { registerSerializer, SchemaSerializer, getStateCallbacks };
17
+ registerSerializer('schema', SchemaSerializer);
18
+ registerSerializer('none', NoneSerializer);
package/src/legacy.ts ADDED
@@ -0,0 +1,29 @@
1
+ //
2
+ // Polyfills for legacy environments
3
+ //
4
+
5
+ /*
6
+ * Support Android 4.4.x
7
+ */
8
+ if (!ArrayBuffer.isView) {
9
+ ArrayBuffer.isView = (a: any): a is ArrayBufferView => {
10
+ return a !== null && typeof (a) === 'object' && a.buffer instanceof ArrayBuffer;
11
+ };
12
+ }
13
+
14
+ // Define globalThis if not available.
15
+ // https://github.com/colyseus/colyseus.js/issues/86
16
+ if (
17
+ typeof (globalThis) === "undefined" &&
18
+ typeof (window) !== "undefined"
19
+ ) {
20
+ // @ts-ignore
21
+ window['globalThis'] = window;
22
+ }
23
+
24
+ // Cocos Creator does not provide "FormData"
25
+ // Define a dummy implementation so it doesn't crash
26
+ if (typeof(FormData) === "undefined") {
27
+ // @ts-ignore
28
+ globalThis['FormData'] = class {};
29
+ }
@@ -0,0 +1,39 @@
1
+ /*
2
+
3
+ // Dependencies:
4
+ // "@gamestdio/state-listener": "^3.1.0",
5
+ // "fossil-delta": "^1.0.0",
6
+
7
+ import { Serializer } from "./Serializer";
8
+
9
+ import { StateContainer } from '@gamestdio/state-listener';
10
+ import * as fossilDelta from 'fossil-delta';
11
+ import * as msgpack from '../msgpack';
12
+
13
+ export class FossilDeltaSerializer<State= any> implements Serializer<State> {
14
+ api: StateContainer<State> = new StateContainer<State>({} as State);
15
+ protected previousState: any;
16
+
17
+ getState(): State {
18
+ return this.api.state;
19
+ }
20
+
21
+ setState(encodedState: any): void {
22
+ this.previousState = new Uint8Array(encodedState);
23
+ this.api.set(msgpack.decode(this.previousState));
24
+ }
25
+
26
+ patch(binaryPatch) {
27
+ // apply patch
28
+ this.previousState = new Uint8Array(fossilDelta.apply(this.previousState, binaryPatch));
29
+
30
+ // trigger update callbacks
31
+ this.api.set(msgpack.decode(this.previousState));
32
+ }
33
+
34
+ teardown() {
35
+ this.api.removeAllListeners();
36
+ }
37
+ }
38
+
39
+ */
@@ -0,0 +1,9 @@
1
+ import type { Serializer } from "./Serializer.ts";
2
+
3
+ export class NoneSerializer<T = any> implements Serializer<T> {
4
+ setState(rawState: any): void {}
5
+ getState() { return null; }
6
+ patch(patches) {}
7
+ teardown() { }
8
+ handshake(bytes: Uint8Array) {}
9
+ }
@@ -0,0 +1,61 @@
1
+ import { Schema, Decoder, Reflection, Iterator, getDecoderStateCallbacks } from "@colyseus/schema";
2
+ import type { Serializer } from "./Serializer.ts";
3
+ import type { Room } from "../Room.ts";
4
+
5
+ export type SchemaConstructor<T = Schema> = new (...args: any[]) => T;
6
+
7
+ //
8
+ // TODO: use a schema interface, which even having duplicate definitions, it could be used to get the callback proxy.
9
+ //
10
+ // ```ts
11
+ // export type SchemaCallbackProxy<RoomState> = (<T extends ISchema>(instance: T) => CallbackProxy<T>);
12
+ // export function getStateCallbacks<T extends ISchema>(room: Room<T>) {
13
+ // ```
14
+ //
15
+ export function getStateCallbacks<T>(room: Room<any, T>) {
16
+ try {
17
+ // SchemaSerializer
18
+ // @ts-ignore
19
+ return getDecoderStateCallbacks<T>((room['serializer'] as unknown as SchemaSerializer<T>).decoder);
20
+ } catch (e) {
21
+ // NoneSerializer
22
+ return undefined;
23
+ }
24
+ }
25
+
26
+ export class SchemaSerializer<T extends Schema = any> implements Serializer<T> {
27
+ state: T;
28
+ decoder: Decoder<T>;
29
+
30
+ setState(encodedState: Uint8Array, it?: Iterator) {
31
+ this.decoder.decode(encodedState, it);
32
+ }
33
+
34
+ getState() {
35
+ return this.state;
36
+ }
37
+
38
+ patch(patches: Uint8Array, it?: Iterator) {
39
+ return this.decoder.decode(patches, it);
40
+ }
41
+
42
+ teardown() {
43
+ this.decoder.root.clearRefs();
44
+ }
45
+
46
+ handshake(bytes: Uint8Array, it?: Iterator) {
47
+ if (this.state) {
48
+ //
49
+ // TODO: validate definitions against concreate this.state instance
50
+ //
51
+ Reflection.decode(bytes, it); // no-op
52
+
53
+ this.decoder = new Decoder(this.state);
54
+
55
+ } else {
56
+ // initialize reflected state from server
57
+ this.decoder = Reflection.decode(bytes, it);
58
+ this.state = this.decoder.state;
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,23 @@
1
+ import type { Iterator } from "@colyseus/schema";
2
+
3
+ export interface Serializer<State> {
4
+ setState(data: Uint8Array, it?: Iterator): void;
5
+ getState(): State;
6
+
7
+ patch(data: Uint8Array, it?: Iterator): void;
8
+ teardown(): void;
9
+
10
+ handshake?(bytes: Uint8Array, it?: any): void;
11
+ }
12
+
13
+ const serializers: { [id: string]: any } = {};
14
+
15
+ export function registerSerializer (id: string, serializer: any) {
16
+ serializers[id] = serializer;
17
+ }
18
+
19
+ export function getSerializer (id: string) {
20
+ const serializer = serializers[id];
21
+ if (!serializer) { throw new Error("missing serializer: " + id); }
22
+ return serializer;
23
+ }
@@ -0,0 +1,199 @@
1
+ import { encode, decode, type Iterator } from '@colyseus/schema';
2
+ import type { ITransport, ITransportEventMap } from "./ITransport.ts";
3
+
4
+ export class H3TransportTransport implements ITransport {
5
+ wt: WebTransport;
6
+ isOpen: boolean = false;
7
+ events: ITransportEventMap;
8
+
9
+ reader: ReadableStreamDefaultReader;
10
+ writer: WritableStreamDefaultWriter;
11
+
12
+ unreliableReader: ReadableStreamDefaultReader<Uint8Array>;
13
+ unreliableWriter: WritableStreamDefaultWriter<Uint8Array>;
14
+
15
+ private lengthPrefixBuffer = new Uint8Array(9); // 9 bytes is the maximum length of a length prefix
16
+
17
+ constructor(events: ITransportEventMap) {
18
+ this.events = events;
19
+ }
20
+
21
+ public connect(url: string, options: any = {}) {
22
+ const wtOpts: WebTransportOptions = options.fingerprint && ({
23
+ // requireUnreliable: true,
24
+ // congestionControl: "default", // "low-latency" || "throughput"
25
+
26
+ serverCertificateHashes: [{
27
+ algorithm: 'sha-256',
28
+ value: new Uint8Array(options.fingerprint).buffer
29
+ }]
30
+ }) || undefined;
31
+
32
+ this.wt = new WebTransport(url, wtOpts);
33
+
34
+ this.wt.ready.then((e) => {
35
+ console.log("WebTransport ready!", e)
36
+ this.isOpen = true;
37
+
38
+ this.unreliableReader = this.wt.datagrams.readable.getReader();
39
+ this.unreliableWriter = this.wt.datagrams.writable.getWriter();
40
+
41
+ const incomingBidi = this.wt.incomingBidirectionalStreams.getReader();
42
+ incomingBidi.read().then((stream) => {
43
+ this.reader = stream.value.readable.getReader();
44
+ this.writer = stream.value.writable.getWriter();
45
+
46
+ // immediately write room/sessionId for establishing the room connection
47
+ this.sendSeatReservation(options.roomId, options.sessionId, options.reconnectionToken, options.skipHandshake);
48
+
49
+ // start reading incoming data
50
+ this.readIncomingData();
51
+ this.readIncomingUnreliableData();
52
+
53
+ }).catch((e) => {
54
+ console.error("failed to read incoming stream", e);
55
+ console.error("TODO: close the connection");
56
+ });
57
+
58
+ // this.events.onopen(e);
59
+ }).catch((e: WebTransportCloseInfo) => {
60
+ // this.events.onerror(e);
61
+ // this.events.onclose({ code: e.closeCode, reason: e.reason });
62
+ console.log("WebTransport not ready!", e)
63
+ this._close();
64
+ });
65
+
66
+ this.wt.closed.then((e: WebTransportCloseInfo) => {
67
+ console.log("WebTransport closed w/ success", e)
68
+ this.events.onclose({ code: e.closeCode, reason: e.reason });
69
+
70
+ }).catch((e: WebTransportCloseInfo) => {
71
+ console.log("WebTransport closed w/ error", e)
72
+ this.events.onerror(e);
73
+ this.events.onclose({ code: e.closeCode, reason: e.reason });
74
+ }).finally(() => {
75
+ this._close();
76
+ });
77
+ }
78
+
79
+ public send(data: Buffer | Uint8Array): void {
80
+ const prefixLength = encode.number(this.lengthPrefixBuffer as any, data.length, { offset: 0 });
81
+ const dataWithPrefixedLength = new Uint8Array(prefixLength + data.length);
82
+ dataWithPrefixedLength.set(this.lengthPrefixBuffer.subarray(0, prefixLength), 0);
83
+ dataWithPrefixedLength.set(data, prefixLength);
84
+ this.writer.write(dataWithPrefixedLength);
85
+ }
86
+
87
+ public sendUnreliable(data: Buffer | Uint8Array): void {
88
+ const prefixLength = encode.number(this.lengthPrefixBuffer as any, data.length, { offset: 0 });
89
+ const dataWithPrefixedLength = new Uint8Array(prefixLength + data.length);
90
+ dataWithPrefixedLength.set(this.lengthPrefixBuffer.subarray(0, prefixLength), 0);
91
+ dataWithPrefixedLength.set(data, prefixLength);
92
+ this.unreliableWriter.write(dataWithPrefixedLength);
93
+ }
94
+
95
+ public close(code?: number, reason?: string) {
96
+ try {
97
+ this.wt.close({ closeCode: code, reason: reason });
98
+ } catch (e) {
99
+ console.error(e);
100
+ }
101
+ }
102
+
103
+ protected async readIncomingData() {
104
+ let result: ReadableStreamReadResult<Uint8Array>;
105
+
106
+ while (this.isOpen) {
107
+ try {
108
+ result = await this.reader.read();
109
+
110
+ //
111
+ // a single read may contain multiple messages
112
+ // each message is prefixed with its length
113
+ //
114
+
115
+ const messages = result.value;
116
+ const it: Iterator = { offset: 0 };
117
+ do {
118
+ //
119
+ // QUESTION: should we buffer the message in case it's not fully read?
120
+ //
121
+
122
+ const length = decode.number(messages as any, it);
123
+ this.events.onmessage({ data: messages.subarray(it.offset, it.offset + length) });
124
+ it.offset += length;
125
+ } while (it.offset < messages.length);
126
+
127
+ } catch (e) {
128
+ if (e.message.indexOf("session is closed") === -1) {
129
+ console.error("H3Transport: failed to read incoming data", e);
130
+ }
131
+ break;
132
+ }
133
+
134
+ if (result.done) {
135
+ break;
136
+ }
137
+ }
138
+ }
139
+
140
+ protected async readIncomingUnreliableData() {
141
+ let result: ReadableStreamReadResult<Uint8Array>;
142
+
143
+ while (this.isOpen) {
144
+ try {
145
+ result = await this.unreliableReader.read();
146
+
147
+ //
148
+ // a single read may contain multiple messages
149
+ // each message is prefixed with its length
150
+ //
151
+
152
+ const messages = result.value;
153
+ const it: Iterator = { offset: 0 };
154
+ do {
155
+ //
156
+ // QUESTION: should we buffer the message in case it's not fully read?
157
+ //
158
+
159
+ const length = decode.number(messages as any, it);
160
+ this.events.onmessage({ data: messages.subarray(it.offset, it.offset + length) });
161
+ it.offset += length;
162
+ } while (it.offset < messages.length);
163
+
164
+ } catch (e) {
165
+ if (e.message.indexOf("session is closed") === -1) {
166
+ console.error("H3Transport: failed to read incoming data", e);
167
+ }
168
+ break;
169
+ }
170
+
171
+ if (result.done) {
172
+ break;
173
+ }
174
+ }
175
+ }
176
+
177
+ protected sendSeatReservation (roomId: string, sessionId: string, reconnectionToken?: string, skipHandshake?: boolean) {
178
+ const it: Iterator = { offset: 0 };
179
+ const bytes: number[] = [];
180
+
181
+ encode.string(bytes, roomId, it);
182
+ encode.string(bytes, sessionId, it);
183
+
184
+ if (reconnectionToken) {
185
+ encode.string(bytes, reconnectionToken, it);
186
+ }
187
+
188
+ if (skipHandshake) {
189
+ encode.boolean(bytes, 1, it);
190
+ }
191
+
192
+ this.writer.write(new Uint8Array(bytes).buffer);
193
+ }
194
+
195
+ protected _close() {
196
+ this.isOpen = false;
197
+ }
198
+
199
+ }
@@ -0,0 +1,18 @@
1
+ export interface ITransportEventMap {
2
+ onopen?: ((ev: any) => any) | null;
3
+ onmessage?: ((ev: any) => any) | null;
4
+ onclose?: ((ev: any) => any) | null;
5
+ onerror?: ((ev: any) => any) | null;
6
+ }
7
+
8
+ export interface ITransportConstructor {
9
+ new (events: ITransportEventMap): ITransport;
10
+ }
11
+
12
+ export interface ITransport {
13
+ isOpen: boolean;
14
+ send(data: Buffer | Uint8Array): void;
15
+ sendUnreliable(data: Buffer | Uint8Array): void;
16
+ connect(url: string, options?: any): void;
17
+ close(code?: number, reason?: string): void;
18
+ }
@@ -0,0 +1,53 @@
1
+ import NodeWebSocket from "ws";
2
+ import type { ITransport, ITransportEventMap } from "./ITransport.ts";
3
+
4
+ const WebSocket = globalThis.WebSocket || NodeWebSocket;
5
+
6
+ export class WebSocketTransport implements ITransport {
7
+ ws: WebSocket | NodeWebSocket;
8
+ protocols?: string | string[];
9
+
10
+ events: ITransportEventMap;
11
+
12
+ constructor(events: ITransportEventMap) {
13
+ this.events = events;
14
+ }
15
+
16
+ public send(data: Buffer | Uint8Array): void {
17
+ this.ws.send(data);
18
+ }
19
+
20
+ public sendUnreliable(data: Uint8Array): void {
21
+ console.warn("colyseus.js: The WebSocket transport does not support unreliable messages");
22
+ }
23
+
24
+ /**
25
+ * @param url URL to connect to
26
+ * @param headers custom headers to send with the connection (only supported in Node.js. Web Browsers do not allow setting custom headers)
27
+ */
28
+ public connect(url: string, headers?: any): void {
29
+ try {
30
+ // Node or Bun environments (supports custom headers)
31
+ this.ws = new WebSocket(url, { headers, protocols: this.protocols });
32
+
33
+ } catch (e) {
34
+ // browser environment (custom headers not supported)
35
+ this.ws = new WebSocket(url, this.protocols);
36
+ }
37
+
38
+ this.ws.binaryType = 'arraybuffer';
39
+ this.ws.onopen = this.events.onopen;
40
+ this.ws.onmessage = this.events.onmessage;
41
+ this.ws.onclose = this.events.onclose;
42
+ this.ws.onerror = this.events.onerror;
43
+ }
44
+
45
+ public close(code?: number, reason?: string) {
46
+ this.ws.close(code, reason);
47
+ }
48
+
49
+ get isOpen() {
50
+ return this.ws.readyState === WebSocket.OPEN;
51
+ }
52
+
53
+ }