@vgai/p2p-colyseus 0.1.0

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/src/events.ts ADDED
@@ -0,0 +1,58 @@
1
+ export class Signal<T extends (...args: never[]) => void> {
2
+ private readonly handlers = new Set<T>();
3
+
4
+ add(handler: T): () => void {
5
+ this.handlers.add(handler);
6
+ return () => this.remove(handler);
7
+ }
8
+
9
+ remove(handler: T): void {
10
+ this.handlers.delete(handler);
11
+ }
12
+
13
+ emit(...args: Parameters<T>): void {
14
+ for (const handler of [...this.handlers]) handler(...args);
15
+ }
16
+ }
17
+
18
+ export interface CallableSignal<T extends (...args: never[]) => void> {
19
+ (handler: T): () => void;
20
+ add(handler: T): () => void;
21
+ once(handler: T): void;
22
+ remove(handler: T): void;
23
+ clear(): void;
24
+ invoke(...args: Parameters<T>): void;
25
+ invokeAsync(...args: Parameters<T>): Promise<unknown[]>;
26
+ emit(...args: Parameters<T>): void;
27
+ }
28
+
29
+ export function createSignal<T extends (...args: never[]) => void>(): CallableSignal<T> {
30
+ const handlers = new Set<T>();
31
+ const signal = ((handler: T) => signal.add(handler)) as CallableSignal<T>;
32
+ signal.add = (handler: T) => {
33
+ handlers.add(handler);
34
+ return () => signal.remove(handler);
35
+ };
36
+ signal.once = (handler: T) => {
37
+ const wrapped = ((...args: Parameters<T>) => {
38
+ signal.remove(wrapped as T);
39
+ handler(...args);
40
+ }) as T;
41
+ signal.add(wrapped);
42
+ };
43
+ signal.remove = (handler: T) => {
44
+ handlers.delete(handler);
45
+ };
46
+ signal.clear = () => {
47
+ handlers.clear();
48
+ };
49
+ signal.invoke = (...args: Parameters<T>) => {
50
+ signal.emit(...args);
51
+ };
52
+ signal.invokeAsync = async (...args: Parameters<T>) =>
53
+ Promise.all([...handlers].map((handler) => handler(...args)));
54
+ signal.emit = (...args: Parameters<T>) => {
55
+ for (const handler of [...handlers]) handler(...args);
56
+ };
57
+ return signal;
58
+ }
package/src/index.ts ADDED
@@ -0,0 +1,196 @@
1
+ export type { P2PAccessTokenClaims } from './access-token';
2
+ export { createP2PAccessToken, verifyP2PAccessToken } from './access-token';
3
+ export { Callbacks } from './callbacks';
4
+ export type { PacketChannel, PacketChannelMode } from './channels';
5
+ export {
6
+ AbortError,
7
+ Auth,
8
+ Client,
9
+ ColyseusSDK,
10
+ CompatRoom,
11
+ configureP2PColyseusClient,
12
+ connectRoomOverChannel,
13
+ getStateCallbacks,
14
+ MatchMakeError,
15
+ registerLoopbackRoom,
16
+ registerSerializer,
17
+ resetP2PColyseusClient,
18
+ } from './client';
19
+ export { SignalingRelayCoordinator } from './cloudflare/coordinator';
20
+ export type { RelayLimits } from './cloudflare/limits';
21
+ export { DEFAULT_RELAY_LIMITS } from './cloudflare/limits';
22
+ export { applyStatePatch, clone, createStatePatch, encodeSnapshot } from './codec';
23
+ export type {
24
+ NetDiagnostics,
25
+ RelayProofDiagnostics,
26
+ RelayQuota,
27
+ TransportName,
28
+ } from './diagnostics';
29
+ export { createLoopbackPair } from './loopback';
30
+ export type { UniversalNodeServer } from './node-websocket';
31
+ export { NodeWebSocketPacketChannel, startUniversalNodeServer } from './node-websocket';
32
+ export type { RoomCache, SeatReservation, ServerOptions, Type } from './platform';
33
+ export {
34
+ __globalEndpoints,
35
+ accept,
36
+ addRoomType,
37
+ auth,
38
+ buildSeatReservation,
39
+ ClientArray,
40
+ ClientState,
41
+ Clock,
42
+ CloseCode,
43
+ connectClientToRoom,
44
+ controller,
45
+ create,
46
+ createEndpoint,
47
+ createInternalContext,
48
+ createMiddleware,
49
+ createRoom,
50
+ createRouter,
51
+ Deferred,
52
+ Delayed,
53
+ debugAndPrintError,
54
+ debugConnection,
55
+ debugDriver,
56
+ debugError,
57
+ debugMatchMaking,
58
+ debugMessage,
59
+ debugPatch,
60
+ debugPresence,
61
+ defineRoom,
62
+ defineRoomType,
63
+ defineServer,
64
+ disconnectAll,
65
+ driver,
66
+ dynamicImport,
67
+ ErrorCode,
68
+ findOneRoomAvailable,
69
+ generateId,
70
+ getAllHandlers,
71
+ getBearerToken,
72
+ getHandler,
73
+ getLocalRoomById,
74
+ getMessageBytes,
75
+ getRoomById,
76
+ getRoomClass,
77
+ getTransport,
78
+ gracefullyShutdown,
79
+ Hash,
80
+ handleCreateRoom,
81
+ healthCheckAllProcesses,
82
+ healthCheckProcessId,
83
+ initializeRoomCache,
84
+ isDevMode,
85
+ JWT,
86
+ join,
87
+ joinById,
88
+ joinOrCreate,
89
+ LobbyRoom,
90
+ LocalDriver,
91
+ LocalPresence,
92
+ listen,
93
+ logger,
94
+ matchMaker,
95
+ monitor,
96
+ NoopTransport,
97
+ OnAuthException,
98
+ OnCreateException,
99
+ OnDisposeException,
100
+ OnJoinException,
101
+ OnLeaveException,
102
+ OnMessageException,
103
+ onReady,
104
+ Protocol,
105
+ playground,
106
+ presence,
107
+ processId,
108
+ publicAddress,
109
+ QueueRoom,
110
+ query,
111
+ RedisDriver,
112
+ RedisPresence,
113
+ RegisteredHandler,
114
+ RelayRoom,
115
+ RoomInternalState,
116
+ reconnect,
117
+ remoteRoomCall,
118
+ removeRoomType,
119
+ requestFromIPC,
120
+ reserveMultipleSeatsFor,
121
+ reserveSeatFor,
122
+ room,
123
+ SchemaSerializer,
124
+ Server,
125
+ ServerError,
126
+ SimulationIntervalException,
127
+ setHealthChecksEnabled,
128
+ setup,
129
+ spliceOne,
130
+ state,
131
+ stats,
132
+ subscribeIPC,
133
+ subscribeLobby,
134
+ TimedEventException,
135
+ Transport,
136
+ toNodeHandler,
137
+ updateLobby,
138
+ validate,
139
+ WebSocketClient,
140
+ WebSocketTransport,
141
+ } from './platform';
142
+ export type { Envelope, Snapshot, StatePatch, StatePatchOperation } from './protocol';
143
+ export { isEnvelope, P2P_CLOSE_CODES } from './protocol';
144
+ export { CloudflareRelayPacketChannel } from './relay';
145
+ export type { CompatClient } from './room';
146
+ export { Room } from './room';
147
+ export type { RoomClass } from './runtime';
148
+ export { UniversalRoomRuntime } from './runtime';
149
+ export {
150
+ $changes,
151
+ $childType,
152
+ $decoder,
153
+ $deleteByIndex,
154
+ $encoder,
155
+ $filter,
156
+ $getByIndex,
157
+ $refId,
158
+ $track,
159
+ ArraySchema,
160
+ Callbacks as SchemaCallbacks,
161
+ ChangeTree,
162
+ CollectionSchema,
163
+ Decoder,
164
+ decode,
165
+ decodeKeyValueOperation,
166
+ decodeSchemaOperation,
167
+ defineCustomTypes,
168
+ defineTypes,
169
+ deprecated,
170
+ dumpChanges,
171
+ Encoder,
172
+ encode,
173
+ encodeArray,
174
+ encodeKeyValueOperation,
175
+ encodeSchemaOperation,
176
+ entity,
177
+ getDecoderStateCallbacks,
178
+ getRawChangesCallback,
179
+ MapSchema,
180
+ Metadata,
181
+ OPERATION,
182
+ Reflection,
183
+ ReflectionField,
184
+ ReflectionType,
185
+ registerType,
186
+ Schema,
187
+ SetSchema,
188
+ StateCallbackStrategy,
189
+ StateView,
190
+ schema,
191
+ TypeContext,
192
+ type,
193
+ view,
194
+ } from './schema';
195
+ export { createWebRTCDataChannelPacketChannel, WebRTCDataChannelPacketChannel } from './webrtc';
196
+ export { createWebSocketPacketChannel, WebSocketPacketChannel } from './websocket';
@@ -0,0 +1,65 @@
1
+ import type { PacketChannel } from './channels';
2
+ import type { Envelope } from './protocol';
3
+
4
+ type EnvelopeHandler = (envelope: Envelope) => void;
5
+ type CloseHandler = (reason?: string) => void;
6
+
7
+ class LoopbackPacketChannel implements PacketChannel {
8
+ readonly mode = 'loopback' as const;
9
+ private peer: LoopbackPacketChannel | null = null;
10
+ private closed = false;
11
+ private readonly envelopeHandlers = new Set<EnvelopeHandler>();
12
+ private readonly closeHandlers = new Set<CloseHandler>();
13
+
14
+ constructor(readonly peerId: string) {}
15
+
16
+ link(peer: LoopbackPacketChannel): void {
17
+ this.peer = peer;
18
+ }
19
+
20
+ send(envelope: Envelope): void {
21
+ if (this.closed) throw new Error(`Loopback channel ${this.peerId} is closed`);
22
+ if (!this.peer || this.peer.closed)
23
+ throw new Error(`Loopback peer for ${this.peerId} is closed`);
24
+ queueMicrotask(() => this.peer?.emitEnvelope(envelope));
25
+ }
26
+
27
+ onEnvelope(handler: EnvelopeHandler): () => void {
28
+ this.envelopeHandlers.add(handler);
29
+ return () => this.envelopeHandlers.delete(handler);
30
+ }
31
+
32
+ onClose(handler: CloseHandler): () => void {
33
+ this.closeHandlers.add(handler);
34
+ return () => this.closeHandlers.delete(handler);
35
+ }
36
+
37
+ close(reason?: string): void {
38
+ if (this.closed) return;
39
+ this.closed = true;
40
+ this.emitClose(reason);
41
+ const peer = this.peer;
42
+ this.peer = null;
43
+ peer?.close(reason);
44
+ }
45
+
46
+ private emitEnvelope(envelope: Envelope): void {
47
+ if (this.closed) return;
48
+ for (const handler of this.envelopeHandlers) handler(envelope);
49
+ }
50
+
51
+ private emitClose(reason?: string): void {
52
+ for (const handler of this.closeHandlers) handler(reason);
53
+ }
54
+ }
55
+
56
+ export function createLoopbackPair(
57
+ aPeerId = 'loopback-a',
58
+ bPeerId = 'loopback-b',
59
+ ): readonly [PacketChannel, PacketChannel] {
60
+ const a = new LoopbackPacketChannel(aPeerId);
61
+ const b = new LoopbackPacketChannel(bPeerId);
62
+ a.link(b);
63
+ b.link(a);
64
+ return [a, b];
65
+ }
@@ -0,0 +1,77 @@
1
+ import type { AddressInfo } from 'node:net';
2
+ import { WebSocket, WebSocketServer } from 'ws';
3
+ import type { PacketChannel } from './channels';
4
+ import type { Envelope } from './protocol';
5
+ import { type RoomClass, UniversalRoomRuntime } from './runtime';
6
+
7
+ type EnvelopeHandler = (envelope: Envelope) => void;
8
+ type CloseHandler = (reason?: string) => void;
9
+
10
+ export class NodeWebSocketPacketChannel implements PacketChannel {
11
+ readonly mode = 'websocket' as const;
12
+ private readonly envelopeHandlers = new Set<EnvelopeHandler>();
13
+ private readonly closeHandlers = new Set<CloseHandler>();
14
+
15
+ constructor(
16
+ readonly peerId: string,
17
+ private readonly socket: WebSocket,
18
+ ) {
19
+ socket.on('message', (data) => {
20
+ const raw = typeof data === 'string' ? data : data.toString();
21
+ const envelope = JSON.parse(raw) as Envelope;
22
+ for (const handler of this.envelopeHandlers) handler(envelope);
23
+ });
24
+ socket.on('close', (_code, reason) => {
25
+ const text = reason.length > 0 ? reason.toString() : undefined;
26
+ for (const handler of this.closeHandlers) handler(text);
27
+ });
28
+ }
29
+
30
+ send(envelope: Envelope): void {
31
+ if (this.socket.readyState !== WebSocket.OPEN) {
32
+ throw new Error(`Node WebSocket for ${this.peerId} is not open`);
33
+ }
34
+ this.socket.send(JSON.stringify(envelope));
35
+ }
36
+
37
+ onEnvelope(handler: EnvelopeHandler): () => void {
38
+ this.envelopeHandlers.add(handler);
39
+ return () => this.envelopeHandlers.delete(handler);
40
+ }
41
+
42
+ onClose(handler: CloseHandler): () => void {
43
+ this.closeHandlers.add(handler);
44
+ return () => this.closeHandlers.delete(handler);
45
+ }
46
+
47
+ close(reason?: string): void {
48
+ this.socket.close(1000, reason);
49
+ }
50
+ }
51
+
52
+ export interface UniversalNodeServer {
53
+ readonly url: string;
54
+ close(): Promise<void>;
55
+ }
56
+
57
+ export async function startUniversalNodeServer(
58
+ roomName: string,
59
+ roomClass: RoomClass,
60
+ port = 0,
61
+ ): Promise<UniversalNodeServer> {
62
+ const runtime = new UniversalRoomRuntime(roomClass, roomName);
63
+ const server = new WebSocketServer({ port });
64
+ let nextPeerId = 1;
65
+
66
+ server.on('connection', (socket) => {
67
+ runtime.attach(new NodeWebSocketPacketChannel(`client-${nextPeerId++}`, socket));
68
+ });
69
+
70
+ await new Promise<void>((resolve) => server.once('listening', resolve));
71
+ const address = server.address() as AddressInfo;
72
+ return {
73
+ url: `ws://127.0.0.1:${address.port}`,
74
+ close: () =>
75
+ new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve()))),
76
+ };
77
+ }