@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.
- package/build/cjs/3rd_party/discord.js +1 -1
- package/build/cjs/Auth.js +1 -1
- package/build/cjs/Client.js +1 -1
- package/build/cjs/Connection.js +1 -1
- package/build/cjs/HTTP.js +1 -1
- package/build/cjs/Protocol.js +1 -1
- package/build/cjs/Room.js +1 -1
- package/build/cjs/Storage.js +1 -1
- package/build/cjs/core/nanoevents.js +1 -1
- package/build/cjs/core/signal.js +1 -1
- package/build/cjs/core/utils.js +1 -1
- package/build/cjs/errors/Errors.js +1 -1
- package/build/cjs/index.js +1 -1
- package/build/cjs/legacy.js +1 -1
- package/build/cjs/serializer/NoneSerializer.js +1 -1
- package/build/cjs/serializer/SchemaSerializer.js +1 -1
- package/build/cjs/serializer/Serializer.js +1 -1
- package/build/cjs/transport/H3Transport.js +1 -1
- package/build/cjs/transport/WebSocketTransport.js +1 -1
- package/build/esm/3rd_party/discord.mjs +1 -1
- package/build/esm/Auth.mjs +1 -1
- package/build/esm/Client.mjs +1 -1
- package/build/esm/Connection.mjs +1 -1
- package/build/esm/HTTP.mjs +1 -1
- package/build/esm/Protocol.mjs +1 -1
- package/build/esm/Room.mjs +1 -1
- package/build/esm/Storage.mjs +1 -1
- package/build/esm/core/nanoevents.mjs +1 -1
- package/build/esm/core/signal.mjs +1 -1
- package/build/esm/core/utils.mjs +1 -1
- package/build/esm/errors/Errors.mjs +1 -1
- package/build/esm/index.mjs +1 -1
- package/build/esm/legacy.mjs +1 -1
- package/build/esm/serializer/NoneSerializer.mjs +1 -1
- package/build/esm/serializer/SchemaSerializer.mjs +1 -1
- package/build/esm/serializer/Serializer.mjs +1 -1
- package/build/esm/transport/H3Transport.mjs +1 -1
- package/build/esm/transport/WebSocketTransport.mjs +1 -1
- package/dist/colyseus-cocos-creator.js +1 -1
- package/dist/colyseus.js +1 -1
- package/dist/debug.js +1 -1
- package/lib/core/http_bkp.d.ts +10 -10
- package/package.json +7 -6
- package/src/3rd_party/discord.ts +48 -0
- package/src/Auth.ts +177 -0
- package/src/Client.ts +459 -0
- package/src/Connection.ts +51 -0
- package/src/HTTP.ts +545 -0
- package/src/HTTP_bkp.ts +67 -0
- package/src/Protocol.ts +25 -0
- package/src/Room.ts +505 -0
- package/src/Storage.ts +94 -0
- package/src/core/http_bkp.ts +358 -0
- package/src/core/nanoevents.ts +38 -0
- package/src/core/signal.ts +62 -0
- package/src/core/utils.ts +3 -0
- package/src/debug.ts +2743 -0
- package/src/errors/Errors.ts +29 -0
- package/src/index.ts +18 -0
- package/src/legacy.ts +29 -0
- package/src/serializer/FossilDeltaSerializer.ts +39 -0
- package/src/serializer/NoneSerializer.ts +9 -0
- package/src/serializer/SchemaSerializer.ts +61 -0
- package/src/serializer/Serializer.ts +23 -0
- package/src/transport/H3Transport.ts +199 -0
- package/src/transport/ITransport.ts +18 -0
- 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,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
|
+
}
|