@phystack/hub-client 4.4.29
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/.prettierignore +10 -0
- package/.prettierrc +10 -0
- package/CHANGELOG.md +1202 -0
- package/README-MEDIASTREAM.md +124 -0
- package/README.md +302 -0
- package/dist/constants/constants.d.ts +7 -0
- package/dist/constants/constants.d.ts.map +1 -0
- package/dist/constants/constants.js +10 -0
- package/dist/constants/constants.js.map +1 -0
- package/dist/helpers/browser.helper.d.ts +5 -0
- package/dist/helpers/browser.helper.d.ts.map +1 -0
- package/dist/helpers/browser.helper.js +19 -0
- package/dist/helpers/browser.helper.js.map +1 -0
- package/dist/helpers/cache.helper.d.ts +6 -0
- package/dist/helpers/cache.helper.d.ts.map +1 -0
- package/dist/helpers/cache.helper.js +46 -0
- package/dist/helpers/cache.helper.js.map +1 -0
- package/dist/helpers/date.helper.d.ts +4 -0
- package/dist/helpers/date.helper.d.ts.map +1 -0
- package/dist/helpers/date.helper.js +13 -0
- package/dist/helpers/date.helper.js.map +1 -0
- package/dist/helpers/session.helper.d.ts +13 -0
- package/dist/helpers/session.helper.d.ts.map +1 -0
- package/dist/helpers/session.helper.js +88 -0
- package/dist/helpers/session.helper.js.map +1 -0
- package/dist/helpers/shorten-look-ups.helper.d.ts +3 -0
- package/dist/helpers/shorten-look-ups.helper.d.ts.map +1 -0
- package/dist/helpers/shorten-look-ups.helper.js +80 -0
- package/dist/helpers/shorten-look-ups.helper.js.map +1 -0
- package/dist/helpers/signals-client.helper.d.ts +9 -0
- package/dist/helpers/signals-client.helper.d.ts.map +1 -0
- package/dist/helpers/signals-client.helper.js +44 -0
- package/dist/helpers/signals-client.helper.js.map +1 -0
- package/dist/helpers/signals.helper.d.ts +19 -0
- package/dist/helpers/signals.helper.d.ts.map +1 -0
- package/dist/helpers/signals.helper.js +48 -0
- package/dist/helpers/signals.helper.js.map +1 -0
- package/dist/helpers/wrtc/browser.d.ts +3 -0
- package/dist/helpers/wrtc/browser.d.ts.map +1 -0
- package/dist/helpers/wrtc/browser.js +11 -0
- package/dist/helpers/wrtc/browser.js.map +1 -0
- package/dist/helpers/wrtc/index.d.ts +5 -0
- package/dist/helpers/wrtc/index.d.ts.map +1 -0
- package/dist/helpers/wrtc/index.js +30 -0
- package/dist/helpers/wrtc/index.js.map +1 -0
- package/dist/helpers/wrtc/node.d.ts +3 -0
- package/dist/helpers/wrtc/node.d.ts.map +1 -0
- package/dist/helpers/wrtc/node.js +18 -0
- package/dist/helpers/wrtc/node.js.map +1 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1013 -0
- package/dist/index.js.map +1 -0
- package/dist/services/online-status-subscription.service.d.ts +28 -0
- package/dist/services/online-status-subscription.service.d.ts.map +1 -0
- package/dist/services/online-status-subscription.service.js +96 -0
- package/dist/services/online-status-subscription.service.js.map +1 -0
- package/dist/services/phyhub-connection.service.d.ts +20 -0
- package/dist/services/phyhub-connection.service.d.ts.map +1 -0
- package/dist/services/phyhub-connection.service.js +176 -0
- package/dist/services/phyhub-connection.service.js.map +1 -0
- package/dist/services/signals.service.d.ts +97 -0
- package/dist/services/signals.service.d.ts.map +1 -0
- package/dist/services/signals.service.js +536 -0
- package/dist/services/signals.service.js.map +1 -0
- package/dist/services/webrtc/datachannel.d.ts +10 -0
- package/dist/services/webrtc/datachannel.d.ts.map +1 -0
- package/dist/services/webrtc/datachannel.js +290 -0
- package/dist/services/webrtc/datachannel.js.map +1 -0
- package/dist/services/webrtc/mediastream.d.ts +10 -0
- package/dist/services/webrtc/mediastream.d.ts.map +1 -0
- package/dist/services/webrtc/mediastream.js +396 -0
- package/dist/services/webrtc/mediastream.js.map +1 -0
- package/dist/services/webrtc/peer-connection-ice.d.ts +32 -0
- package/dist/services/webrtc/peer-connection-ice.d.ts.map +1 -0
- package/dist/services/webrtc/peer-connection-ice.js +483 -0
- package/dist/services/webrtc/peer-connection-ice.js.map +1 -0
- package/dist/types/signal.types.d.ts +354 -0
- package/dist/types/signal.types.d.ts.map +1 -0
- package/dist/types/signal.types.js +53 -0
- package/dist/types/signal.types.js.map +1 -0
- package/dist/types/twin.types.d.ts +705 -0
- package/dist/types/twin.types.d.ts.map +1 -0
- package/dist/types/twin.types.js +21 -0
- package/dist/types/twin.types.js.map +1 -0
- package/dist/types/webrtc.types.d.ts +41 -0
- package/dist/types/webrtc.types.d.ts.map +1 -0
- package/dist/types/webrtc.types.js +3 -0
- package/dist/types/webrtc.types.js.map +1 -0
- package/package.json +50 -0
- package/src/constants/constants.ts +12 -0
- package/src/helpers/browser.helper.ts +15 -0
- package/src/helpers/cache.helper.ts +52 -0
- package/src/helpers/date.helper.ts +8 -0
- package/src/helpers/session.helper.ts +96 -0
- package/src/helpers/shorten-look-ups.helper.ts +75 -0
- package/src/helpers/signals-client.helper.ts +54 -0
- package/src/helpers/signals.helper.ts +41 -0
- package/src/helpers/wrtc/browser.ts +9 -0
- package/src/helpers/wrtc/index.ts +32 -0
- package/src/helpers/wrtc/node.ts +16 -0
- package/src/index.ts +1429 -0
- package/src/services/online-status-subscription.service.ts +127 -0
- package/src/services/phyhub-connection.service.ts +213 -0
- package/src/services/signals.service.ts +783 -0
- package/src/services/webrtc/datachannel.ts +421 -0
- package/src/services/webrtc/mediastream.ts +602 -0
- package/src/services/webrtc/peer-connection-ice.ts +689 -0
- package/src/types/lodash.d.ts +3 -0
- package/src/types/signal.types.ts +382 -0
- package/src/types/twin.types.ts +803 -0
- package/src/types/webrtc.types.ts +48 -0
- package/tsconfig.json +45 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,1429 @@
|
|
|
1
|
+
import { Socket, SocketOptions, ManagerOptions } from 'socket.io-client';
|
|
2
|
+
import {
|
|
3
|
+
TwinStatusEnum,
|
|
4
|
+
ScreenTwinReportedProperties,
|
|
5
|
+
TwinResponse,
|
|
6
|
+
DeviceStatus,
|
|
7
|
+
EdgeTwinResponse,
|
|
8
|
+
EdgeTwinDesiredPropertiesResponse,
|
|
9
|
+
PeripheralTwinResponse,
|
|
10
|
+
TwinTypeEnum,
|
|
11
|
+
Instance,
|
|
12
|
+
PeripheralInstance,
|
|
13
|
+
} from './types/twin.types';
|
|
14
|
+
import {
|
|
15
|
+
PhygridDataChannel,
|
|
16
|
+
PhygridMediaStream,
|
|
17
|
+
WebRTCConnectionOptions,
|
|
18
|
+
} from './types/webrtc.types';
|
|
19
|
+
import { PhyHubConnection } from './services/phyhub-connection.service';
|
|
20
|
+
import { SignalsService } from './services/signals.service';
|
|
21
|
+
import { DataRequestTypeEnum, InitSignalPayload } from './types/signal.types';
|
|
22
|
+
import {
|
|
23
|
+
createWebRTCDataChannelConnection,
|
|
24
|
+
WebRTCDataChannelResult,
|
|
25
|
+
} from './services/webrtc/datachannel';
|
|
26
|
+
import {
|
|
27
|
+
createWebRTCMediaStreamConnection,
|
|
28
|
+
WebRTCMediaStreamResult,
|
|
29
|
+
} from './services/webrtc/mediastream';
|
|
30
|
+
// import { createWebRTCMediaStreamConnection, WebRTCMediaStreamResult } from './helpers/webrtc-mediastream';
|
|
31
|
+
|
|
32
|
+
// Define WebRTC types that might not be available in all environments
|
|
33
|
+
declare global {
|
|
34
|
+
// In browsers, these are standard WebRTC interfaces
|
|
35
|
+
// In Node.js, we polyfill them from @roamhq/wrtc
|
|
36
|
+
interface Window {
|
|
37
|
+
RTCVideoSource?: any;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
namespace NodeJS {
|
|
41
|
+
interface Global {
|
|
42
|
+
MediaStream?: any;
|
|
43
|
+
RTCPeerConnection?: any;
|
|
44
|
+
RTCSessionDescription?: any;
|
|
45
|
+
RTCIceCandidate?: any;
|
|
46
|
+
RTCVideoSource?: any;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Make RTCVideoSource available in function scope when used
|
|
51
|
+
var RTCVideoSource: any;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
(async function initializeWebRTCGlobals() {
|
|
55
|
+
// Only run in Node environment
|
|
56
|
+
if (typeof window !== 'undefined') return;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Dynamically import wrtc
|
|
60
|
+
const wrtc = require('@roamhq/wrtc');
|
|
61
|
+
|
|
62
|
+
// Set all WebRTC globals
|
|
63
|
+
if (typeof global.MediaStream === 'undefined' && wrtc.MediaStream) {
|
|
64
|
+
global.MediaStream = wrtc.MediaStream;
|
|
65
|
+
console.log('Global MediaStream initialized');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof global.RTCPeerConnection === 'undefined' && wrtc.RTCPeerConnection) {
|
|
69
|
+
global.RTCPeerConnection = wrtc.RTCPeerConnection;
|
|
70
|
+
console.log('Global RTCPeerConnection initialized');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof global.RTCSessionDescription === 'undefined' && wrtc.RTCSessionDescription) {
|
|
74
|
+
global.RTCSessionDescription = wrtc.RTCSessionDescription;
|
|
75
|
+
console.log('Global RTCSessionDescription initialized');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (typeof global.RTCIceCandidate === 'undefined' && wrtc.RTCIceCandidate) {
|
|
79
|
+
global.RTCIceCandidate = wrtc.RTCIceCandidate;
|
|
80
|
+
console.log('Global RTCIceCandidate initialized');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (typeof (global as any).RTCVideoSource === 'undefined' && wrtc.nonstandard.RTCVideoSource) {
|
|
84
|
+
(global as any).RTCVideoSource = wrtc.nonstandard.RTCVideoSource;
|
|
85
|
+
console.log('Global RTCVideoSource initialized');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log('WebRTC globals successfully initialized');
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('Failed to initialize WebRTC globals:', error);
|
|
91
|
+
}
|
|
92
|
+
})().catch(err => console.error('Error in WebRTC globals initialization:', err));
|
|
93
|
+
|
|
94
|
+
interface EventPayload<T = any> {
|
|
95
|
+
twinId?: string;
|
|
96
|
+
sourceTwinId?: string;
|
|
97
|
+
sourceDeviceId?: string;
|
|
98
|
+
deviceId?: string;
|
|
99
|
+
status?: TwinStatusEnum;
|
|
100
|
+
data?: T;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type MyIoOptions = Partial<ManagerOptions & SocketOptions>;
|
|
104
|
+
|
|
105
|
+
export class PhyHubClient {
|
|
106
|
+
private static instance: PhyHubClient | null = null;
|
|
107
|
+
private instanceId: string | undefined = undefined;
|
|
108
|
+
private moduleName: string | undefined = undefined;
|
|
109
|
+
private socket: Socket | null = null;
|
|
110
|
+
private socketConnected = false;
|
|
111
|
+
private emitQueue: any[] = [];
|
|
112
|
+
private lastDeviceStatusCheck: number = 0;
|
|
113
|
+
private lastDeviceStatusResponse: DeviceStatus | undefined = undefined;
|
|
114
|
+
private signals: SignalsService | undefined;
|
|
115
|
+
private subscribedTwins: Set<string> = new Set();
|
|
116
|
+
private twinMessageListeners: { [key: string]: Set<(message: any) => void> } = {};
|
|
117
|
+
private instances: Map<string, Instance> = new Map();
|
|
118
|
+
private peripheralInstances: Map<string, PeripheralInstance> = new Map();
|
|
119
|
+
private twinUpdateListeners: { [key: string]: Set<(twin: TwinResponse) => void> } = {};
|
|
120
|
+
|
|
121
|
+
private readonly EVENTS = {
|
|
122
|
+
PING: 'ping',
|
|
123
|
+
PONG: 'pong',
|
|
124
|
+
CONNECT: 'connect',
|
|
125
|
+
RECONNECT: 'reconnect',
|
|
126
|
+
RECONNECT_ERROR: 'reconnect_error',
|
|
127
|
+
RECONNECT_FAILED: 'reconnect_failed',
|
|
128
|
+
DISCONNECT: 'disconnect',
|
|
129
|
+
REPORT_SCREEN_INSTANCE_PROPERTIES: 'setScreenInstanceReportedProperties',
|
|
130
|
+
GET_DEVICE_STATUS: 'getDeviceStatus',
|
|
131
|
+
GET_DEVICE_NETWORKS: 'getDeviceNetworks',
|
|
132
|
+
GET_DEVICE_INSTANCE: 'getDeviceInstance',
|
|
133
|
+
GET_INSTANCE: 'getInstance',
|
|
134
|
+
SIGNAL_EVENT: 'sendEventSignal',
|
|
135
|
+
SIGNAL_CHECKOUT: 'sendEventSignal',
|
|
136
|
+
SIGNAL_PURCHASE: 'sendEventSignal',
|
|
137
|
+
SIGNAL_SESSION: 'sendSessionSignal',
|
|
138
|
+
SIGNAL_CLIENT: 'sendClientSignal',
|
|
139
|
+
TWIN_MESSAGE: 'twinMessage',
|
|
140
|
+
TWIN_SUBSCRIBE: 'twinSubscribe',
|
|
141
|
+
TWIN_UNSUBSCRIBE: 'twinUnsubscribe',
|
|
142
|
+
GET_TWIN_BY_ID: 'getTwinById',
|
|
143
|
+
} as const;
|
|
144
|
+
|
|
145
|
+
constructor(params: { instanceId?: string; moduleName?: string; dataResidency?: string } = {}) {
|
|
146
|
+
this.instanceId = params.instanceId;
|
|
147
|
+
if (!this.instanceId) {
|
|
148
|
+
if (typeof window !== 'undefined') {
|
|
149
|
+
console.log('Running in browser, looking for instanceId in URL hash', window.location.hash);
|
|
150
|
+
this.instanceId =
|
|
151
|
+
new URLSearchParams(window.location.hash.slice(1)).get('instanceId') || undefined;
|
|
152
|
+
console.log('Found instanceId in URL hash', this.instanceId);
|
|
153
|
+
} else {
|
|
154
|
+
this.instanceId = process.env.TWIN_ID;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
this.moduleName = params.moduleName;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private async initializeConnection(): Promise<this> {
|
|
161
|
+
if (this.socket && this.socketConnected) {
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
console.log(
|
|
167
|
+
'initializeConnection()',
|
|
168
|
+
JSON.stringify(
|
|
169
|
+
{
|
|
170
|
+
instanceId: this.instanceId,
|
|
171
|
+
moduleName: this.moduleName,
|
|
172
|
+
},
|
|
173
|
+
null,
|
|
174
|
+
2
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
const phyHubConnection = PhyHubConnection.getInstance({
|
|
178
|
+
instanceId: this.instanceId,
|
|
179
|
+
moduleName: this.moduleName,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
this.socket = await phyHubConnection.getPhyHubSocket();
|
|
183
|
+
|
|
184
|
+
// Wait for socket to be connected
|
|
185
|
+
await new Promise<void>((resolve, reject) => {
|
|
186
|
+
if (this.socket?.connected) {
|
|
187
|
+
this.socketConnected = true;
|
|
188
|
+
resolve();
|
|
189
|
+
} else {
|
|
190
|
+
this.socket?.once('connect', () => {
|
|
191
|
+
this.socketConnected = true;
|
|
192
|
+
resolve();
|
|
193
|
+
});
|
|
194
|
+
this.socket?.once('connect_error', error => {
|
|
195
|
+
reject(error);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Now that socket is connected, set up the listeners
|
|
201
|
+
this.setupSocketListeners(
|
|
202
|
+
(value: this) => value,
|
|
203
|
+
(reason?: any) => {
|
|
204
|
+
throw reason;
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
return this;
|
|
209
|
+
} catch (err) {
|
|
210
|
+
this.socket = null;
|
|
211
|
+
this.socketConnected = false;
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private setupSocketListeners(
|
|
217
|
+
resolve: (value: this) => void,
|
|
218
|
+
_reject: (reason?: any) => void
|
|
219
|
+
): void {
|
|
220
|
+
if (!this.socket) return;
|
|
221
|
+
|
|
222
|
+
this.socket.on(this.EVENTS.PONG, (data: any) => {
|
|
223
|
+
setTimeout(() => {
|
|
224
|
+
this.emit('ping', { count: data.data.count + 1 });
|
|
225
|
+
}, 30000);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
this.socket.on(this.EVENTS.CONNECT, () => {
|
|
229
|
+
this.socketConnected = true;
|
|
230
|
+
this.emit(this.EVENTS.PING, { count: 1 });
|
|
231
|
+
this.processEmitQueue();
|
|
232
|
+
// Re-subscribe to twins after reconnection
|
|
233
|
+
this.subscribedTwins.forEach(twinId => {
|
|
234
|
+
this.subscribeTwin(twinId).catch(error => {
|
|
235
|
+
console.error(`Failed to re-subscribe to twin ${twinId}:`, error);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
resolve(this);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
this.socket.io.on(this.EVENTS.RECONNECT, () => {
|
|
242
|
+
this.socketConnected = true;
|
|
243
|
+
this.processEmitQueue();
|
|
244
|
+
|
|
245
|
+
// Re-subscribe to twins after reconnection
|
|
246
|
+
this.subscribedTwins.forEach(twinId => {
|
|
247
|
+
this.subscribeTwin(twinId).catch(error => {
|
|
248
|
+
console.error(`Failed to re-subscribe to twin ${twinId}:`, error);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
this.socket.io.on(this.EVENTS.RECONNECT_ERROR, () => {
|
|
254
|
+
this.socketConnected = false;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
this.socket.io.on(this.EVENTS.RECONNECT_FAILED, () => {
|
|
258
|
+
this.socketConnected = false;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
this.socket.on(this.EVENTS.DISCONNECT, () => {
|
|
262
|
+
this.socketConnected = false;
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
this.socket.on(this.EVENTS.TWIN_MESSAGE, (payload: EventPayload) => {
|
|
266
|
+
// console.log('Received TWIN_MESSAGE payload:', {
|
|
267
|
+
// JSON: JSON.stringify(payload, null, 2),
|
|
268
|
+
// });
|
|
269
|
+
|
|
270
|
+
if (payload.twinId && payload.data) {
|
|
271
|
+
// console.log('Received TWIN_MESSAGE event:', {
|
|
272
|
+
// hasListeners: !!this.twinMessageListeners[payload.twinId],
|
|
273
|
+
// listenerCount: this.twinMessageListeners[payload.twinId]?.size
|
|
274
|
+
// });
|
|
275
|
+
const listeners = this.twinMessageListeners[payload.twinId];
|
|
276
|
+
if (listeners) {
|
|
277
|
+
// console.log(`Executing ${listeners.size} listeners for twin ${payload.twinId}`);
|
|
278
|
+
listeners.forEach(callback => {
|
|
279
|
+
// console.log('Executing listener for twin:', callback);
|
|
280
|
+
try {
|
|
281
|
+
callback(payload);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.error(`Error in twin message listener for ${payload.twinId}:`, error);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
} else {
|
|
287
|
+
// console.log(`No listeners found for twin ${payload.twinId}`, JSON.stringify(payload, null, 2));
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
// console.log('Invalid twin message payload:', payload);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Add global listener for twin updates
|
|
295
|
+
this.socket.on('twinUpdated', (payload: EventPayload<TwinResponse>) => {
|
|
296
|
+
console.log('twinUpdated event received', JSON.stringify(payload, null, 2));
|
|
297
|
+
if (payload.data) {
|
|
298
|
+
const twinId = payload.twinId || payload.data.id;
|
|
299
|
+
if (twinId && this.twinUpdateListeners[twinId]) {
|
|
300
|
+
// Invoke all callbacks registered for this twin
|
|
301
|
+
this.twinUpdateListeners[twinId].forEach(cb => {
|
|
302
|
+
try {
|
|
303
|
+
cb(payload.data!);
|
|
304
|
+
console.log(
|
|
305
|
+
'twinUpdateListeners[twinId].forEach() callback executed',
|
|
306
|
+
JSON.stringify(payload.data, null, 2)
|
|
307
|
+
);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.error(`Error in twin update listener for ${twinId}:`, error);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private async assureSocketConnection(): Promise<void> {
|
|
318
|
+
if (!this.socket || !this.socketConnected) {
|
|
319
|
+
await this.initializeConnection();
|
|
320
|
+
}
|
|
321
|
+
if (!this.socket) {
|
|
322
|
+
throw new Error('Socket not initialized');
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
public static async connect(
|
|
327
|
+
params: {
|
|
328
|
+
instanceId?: string;
|
|
329
|
+
moduleName?: string;
|
|
330
|
+
dataResidency?: string;
|
|
331
|
+
} = {}
|
|
332
|
+
): Promise<PhyHubClient> {
|
|
333
|
+
console.info(`connect(): Connecting to phyhub`);
|
|
334
|
+
// TODO handle deviceId and accessKey for screen instance scenarios
|
|
335
|
+
|
|
336
|
+
if (!PhyHubClient.instance) {
|
|
337
|
+
PhyHubClient.instance = new PhyHubClient(params);
|
|
338
|
+
await PhyHubClient.instance.initializeConnection();
|
|
339
|
+
console.info(`connect(): Connection to phyhub initialized`);
|
|
340
|
+
}
|
|
341
|
+
return PhyHubClient.instance;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public getSocket = () => {
|
|
345
|
+
return this.socket;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
public initializeSignals = async (initParams?: InitSignalPayload) => {
|
|
349
|
+
let edgeTwin;
|
|
350
|
+
if (!initParams) {
|
|
351
|
+
await this.getDeviceStatus();
|
|
352
|
+
if (!this.lastDeviceStatusResponse) {
|
|
353
|
+
throw new Error('Failed to fetch device settings');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
edgeTwin = (await this.getInstance()) as Instance;
|
|
357
|
+
if (!edgeTwin) {
|
|
358
|
+
throw new Error('Unable to determine app settings');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const initSignalsPayload: InitSignalPayload = {
|
|
363
|
+
deviceId:
|
|
364
|
+
initParams?.deviceId ?? edgeTwin?.deviceId ?? this.lastDeviceStatusResponse?.deviceId ?? '',
|
|
365
|
+
installationId:
|
|
366
|
+
initParams?.installationId ?? edgeTwin?.properties.desired.installationId ?? '',
|
|
367
|
+
spaceId: initParams?.spaceId ?? this.lastDeviceStatusResponse?.spaceId ?? '',
|
|
368
|
+
tenantId: initParams?.tenantId ?? this.lastDeviceStatusResponse?.tenantId ?? '',
|
|
369
|
+
appVersion: initParams?.appVersion ?? 'XXXXXXXXXXXXXXXXXXXXXXXX',
|
|
370
|
+
appId: initParams?.appId ?? 'XXXXXXXXXXXXXXXXXXXXXXXX',
|
|
371
|
+
environment: initParams?.environment ?? this.lastDeviceStatusResponse?.gridEnv ?? '',
|
|
372
|
+
dataResidency: (
|
|
373
|
+
initParams?.dataResidency ??
|
|
374
|
+
this.lastDeviceStatusResponse?.dataResidency ??
|
|
375
|
+
''
|
|
376
|
+
).toUpperCase(),
|
|
377
|
+
country: initParams?.country ?? 'SE',
|
|
378
|
+
installationVersion: initParams?.installationVersion ?? 'XXXXXXXXXXXXXXXXXXXXXXXX',
|
|
379
|
+
accessToken: initParams?.accessToken ?? this.lastDeviceStatusResponse?.accessKey,
|
|
380
|
+
clientUserAgent: initParams?.clientUserAgent ?? undefined,
|
|
381
|
+
ip: initParams?.ip ?? this.lastDeviceStatusResponse?.ip?.[0]?.ipv4 ?? 'N/A',
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
this.signals = new SignalsService(this, initSignalsPayload);
|
|
385
|
+
return this.signals;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
public isConnected(): boolean {
|
|
389
|
+
return this.socketConnected;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
public async sendSignal(type: DataRequestTypeEnum, data: Record<string, any>) {
|
|
393
|
+
let eventToEmit = undefined;
|
|
394
|
+
switch (type) {
|
|
395
|
+
case DataRequestTypeEnum.EVENT:
|
|
396
|
+
eventToEmit = this.EVENTS.SIGNAL_EVENT;
|
|
397
|
+
break;
|
|
398
|
+
case DataRequestTypeEnum.CHECKOUT:
|
|
399
|
+
eventToEmit = this.EVENTS.SIGNAL_CHECKOUT;
|
|
400
|
+
break;
|
|
401
|
+
case DataRequestTypeEnum.PURCHASE:
|
|
402
|
+
eventToEmit = this.EVENTS.SIGNAL_PURCHASE;
|
|
403
|
+
break;
|
|
404
|
+
case DataRequestTypeEnum.SESSION:
|
|
405
|
+
eventToEmit = this.EVENTS.SIGNAL_SESSION;
|
|
406
|
+
break;
|
|
407
|
+
case DataRequestTypeEnum.CLIENT:
|
|
408
|
+
eventToEmit = this.EVENTS.SIGNAL_CLIENT;
|
|
409
|
+
break;
|
|
410
|
+
default:
|
|
411
|
+
throw new Error(`Unsupported type ${type}`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (!eventToEmit) {
|
|
415
|
+
throw new Error(`Unsupported type ${type}`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const payload: EventPayload<Record<string, any>> = {
|
|
419
|
+
data,
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
this.emit(eventToEmit, payload);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
public async getDeviceStatus(): Promise<any> {
|
|
426
|
+
await this.assureSocketConnection();
|
|
427
|
+
|
|
428
|
+
const now = Date.now();
|
|
429
|
+
const tenSeconds = 10 * 1000;
|
|
430
|
+
if (now - this.lastDeviceStatusCheck < tenSeconds) {
|
|
431
|
+
// console.info('getDeviceStatus(): Skipping emit - returning last known status');
|
|
432
|
+
return Promise.resolve(this.lastDeviceStatusResponse);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return new Promise((resolve, reject) => {
|
|
436
|
+
this.lastDeviceStatusCheck = now;
|
|
437
|
+
this.emit(this.EVENTS.GET_DEVICE_STATUS, (response: any) => {
|
|
438
|
+
this.lastDeviceStatusResponse = response;
|
|
439
|
+
resolve(response);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
this.socket?.on('error', (error: any) => {
|
|
443
|
+
reject(error);
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
public getDataChannel = async (targetTwinId: string): Promise<PhygridDataChannel> => {
|
|
449
|
+
const options: WebRTCConnectionOptions = {
|
|
450
|
+
targetTwinId,
|
|
451
|
+
sendTwinMessage: this.sendTwinMessage.bind(this),
|
|
452
|
+
subscribeTwin: this.subscribeTwin.bind(this),
|
|
453
|
+
onTwinMessage: this.onTwinMessage.bind(this),
|
|
454
|
+
offTwinMessage: this.offTwinMessage.bind(this),
|
|
455
|
+
useStun: true,
|
|
456
|
+
isInitiator: true,
|
|
457
|
+
channelPrefix: 'channel',
|
|
458
|
+
mediaOptions: { isMediaConnection: false },
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const result = (await createWebRTCDataChannelConnection(options)) as WebRTCDataChannelResult;
|
|
462
|
+
return result.dataChannel;
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
public onDataChannel = async (
|
|
466
|
+
sourceTwinId: string,
|
|
467
|
+
callback: (dc: PhygridDataChannel) => void
|
|
468
|
+
) => {
|
|
469
|
+
const options: WebRTCConnectionOptions = {
|
|
470
|
+
targetTwinId: sourceTwinId,
|
|
471
|
+
sendTwinMessage: this.sendTwinMessage.bind(this),
|
|
472
|
+
subscribeTwin: this.subscribeTwin.bind(this),
|
|
473
|
+
onTwinMessage: this.onTwinMessage.bind(this),
|
|
474
|
+
offTwinMessage: this.offTwinMessage.bind(this),
|
|
475
|
+
useStun: true,
|
|
476
|
+
isInitiator: false,
|
|
477
|
+
channelPrefix: 'channel',
|
|
478
|
+
mediaOptions: { isMediaConnection: false },
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const result = (await createWebRTCDataChannelConnection(options)) as WebRTCDataChannelResult;
|
|
482
|
+
callback(result.dataChannel);
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
public async getMediaStream(targetTwinId: string): Promise<{
|
|
486
|
+
stream: PhygridMediaStream;
|
|
487
|
+
close: () => void;
|
|
488
|
+
}> {
|
|
489
|
+
const options: WebRTCConnectionOptions = {
|
|
490
|
+
targetTwinId,
|
|
491
|
+
sendTwinMessage: this.sendTwinMessage.bind(this),
|
|
492
|
+
subscribeTwin: this.subscribeTwin.bind(this),
|
|
493
|
+
onTwinMessage: this.onTwinMessage.bind(this),
|
|
494
|
+
offTwinMessage: this.offTwinMessage.bind(this),
|
|
495
|
+
isInitiator: true,
|
|
496
|
+
channelPrefix: 'media',
|
|
497
|
+
mediaOptions: {
|
|
498
|
+
isMediaConnection: true,
|
|
499
|
+
mediaDirection: 'recvonly',
|
|
500
|
+
},
|
|
501
|
+
};
|
|
502
|
+
const result: WebRTCMediaStreamResult = await createWebRTCMediaStreamConnection(options);
|
|
503
|
+
return {
|
|
504
|
+
stream: result.mediaStream,
|
|
505
|
+
close: result.close,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
public onMediaStream = async (
|
|
510
|
+
sourceTwinId: string,
|
|
511
|
+
callback: (stream: PhygridMediaStream) => void
|
|
512
|
+
): Promise<void> => {
|
|
513
|
+
const options: WebRTCConnectionOptions = {
|
|
514
|
+
targetTwinId: sourceTwinId,
|
|
515
|
+
sendTwinMessage: this.sendTwinMessage.bind(this),
|
|
516
|
+
subscribeTwin: this.subscribeTwin.bind(this),
|
|
517
|
+
onTwinMessage: this.onTwinMessage.bind(this),
|
|
518
|
+
offTwinMessage: this.offTwinMessage.bind(this),
|
|
519
|
+
isInitiator: false,
|
|
520
|
+
channelPrefix: 'media',
|
|
521
|
+
mediaOptions: {
|
|
522
|
+
isMediaConnection: true,
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const result: WebRTCMediaStreamResult = await createWebRTCMediaStreamConnection(options);
|
|
527
|
+
callback(result.mediaStream);
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// TODO properties should automatically be updated when the edge twin is updated
|
|
531
|
+
// We could do a refresh method and call that on reconnect like we handle the resubscribes for peripherals
|
|
532
|
+
public async getInstance(): Promise<Instance> {
|
|
533
|
+
// TODO support multiple instances of different twin ids
|
|
534
|
+
if (!this.instanceId) {
|
|
535
|
+
throw new Error('Instance ID not set');
|
|
536
|
+
}
|
|
537
|
+
if (this.instances.has(this.instanceId)) {
|
|
538
|
+
return this.instances.get(this.instanceId)!;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
await this.assureSocketConnection();
|
|
542
|
+
|
|
543
|
+
let instance: Instance | undefined;
|
|
544
|
+
|
|
545
|
+
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
|
+
|
|
578
|
+
const edgeTwinTo = (targetTwinId: string) => ({
|
|
579
|
+
emit: (type: string, payload: any) => {
|
|
580
|
+
// console.log('edgeTwinEmit', type, payload);
|
|
581
|
+
const edgeTwinMessagePayload = {
|
|
582
|
+
type: `${edgeInstanceTypePrefix}:${type}`,
|
|
583
|
+
sourceTwinId: instanceTwin.id,
|
|
584
|
+
sourceDeviceId: instanceTwin.deviceId,
|
|
585
|
+
data: payload,
|
|
586
|
+
};
|
|
587
|
+
// console.log('edgeTwinEmit', edgeTwinMessagePayload);
|
|
588
|
+
this.sendTwinMessage(targetTwinId, edgeTwinMessagePayload);
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
const getPeripheralTwins = async (): Promise<PeripheralTwinResponse[]> => {
|
|
593
|
+
const payload: EventPayload = {
|
|
594
|
+
data: {
|
|
595
|
+
instanceId: instanceTwin.id,
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
return new Promise((resolve, reject) => {
|
|
600
|
+
this.emit('getPeripheralTwins', payload, (response: any) => {
|
|
601
|
+
const { twins } = response;
|
|
602
|
+
resolve(twins || []);
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
this.socket?.on('error', (error: any) => {
|
|
606
|
+
reject(error);
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
const createPeripheralTwin = async (
|
|
612
|
+
peripheralName: string,
|
|
613
|
+
hardwareId: string,
|
|
614
|
+
desiredProperties?: Record<string, any>,
|
|
615
|
+
descriptors?: Record<string, string>
|
|
616
|
+
): Promise<PeripheralTwinResponse> => {
|
|
617
|
+
const existingTwins = (await getPeripheralTwins()) as PeripheralTwinResponse[];
|
|
618
|
+
const existingTwin = existingTwins.find(
|
|
619
|
+
(twin: PeripheralTwinResponse) => twin.properties.desired.hardwareId === hardwareId
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
if (existingTwin) {
|
|
623
|
+
throw new Error(`Peripheral twin with hardwareId ${hardwareId} already exists`);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const payload: EventPayload = {
|
|
627
|
+
data: {
|
|
628
|
+
deviceId: instanceTwin.deviceId,
|
|
629
|
+
tenantId: instanceTwin.tenantId,
|
|
630
|
+
instanceId: instanceTwin.id,
|
|
631
|
+
peripheralName,
|
|
632
|
+
hardwareId,
|
|
633
|
+
desiredProperties,
|
|
634
|
+
descriptors,
|
|
635
|
+
},
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
return new Promise((resolve, reject) => {
|
|
639
|
+
this.emit('createPeripheralTwin', payload, (response: any) => {
|
|
640
|
+
const { twin } = response;
|
|
641
|
+
if (!twin) throw new Error('Failed to create peripheral twin');
|
|
642
|
+
resolve(twin);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
this.socket?.on('error', (error: any) => {
|
|
646
|
+
reject(error);
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
const getDataChannel = async () => {
|
|
652
|
+
return await this.getDataChannel(instanceTwin.id);
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
const onDataChannel = async (callback: (dc: PhygridDataChannel) => void) => {
|
|
656
|
+
return await this.onDataChannel(instanceTwin.id, callback);
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
const getMediaStream = async () => {
|
|
660
|
+
if (!instance) {
|
|
661
|
+
throw new Error('Instance not initialized');
|
|
662
|
+
}
|
|
663
|
+
return await this.getMediaStream(instance.id);
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
const onMediaStream = async (callback: (stream: PhygridMediaStream) => void): Promise<void> => {
|
|
667
|
+
if (!instance) {
|
|
668
|
+
throw new Error('Instance not initialized');
|
|
669
|
+
}
|
|
670
|
+
await this.onMediaStream(instance.id, callback);
|
|
671
|
+
// Return void to match the expected return type
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
const updateReported = async (properties: Record<string, any>) => {
|
|
675
|
+
const newReported = {
|
|
676
|
+
...properties,
|
|
677
|
+
};
|
|
678
|
+
const result = await this.updateReportedProperties(instanceTwin.id, newReported);
|
|
679
|
+
Object.assign(instance!, result);
|
|
680
|
+
return result;
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
instance = {
|
|
684
|
+
...instanceTwin,
|
|
685
|
+
emit: edgeTwinEmit,
|
|
686
|
+
on: edgeTwinOn,
|
|
687
|
+
to: edgeTwinTo,
|
|
688
|
+
createPeripheralTwin,
|
|
689
|
+
updateReported,
|
|
690
|
+
getPeripheralTwins,
|
|
691
|
+
getDataChannel,
|
|
692
|
+
onDataChannel,
|
|
693
|
+
getMediaStream,
|
|
694
|
+
onMediaStream,
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
this.instances.set(this.instanceId, instance);
|
|
698
|
+
return instance;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
public async getEdgeInstance(): Promise<Instance> {
|
|
702
|
+
return this.getInstance();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
public async getScreenInstance(): Promise<Instance> {
|
|
706
|
+
return this.getInstance();
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// TODO properties should automatically be updated when the edge twin is updated
|
|
710
|
+
public async getPeripheralInstance(peripheralTwinId: string): Promise<PeripheralInstance> {
|
|
711
|
+
if (this.peripheralInstances.has(peripheralTwinId)) {
|
|
712
|
+
return this.peripheralInstances.get(peripheralTwinId)!;
|
|
713
|
+
}
|
|
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
|
+
|
|
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);
|
|
1007
|
+
return peripheralInstance;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
private getGridApp() {
|
|
1011
|
+
let currentWindow: Window & typeof globalThis = window;
|
|
1012
|
+
while (currentWindow) {
|
|
1013
|
+
try {
|
|
1014
|
+
if (
|
|
1015
|
+
(currentWindow as any).gridapp &&
|
|
1016
|
+
typeof (currentWindow as any).gridapp.getSettings === 'function'
|
|
1017
|
+
) {
|
|
1018
|
+
return (currentWindow as any).gridapp;
|
|
1019
|
+
}
|
|
1020
|
+
if (currentWindow.parent === currentWindow) break;
|
|
1021
|
+
currentWindow = currentWindow.parent as Window & typeof globalThis;
|
|
1022
|
+
} catch (e) {
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
public async getSettings(): Promise<Partial<EdgeTwinDesiredPropertiesResponse>> {
|
|
1030
|
+
if (typeof window !== 'undefined') {
|
|
1031
|
+
const gridapp = this.getGridApp();
|
|
1032
|
+
if (gridapp) {
|
|
1033
|
+
return gridapp.getSettings() as Partial<EdgeTwinDesiredPropertiesResponse>;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Existing fallback logic
|
|
1038
|
+
const edgeInstance = await this.getInstance();
|
|
1039
|
+
if (!edgeInstance) {
|
|
1040
|
+
throw new Error('Edge instance not found');
|
|
1041
|
+
}
|
|
1042
|
+
return (edgeInstance.properties.desired as EdgeTwinDesiredPropertiesResponse).settings;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
public async getEdgeSettings(): Promise<Partial<EdgeTwinDesiredPropertiesResponse>> {
|
|
1046
|
+
return this.getSettings();
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
public async getScreenSettings(): Promise<Partial<EdgeTwinDesiredPropertiesResponse>> {
|
|
1050
|
+
return this.getSettings();
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// todo: fix return type, remove any
|
|
1054
|
+
public async getDeviceInstance(): Promise<any> {
|
|
1055
|
+
await this.assureSocketConnection();
|
|
1056
|
+
|
|
1057
|
+
return new Promise((resolve, reject) => {
|
|
1058
|
+
this.emit(this.EVENTS.GET_DEVICE_INSTANCE, (response: any) => {
|
|
1059
|
+
resolve(response);
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
this.socket?.on('error', (error: any) => {
|
|
1063
|
+
reject(error);
|
|
1064
|
+
});
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// todo: fix return type, remove any
|
|
1069
|
+
public async getDeviceNetworks(): Promise<any> {
|
|
1070
|
+
await this.assureSocketConnection();
|
|
1071
|
+
|
|
1072
|
+
return new Promise((resolve, reject) => {
|
|
1073
|
+
this.emit(this.EVENTS.GET_DEVICE_NETWORKS, (response: any) => {
|
|
1074
|
+
resolve(response);
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
this.socket?.on('error', (error: any) => {
|
|
1078
|
+
reject(error);
|
|
1079
|
+
});
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// todo: fix return type, remove any
|
|
1084
|
+
public async setScreenInstanceReportedProperties(
|
|
1085
|
+
payload: EventPayload<ScreenTwinReportedProperties>
|
|
1086
|
+
): Promise<any> {
|
|
1087
|
+
await this.assureSocketConnection();
|
|
1088
|
+
|
|
1089
|
+
console.info(`setScreenInstanceReportedProperties(): payload ${JSON.stringify(payload)}`);
|
|
1090
|
+
|
|
1091
|
+
return new Promise((resolve, reject) => {
|
|
1092
|
+
this.emit(this.EVENTS.REPORT_SCREEN_INSTANCE_PROPERTIES, payload, (response: any) => {
|
|
1093
|
+
resolve(response);
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
this.socket?.on('error', (error: any) => {
|
|
1097
|
+
reject(error);
|
|
1098
|
+
});
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// todo: fix return type, remove any for clarity
|
|
1103
|
+
public emit(method: string, ...args: any[]): void {
|
|
1104
|
+
if (!this.socket) {
|
|
1105
|
+
console.error('emit(): Socket not created');
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
const event = this.instanceId;
|
|
1110
|
+
const callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined;
|
|
1111
|
+
const payload = { method, ...args[0] };
|
|
1112
|
+
|
|
1113
|
+
const emitArgs = [event, payload];
|
|
1114
|
+
|
|
1115
|
+
if (callback) {
|
|
1116
|
+
emitArgs.push(callback);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
if (!this.socketConnected) {
|
|
1120
|
+
console.info('emit(): Socket not connected, adding to emitQueue', emitArgs);
|
|
1121
|
+
this.emitQueue.push(emitArgs);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// console.info('hub-client is connected, emit(): Emitting', emitArgs);
|
|
1126
|
+
// TODO: fix ts-ignore
|
|
1127
|
+
// @ts-ignore
|
|
1128
|
+
this.socket.emit(...emitArgs);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// todo: fix return type, remove any
|
|
1132
|
+
private processEmitQueue(): void {
|
|
1133
|
+
if (!this.socket || !this.socketConnected) {
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
while (this.emitQueue.length > 0) {
|
|
1138
|
+
const args = this.emitQueue.shift();
|
|
1139
|
+
if (args) {
|
|
1140
|
+
this.socket.emit.apply(this.socket, args);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
public onTwinUpdate(targetTwinId: string, callback: (twin: TwinResponse) => void) {
|
|
1146
|
+
console.log(`onTwinUpdate() called for twin ${targetTwinId}`);
|
|
1147
|
+
this.assureSocketConnection();
|
|
1148
|
+
|
|
1149
|
+
// Initialize the set of listeners for this twin if it doesn't exist
|
|
1150
|
+
if (!this.twinUpdateListeners[targetTwinId]) {
|
|
1151
|
+
this.twinUpdateListeners[targetTwinId] = new Set();
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// Add the callback to the set of listeners
|
|
1155
|
+
this.twinUpdateListeners[targetTwinId].add(callback);
|
|
1156
|
+
console.log(
|
|
1157
|
+
`Added twin update listener for ${targetTwinId}. Total listeners: ${this.twinUpdateListeners[targetTwinId].size}`
|
|
1158
|
+
);
|
|
1159
|
+
|
|
1160
|
+
// Make sure we're subscribed to this twin
|
|
1161
|
+
if (!this.subscribedTwins.has(targetTwinId)) {
|
|
1162
|
+
this.subscribeTwin(targetTwinId).catch(error => {
|
|
1163
|
+
console.error(`Failed to subscribe to twin ${targetTwinId} for updates:`, error);
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
public offTwinUpdate(targetTwinId: string, callback: (twin: TwinResponse) => void): boolean {
|
|
1169
|
+
console.log(`Removing update listener for twin ${targetTwinId}`);
|
|
1170
|
+
|
|
1171
|
+
if (!this.twinUpdateListeners[targetTwinId]) {
|
|
1172
|
+
console.log(`No update listeners found for twin ${targetTwinId}`);
|
|
1173
|
+
return false;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
const result = this.twinUpdateListeners[targetTwinId].delete(callback);
|
|
1177
|
+
|
|
1178
|
+
if (this.twinUpdateListeners[targetTwinId].size === 0) {
|
|
1179
|
+
delete this.twinUpdateListeners[targetTwinId];
|
|
1180
|
+
console.log(`Removed last update listener for twin ${targetTwinId}, cleaning up`);
|
|
1181
|
+
} else {
|
|
1182
|
+
console.log(
|
|
1183
|
+
`Removed update listener. Remaining listeners: ${this.twinUpdateListeners[targetTwinId].size}`
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
return result;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
public async sendTwinMessage(
|
|
1191
|
+
targetTwinId: string,
|
|
1192
|
+
data: any,
|
|
1193
|
+
callback?: (response: any) => void
|
|
1194
|
+
) {
|
|
1195
|
+
// console.log('sendTwinMessage called with:', {
|
|
1196
|
+
// targetTwinId,
|
|
1197
|
+
// data,
|
|
1198
|
+
// hasCallback: !!callback
|
|
1199
|
+
// });
|
|
1200
|
+
|
|
1201
|
+
const edgeInstance = await this.getInstance();
|
|
1202
|
+
if (!edgeInstance) {
|
|
1203
|
+
console.error('sendTwinMessage failed: Edge instance not found');
|
|
1204
|
+
throw new Error('Edge instance not found');
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
const { id: edgeTwinId, deviceId } = edgeInstance;
|
|
1208
|
+
|
|
1209
|
+
if (!deviceId) {
|
|
1210
|
+
console.error('sendTwinMessage failed: Device ID not available');
|
|
1211
|
+
throw new Error('Device ID not available - ensure device is connected');
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const payload: EventPayload = {
|
|
1215
|
+
twinId: targetTwinId,
|
|
1216
|
+
sourceTwinId: edgeTwinId,
|
|
1217
|
+
sourceDeviceId: deviceId,
|
|
1218
|
+
data,
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1221
|
+
// console.log('sendTwinMessage prepared payload:', JSON.stringify(payload, null, 2));
|
|
1222
|
+
|
|
1223
|
+
if (callback) {
|
|
1224
|
+
// console.log('sendTwinMessage emitting with callback');
|
|
1225
|
+
this.emit(this.EVENTS.TWIN_MESSAGE, payload, (response: any) => {
|
|
1226
|
+
// console.log('sendTwinMessage callback received response:', response);
|
|
1227
|
+
callback(response);
|
|
1228
|
+
});
|
|
1229
|
+
} else {
|
|
1230
|
+
// console.log('sendTwinMessage emitting with promise');
|
|
1231
|
+
return new Promise((resolve, reject) => {
|
|
1232
|
+
this.emit(this.EVENTS.TWIN_MESSAGE, payload, (response: any) => {
|
|
1233
|
+
// console.log('sendTwinMessage promise received response:', response);
|
|
1234
|
+
resolve(response);
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
this.socket?.on('error', (error: any) => {
|
|
1238
|
+
console.error('sendTwinMessage socket error:', error);
|
|
1239
|
+
reject(error);
|
|
1240
|
+
});
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
public async onTwinMessage(
|
|
1246
|
+
targetTwinId: string,
|
|
1247
|
+
callback: (message: any) => void
|
|
1248
|
+
): Promise<void> {
|
|
1249
|
+
console.log('onTwinMessage called for twin:', {
|
|
1250
|
+
targetTwinId,
|
|
1251
|
+
existingListeners: this.twinMessageListeners[targetTwinId]?.size || 0,
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
if (!this.twinMessageListeners[targetTwinId]) {
|
|
1255
|
+
// console.log(`Creating new listener set for twin ${targetTwinId}`);
|
|
1256
|
+
this.twinMessageListeners[targetTwinId] = new Set();
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
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
|
+
}
|
|
1276
|
+
|
|
1277
|
+
public offTwinMessage(targetTwinId: string, callback: (message: any) => void): boolean {
|
|
1278
|
+
console.log(`Removing message listener for twin ${targetTwinId}`);
|
|
1279
|
+
|
|
1280
|
+
if (!this.twinMessageListeners[targetTwinId]) {
|
|
1281
|
+
console.log(`No listeners found for twin ${targetTwinId}`);
|
|
1282
|
+
return false;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
const result = this.twinMessageListeners[targetTwinId].delete(callback);
|
|
1286
|
+
|
|
1287
|
+
if (this.twinMessageListeners[targetTwinId].size === 0) {
|
|
1288
|
+
delete this.twinMessageListeners[targetTwinId];
|
|
1289
|
+
console.log(`Removed last listener for twin ${targetTwinId}, cleaning up`);
|
|
1290
|
+
} else {
|
|
1291
|
+
console.log(
|
|
1292
|
+
`Removed listener. Remaining listeners: ${this.twinMessageListeners[targetTwinId].size}`
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
return result;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
public removeMessageListener(twinId: string, callback: (message: any) => void): void {
|
|
1300
|
+
const listeners = this.twinMessageListeners[twinId];
|
|
1301
|
+
if (listeners) {
|
|
1302
|
+
listeners.delete(callback);
|
|
1303
|
+
if (listeners.size === 0) {
|
|
1304
|
+
delete this.twinMessageListeners[twinId];
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
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
|
+
this.subscribedTwins.add(targetTwinId);
|
|
1315
|
+
const edgeInstance = await this.getInstance();
|
|
1316
|
+
if (!edgeInstance) {
|
|
1317
|
+
throw new Error('Edge instance not found');
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
const { id: edgeTwinId, deviceId } = edgeInstance;
|
|
1321
|
+
|
|
1322
|
+
const payload: EventPayload = {
|
|
1323
|
+
twinId: targetTwinId,
|
|
1324
|
+
sourceTwinId: edgeTwinId,
|
|
1325
|
+
sourceDeviceId: deviceId,
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
console.log(`[subscribeTwin] Emitting TWIN_SUBSCRIBE with payload:`, payload);
|
|
1329
|
+
|
|
1330
|
+
if (callback) {
|
|
1331
|
+
this.emit(this.EVENTS.TWIN_SUBSCRIBE, payload, (response: any) => {
|
|
1332
|
+
console.log(`[subscribeTwin] Subscription callback response:`, response);
|
|
1333
|
+
callback(response);
|
|
1334
|
+
});
|
|
1335
|
+
} else {
|
|
1336
|
+
return new Promise((resolve, reject) => {
|
|
1337
|
+
this.emit(this.EVENTS.TWIN_SUBSCRIBE, payload, (response: any) => {
|
|
1338
|
+
console.log(`[subscribeTwin] Subscription promise response:`, response);
|
|
1339
|
+
resolve(response);
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
this.socket?.on('error', (error: any) => {
|
|
1343
|
+
console.error(`[subscribeTwin] Socket error:`, error);
|
|
1344
|
+
reject(error);
|
|
1345
|
+
});
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
public async getTwinById(twinId: string): Promise<TwinResponse> {
|
|
1351
|
+
const payload: EventPayload<{ twinId: string }> = {
|
|
1352
|
+
data: { twinId },
|
|
1353
|
+
};
|
|
1354
|
+
|
|
1355
|
+
return new Promise((resolve, reject) => {
|
|
1356
|
+
this.emit(this.EVENTS.GET_TWIN_BY_ID, payload, (response: any) => {
|
|
1357
|
+
const { twin } = response;
|
|
1358
|
+
if (!twin) {
|
|
1359
|
+
reject(new Error(`Twin with id ${twinId} not found`));
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
resolve(twin);
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
this.socket?.on('error', (error: any) => {
|
|
1366
|
+
reject(error);
|
|
1367
|
+
});
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// private async updateTwinById(twinId: string, newTwin: Partial<TwinResponse>): Promise<TwinResponse> {
|
|
1372
|
+
// const payload: EventPayload = {
|
|
1373
|
+
// twinId,
|
|
1374
|
+
// data: newTwin
|
|
1375
|
+
// };
|
|
1376
|
+
|
|
1377
|
+
// return new Promise((resolve, reject) => {
|
|
1378
|
+
// this.emit('updateTwin', payload, (response: any) => {
|
|
1379
|
+
// const { twin } = response;
|
|
1380
|
+
// if (!twin) {
|
|
1381
|
+
// reject(new Error(`Failed to update twin ${twinId}`));
|
|
1382
|
+
// return;
|
|
1383
|
+
// }
|
|
1384
|
+
// resolve(twin);
|
|
1385
|
+
// });
|
|
1386
|
+
|
|
1387
|
+
// this.socket?.on('error', (error: any) => {
|
|
1388
|
+
// reject(error);
|
|
1389
|
+
// });
|
|
1390
|
+
// });
|
|
1391
|
+
// }
|
|
1392
|
+
|
|
1393
|
+
private async updateReportedProperties(
|
|
1394
|
+
twinId: string,
|
|
1395
|
+
reportedProperties: Record<string, any>
|
|
1396
|
+
): Promise<TwinResponse> {
|
|
1397
|
+
const payload: EventPayload = {
|
|
1398
|
+
twinId,
|
|
1399
|
+
data: reportedProperties,
|
|
1400
|
+
};
|
|
1401
|
+
|
|
1402
|
+
return new Promise((resolve, reject) => {
|
|
1403
|
+
this.emit('reportPeripheralTwinProperties', payload, (response: any) => {
|
|
1404
|
+
const { twin } = response;
|
|
1405
|
+
if (!twin) {
|
|
1406
|
+
reject(new Error(`Failed to update reported properties for twin ${twinId}`));
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
resolve(twin);
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
this.socket?.on('error', (error: any) => {
|
|
1413
|
+
reject(error);
|
|
1414
|
+
});
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
export const connectPhyClient = (
|
|
1420
|
+
params: { instanceId?: string; moduleName?: string; dataResidency?: string } = {}
|
|
1421
|
+
) => PhyHubClient.connect(params);
|
|
1422
|
+
|
|
1423
|
+
export type { EdgeTwinResponse, Instance, PeripheralInstance };
|
|
1424
|
+
|
|
1425
|
+
export default {
|
|
1426
|
+
connectPhyClient,
|
|
1427
|
+
};
|
|
1428
|
+
|
|
1429
|
+
export type { PhygridDataChannel, PhygridMediaStream };
|