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