@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
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,31 @@ 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
- // });
208
+ this.socket.on(this.EVENTS.TWIN_MESSAGE, (payload: EventPayload, callback?: (response: TwinMessageResult | null) => void) => {
209
+ let result: TwinMessageResult | null = null;
269
210
 
270
211
  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
- // });
212
+ // Look up listeners by twinId (the target of the message)
213
+ // This is the original pre-PR-144 approach
275
214
  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);
215
+
216
+ if (listeners?.size) {
217
+ listeners.forEach(listener => {
280
218
  try {
281
- callback(payload);
219
+ listener(payload);
220
+ result = { status: TwinMessageResultStatus.Success, message: 'Action completed' };
282
221
  } catch (error) {
283
- console.error(`Error in twin message listener for ${payload.twinId}:`, error);
222
+ console.error(`[TWIN_MESSAGE] Error in listener for ${payload.twinId}:`, error);
223
+ result = { status: TwinMessageResultStatus.Error, message: (error as Error)?.message || 'An error occurred' };
284
224
  }
285
225
  });
286
226
  } else {
287
- // console.log(`No listeners found for twin ${payload.twinId}`, JSON.stringify(payload, null, 2));
227
+ result = { status: TwinMessageResultStatus.Warning, message: `No listeners found for twin ${payload.twinId}` };
228
+ }
229
+
230
+ if (callback) {
231
+ callback(result);
288
232
  }
289
- } else {
290
- // console.log('Invalid twin message payload:', payload);
291
233
  }
292
234
  });
293
235
 
@@ -336,6 +278,10 @@ export class PhyHubClient {
336
278
  if (!PhyHubClient.instance) {
337
279
  PhyHubClient.instance = new PhyHubClient(params);
338
280
  await PhyHubClient.instance.initializeConnection();
281
+
282
+ // create twin registry to store instances
283
+ PhyHubClient.instance.twinInstancesRegistry = new TwinRegistry(PhyHubClient.instance);
284
+
339
285
  console.info(`connect(): Connection to phyhub initialized`);
340
286
  }
341
287
  return PhyHubClient.instance;
@@ -445,88 +391,109 @@ export class PhyHubClient {
445
391
  });
446
392
  }
447
393
 
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;
394
+ /**
395
+ * Get a DataChannel connection to a target twin.
396
+ * Initiates a WebRTC DataChannel connection for peer-to-peer messaging.
397
+ * @param targetTwinId - The twin ID to connect to
398
+ * @param channelName - Optional channel name for multiple channels to same peer (default: 'default')
399
+ */
400
+ public getDataChannel = async (targetTwinId: string, channelName?: string): Promise<PhygridDataChannel> => {
401
+ const manager = await this.getWebRTCManager();
402
+ return manager.createDataChannel(targetTwinId, channelName);
463
403
  };
464
404
 
405
+ /**
406
+ * Listen for an incoming DataChannel from a specific twin.
407
+ * Call this when you expect another twin to initiate a connection.
408
+ * @param sourceTwinId - The twin ID that will initiate the connection
409
+ * @param callback - Called when the channel is established
410
+ * @param channelName - Optional channel name for multiple channels (default: 'default')
411
+ */
465
412
  public onDataChannel = async (
466
413
  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);
414
+ callback: (dc: PhygridDataChannel) => void,
415
+ channelName?: string
416
+ ): Promise<void> => {
417
+ const manager = await this.getWebRTCManager();
418
+ const channel = await manager.acceptDataChannel(sourceTwinId, channelName);
419
+ callback(channel);
483
420
  };
484
421
 
485
- public async getMediaStream(targetTwinId: string): Promise<{
422
+ /**
423
+ * Get a MediaStream connection to a target twin.
424
+ * Initiates a WebRTC MediaStream connection for peer-to-peer media.
425
+ * @param targetTwinId - The twin ID to connect to
426
+ * @param options - Optional MediaStream options (including channelName for multiple streams)
427
+ */
428
+ public async getMediaStream(
429
+ targetTwinId: string,
430
+ options?: MediaStreamOptions
431
+ ): Promise<{
486
432
  stream: PhygridMediaStream;
487
433
  close: () => void;
488
434
  }> {
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);
435
+ const manager = await this.getWebRTCManager();
436
+ const channelName = options?.channelName ?? 'default';
437
+ const stream = await manager.createMediaStream(targetTwinId, options, channelName);
503
438
  return {
504
- stream: result.mediaStream,
505
- close: result.close,
439
+ stream,
440
+ close: () => stream.close(),
506
441
  };
507
442
  }
508
443
 
444
+ /**
445
+ * Listen for an incoming MediaStream from a specific twin.
446
+ * Call this when you expect another twin to initiate a media connection.
447
+ * @param sourceTwinId - The twin ID that will initiate the connection
448
+ * @param callback - Callback when stream is received
449
+ * @param options - Optional MediaStream options (including channelName for multiple streams)
450
+ */
509
451
  public onMediaStream = async (
510
452
  sourceTwinId: string,
511
- callback: (stream: PhygridMediaStream) => void
453
+ callback: (stream: PhygridMediaStream) => void,
454
+ options?: MediaStreamOptions
512
455
  ): 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);
456
+ const manager = await this.getWebRTCManager();
457
+ const channelName = options?.channelName ?? 'default';
458
+ const stream = await manager.acceptMediaStream(sourceTwinId, options, channelName);
459
+ callback(stream);
528
460
  };
529
461
 
462
+ /**
463
+ * Get the WebRTCManager for advanced control over WebRTC connections.
464
+ * Provides access to events, connection state, and more.
465
+ */
466
+ public async getWebRTCManager(options?: WebRTCManagerOptions): Promise<WebRTCManager> {
467
+ if (!this.webrtcManager) {
468
+ await this.assureSocketConnection();
469
+
470
+ // Create TwinMessagingInterface adapter
471
+ const twinMessaging: TwinMessagingInterface = {
472
+ sendMessage: async (targetTwinId: string, data: any) => {
473
+ await this.sendTwinMessage(targetTwinId, data);
474
+ },
475
+ subscribe: async (twinId: string) => {
476
+ await this.subscribeTwin(twinId);
477
+ },
478
+ onMessage: (twinId: string, callback: (msg: any) => void) => {
479
+ this.onTwinMessage(twinId, callback);
480
+ },
481
+ offMessage: (twinId: string, callback: (msg: any) => void) => {
482
+ this.offTwinMessage(twinId, callback);
483
+ },
484
+ getOwnTwinId: () => {
485
+ if (!this.instanceId) {
486
+ throw new Error('Instance ID not set');
487
+ }
488
+ return this.instanceId;
489
+ },
490
+ };
491
+
492
+ this.webrtcManager = new WebRTCManager(twinMessaging, options);
493
+ }
494
+ return this.webrtcManager;
495
+ }
496
+
530
497
  // TODO properties should automatically be updated when the edge twin is updated
531
498
  // We could do a refresh method and call that on reconnect like we handle the resubscribes for peripherals
532
499
  public async getInstance(): Promise<Instance> {
@@ -543,50 +510,14 @@ export class PhyHubClient {
543
510
  let instance: Instance | undefined;
544
511
 
545
512
  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
513
 
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
- },
514
+ // Create messaging methods using the shared factory
515
+ const messaging = createTwinMessaging({
516
+ sendEvent: (targetTwinId, payload) => this.sendEvent(targetTwinId, payload),
517
+ onTwinMessage: (twinId, callback) => this.onTwinMessage(twinId, callback),
518
+ twinId: instanceTwin.id,
519
+ deviceId: instanceTwin.deviceId,
520
+ typePrefix: 'edgeInstance',
590
521
  });
591
522
 
592
523
  const getPeripheralTwins = async (): Promise<PeripheralTwinResponse[]> => {
@@ -648,27 +579,26 @@ export class PhyHubClient {
648
579
  });
649
580
  };
650
581
 
651
- const getDataChannel = async () => {
652
- return await this.getDataChannel(instanceTwin.id);
582
+ const getDataChannel = async (channelName?: string) => {
583
+ return await this.getDataChannel(instanceTwin.id, channelName);
653
584
  };
654
585
 
655
- const onDataChannel = async (callback: (dc: PhygridDataChannel) => void) => {
656
- return await this.onDataChannel(instanceTwin.id, callback);
586
+ const onDataChannel = async (callback: (dc: PhygridDataChannel) => void, channelName?: string) => {
587
+ return await this.onDataChannel(instanceTwin.id, callback, channelName);
657
588
  };
658
589
 
659
- const getMediaStream = async () => {
590
+ const getMediaStream = async (channelName?: string) => {
660
591
  if (!instance) {
661
592
  throw new Error('Instance not initialized');
662
593
  }
663
- return await this.getMediaStream(instance.id);
594
+ return await this.getMediaStream(instance.id, { channelName });
664
595
  };
665
596
 
666
- const onMediaStream = async (callback: (stream: PhygridMediaStream) => void): Promise<void> => {
597
+ const onMediaStream = async (callback: (stream: PhygridMediaStream) => void, channelName?: string): Promise<void> => {
667
598
  if (!instance) {
668
599
  throw new Error('Instance not initialized');
669
600
  }
670
- await this.onMediaStream(instance.id, callback);
671
- // Return void to match the expected return type
601
+ await this.onMediaStream(instance.id, callback, { channelName });
672
602
  };
673
603
 
674
604
  const updateReported = async (properties: Record<string, any>) => {
@@ -682,9 +612,9 @@ export class PhyHubClient {
682
612
 
683
613
  instance = {
684
614
  ...instanceTwin,
685
- emit: edgeTwinEmit,
686
- on: edgeTwinOn,
687
- to: edgeTwinTo,
615
+ emit: messaging.emit,
616
+ on: messaging.on,
617
+ to: messaging.to,
688
618
  createPeripheralTwin,
689
619
  updateReported,
690
620
  getPeripheralTwins,
@@ -707,303 +637,15 @@ export class PhyHubClient {
707
637
  }
708
638
 
709
639
  // 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)!;
640
+ /**
641
+ * provides instance of PeripheralTwinInstance class
642
+ */
643
+ public async getPeripheralInstance(peripheralTwinId: string): Promise<IPeripheralTwinInstance | null> {
644
+ if (!this.twinInstancesRegistry) {
645
+ throw new Error('PhyHubClient instance is not initialized yet');
713
646
  }
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
-
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
647
 
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);
648
+ const peripheralInstance = await this.twinInstancesRegistry.getPeripheralInstance(peripheralTwinId);
1007
649
  return peripheralInstance;
1008
650
  }
1009
651
 
@@ -1019,7 +661,7 @@ export class PhyHubClient {
1019
661
  }
1020
662
  if (currentWindow.parent === currentWindow) break;
1021
663
  currentWindow = currentWindow.parent as Window & typeof globalThis;
1022
- } catch (e) {
664
+ } catch {
1023
665
  break;
1024
666
  }
1025
667
  }
@@ -1106,11 +748,22 @@ export class PhyHubClient {
1106
748
  return;
1107
749
  }
1108
750
 
1109
- const event = this.instanceId;
1110
751
  const callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined;
1111
- const payload = { method, ...args[0] };
1112
752
 
1113
- const emitArgs = [event, payload];
753
+ let emitArgs: any[];
754
+
755
+ // In direct connection mode, emit directly to the event name
756
+ // In normal mode, emit to instanceId channel with method in payload (for phyos routing)
757
+ if (PhyHubDirectConnection.isEnabled()) {
758
+ // Direct mode: emit to event name directly
759
+ const payload = args[0] || {};
760
+ emitArgs = [method, payload];
761
+ } else {
762
+ // Normal mode: emit to instanceId channel with method in payload
763
+ const event = this.instanceId;
764
+ const payload = { method, ...args[0] };
765
+ emitArgs = [event, payload];
766
+ }
1114
767
 
1115
768
  if (callback) {
1116
769
  emitArgs.push(callback);
@@ -1187,16 +840,131 @@ export class PhyHubClient {
1187
840
  return result;
1188
841
  }
1189
842
 
843
+ /**
844
+ * Send a fire-and-forget event to a twin.
845
+ * This is the standard way to send events - no response is expected.
846
+ */
847
+ public sendEvent(targetTwinId: string, data: any): void {
848
+ // console.log('[sendEvent] Sending to:', targetTwinId, 'data type:', data?.type || 'unknown');
849
+
850
+ // For fire-and-forget, we need to get the instance synchronously if possible
851
+ // or queue the message. We'll use a simpler approach here.
852
+ const instanceId = this.instanceId;
853
+ if (!instanceId) {
854
+ console.error('[sendEvent] Instance ID not set, cannot send event');
855
+ return;
856
+ }
857
+
858
+ // Get deviceId from cached instance if available
859
+ const cachedInstance = this.instances.get(instanceId);
860
+ const deviceId = cachedInstance?.deviceId || this.lastDeviceStatusResponse?.deviceId;
861
+
862
+ if (!deviceId) {
863
+ console.error('[sendEvent] Device ID not available, cannot send event');
864
+ return;
865
+ }
866
+
867
+ const payload: EventPayload = {
868
+ twinId: targetTwinId,
869
+ sourceTwinId: instanceId,
870
+ sourceDeviceId: deviceId,
871
+ data,
872
+ };
873
+
874
+ // Fire-and-forget: just emit, no callback, no promise
875
+ this.emit(this.EVENTS.TWIN_MESSAGE, payload);
876
+ }
877
+
878
+ /**
879
+ * @deprecated Use instance.request() or instance.to(twinId).request() instead.
880
+ * This method uses socket.io acknowledgements which may not work in all scenarios.
881
+ * The instance API uses a more reliable emit/on pattern for request-response.
882
+ */
883
+ public async request(
884
+ targetTwinId: string,
885
+ data: any,
886
+ callback?: (response: TwinMessageResult) => void
887
+ ): Promise<TwinMessageResult | undefined> {
888
+ console.log('[request] Sending to:', targetTwinId, 'data type:', data?.type || 'unknown');
889
+
890
+ const edgeInstance = await this.getInstance();
891
+ if (!edgeInstance) {
892
+ console.error('[request] Edge instance not found');
893
+ throw new Error('Edge instance not found');
894
+ }
895
+
896
+ const { id: edgeTwinId, deviceId } = edgeInstance;
897
+
898
+ if (!deviceId) {
899
+ console.error('[request] Device ID not available');
900
+ throw new Error('Device ID not available - ensure device is connected');
901
+ }
902
+
903
+ const payload: EventPayload = {
904
+ twinId: targetTwinId,
905
+ sourceTwinId: edgeTwinId,
906
+ sourceDeviceId: deviceId,
907
+ data,
908
+ };
909
+
910
+ if (callback) {
911
+ // Callback pattern: use socket.io acknowledgement
912
+ this.emit(this.EVENTS.TWIN_MESSAGE, payload, (response: TwinMessageResult) => {
913
+ callback(response);
914
+ });
915
+ return;
916
+ }
917
+
918
+ // Promise pattern: wait for response with timeout
919
+ return new Promise((resolve, reject) => {
920
+ const ACTION_TIMEOUT = 10 * 1000;
921
+ const timeoutId = setTimeout(() => {
922
+ if (this.socket) {
923
+ this.socket?.off(payload.data.type);
924
+ }
925
+ reject({
926
+ status: TwinMessageResultStatus.Error,
927
+ message: `Request timed out after ${ACTION_TIMEOUT}ms`
928
+ });
929
+ }, ACTION_TIMEOUT);
930
+
931
+ if (this.socket) {
932
+ this.socket.on(payload.data.type, (response: TwinMessageResult) => {
933
+ if (response && response.status !== TwinMessageResultStatus.Warning) {
934
+ clearTimeout(timeoutId);
935
+ this.socket?.off(payload.data.type);
936
+ resolve(response);
937
+ }
938
+ });
939
+ }
940
+
941
+ this.emit(this.EVENTS.TWIN_MESSAGE, payload, (response: TwinMessageResult) => {
942
+ // Socket.io acknowledgement received
943
+ clearTimeout(timeoutId);
944
+ if (this.socket) {
945
+ this.socket?.off(payload.data.type);
946
+ }
947
+ resolve(response);
948
+ });
949
+
950
+ this.socket?.on('error', (error: any) => {
951
+ clearTimeout(timeoutId);
952
+ console.error('[request] Socket error:', error);
953
+ reject(error);
954
+ });
955
+ });
956
+ }
957
+
958
+ /**
959
+ * @deprecated Use sendEvent() for fire-and-forget events, or request() for request-response.
960
+ * This method is kept for backwards compatibility.
961
+ */
1190
962
  public async sendTwinMessage(
1191
963
  targetTwinId: string,
1192
964
  data: any,
1193
965
  callback?: (response: any) => void
1194
- ) {
1195
- // console.log('sendTwinMessage called with:', {
1196
- // targetTwinId,
1197
- // data,
1198
- // hasCallback: !!callback
1199
- // });
966
+ ): Promise<void> {
967
+ console.log('[sendTwinMessage] Sending to:', targetTwinId, 'data type:', data?.type || 'unknown');
1200
968
 
1201
969
  const edgeInstance = await this.getInstance();
1202
970
  if (!edgeInstance) {
@@ -1218,27 +986,13 @@ export class PhyHubClient {
1218
986
  data,
1219
987
  };
1220
988
 
1221
- // console.log('sendTwinMessage prepared payload:', JSON.stringify(payload, null, 2));
1222
-
1223
989
  if (callback) {
1224
- // console.log('sendTwinMessage emitting with callback');
1225
990
  this.emit(this.EVENTS.TWIN_MESSAGE, payload, (response: any) => {
1226
- // console.log('sendTwinMessage callback received response:', response);
1227
991
  callback(response);
1228
992
  });
1229
993
  } 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
- });
994
+ // Fire-and-forget for backwards compatibility (no timeout/rejection)
995
+ this.emit(this.EVENTS.TWIN_MESSAGE, payload);
1242
996
  }
1243
997
  }
1244
998
 
@@ -1246,32 +1000,10 @@ export class PhyHubClient {
1246
1000
  targetTwinId: string,
1247
1001
  callback: (message: any) => void
1248
1002
  ): Promise<void> {
1249
- console.log('onTwinMessage called for twin:', {
1250
- targetTwinId,
1251
- existingListeners: this.twinMessageListeners[targetTwinId]?.size || 0,
1252
- });
1253
-
1254
1003
  if (!this.twinMessageListeners[targetTwinId]) {
1255
- // console.log(`Creating new listener set for twin ${targetTwinId}`);
1256
1004
  this.twinMessageListeners[targetTwinId] = new Set();
1257
1005
  }
1258
-
1259
1006
  this.twinMessageListeners[targetTwinId].add(callback);
1260
- console.log(
1261
- `Added new callback. Total listeners: ${this.twinMessageListeners[targetTwinId].size}`
1262
- );
1263
-
1264
- // console.log(`Adding callback to listeners for twin ${targetTwinId}`);
1265
- this.twinMessageListeners[targetTwinId].add(callback);
1266
-
1267
- // console.log('Current listener count:', {
1268
- // targetTwinId,
1269
- // listenerCount: this.twinMessageListeners[targetTwinId].size,
1270
- // allListeners: Object.keys(this.twinMessageListeners).map(id => ({
1271
- // twinId: id,
1272
- // count: this.twinMessageListeners[id].size
1273
- // }))
1274
- // });
1275
1007
  }
1276
1008
 
1277
1009
  public offTwinMessage(targetTwinId: string, callback: (message: any) => void): boolean {
@@ -1307,10 +1039,6 @@ export class PhyHubClient {
1307
1039
  }
1308
1040
 
1309
1041
  public async subscribeTwin(targetTwinId: string, callback?: (response: any) => void) {
1310
- console.log(`[subscribeTwin] Starting subscription to twin ${targetTwinId}`);
1311
- console.log(`[subscribeTwin] Current subscribed twins:`, Array.from(this.subscribedTwins));
1312
- console.log(`[subscribeTwin] Current listeners:`, Object.keys(this.twinMessageListeners));
1313
-
1314
1042
  this.subscribedTwins.add(targetTwinId);
1315
1043
  const edgeInstance = await this.getInstance();
1316
1044
  if (!edgeInstance) {
@@ -1325,17 +1053,13 @@ export class PhyHubClient {
1325
1053
  sourceDeviceId: deviceId,
1326
1054
  };
1327
1055
 
1328
- console.log(`[subscribeTwin] Emitting TWIN_SUBSCRIBE with payload:`, payload);
1329
-
1330
1056
  if (callback) {
1331
1057
  this.emit(this.EVENTS.TWIN_SUBSCRIBE, payload, (response: any) => {
1332
- console.log(`[subscribeTwin] Subscription callback response:`, response);
1333
1058
  callback(response);
1334
1059
  });
1335
1060
  } else {
1336
1061
  return new Promise((resolve, reject) => {
1337
1062
  this.emit(this.EVENTS.TWIN_SUBSCRIBE, payload, (response: any) => {
1338
- console.log(`[subscribeTwin] Subscription promise response:`, response);
1339
1063
  resolve(response);
1340
1064
  });
1341
1065
 
@@ -1348,12 +1072,14 @@ export class PhyHubClient {
1348
1072
  }
1349
1073
 
1350
1074
  public async getTwinById(twinId: string): Promise<TwinResponse> {
1075
+ console.log(`[getTwinById] Fetching twin: ${twinId}`);
1351
1076
  const payload: EventPayload<{ twinId: string }> = {
1352
1077
  data: { twinId },
1353
1078
  };
1354
1079
 
1355
1080
  return new Promise((resolve, reject) => {
1356
1081
  this.emit(this.EVENTS.GET_TWIN_BY_ID, payload, (response: any) => {
1082
+ console.log(`[getTwinById] Response for ${twinId}:`, response);
1357
1083
  const { twin } = response;
1358
1084
  if (!twin) {
1359
1085
  reject(new Error(`Twin with id ${twinId} not found`));
@@ -1363,6 +1089,7 @@ export class PhyHubClient {
1363
1089
  });
1364
1090
 
1365
1091
  this.socket?.on('error', (error: any) => {
1092
+ console.error(`[getTwinById] Socket error for ${twinId}:`, error);
1366
1093
  reject(error);
1367
1094
  });
1368
1095
  });
@@ -1390,7 +1117,7 @@ export class PhyHubClient {
1390
1117
  // });
1391
1118
  // }
1392
1119
 
1393
- private async updateReportedProperties(
1120
+ public async updateReportedProperties(
1394
1121
  twinId: string,
1395
1122
  reportedProperties: Record<string, any>
1396
1123
  ): Promise<TwinResponse> {
@@ -1414,16 +1141,99 @@ export class PhyHubClient {
1414
1141
  });
1415
1142
  });
1416
1143
  }
1144
+
1145
+ /**
1146
+ * Initialize the device twin in direct connection mode.
1147
+ * Waits for deviceAuthenticated, then calls connectDevice to get all twins
1148
+ * for this device, including the device twin which provides the actual twinId.
1149
+ */
1150
+ private async initializeDirectConnectionTwin(): Promise<void> {
1151
+ console.log('[initializeDirectConnectionTwin] Waiting for authentication...');
1152
+
1153
+ // First wait for deviceAuthenticated event from server
1154
+ await new Promise<void>((resolve, reject) => {
1155
+ const timeout = setTimeout(() => {
1156
+ reject(new Error('Timeout waiting for deviceAuthenticated'));
1157
+ }, 10000);
1158
+
1159
+ this.socket?.once('deviceAuthenticated', (data: any) => {
1160
+ clearTimeout(timeout);
1161
+ console.log('[initializeDirectConnectionTwin] Authenticated:', data);
1162
+ resolve();
1163
+ });
1164
+ });
1165
+
1166
+ console.log('[initializeDirectConnectionTwin] Calling connectDevice...');
1167
+
1168
+ return new Promise((resolve, reject) => {
1169
+ const timeout = setTimeout(() => {
1170
+ reject(new Error('Timeout waiting for connectDevice response'));
1171
+ }, 10000);
1172
+
1173
+ // Use connectDevice to get all twins for this device
1174
+ this.socket?.emit(
1175
+ 'connectDevice',
1176
+ (response: any) => {
1177
+ clearTimeout(timeout);
1178
+ console.log('[initializeDirectConnectionTwin] connectDevice response:', JSON.stringify(response, null, 2));
1179
+
1180
+ if (response?.status === 'success' && response?.twins?.length > 0) {
1181
+ // Find the device twin from the array (type is "Device" with capital D)
1182
+ const deviceTwin = response.twins.find((t: any) => t.type === 'Device');
1183
+
1184
+ if (deviceTwin) {
1185
+ const actualTwinId = deviceTwin.id || deviceTwin._id;
1186
+ console.log(
1187
+ `[initializeDirectConnectionTwin] Got device twin. DeviceId: ${deviceTwin.deviceId}, TwinId: ${actualTwinId}`
1188
+ );
1189
+ this.instanceId = actualTwinId;
1190
+ resolve();
1191
+ } else {
1192
+ console.error('[initializeDirectConnectionTwin] No device twin found in twins array');
1193
+ reject(new Error('No device twin found for this device'));
1194
+ }
1195
+ } else if (response?.status === 'error') {
1196
+ const errorMessage = response?.message || 'Unknown error';
1197
+ console.error('[initializeDirectConnectionTwin] connectDevice failed:', errorMessage);
1198
+
1199
+ // Provide more helpful error messages
1200
+ if (errorMessage.includes('Failed to find device twin')) {
1201
+ console.error('\n=== DEVICE TWIN NOT FOUND ===');
1202
+ console.error('This device exists in the legacy database but has not been');
1203
+ console.error('properly migrated to PhyHub. The device twins need to be created.');
1204
+ console.error('');
1205
+ console.error('To fix this:');
1206
+ console.error('1. Reset isMigratedToPhyhub flag in legacy DB, OR');
1207
+ console.error('2. Use a different device that has been properly migrated');
1208
+ console.error('==============================\n');
1209
+ }
1210
+
1211
+ reject(new Error(errorMessage));
1212
+ } else {
1213
+ console.error('[initializeDirectConnectionTwin] Unexpected response:', response);
1214
+ reject(new Error('Failed to connect device'));
1215
+ }
1216
+ }
1217
+ );
1218
+ });
1219
+ }
1417
1220
  }
1418
1221
 
1419
1222
  export const connectPhyClient = (
1420
1223
  params: { instanceId?: string; moduleName?: string; dataResidency?: string } = {}
1421
1224
  ) => PhyHubClient.connect(params);
1422
1225
 
1423
- export type { EdgeTwinResponse, Instance, PeripheralInstance };
1226
+ export type { EdgeTwinResponse, Instance, PeripheralInstance, IPeripheralTwinInstance, TwinMessageResult };
1227
+ export { TwinMessageResultStatus };
1424
1228
 
1425
1229
  export default {
1426
1230
  connectPhyClient,
1427
1231
  };
1428
1232
 
1429
- export type { PhygridDataChannel, PhygridMediaStream };
1233
+ // Re-export WebRTC types for convenience
1234
+ export type { PhygridDataChannel, PhygridMediaStream, WebRTCManagerOptions, MediaStreamOptions };
1235
+ export { WebRTCManager } from './services/webrtc';
1236
+
1237
+ // Re-export twin messaging types for custom implementations
1238
+ export type { TwinMessagingContext, TwinMessagingMethods } from './twin-messaging';
1239
+ export { createTwinMessaging } from './twin-messaging';