@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/webrtc.ts ADDED
@@ -0,0 +1,78 @@
1
+ import type { PacketChannel } from './channels';
2
+ import type { Envelope } from './protocol';
3
+
4
+ interface DataChannelLike {
5
+ readonly readyState: string;
6
+ readonly bufferedAmount?: number;
7
+ send(data: string): void;
8
+ close(): void;
9
+ addEventListener(type: 'message', handler: (event: { data: unknown }) => void): void;
10
+ addEventListener(type: 'close', handler: () => void): void;
11
+ }
12
+
13
+ type EnvelopeHandler = (envelope: Envelope) => void;
14
+ type CloseHandler = (reason?: string) => void;
15
+
16
+ export class WebRTCDataChannelPacketChannel implements PacketChannel {
17
+ readonly mode = 'webrtc' as const;
18
+ private readonly envelopeHandlers = new Set<EnvelopeHandler>();
19
+ private readonly closeHandlers = new Set<CloseHandler>();
20
+
21
+ constructor(
22
+ readonly peerId: string,
23
+ private readonly dataChannel: DataChannelLike,
24
+ ) {
25
+ dataChannel.addEventListener('message', (event) => {
26
+ if (typeof event.data !== 'string') return;
27
+ const envelope = JSON.parse(event.data) as Envelope;
28
+ for (const handler of this.envelopeHandlers) handler(envelope);
29
+ });
30
+ dataChannel.addEventListener('close', () => {
31
+ for (const handler of this.closeHandlers) handler('closed');
32
+ });
33
+ }
34
+
35
+ send(envelope: Envelope): void {
36
+ if (this.dataChannel.readyState !== 'open') {
37
+ throw new Error(`WebRTC data channel for ${this.peerId} is not open`);
38
+ }
39
+ this.dataChannel.send(JSON.stringify(envelope));
40
+ }
41
+
42
+ onEnvelope(handler: EnvelopeHandler): () => void {
43
+ this.envelopeHandlers.add(handler);
44
+ return () => this.envelopeHandlers.delete(handler);
45
+ }
46
+
47
+ onClose(handler: CloseHandler): () => void {
48
+ this.closeHandlers.add(handler);
49
+ return () => this.closeHandlers.delete(handler);
50
+ }
51
+
52
+ close(): void {
53
+ this.dataChannel.close();
54
+ }
55
+
56
+ closeAfterFlush(delayMs = 100): void {
57
+ const started = Date.now();
58
+ const minDelayMs = delayMs;
59
+ const maxDelayMs = Math.max(1_000, delayMs);
60
+ const tick = () => {
61
+ const elapsed = Date.now() - started;
62
+ const flushed = (this.dataChannel.bufferedAmount ?? 0) === 0;
63
+ if ((flushed && elapsed >= minDelayMs) || elapsed >= maxDelayMs) {
64
+ this.close();
65
+ return;
66
+ }
67
+ setTimeout(tick, 10);
68
+ };
69
+ tick();
70
+ }
71
+ }
72
+
73
+ export function createWebRTCDataChannelPacketChannel(
74
+ peerId: string,
75
+ dataChannel: RTCDataChannel,
76
+ ): PacketChannel {
77
+ return new WebRTCDataChannelPacketChannel(peerId, dataChannel);
78
+ }
@@ -0,0 +1,59 @@
1
+ import type { PacketChannel } from './channels';
2
+ import type { Envelope } from './protocol';
3
+
4
+ interface WebSocketLike {
5
+ readonly readyState: number;
6
+ send(data: string): void;
7
+ close(code?: number, reason?: string): void;
8
+ addEventListener(type: 'message', handler: (event: { data: unknown }) => void): void;
9
+ addEventListener(type: 'close', handler: (event?: { reason?: string }) => void): void;
10
+ }
11
+
12
+ type EnvelopeHandler = (envelope: Envelope) => void;
13
+ type CloseHandler = (reason?: string) => void;
14
+
15
+ const OPEN = 1;
16
+
17
+ export class WebSocketPacketChannel implements PacketChannel {
18
+ readonly mode = 'websocket' as const;
19
+ private readonly envelopeHandlers = new Set<EnvelopeHandler>();
20
+ private readonly closeHandlers = new Set<CloseHandler>();
21
+
22
+ constructor(
23
+ readonly peerId: string,
24
+ private readonly socket: WebSocketLike,
25
+ ) {
26
+ socket.addEventListener('message', (event) => {
27
+ if (typeof event.data !== 'string') return;
28
+ const envelope = JSON.parse(event.data) as Envelope;
29
+ for (const handler of this.envelopeHandlers) handler(envelope);
30
+ });
31
+ socket.addEventListener('close', (event) => {
32
+ for (const handler of this.closeHandlers) handler(event?.reason);
33
+ });
34
+ }
35
+
36
+ send(envelope: Envelope): void {
37
+ if (this.socket.readyState !== OPEN)
38
+ throw new Error(`WebSocket for ${this.peerId} is not open`);
39
+ this.socket.send(JSON.stringify(envelope));
40
+ }
41
+
42
+ onEnvelope(handler: EnvelopeHandler): () => void {
43
+ this.envelopeHandlers.add(handler);
44
+ return () => this.envelopeHandlers.delete(handler);
45
+ }
46
+
47
+ onClose(handler: CloseHandler): () => void {
48
+ this.closeHandlers.add(handler);
49
+ return () => this.closeHandlers.delete(handler);
50
+ }
51
+
52
+ close(reason?: string): void {
53
+ this.socket.close(1000, reason);
54
+ }
55
+ }
56
+
57
+ export function createWebSocketPacketChannel(peerId: string, socket: WebSocket): PacketChannel {
58
+ return new WebSocketPacketChannel(peerId, socket);
59
+ }