@phystack/hub-client 4.5.19-dev → 4.5.21-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +22 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +252 -378
- package/dist/index.js.map +1 -1
- package/dist/peripheral-twin.d.ts +34 -0
- package/dist/peripheral-twin.d.ts.map +1 -0
- package/dist/peripheral-twin.js +234 -0
- package/dist/peripheral-twin.js.map +1 -0
- package/dist/services/phyhub-connection.service.d.ts +1 -0
- package/dist/services/phyhub-connection.service.d.ts.map +1 -1
- package/dist/services/phyhub-connection.service.js +17 -0
- package/dist/services/phyhub-connection.service.js.map +1 -1
- package/dist/services/phyhub-direct-connection.service.d.ts +21 -0
- package/dist/services/phyhub-direct-connection.service.d.ts.map +1 -0
- package/dist/services/phyhub-direct-connection.service.js +101 -0
- package/dist/services/phyhub-direct-connection.service.js.map +1 -0
- package/dist/services/webrtc/data-channel-handler.d.ts +45 -0
- package/dist/services/webrtc/data-channel-handler.d.ts.map +1 -0
- package/dist/services/webrtc/data-channel-handler.js +260 -0
- package/dist/services/webrtc/data-channel-handler.js.map +1 -0
- package/dist/services/webrtc/index.d.ts +8 -0
- package/dist/services/webrtc/index.d.ts.map +1 -0
- package/dist/services/webrtc/index.js +18 -0
- package/dist/services/webrtc/index.js.map +1 -0
- package/dist/services/webrtc/media-stream-handler.d.ts +57 -0
- package/dist/services/webrtc/media-stream-handler.d.ts.map +1 -0
- package/dist/services/webrtc/media-stream-handler.js +383 -0
- package/dist/services/webrtc/media-stream-handler.js.map +1 -0
- package/dist/services/webrtc/peer-connection-manager.d.ts +40 -0
- package/dist/services/webrtc/peer-connection-manager.d.ts.map +1 -0
- package/dist/services/webrtc/peer-connection-manager.js +336 -0
- package/dist/services/webrtc/peer-connection-manager.js.map +1 -0
- package/dist/services/webrtc/types.d.ts +134 -0
- package/dist/services/webrtc/types.d.ts.map +1 -0
- package/dist/services/webrtc/types.js +12 -0
- package/dist/services/webrtc/types.js.map +1 -0
- package/dist/services/webrtc/webrtc-globals.d.ts +4 -0
- package/dist/services/webrtc/webrtc-globals.d.ts.map +1 -0
- package/dist/services/webrtc/webrtc-globals.js +72 -0
- package/dist/services/webrtc/webrtc-globals.js.map +1 -0
- package/dist/services/webrtc/webrtc-manager.d.ts +35 -0
- package/dist/services/webrtc/webrtc-manager.d.ts.map +1 -0
- package/dist/services/webrtc/webrtc-manager.js +274 -0
- package/dist/services/webrtc/webrtc-manager.js.map +1 -0
- package/dist/test/communication-comprehensive-test.d.ts +8 -0
- package/dist/test/communication-comprehensive-test.d.ts.map +1 -0
- package/dist/test/communication-comprehensive-test.js +356 -0
- package/dist/test/communication-comprehensive-test.js.map +1 -0
- package/dist/test/webrtc-channel-names-test.d.ts +2 -0
- package/dist/test/webrtc-channel-names-test.d.ts.map +1 -0
- package/dist/test/webrtc-channel-names-test.js +177 -0
- package/dist/test/webrtc-channel-names-test.js.map +1 -0
- package/dist/test/webrtc-comprehensive-test.d.ts +2 -0
- package/dist/test/webrtc-comprehensive-test.d.ts.map +1 -0
- package/dist/test/webrtc-comprehensive-test.js +328 -0
- package/dist/test/webrtc-comprehensive-test.js.map +1 -0
- package/dist/test/webrtc-reconnect-test.d.ts +4 -0
- package/dist/test/webrtc-reconnect-test.d.ts.map +1 -0
- package/dist/test/webrtc-reconnect-test.js +244 -0
- package/dist/test/webrtc-reconnect-test.js.map +1 -0
- package/dist/test/webrtc-test-harness.d.ts +4 -0
- package/dist/test/webrtc-test-harness.d.ts.map +1 -0
- package/dist/test/webrtc-test-harness.js +169 -0
- package/dist/test/webrtc-test-harness.js.map +1 -0
- package/dist/twin-messaging.d.ts +20 -0
- package/dist/twin-messaging.d.ts.map +1 -0
- package/dist/twin-messaging.js +94 -0
- package/dist/twin-messaging.js.map +1 -0
- package/dist/twin-registry.d.ts +9 -0
- package/dist/twin-registry.d.ts.map +1 -0
- package/dist/twin-registry.js +26 -0
- package/dist/twin-registry.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +20 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/twin.types.d.ts +62 -14
- package/dist/types/twin.types.d.ts.map +1 -1
- package/dist/types/twin.types.js +8 -1
- package/dist/types/twin.types.js.map +1 -1
- package/docs/webrtc-howto.md +398 -0
- package/docs/webrtc-test.md +330 -0
- package/package.json +3 -3
- package/scripts/webrtc-test.sh +401 -0
- package/src/index.ts +378 -568
- package/src/peripheral-twin.ts +337 -0
- package/src/services/phyhub-connection.service.ts +24 -0
- package/src/services/phyhub-direct-connection.service.ts +159 -0
- package/src/services/webrtc/data-channel-handler.ts +362 -0
- package/src/services/webrtc/index.ts +36 -0
- package/src/services/webrtc/media-stream-handler.ts +536 -0
- package/src/services/webrtc/peer-connection-manager.ts +467 -0
- package/src/services/webrtc/types.ts +273 -0
- package/src/services/webrtc/webrtc-globals.ts +108 -0
- package/src/services/webrtc/webrtc-manager.ts +490 -0
- package/src/test/communication-comprehensive-test.ts +533 -0
- package/src/test/webrtc-channel-names-test.ts +266 -0
- package/src/test/webrtc-comprehensive-test.ts +494 -0
- package/src/test/webrtc-reconnect-test.ts +345 -0
- package/src/test/webrtc-test-harness.ts +254 -0
- package/src/twin-messaging.ts +184 -0
- package/src/twin-registry.ts +39 -0
- package/src/types/index.ts +3 -0
- package/src/types/twin.types.ts +80 -14
- package/dist/services/webrtc/datachannel.d.ts +0 -10
- package/dist/services/webrtc/datachannel.d.ts.map +0 -1
- package/dist/services/webrtc/datachannel.js +0 -290
- package/dist/services/webrtc/datachannel.js.map +0 -1
- package/dist/services/webrtc/mediastream.d.ts +0 -10
- package/dist/services/webrtc/mediastream.d.ts.map +0 -1
- package/dist/services/webrtc/mediastream.js +0 -396
- package/dist/services/webrtc/mediastream.js.map +0 -1
- package/dist/services/webrtc/peer-connection-ice.d.ts +0 -32
- package/dist/services/webrtc/peer-connection-ice.d.ts.map +0 -1
- package/dist/services/webrtc/peer-connection-ice.js +0 -483
- package/dist/services/webrtc/peer-connection-ice.js.map +0 -1
- package/src/services/webrtc/datachannel.ts +0 -421
- package/src/services/webrtc/mediastream.ts +0 -602
- package/src/services/webrtc/peer-connection-ice.ts +0 -689
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { EventPayload, PhygridDataChannel, PhygridMediaStream, PhyHubClient } from ".";
|
|
2
|
+
import { Instance, TwinMessageResult, TwinResponse, TwinTypeEnum } from "./types/twin.types";
|
|
3
|
+
import { createTwinMessaging, TwinMessagingMethods } from "./twin-messaging";
|
|
4
|
+
|
|
5
|
+
export class PeripheralTwinInstance {
|
|
6
|
+
public phyHubClient: PhyHubClient;
|
|
7
|
+
public twinId: string;
|
|
8
|
+
public edgeInstance: Instance | null = null;
|
|
9
|
+
public peripheralTwinResponse: TwinResponse | null = null;
|
|
10
|
+
|
|
11
|
+
private messaging: TwinMessagingMethods | null = null;
|
|
12
|
+
|
|
13
|
+
constructor(phyHubClient: PhyHubClient, twinId: string) {
|
|
14
|
+
this.phyHubClient = phyHubClient;
|
|
15
|
+
this.twinId = twinId;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// initialize the instance by performing sanity checks
|
|
19
|
+
async initialize() {
|
|
20
|
+
const edgeInstance = await this.phyHubClient.getInstance();
|
|
21
|
+
if (!edgeInstance) {
|
|
22
|
+
console.error('Edge instance not found');
|
|
23
|
+
throw new Error('Edge instance not found');
|
|
24
|
+
}
|
|
25
|
+
this.edgeInstance = edgeInstance;
|
|
26
|
+
|
|
27
|
+
const peripheralTwinResponse = await this.phyHubClient.getTwinById(this.twinId);
|
|
28
|
+
if (!peripheralTwinResponse) {
|
|
29
|
+
console.error(`Peripheral twin with id ${this.twinId} not found`);
|
|
30
|
+
throw new Error(`Peripheral twin with id ${this.twinId} not found`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (peripheralTwinResponse.type !== TwinTypeEnum.Peripheral) {
|
|
34
|
+
console.error(`Twin ${this.twinId} is not a peripheral twin`);
|
|
35
|
+
throw new Error(`Twin ${this.twinId} is not a peripheral twin`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.peripheralTwinResponse = peripheralTwinResponse;
|
|
39
|
+
|
|
40
|
+
// Create messaging methods using the shared factory
|
|
41
|
+
this.messaging = createTwinMessaging({
|
|
42
|
+
sendEvent: (targetTwinId, payload) => this.phyHubClient.sendEvent(targetTwinId, payload),
|
|
43
|
+
onTwinMessage: (twinId, callback) => this.phyHubClient.onTwinMessage(twinId, callback),
|
|
44
|
+
twinId: peripheralTwinResponse.id,
|
|
45
|
+
deviceId: edgeInstance.deviceId,
|
|
46
|
+
typePrefix: `peripheralInstance:${peripheralTwinResponse.id}`,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await this.phyHubClient.subscribeTwin(this.twinId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private ensureInitialized(): void {
|
|
53
|
+
if (!this.messaging) {
|
|
54
|
+
throw new Error('[PeripheralTwinInstance] Not initialized. Call initialize() first.');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Send a message to this peripheral.
|
|
60
|
+
* - Without callback: Fire-and-forget (returns void)
|
|
61
|
+
* - With callback: Request-response pattern (returns Promise)
|
|
62
|
+
*
|
|
63
|
+
* Note: Peripheral emit is targeted (not broadcast), so callbacks are supported.
|
|
64
|
+
* Internally uses to(twinId).emit() for request-response pattern.
|
|
65
|
+
*/
|
|
66
|
+
emit(eventType: string, payload: any): void;
|
|
67
|
+
emit(eventType: string, payload: any, callback: (response: TwinMessageResult) => void): Promise<TwinMessageResult>;
|
|
68
|
+
emit(
|
|
69
|
+
eventType: string,
|
|
70
|
+
payload: any,
|
|
71
|
+
callback?: (response: TwinMessageResult) => void
|
|
72
|
+
): void | Promise<TwinMessageResult> {
|
|
73
|
+
this.ensureInitialized();
|
|
74
|
+
if (callback) {
|
|
75
|
+
// Use to().emit() for request-response since peripheral communication is targeted
|
|
76
|
+
return this.messaging!.to(this.twinId).emit(eventType, payload, callback);
|
|
77
|
+
} else {
|
|
78
|
+
this.messaging!.emit(eventType, payload);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Listen for events, with optional respond callback for request-response
|
|
84
|
+
*/
|
|
85
|
+
on(
|
|
86
|
+
eventType: string,
|
|
87
|
+
callback: (message: any, respond?: (result: TwinMessageResult) => void) => void
|
|
88
|
+
) {
|
|
89
|
+
this.ensureInitialized();
|
|
90
|
+
this.messaging!.on(eventType, callback);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
to(targetTwinId: string) {
|
|
94
|
+
this.ensureInitialized();
|
|
95
|
+
return this.messaging!.to(targetTwinId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async getDataChannel() {
|
|
99
|
+
if (!this.peripheralTwinResponse) {
|
|
100
|
+
console.error('Object is not initialized correctly');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return await this.phyHubClient.getDataChannel(this.peripheralTwinResponse.id);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
async onDataChannel(callback: (dc: PhygridDataChannel) => void) {
|
|
108
|
+
if (!this.peripheralTwinResponse) {
|
|
109
|
+
console.error('Object is not initialized correctly');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await this.phyHubClient.onDataChannel(this.peripheralTwinResponse.id, callback);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
async getMediaStream() {
|
|
117
|
+
if (!this.peripheralTwinResponse) {
|
|
118
|
+
console.error('Object is not initialized correctly');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return await this.phyHubClient.getMediaStream(this.peripheralTwinResponse.id);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
async onMediaStream(callback: (stream: PhygridMediaStream) => void): Promise<void> {
|
|
126
|
+
if (!this.peripheralTwinResponse) {
|
|
127
|
+
throw new Error('Peripheral instance not initialized');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await this.phyHubClient.onMediaStream(this.peripheralTwinResponse.id, callback);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
async updateReported(properties: Record<string, any>) {
|
|
134
|
+
if (!this.peripheralTwinResponse) {
|
|
135
|
+
throw new Error('Peripheral instance not initialized');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const newReported = {
|
|
139
|
+
...properties,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = await this.phyHubClient.updateReportedProperties(this.peripheralTwinResponse.id, newReported);
|
|
143
|
+
|
|
144
|
+
this.peripheralTwinResponse = Object.assign({}, this.peripheralTwinResponse, result);
|
|
145
|
+
|
|
146
|
+
return result;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
onUpdateReported(callback: (reportedProperties: Record<string, any>) => void) {
|
|
150
|
+
if (!this.peripheralTwinResponse) {
|
|
151
|
+
throw new Error('Peripheral instance not initialized');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let previousProperties: Record<string, any> | undefined = undefined;
|
|
155
|
+
|
|
156
|
+
console.log(`[onUpdateReported] Registering handler for all reported properties`);
|
|
157
|
+
|
|
158
|
+
this.phyHubClient.onTwinUpdate(this.peripheralTwinResponse.id, (twin: TwinResponse) => {
|
|
159
|
+
console.log(`[onUpdateReported] Twin update received for ${this.peripheralTwinResponse!.id}`);
|
|
160
|
+
|
|
161
|
+
// Only process updates for this specific peripheral
|
|
162
|
+
if (twin.id !== this.peripheralTwinResponse!.id) {
|
|
163
|
+
console.log(`[onUpdateReported] Twin ID mismatch: ${twin.id} !== ${this.peripheralTwinResponse!.id}`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const reported = twin.properties.reported as Record<string, any>;
|
|
168
|
+
|
|
169
|
+
// Check if reported properties exist
|
|
170
|
+
if (reported) {
|
|
171
|
+
// If this is the first update, always trigger
|
|
172
|
+
if (previousProperties === undefined) {
|
|
173
|
+
console.log(`[onUpdateReported] First update - calling callback`);
|
|
174
|
+
previousProperties = JSON.parse(JSON.stringify(reported)); // Deep copy
|
|
175
|
+
callback(reported);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const currentPropertiesStr = JSON.stringify(reported);
|
|
180
|
+
const previousPropertiesStr = JSON.stringify(previousProperties);
|
|
181
|
+
|
|
182
|
+
// Debug by printing parts of the strings - last 100 chars to avoid excessive logs
|
|
183
|
+
console.log(
|
|
184
|
+
`[onUpdateReported] Current (end): ...${currentPropertiesStr.substring(currentPropertiesStr.length - 100)}`
|
|
185
|
+
);
|
|
186
|
+
console.log(
|
|
187
|
+
`[onUpdateReported] Previous (end): ...${previousPropertiesStr.substring(previousPropertiesStr.length - 100)}`
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Additional check - compare string lengths first
|
|
191
|
+
if (currentPropertiesStr.length !== previousPropertiesStr.length) {
|
|
192
|
+
console.log(
|
|
193
|
+
`[onUpdateReported] String length changed: ${currentPropertiesStr.length} vs ${previousPropertiesStr.length}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const hasChanged = currentPropertiesStr !== previousPropertiesStr;
|
|
198
|
+
|
|
199
|
+
console.log(
|
|
200
|
+
`[onUpdateReported] Properties comparison:`,
|
|
201
|
+
hasChanged ? 'CHANGED' : 'UNCHANGED'
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
if (hasChanged) {
|
|
205
|
+
previousProperties = JSON.parse(currentPropertiesStr); // Deep copy
|
|
206
|
+
console.log(`[onUpdateReported] Calling callback with updated reported properties`);
|
|
207
|
+
callback(reported);
|
|
208
|
+
} else {
|
|
209
|
+
// Force a callback call at least every 5 updates to ensure clients get updates
|
|
210
|
+
// This is a safety mechanism
|
|
211
|
+
console.log(`[onUpdateReported] No change detected in properties`);
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
console.log(`[onUpdateReported] No reported properties found`);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
async updateDesired(properties: Record<string, any>): Promise<TwinResponse>{
|
|
220
|
+
if (!this.peripheralTwinResponse) {
|
|
221
|
+
throw new Error('Peripheral instance not initialized');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const payload: EventPayload = {
|
|
225
|
+
twinId: this.peripheralTwinResponse.id,
|
|
226
|
+
data: properties,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const phyClientSocket = this.phyHubClient.getSocket();
|
|
230
|
+
|
|
231
|
+
return new Promise((resolve, reject) => {
|
|
232
|
+
this.phyHubClient.emit('updatePeripheralTwinDesired', payload, (response: any) => {
|
|
233
|
+
const { twin } = response;
|
|
234
|
+
resolve(twin || {});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
phyClientSocket?.on('error', (error: any) => {
|
|
238
|
+
reject(error);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
onUpdateDesired(callback: (desiredProperties: Record<string, any>) => void) {
|
|
244
|
+
if (!this.peripheralTwinResponse) {
|
|
245
|
+
throw new Error('Peripheral instance not initialized');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
let previousProperties: Record<string, any> | undefined = undefined;
|
|
249
|
+
|
|
250
|
+
console.log(`[onUpdateDesired] Registering handler for all desired properties`);
|
|
251
|
+
|
|
252
|
+
this.phyHubClient.onTwinUpdate(this.peripheralTwinResponse.id, (twin: TwinResponse) => {
|
|
253
|
+
console.log(`[onUpdateDesired] Twin update received for ${this.peripheralTwinResponse!.id}`);
|
|
254
|
+
|
|
255
|
+
// Only process updates for this specific peripheral
|
|
256
|
+
if (twin.id !== this.peripheralTwinResponse!.id) {
|
|
257
|
+
console.log(`[onUpdateDesired] Twin ID mismatch: ${twin.id} !== ${this.peripheralTwinResponse!.id}`);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const desired = twin.properties.desired as Record<string, any>;
|
|
262
|
+
|
|
263
|
+
// Check if desired properties exist
|
|
264
|
+
if (desired) {
|
|
265
|
+
// If this is the first update, always trigger
|
|
266
|
+
if (previousProperties === undefined) {
|
|
267
|
+
console.log(`[onUpdateDesired] First update - calling callback`);
|
|
268
|
+
previousProperties = JSON.parse(JSON.stringify(desired)); // Deep copy
|
|
269
|
+
callback(desired);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const currentPropertiesStr = JSON.stringify(desired);
|
|
274
|
+
const previousPropertiesStr = JSON.stringify(previousProperties);
|
|
275
|
+
|
|
276
|
+
// Debug by printing parts of the strings - last 100 chars to avoid excessive logs
|
|
277
|
+
console.log(
|
|
278
|
+
`[onUpdateDesired] Current (end): ...${currentPropertiesStr.substring(currentPropertiesStr.length - 100)}`
|
|
279
|
+
);
|
|
280
|
+
console.log(
|
|
281
|
+
`[onUpdateDesired] Previous (end): ...${previousPropertiesStr.substring(previousPropertiesStr.length - 100)}`
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// Additional check - compare string lengths first
|
|
285
|
+
if (currentPropertiesStr.length !== previousPropertiesStr.length) {
|
|
286
|
+
console.log(
|
|
287
|
+
`[onUpdateDesired] String length changed: ${currentPropertiesStr.length} vs ${previousPropertiesStr.length}`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const hasChanged = currentPropertiesStr !== previousPropertiesStr;
|
|
292
|
+
|
|
293
|
+
console.log(
|
|
294
|
+
`[onUpdateDesired] Properties comparison:`,
|
|
295
|
+
hasChanged ? 'CHANGED' : 'UNCHANGED'
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
if (hasChanged) {
|
|
299
|
+
previousProperties = JSON.parse(currentPropertiesStr); // Deep copy
|
|
300
|
+
console.log(`[onUpdateDesired] Calling callback with updated desired properties`);
|
|
301
|
+
callback(desired);
|
|
302
|
+
} else {
|
|
303
|
+
// Force a callback call at least every 5 updates to ensure clients get updates
|
|
304
|
+
// This is a safety mechanism
|
|
305
|
+
console.log(`[onUpdateDesired] No change detected in properties`);
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
console.log(`[onUpdateDesired] No desired properties found`);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
async remove() : Promise<TwinResponse | void> {
|
|
314
|
+
if (!this.peripheralTwinResponse) {
|
|
315
|
+
throw new Error('Peripheral instance not initialized');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const payload: EventPayload = {
|
|
319
|
+
data: { twinId: this.peripheralTwinResponse.id },
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const phyClientSocket = this.phyHubClient.getSocket();
|
|
323
|
+
|
|
324
|
+
return new Promise((resolve, reject) => {
|
|
325
|
+
this.phyHubClient.emit('deletePeripheralTwin', payload, (response: any) => {
|
|
326
|
+
const { twin } = response;
|
|
327
|
+
resolve(twin);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
phyClientSocket?.on('error', (error: any) => {
|
|
331
|
+
reject(error);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
}
|
|
337
|
+
|
|
@@ -2,6 +2,8 @@ import { Socket } from 'socket.io-client';
|
|
|
2
2
|
import { get } from 'lodash';
|
|
3
3
|
import { isBrowser } from '../helpers/browser.helper';
|
|
4
4
|
import { getSocketIOWithProxy } from '@phystack/socket.io-proxy';
|
|
5
|
+
import { PhyHubDirectConnection } from './phyhub-direct-connection.service';
|
|
6
|
+
|
|
5
7
|
interface UrlConfig {
|
|
6
8
|
url: string;
|
|
7
9
|
timeout: number;
|
|
@@ -14,12 +16,26 @@ export class PhyHubConnection {
|
|
|
14
16
|
private socketUrls: UrlConfig[] = [];
|
|
15
17
|
private instanceId: string | undefined;
|
|
16
18
|
private moduleName: string | undefined;
|
|
19
|
+
private directConnection: PhyHubDirectConnection | null = null;
|
|
17
20
|
|
|
18
21
|
private constructor(
|
|
19
22
|
params: { instanceId?: string; moduleName?: string; dataResidency?: string } = {}
|
|
20
23
|
) {
|
|
21
24
|
this.instanceId = params.instanceId;
|
|
22
25
|
this.moduleName = params.moduleName;
|
|
26
|
+
|
|
27
|
+
// Check for direct connection mode (for testing)
|
|
28
|
+
if (PhyHubDirectConnection.isEnabled()) {
|
|
29
|
+
this.directConnection = PhyHubDirectConnection.fromEnv();
|
|
30
|
+
if (this.directConnection) {
|
|
31
|
+
console.log('[PhyHubConnection] Direct connection mode enabled');
|
|
32
|
+
// Use device ID as instance ID if not provided
|
|
33
|
+
if (!this.instanceId) {
|
|
34
|
+
this.instanceId = this.directConnection.getInstanceId();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
this.setSocketUrls(params.dataResidency);
|
|
24
40
|
this.fetchPhygridSocketInstance();
|
|
25
41
|
}
|
|
@@ -137,6 +153,14 @@ export class PhyHubConnection {
|
|
|
137
153
|
public async getPhyHubSocket(): Promise<Socket> {
|
|
138
154
|
console.info(`getPhyHubSocket(): Getting phyhub socket`);
|
|
139
155
|
|
|
156
|
+
// Use direct connection if enabled (for testing)
|
|
157
|
+
if (this.directConnection) {
|
|
158
|
+
console.log('[PhyHubConnection] Using direct connection');
|
|
159
|
+
const socket = await this.directConnection.connect();
|
|
160
|
+
this.phygridSocketSingleton = socket;
|
|
161
|
+
return socket;
|
|
162
|
+
}
|
|
163
|
+
|
|
140
164
|
// Always check top window first
|
|
141
165
|
if (isBrowser) {
|
|
142
166
|
const topWindow = this.getTopWindow();
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhyHub Direct Connection Service
|
|
3
|
+
*
|
|
4
|
+
* Allows direct connection to PhyHub for testing purposes,
|
|
5
|
+
* bypassing the phyos layer that normally runs on devices.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* - Set PHYHUB_DIRECT=true to enable direct connection
|
|
9
|
+
* - Provide DEVICE_ID and ACCESS_KEY environment variables
|
|
10
|
+
* - Optionally set PHYHUB_URL (defaults to EU region)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Socket } from 'socket.io-client';
|
|
14
|
+
import { getSocketIOWithProxy } from '@phystack/socket.io-proxy';
|
|
15
|
+
|
|
16
|
+
export interface DirectConnectionConfig {
|
|
17
|
+
deviceId: string;
|
|
18
|
+
accessKey: string;
|
|
19
|
+
phyhubUrl?: string;
|
|
20
|
+
instanceId?: string; // Optional: override the twin ID to use
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const DEFAULT_PHYHUB_URLS: Record<string, string> = {
|
|
24
|
+
eu: 'https://phyhub.eu.omborigrid.net',
|
|
25
|
+
us: 'https://phyhub.us.omborigrid.net',
|
|
26
|
+
uae: 'https://phyhub.uae.omborigrid.net',
|
|
27
|
+
au: 'https://phyhub.au.omborigrid.net',
|
|
28
|
+
in: 'https://phyhub.in.omborigrid.net',
|
|
29
|
+
qa: 'https://phyhub.qa.omborigrid.net',
|
|
30
|
+
dev: 'https://phyhub.dev.omborigrid.net',
|
|
31
|
+
local: 'http://localhost:3000',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export class PhyHubDirectConnection {
|
|
35
|
+
private socket: Socket | null = null;
|
|
36
|
+
private config: DirectConnectionConfig;
|
|
37
|
+
|
|
38
|
+
constructor(config: DirectConnectionConfig) {
|
|
39
|
+
this.config = {
|
|
40
|
+
...config,
|
|
41
|
+
phyhubUrl: config.phyhubUrl || DEFAULT_PHYHUB_URLS.eu,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create a direct connection from environment variables
|
|
47
|
+
* @throws Error if required environment variables are missing
|
|
48
|
+
*/
|
|
49
|
+
static fromEnv(): PhyHubDirectConnection {
|
|
50
|
+
const deviceId = process.env.DEVICE_ID || process.env.PHYGRID_DEVICE_ID;
|
|
51
|
+
const accessKey = process.env.ACCESS_KEY || process.env.PHYGRID_DEVICE_KEY;
|
|
52
|
+
|
|
53
|
+
if (!deviceId) {
|
|
54
|
+
throw new Error('[PhyHubDirectConnection] DEVICE_ID or PHYGRID_DEVICE_ID env var required');
|
|
55
|
+
}
|
|
56
|
+
if (!accessKey) {
|
|
57
|
+
throw new Error('[PhyHubDirectConnection] ACCESS_KEY or PHYGRID_DEVICE_KEY env var required');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const region = process.env.PHYHUB_REGION?.toLowerCase();
|
|
61
|
+
const phyhubUrl = process.env.PHYHUB_URL || (region ? DEFAULT_PHYHUB_URLS[region] : undefined);
|
|
62
|
+
|
|
63
|
+
if (!phyhubUrl) {
|
|
64
|
+
throw new Error(`[PhyHubDirectConnection] PHYHUB_URL or valid PHYHUB_REGION required. Valid regions: ${Object.keys(DEFAULT_PHYHUB_URLS).join(', ')}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return new PhyHubDirectConnection({
|
|
68
|
+
deviceId,
|
|
69
|
+
accessKey,
|
|
70
|
+
phyhubUrl,
|
|
71
|
+
instanceId: process.env.TWIN_ID || process.env.INSTANCE_ID,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if direct connection mode is enabled
|
|
77
|
+
*/
|
|
78
|
+
static isEnabled(): boolean {
|
|
79
|
+
return process.env.PHYHUB_DIRECT === 'true' || process.env.PHYHUB_DIRECT === '1';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get the device ID (also used as the primary twin ID)
|
|
84
|
+
*/
|
|
85
|
+
getDeviceId(): string {
|
|
86
|
+
return this.config.deviceId;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get the instance/twin ID to use
|
|
91
|
+
*/
|
|
92
|
+
getInstanceId(): string {
|
|
93
|
+
return this.config.instanceId || this.config.deviceId;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Connect directly to PhyHub
|
|
98
|
+
*/
|
|
99
|
+
async connect(): Promise<Socket> {
|
|
100
|
+
if (this.socket?.connected) {
|
|
101
|
+
return this.socket;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const { deviceId, accessKey, phyhubUrl } = this.config;
|
|
105
|
+
|
|
106
|
+
console.log(`[PhyHubDirectConnection] Connecting to ${phyhubUrl}`);
|
|
107
|
+
console.log(`[PhyHubDirectConnection] Device ID: ${deviceId}`);
|
|
108
|
+
|
|
109
|
+
this.socket = await getSocketIOWithProxy(phyhubUrl!, {
|
|
110
|
+
auth: {
|
|
111
|
+
deviceId,
|
|
112
|
+
accessKey,
|
|
113
|
+
},
|
|
114
|
+
reconnection: true,
|
|
115
|
+
reconnectionAttempts: 5,
|
|
116
|
+
reconnectionDelay: 1000,
|
|
117
|
+
timeout: 10000,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await new Promise<void>((resolve, reject) => {
|
|
121
|
+
const timeout = setTimeout(() => {
|
|
122
|
+
reject(new Error('Connection timeout'));
|
|
123
|
+
}, 15000);
|
|
124
|
+
|
|
125
|
+
this.socket!.on('connect', () => {
|
|
126
|
+
clearTimeout(timeout);
|
|
127
|
+
console.log(`[PhyHubDirectConnection] Connected successfully`);
|
|
128
|
+
resolve();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
this.socket!.on('connect_error', (error) => {
|
|
132
|
+
clearTimeout(timeout);
|
|
133
|
+
console.error(`[PhyHubDirectConnection] Connection error:`, error.message);
|
|
134
|
+
reject(error);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return this.socket;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Disconnect from PhyHub
|
|
143
|
+
*/
|
|
144
|
+
disconnect(): void {
|
|
145
|
+
if (this.socket) {
|
|
146
|
+
this.socket.disconnect();
|
|
147
|
+
this.socket = null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get the socket instance
|
|
153
|
+
*/
|
|
154
|
+
getSocket(): Socket | null {
|
|
155
|
+
return this.socket;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default PhyHubDirectConnection;
|