@phystack/hub-device 4.4.30
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.md +12 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +967 -0
- package/dist/index.js.map +1 -0
- package/dist/storage/browser.d.ts +2 -0
- package/dist/storage/browser.d.ts.map +1 -0
- package/dist/storage/browser.js +20 -0
- package/dist/storage/browser.js.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +31 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/node.d.ts +2 -0
- package/dist/storage/node.d.ts.map +1 -0
- package/dist/storage/node.js +47 -0
- package/dist/storage/node.js.map +1 -0
- package/dist/sysinfo/browser.d.ts +3 -0
- package/dist/sysinfo/browser.d.ts.map +1 -0
- package/dist/sysinfo/browser.js +194 -0
- package/dist/sysinfo/browser.js.map +1 -0
- package/dist/sysinfo/index.d.ts +3 -0
- package/dist/sysinfo/index.d.ts.map +1 -0
- package/dist/sysinfo/index.js +34 -0
- package/dist/sysinfo/index.js.map +1 -0
- package/dist/sysinfo/node.d.ts +3 -0
- package/dist/sysinfo/node.d.ts.map +1 -0
- package/dist/sysinfo/node.js +53 -0
- package/dist/sysinfo/node.js.map +1 -0
- package/dist/sysinfo/tizen.d.ts +8 -0
- package/dist/sysinfo/tizen.d.ts.map +1 -0
- package/dist/sysinfo/tizen.js +168 -0
- package/dist/sysinfo/tizen.js.map +1 -0
- package/dist/types/command.types.d.ts +8 -0
- package/dist/types/command.types.d.ts.map +1 -0
- package/dist/types/command.types.js +8 -0
- package/dist/types/command.types.js.map +1 -0
- package/dist/types/container.types.d.ts +10 -0
- package/dist/types/container.types.d.ts.map +1 -0
- package/dist/types/container.types.js +3 -0
- package/dist/types/container.types.js.map +1 -0
- package/dist/types/job.types.d.ts +31 -0
- package/dist/types/job.types.d.ts.map +1 -0
- package/dist/types/job.types.js +15 -0
- package/dist/types/job.types.js.map +1 -0
- package/dist/types/twin.types.d.ts +653 -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/utilities/get-device-identifier.utility.d.ts +2 -0
- package/dist/utilities/get-device-identifier.utility.d.ts.map +1 -0
- package/dist/utilities/get-device-identifier.utility.js +140 -0
- package/dist/utilities/get-device-identifier.utility.js.map +1 -0
- package/dist/utilities/get-hub-credentials.utility.d.ts +8 -0
- package/dist/utilities/get-hub-credentials.utility.d.ts.map +1 -0
- package/dist/utilities/get-hub-credentials.utility.js +47 -0
- package/dist/utilities/get-hub-credentials.utility.js.map +1 -0
- package/dist/utilities/get-provisioning-code.utility.d.ts +3 -0
- package/dist/utilities/get-provisioning-code.utility.d.ts.map +1 -0
- package/dist/utilities/get-provisioning-code.utility.js +49 -0
- package/dist/utilities/get-provisioning-code.utility.js.map +1 -0
- package/package.json +39 -0
- package/src/hub-device.d.ts +0 -0
- package/src/index.ts +1228 -0
- package/src/storage/browser.ts +16 -0
- package/src/storage/index.ts +46 -0
- package/src/storage/node.ts +42 -0
- package/src/sysinfo/browser.ts +217 -0
- package/src/sysinfo/index.ts +29 -0
- package/src/sysinfo/node.ts +387 -0
- package/src/sysinfo/tizen.ts +203 -0
- package/src/types/command.types.ts +8 -0
- package/src/types/container.types.ts +12 -0
- package/src/types/job.types.ts +36 -0
- package/src/types/twin.types.ts +751 -0
- package/src/utilities/get-device-identifier.utility.ts +179 -0
- package/src/utilities/get-hub-credentials.utility.ts +56 -0
- package/src/utilities/get-provisioning-code.utility.ts +55 -0
- package/tsconfig.json +45 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,1228 @@
|
|
|
1
|
+
import { Socket, SocketOptions, ManagerOptions } from 'socket.io-client';
|
|
2
|
+
import { getSocketIOWithProxy } from '@phystack/socket.io-proxy';
|
|
3
|
+
import getStorage from './storage';
|
|
4
|
+
import getSysinfo, { isTizen } from './sysinfo';
|
|
5
|
+
import getDeviceIdentifier from './utilities/get-device-identifier.utility';
|
|
6
|
+
import getHubCredentials from './utilities/get-hub-credentials.utility';
|
|
7
|
+
import getProvisioningCode from './utilities/get-provisioning-code.utility';
|
|
8
|
+
import {
|
|
9
|
+
DeviceIp,
|
|
10
|
+
DeviceStatus,
|
|
11
|
+
DeviceTwinReportedProperties,
|
|
12
|
+
DeviceTwinResponse,
|
|
13
|
+
EdgeTwinReportedProperties,
|
|
14
|
+
ScreenTwinReportedProperties,
|
|
15
|
+
TwinResponse,
|
|
16
|
+
TwinStatusEnum,
|
|
17
|
+
TwinTypeEnum,
|
|
18
|
+
PeripheralTwinReportedProperties,
|
|
19
|
+
} from './types/twin.types';
|
|
20
|
+
import { Job } from './types/job.types';
|
|
21
|
+
import path from 'path';
|
|
22
|
+
import fs from 'fs';
|
|
23
|
+
import { Mutex } from 'async-mutex';
|
|
24
|
+
|
|
25
|
+
export type MyIoOptions = Partial<ManagerOptions & SocketOptions>;
|
|
26
|
+
interface EventPayload<T = any> {
|
|
27
|
+
twinId?: string;
|
|
28
|
+
deviceId?: string;
|
|
29
|
+
status?: TwinStatusEnum;
|
|
30
|
+
data?: T;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface MigratedCredentials {
|
|
34
|
+
deviceId: string;
|
|
35
|
+
accessKey: string;
|
|
36
|
+
deviceSerial: string;
|
|
37
|
+
phyhubUrl: string;
|
|
38
|
+
region: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
enum PhyHubResponseStatus {
|
|
42
|
+
success = 'success',
|
|
43
|
+
error = 'error',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const logger = console;
|
|
47
|
+
|
|
48
|
+
function getVersion(): string {
|
|
49
|
+
const pkgPath = path.resolve(__dirname, '../package.json');
|
|
50
|
+
try {
|
|
51
|
+
const content = fs.readFileSync(pkgPath, 'utf-8');
|
|
52
|
+
const pkg = JSON.parse(content);
|
|
53
|
+
return pkg.version || 'N/A';
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('Failed to read version from package.json:', error);
|
|
56
|
+
return 'N/A';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const phyhubDeviceProvisioningBaseUrls: {[env: string]: string} = {
|
|
61
|
+
LOCAL: 'https://phyhub-prov.dev.omborigrid.net/api',
|
|
62
|
+
DEV: 'https://phyhub-prov.dev.omborigrid.net/api',
|
|
63
|
+
QA: 'https://phyhub-prov.qa.omborigrid.net/api',
|
|
64
|
+
PROD: 'https://phyhub-prov.eu.omborigrid.net/api',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
let phyHubSingleton: HubDevice | null = null;
|
|
68
|
+
|
|
69
|
+
export const connectToPhyHub = async (
|
|
70
|
+
gridEnv: string = 'PROD',
|
|
71
|
+
osVersion: string = 'N/A',
|
|
72
|
+
migratedCredentials: MigratedCredentials | undefined = undefined
|
|
73
|
+
) => {
|
|
74
|
+
const storage = await getStorage();
|
|
75
|
+
let deviceId = await storage.get('deviceId');
|
|
76
|
+
let accessKey = await storage.get('accessKey');
|
|
77
|
+
let phyhubUrl = await storage.get('phyhubUrl');
|
|
78
|
+
let deviceSerial = await storage.get('deviceSerial');
|
|
79
|
+
let provisioningCode = await storage.get('provisioningCode');
|
|
80
|
+
let region = await storage.get('region');
|
|
81
|
+
|
|
82
|
+
// migrate credentials if legacy device
|
|
83
|
+
if (migratedCredentials) {
|
|
84
|
+
try {
|
|
85
|
+
logger.info(`connectToPhyHub(): Using migrated credentials `, JSON.stringify(migratedCredentials, null, 2));
|
|
86
|
+
const {
|
|
87
|
+
deviceId: migratedDeviceId,
|
|
88
|
+
deviceSerial: migratedDeviceSerial,
|
|
89
|
+
accessKey: migratedAccessKey,
|
|
90
|
+
region: migratedRegion
|
|
91
|
+
} = migratedCredentials;
|
|
92
|
+
if (migratedDeviceId) {
|
|
93
|
+
await storage.set('deviceId', migratedDeviceId);
|
|
94
|
+
deviceId = migratedDeviceId;
|
|
95
|
+
}
|
|
96
|
+
if (migratedDeviceSerial) {
|
|
97
|
+
await storage.set('deviceSerial', migratedDeviceSerial);
|
|
98
|
+
deviceSerial = migratedDeviceSerial;
|
|
99
|
+
}
|
|
100
|
+
if (migratedAccessKey) {
|
|
101
|
+
await storage.set('accessKey', migratedAccessKey);
|
|
102
|
+
accessKey = migratedAccessKey;
|
|
103
|
+
}
|
|
104
|
+
if (migratedRegion) {
|
|
105
|
+
await storage.set('region', migratedRegion);
|
|
106
|
+
region = migratedRegion;
|
|
107
|
+
const migratedPhyhubUrl = `https://phyhub.${migratedRegion}.omborigrid.net`;
|
|
108
|
+
await storage.set('phyhubUrl', migratedPhyhubUrl);
|
|
109
|
+
phyhubUrl = migratedPhyhubUrl;
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
logger.error(`connectToPhyHub(): Unable to migrate credentials:`, error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Ensure we have a device serial and it's properly stored
|
|
117
|
+
if (!deviceSerial) {
|
|
118
|
+
logger.info('connectToPhyHub(): Getting device identifier');
|
|
119
|
+
deviceSerial = await getDeviceIdentifier();
|
|
120
|
+
// Wait for storage to complete before continuing
|
|
121
|
+
await storage.set('deviceSerial', deviceSerial);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Always store updates to gridEnv, osVersion (for future use)
|
|
125
|
+
await storage.set('gridEnv', gridEnv);
|
|
126
|
+
await storage.set('osVersion', osVersion);
|
|
127
|
+
|
|
128
|
+
logger.info(`connectToPhyHub(): Connecting device`, {
|
|
129
|
+
deviceId,
|
|
130
|
+
accessKey,
|
|
131
|
+
deviceSerial,
|
|
132
|
+
gridEnv,
|
|
133
|
+
provisioningCode,
|
|
134
|
+
osVersion,
|
|
135
|
+
phyhubUrl,
|
|
136
|
+
region,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// We are already connected, just return the instance
|
|
140
|
+
if (phyHubSingleton) {
|
|
141
|
+
// logger.info(
|
|
142
|
+
// `connectToPhyHub(): HubDevice singleton instance found for device ${deviceId}.`
|
|
143
|
+
// );
|
|
144
|
+
return { phyHub: phyHubSingleton, deviceSerial };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// If we have credentials, do not wait for provisioning code
|
|
148
|
+
if (deviceId && accessKey && deviceSerial && gridEnv && phyhubUrl) {
|
|
149
|
+
logger.info(
|
|
150
|
+
`connectToPhyHub(): Credentials found. Connecting device ${deviceId} to phyhub.`
|
|
151
|
+
);
|
|
152
|
+
phyHubSingleton = new HubDevice(
|
|
153
|
+
phyhubUrl,
|
|
154
|
+
deviceId,
|
|
155
|
+
accessKey,
|
|
156
|
+
deviceSerial,
|
|
157
|
+
gridEnv,
|
|
158
|
+
osVersion,
|
|
159
|
+
region,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
logger.info(
|
|
163
|
+
`connectToPhyHub(): Device ${deviceId} connected to phyhub.`
|
|
164
|
+
);
|
|
165
|
+
return { phyHub: phyHubSingleton, deviceSerial };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const storedDeviceSerial = await storage.get('deviceSerial');
|
|
169
|
+
// check if deviceSerial is not set, set it.
|
|
170
|
+
if (!storedDeviceSerial) {
|
|
171
|
+
await storage.set('deviceSerial', deviceSerial);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// logger.info(`connectToPhyHub(): Getting credentials for device ${deviceId}.`);
|
|
175
|
+
const credentials = await getHubCredentials(phyhubDeviceProvisioningBaseUrls[gridEnv]);
|
|
176
|
+
|
|
177
|
+
if (credentials) {
|
|
178
|
+
const { deviceId, accessKey, phyhubUrl, region } = credentials;
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
await storage.set('region', region);
|
|
182
|
+
await storage.set('deviceId', deviceId);
|
|
183
|
+
await storage.set('accessKey', accessKey);
|
|
184
|
+
await storage.set('phyhubUrl', phyhubUrl);
|
|
185
|
+
|
|
186
|
+
// logger.info(`connectToPhyHub(): Credentials fetched for device ${deviceId}.`);
|
|
187
|
+
phyHubSingleton = new HubDevice(
|
|
188
|
+
phyhubUrl,
|
|
189
|
+
deviceId,
|
|
190
|
+
accessKey,
|
|
191
|
+
deviceSerial,
|
|
192
|
+
gridEnv,
|
|
193
|
+
osVersion,
|
|
194
|
+
region
|
|
195
|
+
);
|
|
196
|
+
logger.info(
|
|
197
|
+
`connectToPhyHub(): Device ${deviceId} connected to phyhub.`
|
|
198
|
+
);
|
|
199
|
+
return { phyHub: phyHubSingleton, deviceSerial };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await getProvisioningCode(phyhubDeviceProvisioningBaseUrls[gridEnv]);
|
|
203
|
+
const provisioningCodeKey = `provisioningCode_${gridEnv}`;
|
|
204
|
+
provisioningCode = await storage.get(provisioningCodeKey);
|
|
205
|
+
logger.info(
|
|
206
|
+
`connectToPhyHub(): Returning provisioning code ${provisioningCode} for device ${deviceSerial}`
|
|
207
|
+
);
|
|
208
|
+
return provisioningCode;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export const getPhyHubInstance = async () => {
|
|
212
|
+
return phyHubSingleton;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
export const getProvisioningInfo = async () => {
|
|
216
|
+
const storage = await getStorage();
|
|
217
|
+
const deviceSerial = await storage.get('deviceSerial');
|
|
218
|
+
let provisioningCode = await storage.get('provisioningCode');
|
|
219
|
+
|
|
220
|
+
// If we don't have a provisioning code, try to get a new one
|
|
221
|
+
if (!provisioningCode) {
|
|
222
|
+
logger.info('getProvisioningInfo(): No provisioning code found, fetching new one');
|
|
223
|
+
const gridEnv = await storage.get('gridEnv') || 'PROD';
|
|
224
|
+
const apiUrl = phyhubDeviceProvisioningBaseUrls[gridEnv];
|
|
225
|
+
|
|
226
|
+
const result = await getProvisioningCode(apiUrl);
|
|
227
|
+
if (result?.provisioningCode) {
|
|
228
|
+
provisioningCode = result.provisioningCode;
|
|
229
|
+
logger.info('getProvisioningInfo(): Successfully fetched new provisioning code');
|
|
230
|
+
} else {
|
|
231
|
+
logger.error('getProvisioningInfo(): Failed to fetch provisioning code');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
provisioningCode,
|
|
237
|
+
deviceSerial,
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
export class HubDevice {
|
|
242
|
+
public gridEnv: string;
|
|
243
|
+
public osVersion: string;
|
|
244
|
+
public phyhubUrl: string | null = null;
|
|
245
|
+
public deviceId: string | null = null;
|
|
246
|
+
public accessKey: string | null = null;
|
|
247
|
+
public deviceSerial: string | null = null;
|
|
248
|
+
public region: string | null = null;
|
|
249
|
+
|
|
250
|
+
public socket: Socket | null = null;
|
|
251
|
+
private socketConnected: boolean = false;
|
|
252
|
+
private socketAuthenticated: boolean = false;
|
|
253
|
+
private onTwinUpdate: ((twins: TwinResponse) => void) | null = null;
|
|
254
|
+
private onTwinRemove: ((twins: TwinResponse) => void) | null = null;
|
|
255
|
+
private emitQueue: any[] = [];
|
|
256
|
+
private emitAuthQueue: any[] = [];
|
|
257
|
+
private scheduler: any | null = null;
|
|
258
|
+
private deletedTwins: string[] = [];
|
|
259
|
+
|
|
260
|
+
private intervals: any = {};
|
|
261
|
+
private peripheralTwinMutex = new Mutex();
|
|
262
|
+
private eventCallbacks: Map<string, Array<(data: any) => void>> = new Map();
|
|
263
|
+
private reconnectListeners: Array<() => void> = [];
|
|
264
|
+
private authReconnectListeners: Array<() => void> = [];
|
|
265
|
+
|
|
266
|
+
constructor(
|
|
267
|
+
phyhubUrl: string,
|
|
268
|
+
deviceId: string,
|
|
269
|
+
accessKey: string,
|
|
270
|
+
deviceSerial: string,
|
|
271
|
+
gridEnv: string = 'PROD',
|
|
272
|
+
osVersion: string = 'N/A',
|
|
273
|
+
region: string,
|
|
274
|
+
) {
|
|
275
|
+
this.phyhubUrl = phyhubUrl;
|
|
276
|
+
this.deviceId = deviceId;
|
|
277
|
+
this.accessKey = accessKey;
|
|
278
|
+
this.deviceSerial = deviceSerial;
|
|
279
|
+
this.gridEnv = gridEnv.toUpperCase();
|
|
280
|
+
this.osVersion = osVersion;
|
|
281
|
+
this.region = region.toUpperCase();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
public setTwinUpdateHandler(callback: (twins: TwinResponse) => void) {
|
|
285
|
+
this.onTwinUpdate = callback;
|
|
286
|
+
// logger.info('setTwinUpdateHandler(): Twin update handler set');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
public setTwinRemoveHandler(callback: (twins: TwinResponse) => void) {
|
|
290
|
+
this.onTwinRemove = callback;
|
|
291
|
+
// logger.info('setTwinRemoveHandler(): Twin remove handler set');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
public isConnected() {
|
|
295
|
+
return this.socketConnected;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
public isAuthenticated() {
|
|
299
|
+
return this.socketAuthenticated;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
public async connect() {
|
|
303
|
+
try {
|
|
304
|
+
const { deviceId, accessKey, gridEnv, osVersion, phyhubUrl } = this;
|
|
305
|
+
if (!deviceId || !accessKey) {
|
|
306
|
+
logger.error('connect(): DeviceId or AccessKey not set');
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
if (!phyhubUrl) {
|
|
312
|
+
logger.error('connect(): Phyhub URL not set');
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
logger.info(`connect(): Connecting device `, { deviceId, accessKey, gridEnv, osVersion });
|
|
317
|
+
this.socket = await this.connectSocket(deviceId, accessKey, phyhubUrl);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
logger.error(`connect(): Failed to connect socket`, error);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return this;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
public on(event: string, callback: (data: any) => void) {
|
|
326
|
+
const { socket, deviceId } = this;
|
|
327
|
+
// logger.info('SOCKET on(): Registering event handler', { event, deviceId });
|
|
328
|
+
|
|
329
|
+
if (!this.eventCallbacks.has(event)) {
|
|
330
|
+
this.eventCallbacks.set(event, []);
|
|
331
|
+
}
|
|
332
|
+
this.eventCallbacks.get(event)!.push(callback);
|
|
333
|
+
|
|
334
|
+
if (!socket || !deviceId) {
|
|
335
|
+
logger.error('SOCKET on(): No socket or deviceId available', { socket: !!socket, deviceId });
|
|
336
|
+
return callback({ error: `connectDevice ${deviceId}: Not connected` });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
socket.on(event, (data: any) => {
|
|
340
|
+
if (this.isConnected()) {
|
|
341
|
+
// logger.info(`SOCKET on(): Received ${event} event`, { deviceId, data });
|
|
342
|
+
this.eventCallbacks.get(event)!.forEach(cb => cb(data));
|
|
343
|
+
} else {
|
|
344
|
+
logger.warn(`SOCKET on(): Ignoring ${event} event as socket is not connected`, { deviceId });
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
logger.info('SOCKET on(): Successfully registered callback for event', { event, deviceId });
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
public emit(...args: any) {
|
|
352
|
+
const { socket, emitQueue } = this;
|
|
353
|
+
|
|
354
|
+
if (!socket) {
|
|
355
|
+
logger.error('emit(): Socket not created');
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (!this.isConnected()) {
|
|
359
|
+
logger.info('SOCKET emit(): Pushing to emitQueue');
|
|
360
|
+
emitQueue.push(args);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
socket.emit.apply(socket, args);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
public emitAuth(...args: any) {
|
|
367
|
+
const { socket, emitAuthQueue } = this;
|
|
368
|
+
if (!socket) {
|
|
369
|
+
logger.error('emitAuth(): Socket not created');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (!this.isConnected() || !this.isAuthenticated()) {
|
|
373
|
+
// logger.info('SOCKET emitAuth(): Pushing to emitAuthQueue');
|
|
374
|
+
emitAuthQueue.push(args);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// const [event, payload, ...rest] = args;
|
|
379
|
+
// if (event === 'getPeripheralTwins') {
|
|
380
|
+
// this.getPeripheralTwins(payload);
|
|
381
|
+
// return;
|
|
382
|
+
// }
|
|
383
|
+
socket.emit.apply(socket, args);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private processEmitQueue() {
|
|
387
|
+
logger.info('SOCKET processEmitQueue(): processing emit queue');
|
|
388
|
+
const { socket, emitQueue } = this;
|
|
389
|
+
|
|
390
|
+
if (!socket || !this.isConnected()) {
|
|
391
|
+
logger.info('SOCKET processEmitQueue(): Aborting', {
|
|
392
|
+
socket: !!socket,
|
|
393
|
+
socketConnected: this.isConnected(),
|
|
394
|
+
});
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
while (emitQueue.length > 0) {
|
|
399
|
+
const args = emitQueue.shift();
|
|
400
|
+
socket.emit.apply(socket, args);
|
|
401
|
+
}
|
|
402
|
+
this.processEmitAuthQueue();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private processEmitAuthQueue() {
|
|
406
|
+
logger.info('SOCKET processEmitAuthQueue(): processing emit auth queue');
|
|
407
|
+
const { socket, emitAuthQueue } = this;
|
|
408
|
+
|
|
409
|
+
if (!socket || !this.isConnected() || !this.isAuthenticated()) {
|
|
410
|
+
logger.info('SOCKET processEmitAuthQueue(): Aborting', {
|
|
411
|
+
socket: !!socket,
|
|
412
|
+
socketConnected: this.isConnected(),
|
|
413
|
+
socketAuthenticated: this.isAuthenticated(),
|
|
414
|
+
});
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
while (emitAuthQueue.length > 0) {
|
|
419
|
+
const args = emitAuthQueue.shift();
|
|
420
|
+
socket.emit.apply(socket, args);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private executeReconnectListeners() {
|
|
425
|
+
this.reconnectListeners.forEach(listener => {
|
|
426
|
+
try {
|
|
427
|
+
listener();
|
|
428
|
+
} catch (error) {
|
|
429
|
+
logger.error('Error executing reconnect listener:', error);
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private executeAuthReconnectListeners() {
|
|
435
|
+
this.authReconnectListeners.forEach(listener => {
|
|
436
|
+
try {
|
|
437
|
+
listener();
|
|
438
|
+
} catch (error) {
|
|
439
|
+
logger.error('Error executing reconnect listener:', error);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private async connectSocket(
|
|
445
|
+
deviceId: string,
|
|
446
|
+
accessKey: string,
|
|
447
|
+
phyhubUrl: string
|
|
448
|
+
): Promise<Socket | null> {
|
|
449
|
+
// logger.info('SOCKET connectSocket(): Creating new socket', { deviceId, accessKey, phyhubUrl });
|
|
450
|
+
|
|
451
|
+
// Updated to use getSocketIOWithProxy
|
|
452
|
+
const socket: Socket = await getSocketIOWithProxy(phyhubUrl, {
|
|
453
|
+
auth: { deviceId, accessKey },
|
|
454
|
+
reconnection: true,
|
|
455
|
+
reconnectionDelayMax: 1000,
|
|
456
|
+
reconnectionAttempts: Infinity,
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
socket.on('connect', () => {
|
|
460
|
+
logger.info(
|
|
461
|
+
`SOCKET connectSocket() on connect: Connected to phyhub ${phyhubUrl} 🟢-🟢-🟢-🟢-⚪`
|
|
462
|
+
);
|
|
463
|
+
this.socketConnected = true;
|
|
464
|
+
this.processEmitQueue();
|
|
465
|
+
if (this.socketConnected) {
|
|
466
|
+
this.executeReconnectListeners();
|
|
467
|
+
}
|
|
468
|
+
// If we're already authenticated, call reconnect listeners and process auth queue
|
|
469
|
+
if (this.socketAuthenticated) {
|
|
470
|
+
logger.info('SOCKET connectSocket() on reconnect: Socket already authenticated, notifying listeners');
|
|
471
|
+
this.executeAuthReconnectListeners();
|
|
472
|
+
this.processEmitAuthQueue();
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
socket.on('disconnect', () => {
|
|
477
|
+
logger.info('SOCKET connectSocket() on disconnect: Disconnected from server');
|
|
478
|
+
this.socketConnected = false;
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
socket.on('deviceAuthenticated', (response: any) => {
|
|
482
|
+
logger.info('SOCKET connectSocket() on deviceAuthenticated: Device authenticated', response);
|
|
483
|
+
this.socketAuthenticated = true;
|
|
484
|
+
if (response.status === 'success') {
|
|
485
|
+
// Execute reconnect listeners when device is authenticated
|
|
486
|
+
this.executeReconnectListeners();
|
|
487
|
+
this.processEmitAuthQueue();
|
|
488
|
+
} else {
|
|
489
|
+
logger.error('SOCKET connectSocket() on deviceAuthenticated: Device authentication failed', response);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
socket.io.on('reconnect', (attempt: number) => {
|
|
494
|
+
logger.info(`SOCKET connectSocket() on reconnect: Reconnected to server attempt: ${attempt}`);
|
|
495
|
+
this.socketConnected = true;
|
|
496
|
+
this.processEmitQueue();
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
socket.on('twinUpdated', async (payload: EventPayload<TwinResponse>) => {
|
|
500
|
+
logger.info('SOCKET connectSocket() on twinUpdated: syncing twin on device');
|
|
501
|
+
if (!payload.data) return;
|
|
502
|
+
|
|
503
|
+
const twin = payload.data;
|
|
504
|
+
await this.cacheTwins([twin]);
|
|
505
|
+
|
|
506
|
+
if (this.onTwinUpdate) {
|
|
507
|
+
this.onTwinUpdate(twin);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
socket.on('twinCreated', async (payload: EventPayload<TwinResponse>) => {
|
|
512
|
+
logger.info('SOCKET connectSocket() on twinCreated: Adding new twin to device');
|
|
513
|
+
if (payload.data) {
|
|
514
|
+
await this.cacheTwins([payload.data]);
|
|
515
|
+
if (this.onTwinUpdate) {
|
|
516
|
+
this.onTwinUpdate(payload.data);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
socket.on('twinDeleted', async (payload: EventPayload<TwinResponse>) => {
|
|
522
|
+
logger.info('SOCKET connectSocket() on twinDeleted: Removing twin from device');
|
|
523
|
+
if (this.onTwinRemove && payload.data) {
|
|
524
|
+
await this.removeCachedTwin([payload.data]);
|
|
525
|
+
this.onTwinRemove(payload.data);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// socket.on('twinMessage', async (payload: EventPayload) => {
|
|
530
|
+
// logger.info('SOCKET connectSocket() on twinMessage: received twin message', JSON.stringify(payload, null, 2));
|
|
531
|
+
// });
|
|
532
|
+
|
|
533
|
+
socket.io.on('reconnect_attempt', (attempt: number) => {
|
|
534
|
+
logger.info(`SOCKET connectSocket() on reconnect_attempt: Reconnect attempt: ${attempt}`);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
socket.io.on('reconnect_error', (error: Error) => {
|
|
538
|
+
logger.info('SOCKET connectSocket() on reconnect_error: ', error);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
socket.io.on('reconnect_failed', () => {
|
|
542
|
+
logger.info('SOCKET connectSocket() on reconnect_failed: Reconnect failed');
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
return socket;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
public getDeviceStatus = async (): Promise<DeviceStatus> => {
|
|
549
|
+
const storage = await getStorage();
|
|
550
|
+
const deviceId = await storage.get('deviceId');
|
|
551
|
+
const accessKey = await storage.get('accessKey');
|
|
552
|
+
const deviceSerial = await storage.get('deviceSerial');
|
|
553
|
+
const provisioningCode = accessKey ? 'N/A' : await storage.get('provisioningCode');
|
|
554
|
+
const gridEnv = await storage.get('gridEnv');
|
|
555
|
+
const osVersion = await storage.get('osVersion');
|
|
556
|
+
const dataResidency = await storage.get('region');
|
|
557
|
+
const twins = await this.getCachedTwins();
|
|
558
|
+
|
|
559
|
+
let displayName = '';
|
|
560
|
+
let deviceEnv= '';
|
|
561
|
+
let spaceId = '';
|
|
562
|
+
let tenantId = '';
|
|
563
|
+
let ip: DeviceIp[] = [];
|
|
564
|
+
|
|
565
|
+
const cachedTwins = await this.getCachedTwins();
|
|
566
|
+
try {
|
|
567
|
+
const deviceTwinInstanceId = cachedTwins?.[TwinTypeEnum.Device]?.[0];
|
|
568
|
+
if (deviceTwinInstanceId) {
|
|
569
|
+
const deviceTwinInstance: DeviceTwinResponse = await this.getCachedTwins(deviceTwinInstanceId);
|
|
570
|
+
if (deviceTwinInstance) {
|
|
571
|
+
displayName = deviceTwinInstance.properties.desired.displayName;
|
|
572
|
+
deviceEnv = deviceTwinInstance.properties.desired.env;
|
|
573
|
+
spaceId = deviceTwinInstance.properties.desired.spaceId;
|
|
574
|
+
tenantId = deviceTwinInstance.tenantId;
|
|
575
|
+
ip = deviceTwinInstance.properties.reported.ip
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
} catch (error) {
|
|
579
|
+
logger.error(`Device twin not found for device ${deviceId}`, error);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const response = {
|
|
583
|
+
deviceId,
|
|
584
|
+
accessKey,
|
|
585
|
+
deviceSerial,
|
|
586
|
+
gridEnv,
|
|
587
|
+
osVersion,
|
|
588
|
+
provisioningCode,
|
|
589
|
+
displayName,
|
|
590
|
+
deviceEnv,
|
|
591
|
+
spaceId,
|
|
592
|
+
tenantId,
|
|
593
|
+
dataResidency,
|
|
594
|
+
ip,
|
|
595
|
+
socketConnected: this.isConnected(),
|
|
596
|
+
socketAuthenticated: this.isAuthenticated(),
|
|
597
|
+
twins,
|
|
598
|
+
};
|
|
599
|
+
return response;
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
public getDeviceInstance = async () => {
|
|
603
|
+
const storage = await getStorage();
|
|
604
|
+
const deviceId = await storage.get('deviceId');
|
|
605
|
+
const twins = await this.getCachedTwins();
|
|
606
|
+
|
|
607
|
+
try {
|
|
608
|
+
const deviceTwinInstanceId = twins[TwinTypeEnum.Device][0];
|
|
609
|
+
if (deviceTwinInstanceId) {
|
|
610
|
+
const deviceTwinInstance = await this.getCachedTwins(deviceTwinInstanceId);
|
|
611
|
+
if (deviceTwinInstance) {
|
|
612
|
+
return { twin: deviceTwinInstance };
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return {};
|
|
616
|
+
} catch (error) {
|
|
617
|
+
logger.error(`Device twin not found for device ${deviceId}`, error);
|
|
618
|
+
return {};
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
public getDeviceNetworks = async () => {
|
|
623
|
+
try {
|
|
624
|
+
const sysinfo = await getSysinfo();
|
|
625
|
+
const { networkInterfaces, wifiInterfaces, wifiNetworks, wifiConnections } = sysinfo;
|
|
626
|
+
return {
|
|
627
|
+
networkInterfaces,
|
|
628
|
+
wifiInterfaces,
|
|
629
|
+
wifiNetworks,
|
|
630
|
+
wifiConnections,
|
|
631
|
+
};
|
|
632
|
+
} catch (error) {
|
|
633
|
+
logger.error(`Unable to get network information of the device`, error);
|
|
634
|
+
return {
|
|
635
|
+
wifiNetworks: [],
|
|
636
|
+
wifiConnections: [],
|
|
637
|
+
networkInterfaces: [],
|
|
638
|
+
wifiInterfaces: [],
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
public getScreenInstance = async () => {
|
|
644
|
+
const storage = await getStorage();
|
|
645
|
+
const deviceId = await storage.get('deviceId');
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
const twins = await this.getCachedTwins();
|
|
649
|
+
console.log('getScreenInstance(): twins', JSON.stringify(twins, null, 2));
|
|
650
|
+
if(!twins[TwinTypeEnum.Screen]) {
|
|
651
|
+
return {};
|
|
652
|
+
}
|
|
653
|
+
const screenTwinInstanceId = twins[TwinTypeEnum.Screen][0];
|
|
654
|
+
|
|
655
|
+
if (screenTwinInstanceId) {
|
|
656
|
+
const screenTwinInstance = await this.getCachedTwins(screenTwinInstanceId);
|
|
657
|
+
if (screenTwinInstance) {
|
|
658
|
+
return { twin: screenTwinInstance };
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return {};
|
|
662
|
+
} catch (error) {
|
|
663
|
+
logger.error(`Screen instance not found for device ${deviceId}`, error);
|
|
664
|
+
return {};
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
public async connectDevice(callback: (data: any) => void) {
|
|
669
|
+
const { socket, deviceId } = this;
|
|
670
|
+
logger.info(`connectDevice(): Connecting device ${deviceId}`);
|
|
671
|
+
|
|
672
|
+
if (!socket || !deviceId) {
|
|
673
|
+
return callback({ error: `connectDevice ${deviceId}: Not connected` });
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
let deviceTwin: any = undefined;
|
|
677
|
+
let cachedTwinsInfo: Record<string, string[]> | undefined;
|
|
678
|
+
let shouldUseCachedResponse = false;
|
|
679
|
+
|
|
680
|
+
try {
|
|
681
|
+
cachedTwinsInfo = await this.getCachedTwins();
|
|
682
|
+
if (cachedTwinsInfo && cachedTwinsInfo[TwinTypeEnum.Device] && cachedTwinsInfo[TwinTypeEnum.Device].length > 0) {
|
|
683
|
+
const deviceTwinId = cachedTwinsInfo[TwinTypeEnum.Device][0];
|
|
684
|
+
deviceTwin = await this.getCachedTwins(deviceTwinId);
|
|
685
|
+
// Only use cached response if device twin exists and has valid properties
|
|
686
|
+
shouldUseCachedResponse = deviceTwin && deviceTwin.properties && deviceTwin.properties.desired;
|
|
687
|
+
}
|
|
688
|
+
} catch (error) {
|
|
689
|
+
logger.info(`connectDevice(): No cached twins found, will fetch fresh twins`);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const reportSystemInformation = async () => {
|
|
693
|
+
logger.info('reportSystemInformation(): About to fetch system information');
|
|
694
|
+
const deviceInfo = await getSysinfo();
|
|
695
|
+
let ips: Array<{interface: string; ipv4: string; ipv6: string}> = [];
|
|
696
|
+
|
|
697
|
+
try {
|
|
698
|
+
const { networkInterfaces } = deviceInfo;
|
|
699
|
+
for (const iface of networkInterfaces) {
|
|
700
|
+
if (iface.ip4 && iface.ip4 !== '127.0.0.1') { // Skip loopback address
|
|
701
|
+
ips.push({
|
|
702
|
+
interface: iface.ifaceName,
|
|
703
|
+
ipv4: iface.ip4,
|
|
704
|
+
ipv6: iface.ip6,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
} catch (error) {
|
|
709
|
+
logger.error('Failed to detect IP address of device', error);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// If we're in the browser, pick the osVersion from the last path segment.
|
|
713
|
+
// Otherwise (Node environment), fallback to whatever was stored.
|
|
714
|
+
const storage = await getStorage();
|
|
715
|
+
const isBrowser = typeof window !== 'undefined';
|
|
716
|
+
|
|
717
|
+
let finalOsVersion;
|
|
718
|
+
if (isTizen) {
|
|
719
|
+
finalOsVersion = deviceInfo?.os?.osVersion || '';
|
|
720
|
+
} else if (isBrowser && window.location && window.location.pathname) {
|
|
721
|
+
const segments = window.location.pathname.split('/').filter(Boolean);
|
|
722
|
+
finalOsVersion = segments.length ? segments[segments.length - 1] : 'N/A';
|
|
723
|
+
} else {
|
|
724
|
+
finalOsVersion = (await storage.get('osVersion')) || 'N/A';
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const gridEnv = await storage.get('gridEnv');
|
|
728
|
+
|
|
729
|
+
const deviceTwinReportedProperties: EventPayload<DeviceTwinReportedProperties> = {
|
|
730
|
+
data: {
|
|
731
|
+
...deviceInfo,
|
|
732
|
+
ip: deviceInfo?.ip && deviceInfo?.ip?.length > 0 ? deviceInfo.ip : ips,
|
|
733
|
+
os: {
|
|
734
|
+
osVersion: finalOsVersion,
|
|
735
|
+
},
|
|
736
|
+
env: {
|
|
737
|
+
gridEnv,
|
|
738
|
+
},
|
|
739
|
+
phydeviceVersion: getVersion(),
|
|
740
|
+
},
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
logger.info('reportSystemInformation(): About to emit system information');
|
|
744
|
+
socket.emit(
|
|
745
|
+
'reportDeviceTwinProperties',
|
|
746
|
+
deviceTwinReportedProperties,
|
|
747
|
+
async (_response: any) => {
|
|
748
|
+
logger.info('reportSystemInformation(): Emitted system information');
|
|
749
|
+
}
|
|
750
|
+
);
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
// Immediately send system information upon a new socket connection
|
|
754
|
+
reportSystemInformation();
|
|
755
|
+
|
|
756
|
+
if (!this.intervals['reportSystemInformation']) {
|
|
757
|
+
// Changed the interval from 60000 (1 minute) to 300000 (5 minutes)
|
|
758
|
+
this.intervals['reportSystemInformation'] = setInterval(async () => {
|
|
759
|
+
// Only emit if connected and authenticated
|
|
760
|
+
if (!this.isConnected() || !this.isAuthenticated()) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
reportSystemInformation();
|
|
764
|
+
}, 300000);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const connector = async () => {
|
|
768
|
+
this.emitAuth('connectDevice', async (response: any) => {
|
|
769
|
+
console.log('connector connectDevice(): response', response);
|
|
770
|
+
|
|
771
|
+
// If server response is valid, use server response
|
|
772
|
+
if (response.status === 'success' && response.twins) {
|
|
773
|
+
callback(response);
|
|
774
|
+
const { twins }: { twins: TwinResponse[] } = response;
|
|
775
|
+
logger.info(
|
|
776
|
+
`SOCKET connector(): received ${twins.length} twins in response. 🟢-🟢-🟢-🟢-🟢`
|
|
777
|
+
);
|
|
778
|
+
await this.cacheTwins(twins);
|
|
779
|
+
} else if (shouldUseCachedResponse && deviceTwin && cachedTwinsInfo) {
|
|
780
|
+
// Fallback to cached response if server fails
|
|
781
|
+
const storage = await getStorage();
|
|
782
|
+
const region = await storage.get('region');
|
|
783
|
+
const phyhubUrl = await storage.get('phyhubUrl');
|
|
784
|
+
const transformedTwins = await this.transformCachedTwinsToArray(cachedTwinsInfo);
|
|
785
|
+
logger.info('connectDevice(): Using cached twins as fallback due to server error');
|
|
786
|
+
callback({ status: 'success', twins: transformedTwins, region, phyhubUrl });
|
|
787
|
+
} else {
|
|
788
|
+
// No cached twins available and server failed
|
|
789
|
+
logger.error(
|
|
790
|
+
`SOCKET connector(): No twins received in response and no cached fallback available. 🟢-🟢-🟢-🟢-🔴`
|
|
791
|
+
);
|
|
792
|
+
callback(response);
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
};
|
|
796
|
+
connector();
|
|
797
|
+
socket.on('reconnect', connector);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Add new helper method to transform cached twins format
|
|
801
|
+
private async transformCachedTwinsToArray(cachedTwins: Record<string, string[]>): Promise<TwinResponse[]> {
|
|
802
|
+
const transformedTwins: TwinResponse[] = [];
|
|
803
|
+
|
|
804
|
+
for (const [_type, twinIds] of Object.entries(cachedTwins)) {
|
|
805
|
+
for (const twinId of twinIds) {
|
|
806
|
+
const twin = await this.getCachedTwins(twinId);
|
|
807
|
+
if (twin) {
|
|
808
|
+
transformedTwins.push(twin);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return transformedTwins;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
private cacheTwins = async (twins: TwinResponse[]) => {
|
|
817
|
+
// logger.info(`cacheTwins(): Caching twins`);
|
|
818
|
+
|
|
819
|
+
for (const twin of twins) {
|
|
820
|
+
try {
|
|
821
|
+
// Handle peripheral twin separately
|
|
822
|
+
if (twin.type === TwinTypeEnum.Peripheral) {
|
|
823
|
+
await this.cachePeripheralTwins(twin);
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Existing job scheduling logic
|
|
828
|
+
const { type, id } = twin;
|
|
829
|
+
if (type === TwinTypeEnum.Device) {
|
|
830
|
+
const reported = twin.properties?.reported as DeviceTwinReportedProperties | undefined;
|
|
831
|
+
const jobs = reported?.jobs ?? [];
|
|
832
|
+
if (jobs.length > 0 && this.scheduler) {
|
|
833
|
+
this.scheduler.processJobs(jobs, twin?.properties?.desired?.timezone ?? 'UTC', id);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
} catch (error) {
|
|
837
|
+
logger.error(`cacheTwins(): Failed to schedule job for device twin ${twin.id}`, error);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const storage = await getStorage();
|
|
842
|
+
const twinsInfo: Record<string, string[]> = {};
|
|
843
|
+
let twinsInfoCachePath = ``;
|
|
844
|
+
for (const twin of twins) {
|
|
845
|
+
if (twin && !this.deletedTwins.includes(twin.id)) {
|
|
846
|
+
const { type, deviceId, id } = twin;
|
|
847
|
+
twinsInfoCachePath = `twins-device-${deviceId}`;
|
|
848
|
+
|
|
849
|
+
const twinCachePath = `${id}-device-${deviceId}`;
|
|
850
|
+
// logger.info(`cacheTwins(): Caching ${type} twin ${id}`);
|
|
851
|
+
await storage.set(twinCachePath, twin);
|
|
852
|
+
|
|
853
|
+
if (!twinsInfo[type]) {
|
|
854
|
+
twinsInfo[type] = [];
|
|
855
|
+
}
|
|
856
|
+
twinsInfo[type].push(id);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
let cachedTwinsInfo = undefined;
|
|
861
|
+
try {
|
|
862
|
+
cachedTwinsInfo = await storage.get(twinsInfoCachePath);
|
|
863
|
+
} catch (error) {
|
|
864
|
+
// logger.info(`cacheTwins(): ${twinsInfoCachePath} not found, creating cache`);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (cachedTwinsInfo) {
|
|
868
|
+
// merge cached twins info with fresh twins info
|
|
869
|
+
const updatedTwinsInfo = { ...cachedTwinsInfo };
|
|
870
|
+
for (const key in twinsInfo) {
|
|
871
|
+
if (Object.prototype.hasOwnProperty.call(updatedTwinsInfo, key)) {
|
|
872
|
+
updatedTwinsInfo[key] = Array.from(
|
|
873
|
+
new Set([...updatedTwinsInfo[key], ...twinsInfo[key]])
|
|
874
|
+
);
|
|
875
|
+
} else {
|
|
876
|
+
updatedTwinsInfo[key] = twinsInfo[key];
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// logger.info(`cacheTwins(): Updating twins info cache`);
|
|
881
|
+
await storage.set(twinsInfoCachePath, updatedTwinsInfo);
|
|
882
|
+
} else {
|
|
883
|
+
// logger.info(`cacheTwins(): Updating twins info cache`);
|
|
884
|
+
await storage.set(twinsInfoCachePath, twinsInfo);
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
private async removeCachedTwin(twins: TwinResponse[] | TwinResponse | string) {
|
|
889
|
+
const storage = await getStorage();
|
|
890
|
+
const twinsArray = Array.isArray(twins) ? twins : [twins];
|
|
891
|
+
|
|
892
|
+
for (const twin of twinsArray) {
|
|
893
|
+
const twinData = typeof twin === 'string'
|
|
894
|
+
? await this.getCachedTwins(twin)
|
|
895
|
+
: twin;
|
|
896
|
+
|
|
897
|
+
if (!twinData) continue;
|
|
898
|
+
|
|
899
|
+
const { type, deviceId, id, instanceId } = twinData;
|
|
900
|
+
|
|
901
|
+
// Handle peripheral twins differently
|
|
902
|
+
if (type === TwinTypeEnum.Peripheral && instanceId) {
|
|
903
|
+
await this.peripheralTwinMutex.runExclusive(async () => {
|
|
904
|
+
const cachePath = `peripheral-twins-${instanceId}`;
|
|
905
|
+
const peripheralTwins = await storage.get(cachePath) || {};
|
|
906
|
+
delete peripheralTwins[id];
|
|
907
|
+
await storage.set(cachePath, peripheralTwins);
|
|
908
|
+
});
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Existing logic for other twin types
|
|
913
|
+
const twinCachePath = `${id}-device-${deviceId}`;
|
|
914
|
+
const twinsInfoCachePath = `twins-device-${deviceId}`;
|
|
915
|
+
try {
|
|
916
|
+
// Remove individual twin cache
|
|
917
|
+
logger.info(`removeCachedTwin(): Removing ${type} twin ${id}`);
|
|
918
|
+
await storage.remove(twinCachePath);
|
|
919
|
+
|
|
920
|
+
// Update twins info cache
|
|
921
|
+
const cachedTwinsInfo = await storage.get(twinsInfoCachePath);
|
|
922
|
+
if (cachedTwinsInfo && cachedTwinsInfo[type]) {
|
|
923
|
+
cachedTwinsInfo[type] = cachedTwinsInfo[type].filter(
|
|
924
|
+
(cachedId: string) => cachedId !== id
|
|
925
|
+
);
|
|
926
|
+
|
|
927
|
+
if (cachedTwinsInfo[type].length === 0) {
|
|
928
|
+
delete cachedTwinsInfo[type];
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (Object.keys(cachedTwinsInfo).length > 0) {
|
|
932
|
+
await storage.set(twinsInfoCachePath, cachedTwinsInfo);
|
|
933
|
+
} else {
|
|
934
|
+
await storage.remove(twinsInfoCachePath);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
this.deletedTwins.push(id);
|
|
938
|
+
} catch (error) {
|
|
939
|
+
logger.info(`removeCachedTwin(): No twins info cache found for ${twinsInfoCachePath}`);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
public getCachedTwins = async (twinId: string | undefined = undefined) => {
|
|
945
|
+
const storage = await getStorage();
|
|
946
|
+
if (twinId) {
|
|
947
|
+
const twinCachePath = `${twinId}-device-${this.deviceId}`;
|
|
948
|
+
const cachedTwin = await storage.get(twinCachePath);
|
|
949
|
+
return cachedTwin;
|
|
950
|
+
} else {
|
|
951
|
+
const twinsInfoCachePath = `twins-device-${this.deviceId}`;
|
|
952
|
+
const cachedTwinsInfo = await storage.get(twinsInfoCachePath) || {};
|
|
953
|
+
return cachedTwinsInfo;
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
public async reportScreenInstanceProperties(
|
|
958
|
+
properties: EventPayload<ScreenTwinReportedProperties>,
|
|
959
|
+
callback: (data: any) => void
|
|
960
|
+
) {
|
|
961
|
+
const { socket, deviceId } = this;
|
|
962
|
+
if (!socket || !deviceId) {
|
|
963
|
+
return callback({ error: `reportScreenInstanceProperties() ${deviceId}: Not connected` });
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
this.emitAuth('reportScreenTwinProperties', properties, async (response: any) => {
|
|
967
|
+
if (response.status === PhyHubResponseStatus.success) {
|
|
968
|
+
// logger.info(
|
|
969
|
+
// `reportScreenInstanceProperties() on reportScreenProperties: device ${deviceId} screen app properties reported`
|
|
970
|
+
// );
|
|
971
|
+
const { twin } = response;
|
|
972
|
+
if (!twin) {
|
|
973
|
+
logger.info(
|
|
974
|
+
`reportScreenInstanceProperties() on reportScreenProperties: failed to cache screen twin updates for device ${deviceId}`
|
|
975
|
+
);
|
|
976
|
+
} else {
|
|
977
|
+
await this.cacheTwins([twin]);
|
|
978
|
+
}
|
|
979
|
+
} else {
|
|
980
|
+
logger.info(
|
|
981
|
+
`reportScreenInstanceProperties() on reportScreenProperties: failed to report screen app properties of device ${deviceId}`
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
callback(response);
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
public async getEdgeInstance(
|
|
989
|
+
payload: EventPayload
|
|
990
|
+
) {
|
|
991
|
+
const storage = await getStorage();
|
|
992
|
+
const deviceId = await storage.get('deviceId');
|
|
993
|
+
|
|
994
|
+
try {
|
|
995
|
+
const { twinId } = payload;
|
|
996
|
+
const twins = await this.getCachedTwins();
|
|
997
|
+
const edgeTwin = twins[TwinTypeEnum.Edge].filter((id: string) => id === twinId);
|
|
998
|
+
|
|
999
|
+
if (edgeTwin && edgeTwin.length > 0) {
|
|
1000
|
+
const edgeTwinInstance = await this.getCachedTwins(edgeTwin[0]);
|
|
1001
|
+
if (edgeTwinInstance) {
|
|
1002
|
+
return { twin: edgeTwinInstance };
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return {};
|
|
1006
|
+
} catch (error) {
|
|
1007
|
+
logger.error(`Edge instance not found for device ${deviceId}`, error);
|
|
1008
|
+
return {};
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
public async sendEventSignal(
|
|
1013
|
+
payload: EventPayload<Record<string, any>>
|
|
1014
|
+
) {
|
|
1015
|
+
logger.info(`sendEventSignal(): sending Event Signal from device ${this.deviceId}`, payload);
|
|
1016
|
+
const { socket, deviceId } = this;
|
|
1017
|
+
if (!socket || !deviceId) {
|
|
1018
|
+
logger.error(`sendEventSignal(): Device or socket not connected`);
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
this.emitAuth('signalEvent', payload);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
public async sendClientSignal(
|
|
1026
|
+
payload: EventPayload<Record<string, any>>
|
|
1027
|
+
) {
|
|
1028
|
+
logger.info(`sendClientSignal(): sending Client Signal from device ${this.deviceId}`, payload);
|
|
1029
|
+
const { socket, deviceId } = this;
|
|
1030
|
+
if (!socket || !deviceId) {
|
|
1031
|
+
logger.error(`sendClientSignal(): Device or socket not connected`);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
this.emitAuth('signalClient', payload);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
public async sendSessionSignal(
|
|
1039
|
+
payload: EventPayload<Record<string, any>>
|
|
1040
|
+
) {
|
|
1041
|
+
logger.info(`sendSessionSignal(): sending Session Signal from device ${this.deviceId}`, payload);
|
|
1042
|
+
const { socket, deviceId } = this;
|
|
1043
|
+
if (!socket || !deviceId) {
|
|
1044
|
+
logger.error(`sendSessionSignal(): Device or socket not connected`);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
this.emitAuth('signalSession', payload);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
public async reportEdgeTwinProperties(
|
|
1052
|
+
instanceTwinId: string,
|
|
1053
|
+
properties: EdgeTwinReportedProperties,
|
|
1054
|
+
callback: (data: any) => void
|
|
1055
|
+
) {
|
|
1056
|
+
logger.info(`reportEdgeTwinProperties(): Reporting twin ${instanceTwinId} properties`);
|
|
1057
|
+
const { socket, deviceId } = this;
|
|
1058
|
+
if (!socket || !deviceId) {
|
|
1059
|
+
return callback({ error: `reportEdgeTwinProperties(): Device or socket not connected` });
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const payload: EventPayload = {
|
|
1063
|
+
twinId: instanceTwinId,
|
|
1064
|
+
data: properties,
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
this.emitAuth('reportEdgeTwinProperties', payload, async (response: any) => {
|
|
1068
|
+
const { twin } = response;
|
|
1069
|
+
if (twin) {
|
|
1070
|
+
await this.cacheTwins([twin]);
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
public async reportDeviceTwinProperties(
|
|
1076
|
+
instanceTwinId: string,
|
|
1077
|
+
properties: DeviceTwinReportedProperties,
|
|
1078
|
+
callback: (data: any) => void
|
|
1079
|
+
) {
|
|
1080
|
+
// logger.info(`reportDeviceTwinProperties(): Reporting device twin ${instanceTwinId} properties`);
|
|
1081
|
+
const { socket, deviceId } = this;
|
|
1082
|
+
if (!socket || !deviceId) {
|
|
1083
|
+
return callback({ error: `reportDeviceTwinProperties(): Device or socket not connected` });
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
const payload: EventPayload<DeviceTwinReportedProperties> = {
|
|
1087
|
+
twinId: instanceTwinId,
|
|
1088
|
+
data: properties,
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
this.emitAuth('reportDeviceTwinProperties', payload, async (response: any) => {
|
|
1092
|
+
const { twin } = response;
|
|
1093
|
+
if (twin) {
|
|
1094
|
+
await this.cacheTwins([twin]);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
public async reportDeviceJob(instanceTwinId: string, job: Job, callback: (data: any) => void) {
|
|
1100
|
+
// logger.info(`reportDeviceJob(): Reporting device twin ${instanceTwinId} job ${job.id}`);
|
|
1101
|
+
const { socket, deviceId } = this;
|
|
1102
|
+
if (!socket || !deviceId) {
|
|
1103
|
+
return callback({ error: `reportDeviceJob(): Device or socket not connected` });
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
const payload: EventPayload<Job> = {
|
|
1107
|
+
twinId: instanceTwinId,
|
|
1108
|
+
data: job,
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
this.emitAuth('reportDeviceJob', payload, async (response: any) => {
|
|
1112
|
+
const { twin } = response;
|
|
1113
|
+
if (twin) {
|
|
1114
|
+
await this.cacheTwins([twin]);
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
public async reportPeripheralTwinProperties(
|
|
1120
|
+
instanceTwinId: string,
|
|
1121
|
+
properties: PeripheralTwinReportedProperties,
|
|
1122
|
+
callback?: (data: any) => void
|
|
1123
|
+
) {
|
|
1124
|
+
// logger.info(`reportPeripheralTwinProperties(): Reporting twin ${instanceTwinId} properties`);
|
|
1125
|
+
const { socket, deviceId } = this;
|
|
1126
|
+
if (!socket || !deviceId) {
|
|
1127
|
+
return callback?.({ error: `reportPeripheralTwinProperties(): Device or socket not connected` });
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
const payload: EventPayload = {
|
|
1131
|
+
twinId: instanceTwinId,
|
|
1132
|
+
data: properties,
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
this.emitAuth('reportPeripheralTwinProperties', payload, async (response: any) => {
|
|
1136
|
+
const { twin } = response;
|
|
1137
|
+
if (twin) {
|
|
1138
|
+
await this.cacheTwins([twin]);
|
|
1139
|
+
}
|
|
1140
|
+
callback?.(response);
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
public setScheduler(scheduler: any) {
|
|
1145
|
+
this.scheduler = scheduler;
|
|
1146
|
+
if (scheduler && typeof scheduler.setHubDevice === 'function') {
|
|
1147
|
+
scheduler.setHubDevice(this);
|
|
1148
|
+
}
|
|
1149
|
+
logger.info('setScheduler(): Scheduler has been set');
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
private async cachePeripheralTwins(twin: TwinResponse) {
|
|
1153
|
+
if (twin.type !== TwinTypeEnum.Peripheral || !twin.instanceId) return;
|
|
1154
|
+
|
|
1155
|
+
return await this.peripheralTwinMutex.runExclusive(async () => {
|
|
1156
|
+
const storage = await getStorage();
|
|
1157
|
+
const cachePath = `peripheral-twins-${twin.instanceId}`;
|
|
1158
|
+
let peripheralTwins = await storage.get(cachePath) || {};
|
|
1159
|
+
|
|
1160
|
+
peripheralTwins[twin.id] = twin;
|
|
1161
|
+
await storage.set(cachePath, peripheralTwins);
|
|
1162
|
+
return peripheralTwins;
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
public async getPeripheralTwins(payload: EventPayload<{ instanceId: string }>) {
|
|
1167
|
+
const { data } = payload;
|
|
1168
|
+
// console.log('getPeripheralTwins(): payload', payload);
|
|
1169
|
+
if (!data?.instanceId) {
|
|
1170
|
+
logger.error('getPeripheralTwins(): Missing instanceId in payload');
|
|
1171
|
+
return { peripheralTwins: {} };
|
|
1172
|
+
}
|
|
1173
|
+
return await this.peripheralTwinMutex.runExclusive(async () => {
|
|
1174
|
+
// Get current cached twins
|
|
1175
|
+
const storage = await getStorage();
|
|
1176
|
+
const cachePath = `peripheral-twins-${data.instanceId}`;
|
|
1177
|
+
const cachedTwins = await storage.get(cachePath) || {};
|
|
1178
|
+
|
|
1179
|
+
try {
|
|
1180
|
+
// Get fresh twins from server
|
|
1181
|
+
const response = await new Promise<{ peripheralTwins: Record<string, TwinResponse> }>((resolve, reject) => {
|
|
1182
|
+
this.emitAuth('getPeripheralTwins', payload, resolve);
|
|
1183
|
+
this.socket?.on('error', reject);
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
// Ensure peripheralTwins exists and is an object
|
|
1187
|
+
const peripheralTwins = response?.peripheralTwins || {};
|
|
1188
|
+
|
|
1189
|
+
// Cache new twins using cachePeripheralTwins
|
|
1190
|
+
for (const twin of Object.values(peripheralTwins)) {
|
|
1191
|
+
await this.cachePeripheralTwins(twin);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// Find and remove deleted twins
|
|
1195
|
+
const cachedTwinIds = Object.keys(cachedTwins);
|
|
1196
|
+
const currentTwinIds = Object.keys(peripheralTwins);
|
|
1197
|
+
const deletedTwinIds = cachedTwinIds.filter(id => !currentTwinIds.includes(id));
|
|
1198
|
+
|
|
1199
|
+
if (deletedTwinIds.length > 0) {
|
|
1200
|
+
await this.removeCachedTwin(deletedTwinIds.map(id => cachedTwins[id]));
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
return { peripheralTwins };
|
|
1204
|
+
} catch (error) {
|
|
1205
|
+
logger.error('getPeripheralTwins(): Error during execution', error);
|
|
1206
|
+
return { peripheralTwins: {} };
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
public onReconnect(listener: () => void) {
|
|
1212
|
+
this.reconnectListeners.push(listener);
|
|
1213
|
+
// logger.info('onReconnect(): Added new reconnect listener');
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
public onAuthReconnect(listener: () => void) {
|
|
1217
|
+
this.authReconnectListeners.push(listener);
|
|
1218
|
+
// logger.info('onAuthReconnect(): Added new auth reconnect listener');
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
export default {
|
|
1225
|
+
connectToPhyHub,
|
|
1226
|
+
getPhyHubInstance,
|
|
1227
|
+
getProvisioningInfo,
|
|
1228
|
+
};
|