@phystack/hub-client 4.5.19-dev → 4.5.20-dev

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 (119) hide show
  1. package/dist/index.d.ts +22 -28
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +252 -378
  4. package/dist/index.js.map +1 -1
  5. package/dist/peripheral-twin.d.ts +34 -0
  6. package/dist/peripheral-twin.d.ts.map +1 -0
  7. package/dist/peripheral-twin.js +234 -0
  8. package/dist/peripheral-twin.js.map +1 -0
  9. package/dist/services/phyhub-connection.service.d.ts +1 -0
  10. package/dist/services/phyhub-connection.service.d.ts.map +1 -1
  11. package/dist/services/phyhub-connection.service.js +17 -0
  12. package/dist/services/phyhub-connection.service.js.map +1 -1
  13. package/dist/services/phyhub-direct-connection.service.d.ts +21 -0
  14. package/dist/services/phyhub-direct-connection.service.d.ts.map +1 -0
  15. package/dist/services/phyhub-direct-connection.service.js +101 -0
  16. package/dist/services/phyhub-direct-connection.service.js.map +1 -0
  17. package/dist/services/webrtc/data-channel-handler.d.ts +45 -0
  18. package/dist/services/webrtc/data-channel-handler.d.ts.map +1 -0
  19. package/dist/services/webrtc/data-channel-handler.js +260 -0
  20. package/dist/services/webrtc/data-channel-handler.js.map +1 -0
  21. package/dist/services/webrtc/index.d.ts +8 -0
  22. package/dist/services/webrtc/index.d.ts.map +1 -0
  23. package/dist/services/webrtc/index.js +18 -0
  24. package/dist/services/webrtc/index.js.map +1 -0
  25. package/dist/services/webrtc/media-stream-handler.d.ts +57 -0
  26. package/dist/services/webrtc/media-stream-handler.d.ts.map +1 -0
  27. package/dist/services/webrtc/media-stream-handler.js +383 -0
  28. package/dist/services/webrtc/media-stream-handler.js.map +1 -0
  29. package/dist/services/webrtc/peer-connection-manager.d.ts +40 -0
  30. package/dist/services/webrtc/peer-connection-manager.d.ts.map +1 -0
  31. package/dist/services/webrtc/peer-connection-manager.js +336 -0
  32. package/dist/services/webrtc/peer-connection-manager.js.map +1 -0
  33. package/dist/services/webrtc/types.d.ts +134 -0
  34. package/dist/services/webrtc/types.d.ts.map +1 -0
  35. package/dist/services/webrtc/types.js +12 -0
  36. package/dist/services/webrtc/types.js.map +1 -0
  37. package/dist/services/webrtc/webrtc-globals.d.ts +4 -0
  38. package/dist/services/webrtc/webrtc-globals.d.ts.map +1 -0
  39. package/dist/services/webrtc/webrtc-globals.js +72 -0
  40. package/dist/services/webrtc/webrtc-globals.js.map +1 -0
  41. package/dist/services/webrtc/webrtc-manager.d.ts +35 -0
  42. package/dist/services/webrtc/webrtc-manager.d.ts.map +1 -0
  43. package/dist/services/webrtc/webrtc-manager.js +274 -0
  44. package/dist/services/webrtc/webrtc-manager.js.map +1 -0
  45. package/dist/test/communication-comprehensive-test.d.ts +8 -0
  46. package/dist/test/communication-comprehensive-test.d.ts.map +1 -0
  47. package/dist/test/communication-comprehensive-test.js +356 -0
  48. package/dist/test/communication-comprehensive-test.js.map +1 -0
  49. package/dist/test/webrtc-channel-names-test.d.ts +2 -0
  50. package/dist/test/webrtc-channel-names-test.d.ts.map +1 -0
  51. package/dist/test/webrtc-channel-names-test.js +177 -0
  52. package/dist/test/webrtc-channel-names-test.js.map +1 -0
  53. package/dist/test/webrtc-comprehensive-test.d.ts +2 -0
  54. package/dist/test/webrtc-comprehensive-test.d.ts.map +1 -0
  55. package/dist/test/webrtc-comprehensive-test.js +328 -0
  56. package/dist/test/webrtc-comprehensive-test.js.map +1 -0
  57. package/dist/test/webrtc-reconnect-test.d.ts +4 -0
  58. package/dist/test/webrtc-reconnect-test.d.ts.map +1 -0
  59. package/dist/test/webrtc-reconnect-test.js +244 -0
  60. package/dist/test/webrtc-reconnect-test.js.map +1 -0
  61. package/dist/test/webrtc-test-harness.d.ts +4 -0
  62. package/dist/test/webrtc-test-harness.d.ts.map +1 -0
  63. package/dist/test/webrtc-test-harness.js +169 -0
  64. package/dist/test/webrtc-test-harness.js.map +1 -0
  65. package/dist/twin-messaging.d.ts +20 -0
  66. package/dist/twin-messaging.d.ts.map +1 -0
  67. package/dist/twin-messaging.js +94 -0
  68. package/dist/twin-messaging.js.map +1 -0
  69. package/dist/twin-registry.d.ts +9 -0
  70. package/dist/twin-registry.d.ts.map +1 -0
  71. package/dist/twin-registry.js +26 -0
  72. package/dist/twin-registry.js.map +1 -0
  73. package/dist/types/index.d.ts +4 -0
  74. package/dist/types/index.d.ts.map +1 -0
  75. package/dist/types/index.js +20 -0
  76. package/dist/types/index.js.map +1 -0
  77. package/dist/types/twin.types.d.ts +62 -14
  78. package/dist/types/twin.types.d.ts.map +1 -1
  79. package/dist/types/twin.types.js +8 -1
  80. package/dist/types/twin.types.js.map +1 -1
  81. package/docs/webrtc-howto.md +398 -0
  82. package/docs/webrtc-test.md +330 -0
  83. package/package.json +3 -3
  84. package/scripts/webrtc-test.sh +401 -0
  85. package/src/index.ts +378 -568
  86. package/src/peripheral-twin.ts +337 -0
  87. package/src/services/phyhub-connection.service.ts +24 -0
  88. package/src/services/phyhub-direct-connection.service.ts +159 -0
  89. package/src/services/webrtc/data-channel-handler.ts +362 -0
  90. package/src/services/webrtc/index.ts +36 -0
  91. package/src/services/webrtc/media-stream-handler.ts +536 -0
  92. package/src/services/webrtc/peer-connection-manager.ts +467 -0
  93. package/src/services/webrtc/types.ts +273 -0
  94. package/src/services/webrtc/webrtc-globals.ts +108 -0
  95. package/src/services/webrtc/webrtc-manager.ts +490 -0
  96. package/src/test/communication-comprehensive-test.ts +533 -0
  97. package/src/test/webrtc-channel-names-test.ts +266 -0
  98. package/src/test/webrtc-comprehensive-test.ts +494 -0
  99. package/src/test/webrtc-reconnect-test.ts +345 -0
  100. package/src/test/webrtc-test-harness.ts +254 -0
  101. package/src/twin-messaging.ts +184 -0
  102. package/src/twin-registry.ts +39 -0
  103. package/src/types/index.ts +3 -0
  104. package/src/types/twin.types.ts +80 -14
  105. package/dist/services/webrtc/datachannel.d.ts +0 -10
  106. package/dist/services/webrtc/datachannel.d.ts.map +0 -1
  107. package/dist/services/webrtc/datachannel.js +0 -290
  108. package/dist/services/webrtc/datachannel.js.map +0 -1
  109. package/dist/services/webrtc/mediastream.d.ts +0 -10
  110. package/dist/services/webrtc/mediastream.d.ts.map +0 -1
  111. package/dist/services/webrtc/mediastream.js +0 -396
  112. package/dist/services/webrtc/mediastream.js.map +0 -1
  113. package/dist/services/webrtc/peer-connection-ice.d.ts +0 -32
  114. package/dist/services/webrtc/peer-connection-ice.d.ts.map +0 -1
  115. package/dist/services/webrtc/peer-connection-ice.js +0 -483
  116. package/dist/services/webrtc/peer-connection-ice.js.map +0 -1
  117. package/src/services/webrtc/datachannel.ts +0 -421
  118. package/src/services/webrtc/mediastream.ts +0 -602
  119. package/src/services/webrtc/peer-connection-ice.ts +0 -689
@@ -0,0 +1,362 @@
1
+ /**
2
+ * DataChannelHandler
3
+ *
4
+ * Manages RTCDataChannel lifecycle including:
5
+ * - Channel creation (initiator) or reception (responder)
6
+ * - Message serialization/deserialization
7
+ * - Persistent channel abstraction across reconnections
8
+ * - Message buffering during reconnection
9
+ */
10
+
11
+ import { PeerConnectionManager, PeerConnectionManagerOptions } from './peer-connection-manager';
12
+ import {
13
+ TwinMessagingInterface,
14
+ PhygridDataChannel,
15
+ PeerConnectionConfig,
16
+ DEFAULT_WEBRTC_OPTIONS,
17
+ } from './types';
18
+
19
+ export interface DataChannelHandlerOptions extends Partial<PeerConnectionManagerOptions> {
20
+ /** Use STUN servers. Default: true */
21
+ useStun?: boolean;
22
+
23
+ /** STUN server URLs */
24
+ stunServers?: string[];
25
+ }
26
+
27
+ export class DataChannelHandler {
28
+ private targetTwinId: string;
29
+ private channelName: string;
30
+ private isInitiator: boolean;
31
+ private twinMessaging: TwinMessagingInterface;
32
+ private options: Required<DataChannelHandlerOptions>;
33
+
34
+ private pcManager: PeerConnectionManager | null = null;
35
+ private dataChannel: RTCDataChannel | null = null;
36
+ private isOpen = false;
37
+ private isClosed = false;
38
+
39
+ // Connection status
40
+ private isConnecting = false;
41
+
42
+ // Event listeners
43
+ private messageListeners: Set<(data: any) => void> = new Set();
44
+ private closeListeners: Set<() => void> = new Set();
45
+
46
+ // Callbacks for WebRTCManager
47
+ private onConnectedCallback?: () => void;
48
+ private onDisconnectedCallback?: () => void;
49
+ private onErrorCallback?: (error: Error) => void;
50
+ private onReconnectingCallback?: (attempt: number) => void;
51
+ private onReconnectedCallback?: (attempt: number) => void;
52
+
53
+ constructor(
54
+ targetTwinId: string,
55
+ isInitiator: boolean,
56
+ twinMessaging: TwinMessagingInterface,
57
+ options: DataChannelHandlerOptions = {},
58
+ channelName: string = 'default'
59
+ ) {
60
+ this.targetTwinId = targetTwinId;
61
+ this.channelName = channelName;
62
+ this.isInitiator = isInitiator;
63
+ this.twinMessaging = twinMessaging;
64
+ this.options = {
65
+ useStun: options.useStun ?? DEFAULT_WEBRTC_OPTIONS.useStun,
66
+ stunServers: options.stunServers ?? DEFAULT_WEBRTC_OPTIONS.stunServers,
67
+ connectionTimeout: options.connectionTimeout ?? DEFAULT_WEBRTC_OPTIONS.connectionTimeout,
68
+ initialRetryDelay: options.initialRetryDelay ?? DEFAULT_WEBRTC_OPTIONS.initialRetryDelay,
69
+ maxRetryDelay: options.maxRetryDelay ?? DEFAULT_WEBRTC_OPTIONS.maxRetryDelay,
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Set callbacks for connection events
75
+ */
76
+ setCallbacks(callbacks: {
77
+ onConnected?: () => void;
78
+ onDisconnected?: () => void;
79
+ onError?: (error: Error) => void;
80
+ onReconnecting?: (attempt: number) => void;
81
+ onReconnected?: (attempt: number) => void;
82
+ }): void {
83
+ this.onConnectedCallback = callbacks.onConnected;
84
+ this.onDisconnectedCallback = callbacks.onDisconnected;
85
+ this.onErrorCallback = callbacks.onError;
86
+ this.onReconnectingCallback = callbacks.onReconnecting;
87
+ this.onReconnectedCallback = callbacks.onReconnected;
88
+ }
89
+
90
+ /**
91
+ * Connect and return a PhygridDataChannel
92
+ */
93
+ async connect(): Promise<PhygridDataChannel> {
94
+ if (this.isClosed) {
95
+ throw new Error('DataChannelHandler has been closed');
96
+ }
97
+
98
+ this.isConnecting = true;
99
+ // Channel ID includes the name to support multiple channels to same peer
100
+ const channelId = `dc-${this.channelName}-${this.targetTwinId}`;
101
+ // Prefix for signaling messages - unique per channel name
102
+ const channelPrefix = `dc-${this.channelName}`;
103
+
104
+ const pcConfig: PeerConnectionConfig = {
105
+ targetTwinId: this.targetTwinId,
106
+ isInitiator: this.isInitiator,
107
+ connectionType: 'datachannel',
108
+ channelPrefix,
109
+ useStun: this.options.useStun,
110
+ stunServers: this.options.stunServers,
111
+ onConnected: () => this.handlePeerConnected(),
112
+ onDisconnected: () => this.handlePeerDisconnected(),
113
+ onError: (err) => this.handleError(err),
114
+ onReconnecting: (attempt) => {
115
+ this.isConnecting = true;
116
+ this.isOpen = false;
117
+ // Clear old data channel reference - new one will be created/received
118
+ this.dataChannel = null;
119
+ this.onReconnectingCallback?.(attempt);
120
+ },
121
+ onReconnected: (attempt) => this.handleReconnected(attempt),
122
+ // Setup data channel/handler before signaling begins
123
+ onPeerConnectionCreated: (pc) => {
124
+ if (this.isInitiator) {
125
+ // Initiator: Create data channel before offer is sent
126
+ console.log('[DataChannelHandler] Creating data channel before offer');
127
+ this.dataChannel = pc.createDataChannel(channelId);
128
+ this.dataChannel.binaryType = 'arraybuffer';
129
+ } else {
130
+ // Responder: Set up ondatachannel handler BEFORE receiving offer
131
+ // This ensures we don't miss the datachannel event
132
+ console.log('[DataChannelHandler] Setting up ondatachannel handler');
133
+ pc.ondatachannel = (event) => {
134
+ console.log('[DataChannelHandler] Received data channel from initiator');
135
+ this.dataChannel = event.channel;
136
+ this.dataChannel.binaryType = 'arraybuffer';
137
+ this.attachDataChannelHandlers(this.dataChannel, () => {
138
+ // No-op: Channel setup is complete. Handler registration and callbacks
139
+ // are managed inside attachDataChannelHandlers.
140
+ });
141
+ };
142
+ }
143
+ },
144
+ };
145
+
146
+ this.pcManager = new PeerConnectionManager(pcConfig, this.twinMessaging, {
147
+ connectionTimeout: this.options.connectionTimeout,
148
+ initialRetryDelay: this.options.initialRetryDelay,
149
+ maxRetryDelay: this.options.maxRetryDelay,
150
+ });
151
+
152
+ const pc = await this.pcManager.connect();
153
+ await this.setupDataChannel(pc);
154
+
155
+ return this.createPhygridDataChannel();
156
+ }
157
+
158
+ /**
159
+ * Close the data channel and clean up
160
+ */
161
+ close(): void {
162
+ if (this.isClosed) return;
163
+
164
+ this.isClosed = true;
165
+ this.isOpen = false;
166
+
167
+ if (this.dataChannel) {
168
+ try {
169
+ this.dataChannel.close();
170
+ } catch (err) {
171
+ console.error('[DataChannelHandler] Error closing data channel:', err);
172
+ }
173
+ this.dataChannel = null;
174
+ }
175
+
176
+ if (this.pcManager) {
177
+ this.pcManager.close();
178
+ this.pcManager = null;
179
+ }
180
+
181
+ this.notifyCloseListeners();
182
+ this.messageListeners.clear();
183
+ this.closeListeners.clear();
184
+ }
185
+
186
+ // ===========================================================================
187
+ // Private Methods
188
+ // ===========================================================================
189
+
190
+ private async setupDataChannel(pc: RTCPeerConnection): Promise<void> {
191
+ return new Promise((resolve) => {
192
+ // Data channel was already created/received in onPeerConnectionCreated
193
+ // Just need to attach handlers if not already done
194
+ if (this.dataChannel) {
195
+ this.attachDataChannelHandlers(this.dataChannel, resolve);
196
+ } else if (this.isInitiator) {
197
+ // Fallback: create data channel now (shouldn't happen normally)
198
+ const channelId = `channel-${this.targetTwinId}`;
199
+ this.dataChannel = pc.createDataChannel(channelId);
200
+ this.dataChannel.binaryType = 'arraybuffer';
201
+ this.attachDataChannelHandlers(this.dataChannel, resolve);
202
+ } else {
203
+ // Responder: ondatachannel handler should already be set,
204
+ // but it may not have fired yet - we'll resolve when it does
205
+ const existingHandler = pc.ondatachannel;
206
+ pc.ondatachannel = (event) => {
207
+ existingHandler?.call(pc, event);
208
+ resolve();
209
+ };
210
+ }
211
+ });
212
+ }
213
+
214
+ private attachDataChannelHandlers(dc: RTCDataChannel, onReady: () => void): void {
215
+ dc.onopen = () => {
216
+ this.isOpen = true;
217
+ this.isConnecting = false;
218
+ this.onConnectedCallback?.();
219
+ onReady();
220
+ };
221
+
222
+ dc.onmessage = (event) => {
223
+ this.handleMessage(event.data);
224
+ };
225
+
226
+ dc.onclose = () => {
227
+ this.isOpen = false;
228
+ if (!this.isClosed) {
229
+ this.onDisconnectedCallback?.();
230
+ }
231
+ };
232
+
233
+ dc.onerror = (event) => {
234
+ const error = new Error(`DataChannel error: ${(event as any).error?.message || 'Unknown error'}`);
235
+ this.handleError(error);
236
+ };
237
+
238
+ // If already open (can happen on reconnect), resolve immediately
239
+ if (dc.readyState === 'open') {
240
+ this.isOpen = true;
241
+ this.isConnecting = false;
242
+ onReady();
243
+ }
244
+ }
245
+
246
+ private handleMessage(data: string | ArrayBuffer): void {
247
+ let parsedData = data;
248
+
249
+ // Try to parse JSON strings
250
+ if (typeof data === 'string') {
251
+ try {
252
+ parsedData = JSON.parse(data);
253
+ } catch {
254
+ // Not JSON, use as-is
255
+ }
256
+ }
257
+
258
+ this.messageListeners.forEach((listener) => {
259
+ try {
260
+ listener(parsedData);
261
+ } catch (err) {
262
+ console.error('[DataChannelHandler] Error in message listener:', err);
263
+ }
264
+ });
265
+ }
266
+
267
+ private handlePeerConnected(): void {
268
+ // No-op: Peer connection is established but we don't need to do anything here.
269
+ // DataChannel setup is handled by onPeerConnectionCreated callback.
270
+ }
271
+
272
+ private handlePeerDisconnected(): void {
273
+ this.isOpen = false;
274
+ this.onDisconnectedCallback?.();
275
+ }
276
+
277
+ private handleReconnected(attempt: number): void {
278
+ // Re-setup data channel after reconnection
279
+ const pc = this.pcManager?.getPeerConnection();
280
+ if (pc) {
281
+ this.setupDataChannel(pc).catch((err) => {
282
+ console.error('[DataChannelHandler] Error re-setting up data channel:', err);
283
+ });
284
+ }
285
+ this.onReconnectedCallback?.(attempt);
286
+ }
287
+
288
+ private handleError(error: Error): void {
289
+ console.error('[DataChannelHandler] DataChannel error:', error);
290
+ this.onErrorCallback?.(error);
291
+ }
292
+
293
+
294
+ private notifyCloseListeners(): void {
295
+ this.closeListeners.forEach((listener) => {
296
+ try {
297
+ listener();
298
+ } catch (err) {
299
+ console.error('[DataChannelHandler] Error in close listener:', err);
300
+ }
301
+ });
302
+ }
303
+
304
+ private createPhygridDataChannel(): PhygridDataChannel {
305
+ return {
306
+ send: (data: string | ArrayBuffer | object) => {
307
+ let messageToSend: string | ArrayBuffer;
308
+
309
+ if (typeof data === 'object' && !(data instanceof ArrayBuffer)) {
310
+ messageToSend = JSON.stringify(data);
311
+ } else {
312
+ messageToSend = data as string | ArrayBuffer;
313
+ }
314
+
315
+ if (this.dataChannel?.readyState === 'open') {
316
+ try {
317
+ this.dataChannel.send(messageToSend as any);
318
+ } catch (err) {
319
+ console.error('[DataChannelHandler] Error sending message:', err);
320
+ }
321
+ }
322
+ // If not open, silently drop the message - no buffering
323
+ },
324
+
325
+ onMessage: (callback: (data: any) => void) => {
326
+ this.messageListeners.add(callback);
327
+ },
328
+
329
+ offMessage: (callback: (data: any) => void) => {
330
+ this.messageListeners.delete(callback);
331
+ },
332
+
333
+ onClose: (callback: () => void) => {
334
+ this.closeListeners.add(callback);
335
+ },
336
+
337
+ offClose: (callback: () => void) => {
338
+ this.closeListeners.delete(callback);
339
+ },
340
+
341
+ close: () => {
342
+ this.close();
343
+ },
344
+
345
+ isOpen: () => {
346
+ return this.isOpen && this.dataChannel?.readyState === 'open';
347
+ },
348
+
349
+ isConnecting: () => {
350
+ return this.isConnecting;
351
+ },
352
+
353
+ getTargetTwinId: () => {
354
+ return this.targetTwinId;
355
+ },
356
+
357
+ getChannelName: () => {
358
+ return this.channelName;
359
+ },
360
+ };
361
+ }
362
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * WebRTC Module Exports
3
+ *
4
+ * This module provides WebRTC functionality for peer-to-peer
5
+ * DataChannel and MediaStream connections between twins.
6
+ */
7
+
8
+ // Main manager class
9
+ export { WebRTCManager } from './webrtc-manager';
10
+
11
+ // Handler classes (for advanced usage)
12
+ export { DataChannelHandler } from './data-channel-handler';
13
+ export { MediaStreamHandler } from './media-stream-handler';
14
+ export { PeerConnectionManager } from './peer-connection-manager';
15
+
16
+ // Globals utilities
17
+ export { ensureWebRTCGlobals, isWebRTCAvailable, isWebRTCInitialized } from './webrtc-globals';
18
+
19
+ // Types
20
+ export type {
21
+ TwinMessagingInterface,
22
+ WebRTCManagerOptions,
23
+ WebRTCEvent,
24
+ WebRTCStandardEvent,
25
+ WebRTCVerboseEvent,
26
+ WebRTCEventData,
27
+ WebRTCEventCallback,
28
+ PhygridDataChannel,
29
+ PhygridMediaStream,
30
+ MediaStreamOptions,
31
+ ExtendedMediaStreamTrack,
32
+ ConnectionType,
33
+ ConnectionState,
34
+ } from './types';
35
+
36
+ export { DEFAULT_WEBRTC_OPTIONS } from './types';