@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.
Files changed (119) hide show
  1. package/dist/index.d.ts +22 -28
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +252 -378
  4. package/dist/index.js.map +1 -1
  5. package/dist/peripheral-twin.d.ts +34 -0
  6. package/dist/peripheral-twin.d.ts.map +1 -0
  7. package/dist/peripheral-twin.js +234 -0
  8. package/dist/peripheral-twin.js.map +1 -0
  9. package/dist/services/phyhub-connection.service.d.ts +1 -0
  10. package/dist/services/phyhub-connection.service.d.ts.map +1 -1
  11. package/dist/services/phyhub-connection.service.js +17 -0
  12. package/dist/services/phyhub-connection.service.js.map +1 -1
  13. package/dist/services/phyhub-direct-connection.service.d.ts +21 -0
  14. package/dist/services/phyhub-direct-connection.service.d.ts.map +1 -0
  15. package/dist/services/phyhub-direct-connection.service.js +101 -0
  16. package/dist/services/phyhub-direct-connection.service.js.map +1 -0
  17. package/dist/services/webrtc/data-channel-handler.d.ts +45 -0
  18. package/dist/services/webrtc/data-channel-handler.d.ts.map +1 -0
  19. package/dist/services/webrtc/data-channel-handler.js +260 -0
  20. package/dist/services/webrtc/data-channel-handler.js.map +1 -0
  21. package/dist/services/webrtc/index.d.ts +8 -0
  22. package/dist/services/webrtc/index.d.ts.map +1 -0
  23. package/dist/services/webrtc/index.js +18 -0
  24. package/dist/services/webrtc/index.js.map +1 -0
  25. package/dist/services/webrtc/media-stream-handler.d.ts +57 -0
  26. package/dist/services/webrtc/media-stream-handler.d.ts.map +1 -0
  27. package/dist/services/webrtc/media-stream-handler.js +383 -0
  28. package/dist/services/webrtc/media-stream-handler.js.map +1 -0
  29. package/dist/services/webrtc/peer-connection-manager.d.ts +40 -0
  30. package/dist/services/webrtc/peer-connection-manager.d.ts.map +1 -0
  31. package/dist/services/webrtc/peer-connection-manager.js +336 -0
  32. package/dist/services/webrtc/peer-connection-manager.js.map +1 -0
  33. package/dist/services/webrtc/types.d.ts +134 -0
  34. package/dist/services/webrtc/types.d.ts.map +1 -0
  35. package/dist/services/webrtc/types.js +12 -0
  36. package/dist/services/webrtc/types.js.map +1 -0
  37. package/dist/services/webrtc/webrtc-globals.d.ts +4 -0
  38. package/dist/services/webrtc/webrtc-globals.d.ts.map +1 -0
  39. package/dist/services/webrtc/webrtc-globals.js +72 -0
  40. package/dist/services/webrtc/webrtc-globals.js.map +1 -0
  41. package/dist/services/webrtc/webrtc-manager.d.ts +35 -0
  42. package/dist/services/webrtc/webrtc-manager.d.ts.map +1 -0
  43. package/dist/services/webrtc/webrtc-manager.js +274 -0
  44. package/dist/services/webrtc/webrtc-manager.js.map +1 -0
  45. package/dist/test/communication-comprehensive-test.d.ts +8 -0
  46. package/dist/test/communication-comprehensive-test.d.ts.map +1 -0
  47. package/dist/test/communication-comprehensive-test.js +356 -0
  48. package/dist/test/communication-comprehensive-test.js.map +1 -0
  49. package/dist/test/webrtc-channel-names-test.d.ts +2 -0
  50. package/dist/test/webrtc-channel-names-test.d.ts.map +1 -0
  51. package/dist/test/webrtc-channel-names-test.js +177 -0
  52. package/dist/test/webrtc-channel-names-test.js.map +1 -0
  53. package/dist/test/webrtc-comprehensive-test.d.ts +2 -0
  54. package/dist/test/webrtc-comprehensive-test.d.ts.map +1 -0
  55. package/dist/test/webrtc-comprehensive-test.js +328 -0
  56. package/dist/test/webrtc-comprehensive-test.js.map +1 -0
  57. package/dist/test/webrtc-reconnect-test.d.ts +4 -0
  58. package/dist/test/webrtc-reconnect-test.d.ts.map +1 -0
  59. package/dist/test/webrtc-reconnect-test.js +244 -0
  60. package/dist/test/webrtc-reconnect-test.js.map +1 -0
  61. package/dist/test/webrtc-test-harness.d.ts +4 -0
  62. package/dist/test/webrtc-test-harness.d.ts.map +1 -0
  63. package/dist/test/webrtc-test-harness.js +169 -0
  64. package/dist/test/webrtc-test-harness.js.map +1 -0
  65. package/dist/twin-messaging.d.ts +20 -0
  66. package/dist/twin-messaging.d.ts.map +1 -0
  67. package/dist/twin-messaging.js +94 -0
  68. package/dist/twin-messaging.js.map +1 -0
  69. package/dist/twin-registry.d.ts +9 -0
  70. package/dist/twin-registry.d.ts.map +1 -0
  71. package/dist/twin-registry.js +26 -0
  72. package/dist/twin-registry.js.map +1 -0
  73. package/dist/types/index.d.ts +4 -0
  74. package/dist/types/index.d.ts.map +1 -0
  75. package/dist/types/index.js +20 -0
  76. package/dist/types/index.js.map +1 -0
  77. package/dist/types/twin.types.d.ts +62 -14
  78. package/dist/types/twin.types.d.ts.map +1 -1
  79. package/dist/types/twin.types.js +8 -1
  80. package/dist/types/twin.types.js.map +1 -1
  81. package/docs/webrtc-howto.md +398 -0
  82. package/docs/webrtc-test.md +330 -0
  83. package/package.json +3 -3
  84. package/scripts/webrtc-test.sh +401 -0
  85. package/src/index.ts +378 -568
  86. package/src/peripheral-twin.ts +337 -0
  87. package/src/services/phyhub-connection.service.ts +24 -0
  88. package/src/services/phyhub-direct-connection.service.ts +159 -0
  89. package/src/services/webrtc/data-channel-handler.ts +362 -0
  90. package/src/services/webrtc/index.ts +36 -0
  91. package/src/services/webrtc/media-stream-handler.ts +536 -0
  92. package/src/services/webrtc/peer-connection-manager.ts +467 -0
  93. package/src/services/webrtc/types.ts +273 -0
  94. package/src/services/webrtc/webrtc-globals.ts +108 -0
  95. package/src/services/webrtc/webrtc-manager.ts +490 -0
  96. package/src/test/communication-comprehensive-test.ts +533 -0
  97. package/src/test/webrtc-channel-names-test.ts +266 -0
  98. package/src/test/webrtc-comprehensive-test.ts +494 -0
  99. package/src/test/webrtc-reconnect-test.ts +345 -0
  100. package/src/test/webrtc-test-harness.ts +254 -0
  101. package/src/twin-messaging.ts +184 -0
  102. package/src/twin-registry.ts +39 -0
  103. package/src/types/index.ts +3 -0
  104. package/src/types/twin.types.ts +80 -14
  105. package/dist/services/webrtc/datachannel.d.ts +0 -10
  106. package/dist/services/webrtc/datachannel.d.ts.map +0 -1
  107. package/dist/services/webrtc/datachannel.js +0 -290
  108. package/dist/services/webrtc/datachannel.js.map +0 -1
  109. package/dist/services/webrtc/mediastream.d.ts +0 -10
  110. package/dist/services/webrtc/mediastream.d.ts.map +0 -1
  111. package/dist/services/webrtc/mediastream.js +0 -396
  112. package/dist/services/webrtc/mediastream.js.map +0 -1
  113. package/dist/services/webrtc/peer-connection-ice.d.ts +0 -32
  114. package/dist/services/webrtc/peer-connection-ice.d.ts.map +0 -1
  115. package/dist/services/webrtc/peer-connection-ice.js +0 -483
  116. package/dist/services/webrtc/peer-connection-ice.js.map +0 -1
  117. package/src/services/webrtc/datachannel.ts +0 -421
  118. package/src/services/webrtc/mediastream.ts +0 -602
  119. 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;