@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.
- package/dist/index.d.ts +22 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +252 -378
- package/dist/index.js.map +1 -1
- package/dist/peripheral-twin.d.ts +34 -0
- package/dist/peripheral-twin.d.ts.map +1 -0
- package/dist/peripheral-twin.js +234 -0
- package/dist/peripheral-twin.js.map +1 -0
- package/dist/services/phyhub-connection.service.d.ts +1 -0
- package/dist/services/phyhub-connection.service.d.ts.map +1 -1
- package/dist/services/phyhub-connection.service.js +17 -0
- package/dist/services/phyhub-connection.service.js.map +1 -1
- package/dist/services/phyhub-direct-connection.service.d.ts +21 -0
- package/dist/services/phyhub-direct-connection.service.d.ts.map +1 -0
- package/dist/services/phyhub-direct-connection.service.js +101 -0
- package/dist/services/phyhub-direct-connection.service.js.map +1 -0
- package/dist/services/webrtc/data-channel-handler.d.ts +45 -0
- package/dist/services/webrtc/data-channel-handler.d.ts.map +1 -0
- package/dist/services/webrtc/data-channel-handler.js +260 -0
- package/dist/services/webrtc/data-channel-handler.js.map +1 -0
- package/dist/services/webrtc/index.d.ts +8 -0
- package/dist/services/webrtc/index.d.ts.map +1 -0
- package/dist/services/webrtc/index.js +18 -0
- package/dist/services/webrtc/index.js.map +1 -0
- package/dist/services/webrtc/media-stream-handler.d.ts +57 -0
- package/dist/services/webrtc/media-stream-handler.d.ts.map +1 -0
- package/dist/services/webrtc/media-stream-handler.js +383 -0
- package/dist/services/webrtc/media-stream-handler.js.map +1 -0
- package/dist/services/webrtc/peer-connection-manager.d.ts +40 -0
- package/dist/services/webrtc/peer-connection-manager.d.ts.map +1 -0
- package/dist/services/webrtc/peer-connection-manager.js +336 -0
- package/dist/services/webrtc/peer-connection-manager.js.map +1 -0
- package/dist/services/webrtc/types.d.ts +134 -0
- package/dist/services/webrtc/types.d.ts.map +1 -0
- package/dist/services/webrtc/types.js +12 -0
- package/dist/services/webrtc/types.js.map +1 -0
- package/dist/services/webrtc/webrtc-globals.d.ts +4 -0
- package/dist/services/webrtc/webrtc-globals.d.ts.map +1 -0
- package/dist/services/webrtc/webrtc-globals.js +72 -0
- package/dist/services/webrtc/webrtc-globals.js.map +1 -0
- package/dist/services/webrtc/webrtc-manager.d.ts +35 -0
- package/dist/services/webrtc/webrtc-manager.d.ts.map +1 -0
- package/dist/services/webrtc/webrtc-manager.js +274 -0
- package/dist/services/webrtc/webrtc-manager.js.map +1 -0
- package/dist/test/communication-comprehensive-test.d.ts +8 -0
- package/dist/test/communication-comprehensive-test.d.ts.map +1 -0
- package/dist/test/communication-comprehensive-test.js +356 -0
- package/dist/test/communication-comprehensive-test.js.map +1 -0
- package/dist/test/webrtc-channel-names-test.d.ts +2 -0
- package/dist/test/webrtc-channel-names-test.d.ts.map +1 -0
- package/dist/test/webrtc-channel-names-test.js +177 -0
- package/dist/test/webrtc-channel-names-test.js.map +1 -0
- package/dist/test/webrtc-comprehensive-test.d.ts +2 -0
- package/dist/test/webrtc-comprehensive-test.d.ts.map +1 -0
- package/dist/test/webrtc-comprehensive-test.js +328 -0
- package/dist/test/webrtc-comprehensive-test.js.map +1 -0
- package/dist/test/webrtc-reconnect-test.d.ts +4 -0
- package/dist/test/webrtc-reconnect-test.d.ts.map +1 -0
- package/dist/test/webrtc-reconnect-test.js +244 -0
- package/dist/test/webrtc-reconnect-test.js.map +1 -0
- package/dist/test/webrtc-test-harness.d.ts +4 -0
- package/dist/test/webrtc-test-harness.d.ts.map +1 -0
- package/dist/test/webrtc-test-harness.js +169 -0
- package/dist/test/webrtc-test-harness.js.map +1 -0
- package/dist/twin-messaging.d.ts +20 -0
- package/dist/twin-messaging.d.ts.map +1 -0
- package/dist/twin-messaging.js +94 -0
- package/dist/twin-messaging.js.map +1 -0
- package/dist/twin-registry.d.ts +9 -0
- package/dist/twin-registry.d.ts.map +1 -0
- package/dist/twin-registry.js +26 -0
- package/dist/twin-registry.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +20 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/twin.types.d.ts +62 -14
- package/dist/types/twin.types.d.ts.map +1 -1
- package/dist/types/twin.types.js +8 -1
- package/dist/types/twin.types.js.map +1 -1
- package/docs/webrtc-howto.md +398 -0
- package/docs/webrtc-test.md +330 -0
- package/package.json +3 -3
- package/scripts/webrtc-test.sh +401 -0
- package/src/index.ts +378 -568
- package/src/peripheral-twin.ts +337 -0
- package/src/services/phyhub-connection.service.ts +24 -0
- package/src/services/phyhub-direct-connection.service.ts +159 -0
- package/src/services/webrtc/data-channel-handler.ts +362 -0
- package/src/services/webrtc/index.ts +36 -0
- package/src/services/webrtc/media-stream-handler.ts +536 -0
- package/src/services/webrtc/peer-connection-manager.ts +467 -0
- package/src/services/webrtc/types.ts +273 -0
- package/src/services/webrtc/webrtc-globals.ts +108 -0
- package/src/services/webrtc/webrtc-manager.ts +490 -0
- package/src/test/communication-comprehensive-test.ts +533 -0
- package/src/test/webrtc-channel-names-test.ts +266 -0
- package/src/test/webrtc-comprehensive-test.ts +494 -0
- package/src/test/webrtc-reconnect-test.ts +345 -0
- package/src/test/webrtc-test-harness.ts +254 -0
- package/src/twin-messaging.ts +184 -0
- package/src/twin-registry.ts +39 -0
- package/src/types/index.ts +3 -0
- package/src/types/twin.types.ts +80 -14
- package/dist/services/webrtc/datachannel.d.ts +0 -10
- package/dist/services/webrtc/datachannel.d.ts.map +0 -1
- package/dist/services/webrtc/datachannel.js +0 -290
- package/dist/services/webrtc/datachannel.js.map +0 -1
- package/dist/services/webrtc/mediastream.d.ts +0 -10
- package/dist/services/webrtc/mediastream.d.ts.map +0 -1
- package/dist/services/webrtc/mediastream.js +0 -396
- package/dist/services/webrtc/mediastream.js.map +0 -1
- package/dist/services/webrtc/peer-connection-ice.d.ts +0 -32
- package/dist/services/webrtc/peer-connection-ice.d.ts.map +0 -1
- package/dist/services/webrtc/peer-connection-ice.js +0 -483
- package/dist/services/webrtc/peer-connection-ice.js.map +0 -1
- package/src/services/webrtc/datachannel.ts +0 -421
- package/src/services/webrtc/mediastream.ts +0 -602
- 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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} from './services/webrtc
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
272
|
-
//
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
listeners.forEach(
|
|
279
|
-
// console.log('Executing listener for twin:', callback);
|
|
215
|
+
|
|
216
|
+
if (listeners?.size) {
|
|
217
|
+
listeners.forEach(listener => {
|
|
280
218
|
try {
|
|
281
|
-
|
|
219
|
+
listener(payload);
|
|
220
|
+
result = { status: TwinMessageResultStatus.Success, message: 'Action completed' };
|
|
282
221
|
} catch (error) {
|
|
283
|
-
console.error(`Error in
|
|
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
|
-
|
|
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
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
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
|
|
490
|
-
|
|
491
|
-
|
|
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
|
|
505
|
-
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
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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:
|
|
686
|
-
on:
|
|
687
|
-
to:
|
|
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
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1231
|
-
|
|
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
|
-
|
|
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
|
|
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';
|