@phystack/hub-client 4.4.52 → 4.4.54

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 +268 -365
  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 +367 -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 +335 -0
  32. package/dist/services/webrtc/peer-connection-manager.js.map +1 -0
  33. package/dist/services/webrtc/types.d.ts +133 -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 +23 -0
  66. package/dist/twin-messaging.d.ts.map +1 -0
  67. package/dist/twin-messaging.js +91 -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 +66 -15
  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 +399 -540
  86. package/src/peripheral-twin.ts +333 -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 +515 -0
  92. package/src/services/webrtc/peer-connection-manager.ts +463 -0
  93. package/src/services/webrtc/types.ts +270 -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 +188 -0
  102. package/src/twin-registry.ts +39 -0
  103. package/src/types/index.ts +3 -0
  104. package/src/types/twin.types.ts +87 -15
  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
package/src/index.ts CHANGED
@@ -7,91 +7,28 @@ import {
7
7
  EdgeTwinResponse,
8
8
  EdgeTwinDesiredPropertiesResponse,
9
9
  PeripheralTwinResponse,
10
- TwinTypeEnum,
11
10
  Instance,
12
11
  PeripheralInstance,
12
+ IPeripheralTwinInstance,
13
+ TwinMessageResult,
14
+ TwinMessageResultStatus,
13
15
  } from './types/twin.types';
14
- import {
15
- PhygridDataChannel,
16
- PhygridMediaStream,
17
- WebRTCConnectionOptions,
18
- } from './types/webrtc.types';
19
16
  import { PhyHubConnection } from './services/phyhub-connection.service';
17
+ import { PhyHubDirectConnection } from './services/phyhub-direct-connection.service';
20
18
  import { SignalsService } from './services/signals.service';
21
19
  import { DataRequestTypeEnum, InitSignalPayload } from './types/signal.types';
22
20
  import {
23
- createWebRTCDataChannelConnection,
24
- WebRTCDataChannelResult,
25
- } from './services/webrtc/datachannel';
26
- import {
27
- createWebRTCMediaStreamConnection,
28
- WebRTCMediaStreamResult,
29
- } from './services/webrtc/mediastream';
30
- // import { createWebRTCMediaStreamConnection, WebRTCMediaStreamResult } from './helpers/webrtc-mediastream';
31
-
32
- // Define WebRTC types that might not be available in all environments
33
- declare global {
34
- // In browsers, these are standard WebRTC interfaces
35
- // In Node.js, we polyfill them from @roamhq/wrtc
36
- interface Window {
37
- RTCVideoSource?: any;
38
- }
39
-
40
- namespace NodeJS {
41
- interface Global {
42
- MediaStream?: any;
43
- RTCPeerConnection?: any;
44
- RTCSessionDescription?: any;
45
- RTCIceCandidate?: any;
46
- RTCVideoSource?: any;
47
- }
48
- }
49
-
50
- // Make RTCVideoSource available in function scope when used
51
- var RTCVideoSource: any;
52
- }
53
-
54
- (async function initializeWebRTCGlobals() {
55
- // Only run in Node environment
56
- if (typeof window !== 'undefined') return;
57
-
58
- try {
59
- // Dynamically import wrtc
60
- const wrtc = require('@roamhq/wrtc');
61
-
62
- // Set all WebRTC globals
63
- if (typeof global.MediaStream === 'undefined' && wrtc.MediaStream) {
64
- global.MediaStream = wrtc.MediaStream;
65
- console.log('Global MediaStream initialized');
66
- }
67
-
68
- if (typeof global.RTCPeerConnection === 'undefined' && wrtc.RTCPeerConnection) {
69
- global.RTCPeerConnection = wrtc.RTCPeerConnection;
70
- console.log('Global RTCPeerConnection initialized');
71
- }
72
-
73
- if (typeof global.RTCSessionDescription === 'undefined' && wrtc.RTCSessionDescription) {
74
- global.RTCSessionDescription = wrtc.RTCSessionDescription;
75
- console.log('Global RTCSessionDescription initialized');
76
- }
77
-
78
- if (typeof global.RTCIceCandidate === 'undefined' && wrtc.RTCIceCandidate) {
79
- global.RTCIceCandidate = wrtc.RTCIceCandidate;
80
- console.log('Global RTCIceCandidate initialized');
81
- }
82
-
83
- if (typeof (global as any).RTCVideoSource === 'undefined' && wrtc.nonstandard.RTCVideoSource) {
84
- (global as any).RTCVideoSource = wrtc.nonstandard.RTCVideoSource;
85
- console.log('Global RTCVideoSource initialized');
86
- }
87
-
88
- console.log('WebRTC globals successfully initialized');
89
- } catch (error) {
90
- console.error('Failed to initialize WebRTC globals:', error);
91
- }
92
- })().catch(err => console.error('Error in WebRTC globals initialization:', err));
21
+ WebRTCManager,
22
+ TwinMessagingInterface,
23
+ PhygridDataChannel,
24
+ PhygridMediaStream,
25
+ WebRTCManagerOptions,
26
+ MediaStreamOptions,
27
+ } from './services/webrtc';
28
+ import { TwinRegistry } from './twin-registry';
29
+ import { createTwinMessaging } from './twin-messaging';
93
30
 
94
- interface EventPayload<T = any> {
31
+ export interface EventPayload<T = any> {
95
32
  twinId?: string;
96
33
  sourceTwinId?: string;
97
34
  sourceDeviceId?: string;
@@ -115,8 +52,9 @@ export class PhyHubClient {
115
52
  private subscribedTwins: Set<string> = new Set();
116
53
  private twinMessageListeners: { [key: string]: Set<(message: any) => void> } = {};
117
54
  private instances: Map<string, Instance> = new Map();
118
- private peripheralInstances: Map<string, PeripheralInstance> = new Map();
119
55
  private twinUpdateListeners: { [key: string]: Set<(twin: TwinResponse) => void> } = {};
56
+ private twinInstancesRegistry: TwinRegistry | null = null;
57
+ private webrtcManager: WebRTCManager | null = null;
120
58
 
121
59
  private readonly EVENTS = {
122
60
  PING: 'ping',
@@ -205,6 +143,11 @@ export class PhyHubClient {
205
143
  }
206
144
  );
207
145
 
146
+ // In direct connection mode, fetch our device twin to get the actual twinId (ObjectId)
147
+ if (PhyHubDirectConnection.isEnabled()) {
148
+ await this.initializeDirectConnectionTwin();
149
+ }
150
+
208
151
  return this;
209
152
  } catch (err) {
210
153
  this.socket = null;
@@ -262,32 +205,50 @@ export class PhyHubClient {
262
205
  this.socketConnected = false;
263
206
  });
264
207
 
265
- this.socket.on(this.EVENTS.TWIN_MESSAGE, (payload: EventPayload) => {
266
- // console.log('Received TWIN_MESSAGE payload:', {
267
- // JSON: JSON.stringify(payload, null, 2),
268
- // });
269
-
270
- if (payload.twinId && payload.data) {
271
- // console.log('Received TWIN_MESSAGE event:', {
272
- // hasListeners: !!this.twinMessageListeners[payload.twinId],
273
- // listenerCount: this.twinMessageListeners[payload.twinId]?.size
274
- // });
275
- const listeners = this.twinMessageListeners[payload.twinId];
276
- if (listeners) {
277
- // console.log(`Executing ${listeners.size} listeners for twin ${payload.twinId}`);
278
- listeners.forEach(callback => {
279
- // console.log('Executing listener for twin:', callback);
208
+ this.socket.on(this.EVENTS.TWIN_MESSAGE, (payload: EventPayload, callback?: (response: TwinMessageResult | null) => void) => {
209
+ console.log('[TWIN_MESSAGE] Received:', {
210
+ twinId: payload.twinId,
211
+ sourceTwinId: payload.sourceTwinId,
212
+ dataType: payload.data?.type,
213
+ });
214
+
215
+ let result: TwinMessageResult | null = null;
216
+
217
+ if (payload.data) {
218
+ // Try to find listeners by sourceTwinId (messages FROM this twin)
219
+ // This is the correct lookup - we register for a peer's twin ID to receive messages FROM them
220
+ const sourceTwinId = payload.sourceTwinId;
221
+ const targetTwinId = payload.twinId;
222
+
223
+ // First try sourceTwinId (the sender) - this is what onTwinMessage registers for
224
+ let listeners = sourceTwinId ? this.twinMessageListeners[sourceTwinId] : undefined;
225
+
226
+ // Also check targetTwinId for backwards compatibility
227
+ if (!listeners && targetTwinId) {
228
+ listeners = this.twinMessageListeners[targetTwinId];
229
+ }
230
+
231
+ if (listeners?.size) {
232
+ console.log(`[TWIN_MESSAGE] Executing ${listeners.size} listeners`);
233
+ listeners.forEach(listener => {
280
234
  try {
281
- callback(payload);
235
+ listener(payload);
236
+ result = { status: TwinMessageResultStatus.Success, message: 'Action completed' };
282
237
  } catch (error) {
283
- console.error(`Error in twin message listener for ${payload.twinId}:`, error);
238
+ console.error(`[TWIN_MESSAGE] Error in listener:`, error);
239
+ result = { status: TwinMessageResultStatus.Error, message: (error as Error)?.message || 'An error occurred' };
284
240
  }
285
241
  });
286
242
  } else {
287
- // console.log(`No listeners found for twin ${payload.twinId}`, JSON.stringify(payload, null, 2));
243
+ console.log(`[TWIN_MESSAGE] No listeners found for source=${sourceTwinId} or target=${targetTwinId}`);
244
+ result = { status: TwinMessageResultStatus.Warning, message: `No listeners found` };
245
+ }
246
+
247
+ if (callback) {
248
+ callback(result);
288
249
  }
289
250
  } else {
290
- // console.log('Invalid twin message payload:', payload);
251
+ console.log('[TWIN_MESSAGE] Invalid payload (no data):', payload);
291
252
  }
292
253
  });
293
254
 
@@ -336,6 +297,10 @@ export class PhyHubClient {
336
297
  if (!PhyHubClient.instance) {
337
298
  PhyHubClient.instance = new PhyHubClient(params);
338
299
  await PhyHubClient.instance.initializeConnection();
300
+
301
+ // create twin registry to store instances
302
+ PhyHubClient.instance.twinInstancesRegistry = new TwinRegistry(PhyHubClient.instance);
303
+
339
304
  console.info(`connect(): Connection to phyhub initialized`);
340
305
  }
341
306
  return PhyHubClient.instance;
@@ -445,88 +410,109 @@ export class PhyHubClient {
445
410
  });
446
411
  }
447
412
 
448
- public getDataChannel = async (targetTwinId: string): Promise<PhygridDataChannel> => {
449
- const options: WebRTCConnectionOptions = {
450
- targetTwinId,
451
- sendTwinMessage: this.sendTwinMessage.bind(this),
452
- subscribeTwin: this.subscribeTwin.bind(this),
453
- onTwinMessage: this.onTwinMessage.bind(this),
454
- offTwinMessage: this.offTwinMessage.bind(this),
455
- useStun: true,
456
- isInitiator: true,
457
- channelPrefix: 'channel',
458
- mediaOptions: { isMediaConnection: false },
459
- };
460
-
461
- const result = (await createWebRTCDataChannelConnection(options)) as WebRTCDataChannelResult;
462
- return result.dataChannel;
413
+ /**
414
+ * Get a DataChannel connection to a target twin.
415
+ * Initiates a WebRTC DataChannel connection for peer-to-peer messaging.
416
+ * @param targetTwinId - The twin ID to connect to
417
+ * @param channelName - Optional channel name for multiple channels to same peer (default: 'default')
418
+ */
419
+ public getDataChannel = async (targetTwinId: string, channelName?: string): Promise<PhygridDataChannel> => {
420
+ const manager = await this.getWebRTCManager();
421
+ return manager.createDataChannel(targetTwinId, channelName);
463
422
  };
464
423
 
424
+ /**
425
+ * Listen for an incoming DataChannel from a specific twin.
426
+ * Call this when you expect another twin to initiate a connection.
427
+ * @param sourceTwinId - The twin ID that will initiate the connection
428
+ * @param callback - Called when the channel is established
429
+ * @param channelName - Optional channel name for multiple channels (default: 'default')
430
+ */
465
431
  public onDataChannel = async (
466
432
  sourceTwinId: string,
467
- callback: (dc: PhygridDataChannel) => void
468
- ) => {
469
- const options: WebRTCConnectionOptions = {
470
- targetTwinId: sourceTwinId,
471
- sendTwinMessage: this.sendTwinMessage.bind(this),
472
- subscribeTwin: this.subscribeTwin.bind(this),
473
- onTwinMessage: this.onTwinMessage.bind(this),
474
- offTwinMessage: this.offTwinMessage.bind(this),
475
- useStun: true,
476
- isInitiator: false,
477
- channelPrefix: 'channel',
478
- mediaOptions: { isMediaConnection: false },
479
- };
480
-
481
- const result = (await createWebRTCDataChannelConnection(options)) as WebRTCDataChannelResult;
482
- callback(result.dataChannel);
433
+ callback: (dc: PhygridDataChannel) => void,
434
+ channelName?: string
435
+ ): Promise<void> => {
436
+ const manager = await this.getWebRTCManager();
437
+ const channel = await manager.acceptDataChannel(sourceTwinId, channelName);
438
+ callback(channel);
483
439
  };
484
440
 
485
- public async getMediaStream(targetTwinId: string): Promise<{
441
+ /**
442
+ * Get a MediaStream connection to a target twin.
443
+ * Initiates a WebRTC MediaStream connection for peer-to-peer media.
444
+ * @param targetTwinId - The twin ID to connect to
445
+ * @param options - Optional MediaStream options (including channelName for multiple streams)
446
+ */
447
+ public async getMediaStream(
448
+ targetTwinId: string,
449
+ options?: MediaStreamOptions
450
+ ): Promise<{
486
451
  stream: PhygridMediaStream;
487
452
  close: () => void;
488
453
  }> {
489
- const options: WebRTCConnectionOptions = {
490
- targetTwinId,
491
- sendTwinMessage: this.sendTwinMessage.bind(this),
492
- subscribeTwin: this.subscribeTwin.bind(this),
493
- onTwinMessage: this.onTwinMessage.bind(this),
494
- offTwinMessage: this.offTwinMessage.bind(this),
495
- isInitiator: true,
496
- channelPrefix: 'media',
497
- mediaOptions: {
498
- isMediaConnection: true,
499
- mediaDirection: 'recvonly',
500
- },
501
- };
502
- const result: WebRTCMediaStreamResult = await createWebRTCMediaStreamConnection(options);
454
+ const manager = await this.getWebRTCManager();
455
+ const channelName = options?.channelName ?? 'default';
456
+ const stream = await manager.createMediaStream(targetTwinId, options, channelName);
503
457
  return {
504
- stream: result.mediaStream,
505
- close: result.close,
458
+ stream,
459
+ close: () => stream.close(),
506
460
  };
507
461
  }
508
462
 
463
+ /**
464
+ * Listen for an incoming MediaStream from a specific twin.
465
+ * Call this when you expect another twin to initiate a media connection.
466
+ * @param sourceTwinId - The twin ID that will initiate the connection
467
+ * @param callback - Callback when stream is received
468
+ * @param options - Optional MediaStream options (including channelName for multiple streams)
469
+ */
509
470
  public onMediaStream = async (
510
471
  sourceTwinId: string,
511
- callback: (stream: PhygridMediaStream) => void
472
+ callback: (stream: PhygridMediaStream) => void,
473
+ options?: MediaStreamOptions
512
474
  ): Promise<void> => {
513
- const options: WebRTCConnectionOptions = {
514
- targetTwinId: sourceTwinId,
515
- sendTwinMessage: this.sendTwinMessage.bind(this),
516
- subscribeTwin: this.subscribeTwin.bind(this),
517
- onTwinMessage: this.onTwinMessage.bind(this),
518
- offTwinMessage: this.offTwinMessage.bind(this),
519
- isInitiator: false,
520
- channelPrefix: 'media',
521
- mediaOptions: {
522
- isMediaConnection: true,
523
- },
524
- };
525
-
526
- const result: WebRTCMediaStreamResult = await createWebRTCMediaStreamConnection(options);
527
- callback(result.mediaStream);
475
+ const manager = await this.getWebRTCManager();
476
+ const channelName = options?.channelName ?? 'default';
477
+ const stream = await manager.acceptMediaStream(sourceTwinId, options, channelName);
478
+ callback(stream);
528
479
  };
529
480
 
481
+ /**
482
+ * Get the WebRTCManager for advanced control over WebRTC connections.
483
+ * Provides access to events, connection state, and more.
484
+ */
485
+ public async getWebRTCManager(options?: WebRTCManagerOptions): Promise<WebRTCManager> {
486
+ if (!this.webrtcManager) {
487
+ await this.assureSocketConnection();
488
+
489
+ // Create TwinMessagingInterface adapter
490
+ const twinMessaging: TwinMessagingInterface = {
491
+ sendMessage: async (targetTwinId: string, data: any) => {
492
+ await this.sendTwinMessage(targetTwinId, data);
493
+ },
494
+ subscribe: async (twinId: string) => {
495
+ await this.subscribeTwin(twinId);
496
+ },
497
+ onMessage: (twinId: string, callback: (msg: any) => void) => {
498
+ this.onTwinMessage(twinId, callback);
499
+ },
500
+ offMessage: (twinId: string, callback: (msg: any) => void) => {
501
+ this.offTwinMessage(twinId, callback);
502
+ },
503
+ getOwnTwinId: () => {
504
+ if (!this.instanceId) {
505
+ throw new Error('Instance ID not set');
506
+ }
507
+ return this.instanceId;
508
+ },
509
+ };
510
+
511
+ this.webrtcManager = new WebRTCManager(twinMessaging, options);
512
+ }
513
+ return this.webrtcManager;
514
+ }
515
+
530
516
  // TODO properties should automatically be updated when the edge twin is updated
531
517
  // We could do a refresh method and call that on reconnect like we handle the resubscribes for peripherals
532
518
  public async getInstance(): Promise<Instance> {
@@ -543,50 +529,14 @@ export class PhyHubClient {
543
529
  let instance: Instance | undefined;
544
530
 
545
531
  let instanceTwin = await this.getTwinById(this.instanceId);
546
- // console.log('getTwinById response', instanceTwin);
547
-
548
- const edgeInstanceTypePrefix = 'edgeInstance';
549
-
550
- const edgeTwinEmit = (type: string, payload: any) => {
551
- // console.log('edgeTwinEmit', type, payload);
552
- const { id: twinId } = instanceTwin;
553
- const edgeTwinMessagePayload = {
554
- type: `${edgeInstanceTypePrefix}:${type}`,
555
- sourceTwinId: twinId,
556
- sourceDeviceId: instanceTwin.deviceId,
557
- data: payload,
558
- };
559
- // console.log('edgeTwinEmit', edgeTwinMessagePayload);
560
- this.sendTwinMessage(twinId, edgeTwinMessagePayload);
561
- };
562
-
563
- const edgeTwinOn = (type: string, callback: (message: any) => void) => {
564
- const { id: twinId } = instanceTwin;
565
- const onEdgeTwinMessage = (payload: any) => {
566
- // console.log('onEdgeTwinMessage callback', payload);
567
- const messageType = payload.data?.type || payload.type;
568
- if (messageType === `${edgeInstanceTypePrefix}:${type}`) {
569
- // console.log('onEdgeTwinMessage', payload.data?.data || payload.data);
570
- callback(payload.data?.data || payload.data);
571
- } else {
572
- // console.log('onEdgeTwinMessage', 'ignoring', messageType);
573
- }
574
- };
575
- this.onTwinMessage(twinId, onEdgeTwinMessage);
576
- };
577
532
 
578
- const edgeTwinTo = (targetTwinId: string) => ({
579
- emit: (type: string, payload: any) => {
580
- // console.log('edgeTwinEmit', type, payload);
581
- const edgeTwinMessagePayload = {
582
- type: `${edgeInstanceTypePrefix}:${type}`,
583
- sourceTwinId: instanceTwin.id,
584
- sourceDeviceId: instanceTwin.deviceId,
585
- data: payload,
586
- };
587
- // console.log('edgeTwinEmit', edgeTwinMessagePayload);
588
- this.sendTwinMessage(targetTwinId, edgeTwinMessagePayload);
589
- },
533
+ // Create messaging methods using the shared factory
534
+ const messaging = createTwinMessaging({
535
+ sendEvent: (targetTwinId, payload) => this.sendEvent(targetTwinId, payload),
536
+ onTwinMessage: (twinId, callback) => this.onTwinMessage(twinId, callback),
537
+ twinId: instanceTwin.id,
538
+ deviceId: instanceTwin.deviceId,
539
+ typePrefix: 'edgeInstance',
590
540
  });
591
541
 
592
542
  const getPeripheralTwins = async (): Promise<PeripheralTwinResponse[]> => {
@@ -648,27 +598,26 @@ export class PhyHubClient {
648
598
  });
649
599
  };
650
600
 
651
- const getDataChannel = async () => {
652
- return await this.getDataChannel(instanceTwin.id);
601
+ const getDataChannel = async (channelName?: string) => {
602
+ return await this.getDataChannel(instanceTwin.id, channelName);
653
603
  };
654
604
 
655
- const onDataChannel = async (callback: (dc: PhygridDataChannel) => void) => {
656
- return await this.onDataChannel(instanceTwin.id, callback);
605
+ const onDataChannel = async (callback: (dc: PhygridDataChannel) => void, channelName?: string) => {
606
+ return await this.onDataChannel(instanceTwin.id, callback, channelName);
657
607
  };
658
608
 
659
- const getMediaStream = async () => {
609
+ const getMediaStream = async (channelName?: string) => {
660
610
  if (!instance) {
661
611
  throw new Error('Instance not initialized');
662
612
  }
663
- return await this.getMediaStream(instance.id);
613
+ return await this.getMediaStream(instance.id, { channelName });
664
614
  };
665
615
 
666
- const onMediaStream = async (callback: (stream: PhygridMediaStream) => void): Promise<void> => {
616
+ const onMediaStream = async (callback: (stream: PhygridMediaStream) => void, channelName?: string): Promise<void> => {
667
617
  if (!instance) {
668
618
  throw new Error('Instance not initialized');
669
619
  }
670
- await this.onMediaStream(instance.id, callback);
671
- // Return void to match the expected return type
620
+ await this.onMediaStream(instance.id, callback, { channelName });
672
621
  };
673
622
 
674
623
  const updateReported = async (properties: Record<string, any>) => {
@@ -682,9 +631,9 @@ export class PhyHubClient {
682
631
 
683
632
  instance = {
684
633
  ...instanceTwin,
685
- emit: edgeTwinEmit,
686
- on: edgeTwinOn,
687
- to: edgeTwinTo,
634
+ emit: messaging.emit,
635
+ on: messaging.on,
636
+ to: messaging.to,
688
637
  createPeripheralTwin,
689
638
  updateReported,
690
639
  getPeripheralTwins,
@@ -707,303 +656,15 @@ export class PhyHubClient {
707
656
  }
708
657
 
709
658
  // TODO properties should automatically be updated when the edge twin is updated
710
- public async getPeripheralInstance(peripheralTwinId: string): Promise<PeripheralInstance> {
711
- if (this.peripheralInstances.has(peripheralTwinId)) {
712
- return this.peripheralInstances.get(peripheralTwinId)!;
659
+ /**
660
+ * provides instance of PeripheralTwinInstance class
661
+ */
662
+ public async getPeripheralInstance(peripheralTwinId: string): Promise<IPeripheralTwinInstance | null> {
663
+ if (!this.twinInstancesRegistry) {
664
+ throw new Error('PhyHubClient instance is not initialized yet');
713
665
  }
714
- // console.log(`Getting peripheral instance for twin ID: ${peripheralTwinId}`);
715
- let peripheralInstance: PeripheralInstance | undefined;
716
- const edgeInstance = await this.getInstance();
717
- if (!edgeInstance) {
718
- console.error('Edge instance not found');
719
- throw new Error('Edge instance not found');
720
- }
721
-
722
- // console.log('getting peripheral twin', peripheralTwinId);
723
-
724
- const peripheralTwin = await this.getTwinById(peripheralTwinId);
725
- // console.log('peripheralTwinp', peripheralTwin);
726
- if (!peripheralTwin) {
727
- console.error(`Peripheral twin with id ${peripheralTwinId} not found`);
728
- throw new Error(`Peripheral twin with id ${peripheralTwinId} not found`);
729
- }
730
-
731
- if (peripheralTwin.type !== TwinTypeEnum.Peripheral) {
732
- throw new Error(`Twin ${peripheralTwinId} is not a peripheral twin`);
733
- }
734
-
735
- // Auto-subscribe if peripheral belongs to a different device
736
- // if (peripheralTwin.deviceId !== edgeInstance.deviceId) {
737
- // console.log('Auto-subscribing to peripheral twin', peripheralTwinId);
738
- // const response =
739
- await this.subscribeTwin(peripheralTwinId);
740
- // console.log('subscribeTwin response', response);
741
- // }
742
-
743
- const instanceTypePrefix = 'peripheralInstance';
744
-
745
- const peripheralTwinEmit = (type: string, payload: any) => {
746
- // console.log(`Peripheral ${peripheralTwinId} emitting event:`, {
747
- // type,
748
- // payload
749
- // });
750
-
751
- const messagePayload = {
752
- type: `${instanceTypePrefix}:${peripheralTwin.id}:${type}`,
753
- sourceTwinId: peripheralTwin.id,
754
- sourceDeviceId: edgeInstance.deviceId,
755
- data: payload,
756
- };
757
-
758
- // console.log('Sending twin message:', JSON.stringify(messagePayload, null, 2));
759
- this.sendTwinMessage(peripheralTwin.id, messagePayload);
760
- };
761
-
762
- const peripheralTwinOn = (type: string, callback: (message: any) => void) => {
763
- console.log(`Setting up listener for peripheral ${peripheralTwinId} event: ${type}`);
764
-
765
- const onMessage = (payload: any) => {
766
- // console.log(`Received message for peripheral ${peripheralTwinId}:`, payload);
767
- const messageType = payload.data?.type || payload.type;
768
- if (messageType === `${instanceTypePrefix}:${peripheralTwin.id}:${type}`) {
769
- // console.log('Processing matching message type');
770
- callback(payload.data?.data || payload.data);
771
- } else {
772
- // console.log('Ignoring non-matching message type:', messageType);
773
- }
774
- };
775
-
776
- this.onTwinMessage(peripheralTwin.id, onMessage);
777
- };
778
-
779
- const peripheralTwinTo = (targetTwinId: string) => ({
780
- emit: (type: string, payload: any) => {
781
- const messagePayload = {
782
- type: `${instanceTypePrefix}:${peripheralTwin.id}:${type}`,
783
- sourceTwinId: edgeInstance.id,
784
- sourceDeviceId: edgeInstance.deviceId,
785
- data: payload,
786
- };
787
- this.sendTwinMessage(targetTwinId, messagePayload);
788
- },
789
- });
790
-
791
- const getDataChannel = async () => {
792
- return await this.getDataChannel(peripheralTwin.id);
793
- };
794
-
795
- const onDataChannel = async (callback: (dc: PhygridDataChannel) => void) => {
796
- return await this.onDataChannel(peripheralTwin.id, callback);
797
- };
798
-
799
- const getMediaStream = async () => {
800
- if (!peripheralInstance) {
801
- throw new Error('Peripheral instance not initialized');
802
- }
803
- return await this.getMediaStream(peripheralInstance.id);
804
- };
805
-
806
- const onMediaStream = async (callback: (stream: PhygridMediaStream) => void): Promise<void> => {
807
- if (!peripheralInstance) {
808
- throw new Error('Peripheral instance not initialized');
809
- }
810
- await this.onMediaStream(peripheralInstance.id, callback);
811
- // Return void to match the expected return type
812
- };
813
-
814
- const updateReported = async (properties: Record<string, any>) => {
815
- const newReported = {
816
- ...properties,
817
- };
818
- const result = await this.updateReportedProperties(peripheralTwin.id, newReported);
819
- // console.log('hub-client updateReported result', result);
820
- Object.assign(peripheralInstance!, result);
821
- return result;
822
- };
823
-
824
- const onUpdateReported = (callback: (reportedProperties: Record<string, any>) => void) => {
825
- let previousProperties: Record<string, any> | undefined = undefined;
826
-
827
- console.log(`[onUpdateReported] Registering handler for all reported properties`);
828
-
829
- this.onTwinUpdate(peripheralTwin.id, (twin: TwinResponse) => {
830
- console.log(`[onUpdateReported] Twin update received for ${peripheralTwin.id}`);
831
-
832
- // Only process updates for this specific peripheral
833
- if (twin.id !== peripheralTwin.id) {
834
- console.log(`[onUpdateReported] Twin ID mismatch: ${twin.id} !== ${peripheralTwin.id}`);
835
- return;
836
- }
837
-
838
- const reported = twin.properties.reported as Record<string, any>;
839
-
840
- // Check if reported properties exist
841
- if (reported) {
842
- // If this is the first update, always trigger
843
- if (previousProperties === undefined) {
844
- console.log(`[onUpdateReported] First update - calling callback`);
845
- previousProperties = JSON.parse(JSON.stringify(reported)); // Deep copy
846
- callback(reported);
847
- return;
848
- }
849
-
850
- const currentPropertiesStr = JSON.stringify(reported);
851
- const previousPropertiesStr = JSON.stringify(previousProperties);
852
-
853
- // Debug by printing parts of the strings - last 100 chars to avoid excessive logs
854
- console.log(
855
- `[onUpdateReported] Current (end): ...${currentPropertiesStr.substring(currentPropertiesStr.length - 100)}`
856
- );
857
- console.log(
858
- `[onUpdateReported] Previous (end): ...${previousPropertiesStr.substring(previousPropertiesStr.length - 100)}`
859
- );
860
-
861
- // Additional check - compare string lengths first
862
- if (currentPropertiesStr.length !== previousPropertiesStr.length) {
863
- console.log(
864
- `[onUpdateReported] String length changed: ${currentPropertiesStr.length} vs ${previousPropertiesStr.length}`
865
- );
866
- }
867
-
868
- const hasChanged = currentPropertiesStr !== previousPropertiesStr;
869
-
870
- console.log(
871
- `[onUpdateReported] Properties comparison:`,
872
- hasChanged ? 'CHANGED' : 'UNCHANGED'
873
- );
874
-
875
- if (hasChanged) {
876
- previousProperties = JSON.parse(currentPropertiesStr); // Deep copy
877
- console.log(`[onUpdateReported] Calling callback with updated reported properties`);
878
- callback(reported);
879
- } else {
880
- // Force a callback call at least every 5 updates to ensure clients get updates
881
- // This is a safety mechanism
882
- console.log(`[onUpdateReported] No change detected in properties`);
883
- }
884
- } else {
885
- console.log(`[onUpdateReported] No reported properties found`);
886
- }
887
- });
888
- };
889
-
890
- const onUpdateDesired = (callback: (desiredProperties: Record<string, any>) => void) => {
891
- let previousProperties: Record<string, any> | undefined = undefined;
892
-
893
- console.log(`[onUpdateDesired] Registering handler for all desired properties`);
894
-
895
- this.onTwinUpdate(peripheralTwin.id, (twin: TwinResponse) => {
896
- console.log(`[onUpdateDesired] Twin update received for ${peripheralTwin.id}`);
897
-
898
- // Only process updates for this specific peripheral
899
- if (twin.id !== peripheralTwin.id) {
900
- console.log(`[onUpdateDesired] Twin ID mismatch: ${twin.id} !== ${peripheralTwin.id}`);
901
- return;
902
- }
903
-
904
- const desired = twin.properties.desired as Record<string, any>;
905
-
906
- // Check if desired properties exist
907
- if (desired) {
908
- // If this is the first update, always trigger
909
- if (previousProperties === undefined) {
910
- console.log(`[onUpdateDesired] First update - calling callback`);
911
- previousProperties = JSON.parse(JSON.stringify(desired)); // Deep copy
912
- callback(desired);
913
- return;
914
- }
915
666
 
916
- const currentPropertiesStr = JSON.stringify(desired);
917
- const previousPropertiesStr = JSON.stringify(previousProperties);
918
-
919
- // Debug by printing parts of the strings - last 100 chars to avoid excessive logs
920
- console.log(
921
- `[onUpdateDesired] Current (end): ...${currentPropertiesStr.substring(currentPropertiesStr.length - 100)}`
922
- );
923
- console.log(
924
- `[onUpdateDesired] Previous (end): ...${previousPropertiesStr.substring(previousPropertiesStr.length - 100)}`
925
- );
926
-
927
- // Additional check - compare string lengths first
928
- if (currentPropertiesStr.length !== previousPropertiesStr.length) {
929
- console.log(
930
- `[onUpdateDesired] String length changed: ${currentPropertiesStr.length} vs ${previousPropertiesStr.length}`
931
- );
932
- }
933
-
934
- const hasChanged = currentPropertiesStr !== previousPropertiesStr;
935
-
936
- console.log(
937
- `[onUpdateDesired] Properties comparison:`,
938
- hasChanged ? 'CHANGED' : 'UNCHANGED'
939
- );
940
-
941
- if (hasChanged) {
942
- previousProperties = JSON.parse(currentPropertiesStr); // Deep copy
943
- console.log(`[onUpdateDesired] Calling callback with updated desired properties`);
944
- callback(desired);
945
- } else {
946
- // Force a callback call at least every 5 updates to ensure clients get updates
947
- // This is a safety mechanism
948
- console.log(`[onUpdateDesired] No change detected in properties`);
949
- }
950
- } else {
951
- console.log(`[onUpdateDesired] No desired properties found`);
952
- }
953
- });
954
- };
955
-
956
- const remove = async (): Promise<TwinResponse | void> => {
957
- const payload: EventPayload = {
958
- data: { twinId: peripheralTwin.id },
959
- };
960
-
961
- return new Promise((resolve, reject) => {
962
- this.emit('deletePeripheralTwin', payload, (response: any) => {
963
- const { twin } = response;
964
- resolve(twin);
965
- });
966
-
967
- this.socket?.on('error', (error: any) => {
968
- reject(error);
969
- });
970
- });
971
- };
972
-
973
- const updateDesired = async (properties: Record<string, any>): Promise<TwinResponse> => {
974
- const payload: EventPayload = {
975
- twinId: peripheralTwin.id,
976
- data: properties,
977
- };
978
-
979
- return new Promise((resolve, reject) => {
980
- this.emit('updatePeripheralTwinDesired', payload, (response: any) => {
981
- const { twin } = response;
982
- resolve(twin || {});
983
- });
984
-
985
- this.socket?.on('error', (error: any) => {
986
- reject(error);
987
- });
988
- });
989
- };
990
-
991
- peripheralInstance = {
992
- ...peripheralTwin,
993
- emit: peripheralTwinEmit,
994
- on: peripheralTwinOn,
995
- to: peripheralTwinTo,
996
- updateReported,
997
- updateDesired,
998
- onUpdateReported,
999
- onUpdateDesired,
1000
- remove,
1001
- getDataChannel,
1002
- onDataChannel,
1003
- getMediaStream,
1004
- onMediaStream,
1005
- };
1006
- this.peripheralInstances.set(peripheralTwinId, peripheralInstance);
667
+ const peripheralInstance = await this.twinInstancesRegistry.getPeripheralInstance(peripheralTwinId);
1007
668
  return peripheralInstance;
1008
669
  }
1009
670
 
@@ -1019,7 +680,7 @@ export class PhyHubClient {
1019
680
  }
1020
681
  if (currentWindow.parent === currentWindow) break;
1021
682
  currentWindow = currentWindow.parent as Window & typeof globalThis;
1022
- } catch (e) {
683
+ } catch {
1023
684
  break;
1024
685
  }
1025
686
  }
@@ -1106,11 +767,22 @@ export class PhyHubClient {
1106
767
  return;
1107
768
  }
1108
769
 
1109
- const event = this.instanceId;
1110
770
  const callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined;
1111
- const payload = { method, ...args[0] };
1112
771
 
1113
- const emitArgs = [event, payload];
772
+ let emitArgs: any[];
773
+
774
+ // In direct connection mode, emit directly to the event name
775
+ // In normal mode, emit to instanceId channel with method in payload (for phyos routing)
776
+ if (PhyHubDirectConnection.isEnabled()) {
777
+ // Direct mode: emit to event name directly
778
+ const payload = args[0] || {};
779
+ emitArgs = [method, payload];
780
+ } else {
781
+ // Normal mode: emit to instanceId channel with method in payload
782
+ const event = this.instanceId;
783
+ const payload = { method, ...args[0] };
784
+ emitArgs = [event, payload];
785
+ }
1114
786
 
1115
787
  if (callback) {
1116
788
  emitArgs.push(callback);
@@ -1187,16 +859,131 @@ export class PhyHubClient {
1187
859
  return result;
1188
860
  }
1189
861
 
862
+ /**
863
+ * Send a fire-and-forget event to a twin.
864
+ * This is the standard way to send events - no response is expected.
865
+ */
866
+ public sendEvent(targetTwinId: string, data: any): void {
867
+ // console.log('[sendEvent] Sending to:', targetTwinId, 'data type:', data?.type || 'unknown');
868
+
869
+ // For fire-and-forget, we need to get the instance synchronously if possible
870
+ // or queue the message. We'll use a simpler approach here.
871
+ const instanceId = this.instanceId;
872
+ if (!instanceId) {
873
+ console.error('[sendEvent] Instance ID not set, cannot send event');
874
+ return;
875
+ }
876
+
877
+ // Get deviceId from cached instance if available
878
+ const cachedInstance = this.instances.get(instanceId);
879
+ const deviceId = cachedInstance?.deviceId || this.lastDeviceStatusResponse?.deviceId;
880
+
881
+ if (!deviceId) {
882
+ console.error('[sendEvent] Device ID not available, cannot send event');
883
+ return;
884
+ }
885
+
886
+ const payload: EventPayload = {
887
+ twinId: targetTwinId,
888
+ sourceTwinId: instanceId,
889
+ sourceDeviceId: deviceId,
890
+ data,
891
+ };
892
+
893
+ // Fire-and-forget: just emit, no callback, no promise
894
+ this.emit(this.EVENTS.TWIN_MESSAGE, payload);
895
+ }
896
+
897
+ /**
898
+ * @deprecated Use instance.request() or instance.to(twinId).request() instead.
899
+ * This method uses socket.io acknowledgements which may not work in all scenarios.
900
+ * The instance API uses a more reliable emit/on pattern for request-response.
901
+ */
902
+ public async request(
903
+ targetTwinId: string,
904
+ data: any,
905
+ callback?: (response: TwinMessageResult) => void
906
+ ): Promise<TwinMessageResult | undefined> {
907
+ console.log('[request] Sending to:', targetTwinId, 'data type:', data?.type || 'unknown');
908
+
909
+ const edgeInstance = await this.getInstance();
910
+ if (!edgeInstance) {
911
+ console.error('[request] Edge instance not found');
912
+ throw new Error('Edge instance not found');
913
+ }
914
+
915
+ const { id: edgeTwinId, deviceId } = edgeInstance;
916
+
917
+ if (!deviceId) {
918
+ console.error('[request] Device ID not available');
919
+ throw new Error('Device ID not available - ensure device is connected');
920
+ }
921
+
922
+ const payload: EventPayload = {
923
+ twinId: targetTwinId,
924
+ sourceTwinId: edgeTwinId,
925
+ sourceDeviceId: deviceId,
926
+ data,
927
+ };
928
+
929
+ if (callback) {
930
+ // Callback pattern: use socket.io acknowledgement
931
+ this.emit(this.EVENTS.TWIN_MESSAGE, payload, (response: TwinMessageResult) => {
932
+ callback(response);
933
+ });
934
+ return;
935
+ }
936
+
937
+ // Promise pattern: wait for response with timeout
938
+ return new Promise((resolve, reject) => {
939
+ const ACTION_TIMEOUT = 10 * 1000;
940
+ const timeoutId = setTimeout(() => {
941
+ if (this.socket) {
942
+ this.socket?.off(payload.data.type);
943
+ }
944
+ reject({
945
+ status: TwinMessageResultStatus.Error,
946
+ message: `Request timed out after ${ACTION_TIMEOUT}ms`
947
+ });
948
+ }, ACTION_TIMEOUT);
949
+
950
+ if (this.socket) {
951
+ this.socket.on(payload.data.type, (response: TwinMessageResult) => {
952
+ if (response && response.status !== TwinMessageResultStatus.Warning) {
953
+ clearTimeout(timeoutId);
954
+ this.socket?.off(payload.data.type);
955
+ resolve(response);
956
+ }
957
+ });
958
+ }
959
+
960
+ this.emit(this.EVENTS.TWIN_MESSAGE, payload, (response: TwinMessageResult) => {
961
+ // Socket.io acknowledgement received
962
+ clearTimeout(timeoutId);
963
+ if (this.socket) {
964
+ this.socket?.off(payload.data.type);
965
+ }
966
+ resolve(response);
967
+ });
968
+
969
+ this.socket?.on('error', (error: any) => {
970
+ clearTimeout(timeoutId);
971
+ console.error('[request] Socket error:', error);
972
+ reject(error);
973
+ });
974
+ });
975
+ }
976
+
977
+ /**
978
+ * @deprecated Use sendEvent() for fire-and-forget events, or request() for request-response.
979
+ * This method is kept for backwards compatibility.
980
+ */
1190
981
  public async sendTwinMessage(
1191
982
  targetTwinId: string,
1192
983
  data: any,
1193
984
  callback?: (response: any) => void
1194
- ) {
1195
- // console.log('sendTwinMessage called with:', {
1196
- // targetTwinId,
1197
- // data,
1198
- // hasCallback: !!callback
1199
- // });
985
+ ): Promise<void> {
986
+ console.log('[sendTwinMessage] Sending to:', targetTwinId, 'data type:', data?.type || 'unknown');
1200
987
 
1201
988
  const edgeInstance = await this.getInstance();
1202
989
  if (!edgeInstance) {
@@ -1218,27 +1005,13 @@ export class PhyHubClient {
1218
1005
  data,
1219
1006
  };
1220
1007
 
1221
- // console.log('sendTwinMessage prepared payload:', JSON.stringify(payload, null, 2));
1222
-
1223
1008
  if (callback) {
1224
- // console.log('sendTwinMessage emitting with callback');
1225
1009
  this.emit(this.EVENTS.TWIN_MESSAGE, payload, (response: any) => {
1226
- // console.log('sendTwinMessage callback received response:', response);
1227
1010
  callback(response);
1228
1011
  });
1229
1012
  } else {
1230
- // console.log('sendTwinMessage emitting with promise');
1231
- return new Promise((resolve, reject) => {
1232
- this.emit(this.EVENTS.TWIN_MESSAGE, payload, (response: any) => {
1233
- // console.log('sendTwinMessage promise received response:', response);
1234
- resolve(response);
1235
- });
1236
-
1237
- this.socket?.on('error', (error: any) => {
1238
- console.error('sendTwinMessage socket error:', error);
1239
- reject(error);
1240
- });
1241
- });
1013
+ // Fire-and-forget for backwards compatibility (no timeout/rejection)
1014
+ this.emit(this.EVENTS.TWIN_MESSAGE, payload);
1242
1015
  }
1243
1016
  }
1244
1017
 
@@ -1348,12 +1121,14 @@ export class PhyHubClient {
1348
1121
  }
1349
1122
 
1350
1123
  public async getTwinById(twinId: string): Promise<TwinResponse> {
1124
+ console.log(`[getTwinById] Fetching twin: ${twinId}`);
1351
1125
  const payload: EventPayload<{ twinId: string }> = {
1352
1126
  data: { twinId },
1353
1127
  };
1354
1128
 
1355
1129
  return new Promise((resolve, reject) => {
1356
1130
  this.emit(this.EVENTS.GET_TWIN_BY_ID, payload, (response: any) => {
1131
+ console.log(`[getTwinById] Response for ${twinId}:`, response);
1357
1132
  const { twin } = response;
1358
1133
  if (!twin) {
1359
1134
  reject(new Error(`Twin with id ${twinId} not found`));
@@ -1363,6 +1138,7 @@ export class PhyHubClient {
1363
1138
  });
1364
1139
 
1365
1140
  this.socket?.on('error', (error: any) => {
1141
+ console.error(`[getTwinById] Socket error for ${twinId}:`, error);
1366
1142
  reject(error);
1367
1143
  });
1368
1144
  });
@@ -1390,7 +1166,7 @@ export class PhyHubClient {
1390
1166
  // });
1391
1167
  // }
1392
1168
 
1393
- private async updateReportedProperties(
1169
+ public async updateReportedProperties(
1394
1170
  twinId: string,
1395
1171
  reportedProperties: Record<string, any>
1396
1172
  ): Promise<TwinResponse> {
@@ -1414,16 +1190,99 @@ export class PhyHubClient {
1414
1190
  });
1415
1191
  });
1416
1192
  }
1193
+
1194
+ /**
1195
+ * Initialize the device twin in direct connection mode.
1196
+ * Waits for deviceAuthenticated, then calls connectDevice to get all twins
1197
+ * for this device, including the device twin which provides the actual twinId.
1198
+ */
1199
+ private async initializeDirectConnectionTwin(): Promise<void> {
1200
+ console.log('[initializeDirectConnectionTwin] Waiting for authentication...');
1201
+
1202
+ // First wait for deviceAuthenticated event from server
1203
+ await new Promise<void>((resolve, reject) => {
1204
+ const timeout = setTimeout(() => {
1205
+ reject(new Error('Timeout waiting for deviceAuthenticated'));
1206
+ }, 10000);
1207
+
1208
+ this.socket?.once('deviceAuthenticated', (data: any) => {
1209
+ clearTimeout(timeout);
1210
+ console.log('[initializeDirectConnectionTwin] Authenticated:', data);
1211
+ resolve();
1212
+ });
1213
+ });
1214
+
1215
+ console.log('[initializeDirectConnectionTwin] Calling connectDevice...');
1216
+
1217
+ return new Promise((resolve, reject) => {
1218
+ const timeout = setTimeout(() => {
1219
+ reject(new Error('Timeout waiting for connectDevice response'));
1220
+ }, 10000);
1221
+
1222
+ // Use connectDevice to get all twins for this device
1223
+ this.socket?.emit(
1224
+ 'connectDevice',
1225
+ (response: any) => {
1226
+ clearTimeout(timeout);
1227
+ console.log('[initializeDirectConnectionTwin] connectDevice response:', JSON.stringify(response, null, 2));
1228
+
1229
+ if (response?.status === 'success' && response?.twins?.length > 0) {
1230
+ // Find the device twin from the array (type is "Device" with capital D)
1231
+ const deviceTwin = response.twins.find((t: any) => t.type === 'Device');
1232
+
1233
+ if (deviceTwin) {
1234
+ const actualTwinId = deviceTwin.id || deviceTwin._id;
1235
+ console.log(
1236
+ `[initializeDirectConnectionTwin] Got device twin. DeviceId: ${deviceTwin.deviceId}, TwinId: ${actualTwinId}`
1237
+ );
1238
+ this.instanceId = actualTwinId;
1239
+ resolve();
1240
+ } else {
1241
+ console.error('[initializeDirectConnectionTwin] No device twin found in twins array');
1242
+ reject(new Error('No device twin found for this device'));
1243
+ }
1244
+ } else if (response?.status === 'error') {
1245
+ const errorMessage = response?.message || 'Unknown error';
1246
+ console.error('[initializeDirectConnectionTwin] connectDevice failed:', errorMessage);
1247
+
1248
+ // Provide more helpful error messages
1249
+ if (errorMessage.includes('Failed to find device twin')) {
1250
+ console.error('\n=== DEVICE TWIN NOT FOUND ===');
1251
+ console.error('This device exists in the legacy database but has not been');
1252
+ console.error('properly migrated to PhyHub. The device twins need to be created.');
1253
+ console.error('');
1254
+ console.error('To fix this:');
1255
+ console.error('1. Reset isMigratedToPhyhub flag in legacy DB, OR');
1256
+ console.error('2. Use a different device that has been properly migrated');
1257
+ console.error('==============================\n');
1258
+ }
1259
+
1260
+ reject(new Error(errorMessage));
1261
+ } else {
1262
+ console.error('[initializeDirectConnectionTwin] Unexpected response:', response);
1263
+ reject(new Error('Failed to connect device'));
1264
+ }
1265
+ }
1266
+ );
1267
+ });
1268
+ }
1417
1269
  }
1418
1270
 
1419
1271
  export const connectPhyClient = (
1420
1272
  params: { instanceId?: string; moduleName?: string; dataResidency?: string } = {}
1421
1273
  ) => PhyHubClient.connect(params);
1422
1274
 
1423
- export type { EdgeTwinResponse, Instance, PeripheralInstance };
1275
+ export type { EdgeTwinResponse, Instance, PeripheralInstance, IPeripheralTwinInstance, TwinMessageResult };
1276
+ export { TwinMessageResultStatus };
1424
1277
 
1425
1278
  export default {
1426
1279
  connectPhyClient,
1427
1280
  };
1428
1281
 
1429
- export type { PhygridDataChannel, PhygridMediaStream };
1282
+ // Re-export WebRTC types for convenience
1283
+ export type { PhygridDataChannel, PhygridMediaStream, WebRTCManagerOptions, MediaStreamOptions };
1284
+ export { WebRTCManager } from './services/webrtc';
1285
+
1286
+ // Re-export twin messaging types for custom implementations
1287
+ export type { TwinMessagingContext, TwinMessagingMethods } from './twin-messaging';
1288
+ export { createTwinMessaging } from './twin-messaging';