@phystack/hub-client 4.4.52 → 4.4.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +268 -365
  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 +367 -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 +335 -0
  32. package/dist/services/webrtc/peer-connection-manager.js.map +1 -0
  33. package/dist/services/webrtc/types.d.ts +133 -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 +23 -0
  66. package/dist/twin-messaging.d.ts.map +1 -0
  67. package/dist/twin-messaging.js +91 -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 +66 -15
  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 +399 -540
  86. package/src/peripheral-twin.ts +333 -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 +515 -0
  92. package/src/services/webrtc/peer-connection-manager.ts +463 -0
  93. package/src/services/webrtc/types.ts +270 -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 +188 -0
  102. package/src/twin-registry.ts +39 -0
  103. package/src/types/index.ts +3 -0
  104. package/src/types/twin.types.ts +87 -15
  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,333 @@
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 the twin.
60
+ * - Without callback: Fire-and-forget (returns void)
61
+ * - With callback: Request-response pattern (returns Promise)
62
+ */
63
+ emit(eventType: string, payload: any): void;
64
+ emit(eventType: string, payload: any, callback: (response: TwinMessageResult) => void): Promise<TwinMessageResult>;
65
+ emit(
66
+ eventType: string,
67
+ payload: any,
68
+ callback?: (response: TwinMessageResult) => void
69
+ ): void | Promise<TwinMessageResult> {
70
+ this.ensureInitialized();
71
+ if (callback) {
72
+ return this.messaging!.emit(eventType, payload, callback);
73
+ } else {
74
+ this.messaging!.emit(eventType, payload);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Listen for events, with optional respond callback for request-response
80
+ */
81
+ on(
82
+ eventType: string,
83
+ callback: (message: any, respond?: (result: TwinMessageResult) => void) => void
84
+ ) {
85
+ this.ensureInitialized();
86
+ this.messaging!.on(eventType, callback);
87
+ }
88
+
89
+ to(targetTwinId: string) {
90
+ this.ensureInitialized();
91
+ return this.messaging!.to(targetTwinId);
92
+ }
93
+
94
+ async getDataChannel() {
95
+ if (!this.peripheralTwinResponse) {
96
+ console.error('Object is not initialized correctly');
97
+ return;
98
+ }
99
+
100
+ return await this.phyHubClient.getDataChannel(this.peripheralTwinResponse.id);
101
+ };
102
+
103
+ async onDataChannel(callback: (dc: PhygridDataChannel) => void) {
104
+ if (!this.peripheralTwinResponse) {
105
+ console.error('Object is not initialized correctly');
106
+ return;
107
+ }
108
+
109
+ await this.phyHubClient.onDataChannel(this.peripheralTwinResponse.id, callback);
110
+ };
111
+
112
+ async getMediaStream() {
113
+ if (!this.peripheralTwinResponse) {
114
+ console.error('Object is not initialized correctly');
115
+ return;
116
+ }
117
+
118
+ return await this.phyHubClient.getMediaStream(this.peripheralTwinResponse.id);
119
+ };
120
+
121
+ async onMediaStream(callback: (stream: PhygridMediaStream) => void): Promise<void> {
122
+ if (!this.peripheralTwinResponse) {
123
+ throw new Error('Peripheral instance not initialized');
124
+ }
125
+
126
+ await this.phyHubClient.onMediaStream(this.peripheralTwinResponse.id, callback);
127
+ };
128
+
129
+ async updateReported(properties: Record<string, any>) {
130
+ if (!this.peripheralTwinResponse) {
131
+ throw new Error('Peripheral instance not initialized');
132
+ }
133
+
134
+ const newReported = {
135
+ ...properties,
136
+ };
137
+
138
+ const result = await this.phyHubClient.updateReportedProperties(this.peripheralTwinResponse.id, newReported);
139
+
140
+ this.peripheralTwinResponse = Object.assign({}, this.peripheralTwinResponse, result);
141
+
142
+ return result;
143
+ };
144
+
145
+ onUpdateReported(callback: (reportedProperties: Record<string, any>) => void) {
146
+ if (!this.peripheralTwinResponse) {
147
+ throw new Error('Peripheral instance not initialized');
148
+ }
149
+
150
+ let previousProperties: Record<string, any> | undefined = undefined;
151
+
152
+ console.log(`[onUpdateReported] Registering handler for all reported properties`);
153
+
154
+ this.phyHubClient.onTwinUpdate(this.peripheralTwinResponse.id, (twin: TwinResponse) => {
155
+ console.log(`[onUpdateReported] Twin update received for ${this.peripheralTwinResponse!.id}`);
156
+
157
+ // Only process updates for this specific peripheral
158
+ if (twin.id !== this.peripheralTwinResponse!.id) {
159
+ console.log(`[onUpdateReported] Twin ID mismatch: ${twin.id} !== ${this.peripheralTwinResponse!.id}`);
160
+ return;
161
+ }
162
+
163
+ const reported = twin.properties.reported as Record<string, any>;
164
+
165
+ // Check if reported properties exist
166
+ if (reported) {
167
+ // If this is the first update, always trigger
168
+ if (previousProperties === undefined) {
169
+ console.log(`[onUpdateReported] First update - calling callback`);
170
+ previousProperties = JSON.parse(JSON.stringify(reported)); // Deep copy
171
+ callback(reported);
172
+ return;
173
+ }
174
+
175
+ const currentPropertiesStr = JSON.stringify(reported);
176
+ const previousPropertiesStr = JSON.stringify(previousProperties);
177
+
178
+ // Debug by printing parts of the strings - last 100 chars to avoid excessive logs
179
+ console.log(
180
+ `[onUpdateReported] Current (end): ...${currentPropertiesStr.substring(currentPropertiesStr.length - 100)}`
181
+ );
182
+ console.log(
183
+ `[onUpdateReported] Previous (end): ...${previousPropertiesStr.substring(previousPropertiesStr.length - 100)}`
184
+ );
185
+
186
+ // Additional check - compare string lengths first
187
+ if (currentPropertiesStr.length !== previousPropertiesStr.length) {
188
+ console.log(
189
+ `[onUpdateReported] String length changed: ${currentPropertiesStr.length} vs ${previousPropertiesStr.length}`
190
+ );
191
+ }
192
+
193
+ const hasChanged = currentPropertiesStr !== previousPropertiesStr;
194
+
195
+ console.log(
196
+ `[onUpdateReported] Properties comparison:`,
197
+ hasChanged ? 'CHANGED' : 'UNCHANGED'
198
+ );
199
+
200
+ if (hasChanged) {
201
+ previousProperties = JSON.parse(currentPropertiesStr); // Deep copy
202
+ console.log(`[onUpdateReported] Calling callback with updated reported properties`);
203
+ callback(reported);
204
+ } else {
205
+ // Force a callback call at least every 5 updates to ensure clients get updates
206
+ // This is a safety mechanism
207
+ console.log(`[onUpdateReported] No change detected in properties`);
208
+ }
209
+ } else {
210
+ console.log(`[onUpdateReported] No reported properties found`);
211
+ }
212
+ });
213
+ };
214
+
215
+ async updateDesired(properties: Record<string, any>): Promise<TwinResponse>{
216
+ if (!this.peripheralTwinResponse) {
217
+ throw new Error('Peripheral instance not initialized');
218
+ }
219
+
220
+ const payload: EventPayload = {
221
+ twinId: this.peripheralTwinResponse.id,
222
+ data: properties,
223
+ };
224
+
225
+ const phyClientSocket = this.phyHubClient.getSocket();
226
+
227
+ return new Promise((resolve, reject) => {
228
+ this.phyHubClient.emit('updatePeripheralTwinDesired', payload, (response: any) => {
229
+ const { twin } = response;
230
+ resolve(twin || {});
231
+ });
232
+
233
+ phyClientSocket?.on('error', (error: any) => {
234
+ reject(error);
235
+ });
236
+ });
237
+ };
238
+
239
+ onUpdateDesired(callback: (desiredProperties: Record<string, any>) => void) {
240
+ if (!this.peripheralTwinResponse) {
241
+ throw new Error('Peripheral instance not initialized');
242
+ }
243
+
244
+ let previousProperties: Record<string, any> | undefined = undefined;
245
+
246
+ console.log(`[onUpdateDesired] Registering handler for all desired properties`);
247
+
248
+ this.phyHubClient.onTwinUpdate(this.peripheralTwinResponse.id, (twin: TwinResponse) => {
249
+ console.log(`[onUpdateDesired] Twin update received for ${this.peripheralTwinResponse!.id}`);
250
+
251
+ // Only process updates for this specific peripheral
252
+ if (twin.id !== this.peripheralTwinResponse!.id) {
253
+ console.log(`[onUpdateDesired] Twin ID mismatch: ${twin.id} !== ${this.peripheralTwinResponse!.id}`);
254
+ return;
255
+ }
256
+
257
+ const desired = twin.properties.desired as Record<string, any>;
258
+
259
+ // Check if desired properties exist
260
+ if (desired) {
261
+ // If this is the first update, always trigger
262
+ if (previousProperties === undefined) {
263
+ console.log(`[onUpdateDesired] First update - calling callback`);
264
+ previousProperties = JSON.parse(JSON.stringify(desired)); // Deep copy
265
+ callback(desired);
266
+ return;
267
+ }
268
+
269
+ const currentPropertiesStr = JSON.stringify(desired);
270
+ const previousPropertiesStr = JSON.stringify(previousProperties);
271
+
272
+ // Debug by printing parts of the strings - last 100 chars to avoid excessive logs
273
+ console.log(
274
+ `[onUpdateDesired] Current (end): ...${currentPropertiesStr.substring(currentPropertiesStr.length - 100)}`
275
+ );
276
+ console.log(
277
+ `[onUpdateDesired] Previous (end): ...${previousPropertiesStr.substring(previousPropertiesStr.length - 100)}`
278
+ );
279
+
280
+ // Additional check - compare string lengths first
281
+ if (currentPropertiesStr.length !== previousPropertiesStr.length) {
282
+ console.log(
283
+ `[onUpdateDesired] String length changed: ${currentPropertiesStr.length} vs ${previousPropertiesStr.length}`
284
+ );
285
+ }
286
+
287
+ const hasChanged = currentPropertiesStr !== previousPropertiesStr;
288
+
289
+ console.log(
290
+ `[onUpdateDesired] Properties comparison:`,
291
+ hasChanged ? 'CHANGED' : 'UNCHANGED'
292
+ );
293
+
294
+ if (hasChanged) {
295
+ previousProperties = JSON.parse(currentPropertiesStr); // Deep copy
296
+ console.log(`[onUpdateDesired] Calling callback with updated desired properties`);
297
+ callback(desired);
298
+ } else {
299
+ // Force a callback call at least every 5 updates to ensure clients get updates
300
+ // This is a safety mechanism
301
+ console.log(`[onUpdateDesired] No change detected in properties`);
302
+ }
303
+ } else {
304
+ console.log(`[onUpdateDesired] No desired properties found`);
305
+ }
306
+ });
307
+ };
308
+
309
+ async remove() : Promise<TwinResponse | void> {
310
+ if (!this.peripheralTwinResponse) {
311
+ throw new Error('Peripheral instance not initialized');
312
+ }
313
+
314
+ const payload: EventPayload = {
315
+ data: { twinId: this.peripheralTwinResponse.id },
316
+ };
317
+
318
+ const phyClientSocket = this.phyHubClient.getSocket();
319
+
320
+ return new Promise((resolve, reject) => {
321
+ this.phyHubClient.emit('deletePeripheralTwin', payload, (response: any) => {
322
+ const { twin } = response;
323
+ resolve(twin);
324
+ });
325
+
326
+ phyClientSocket?.on('error', (error: any) => {
327
+ reject(error);
328
+ });
329
+ });
330
+ };
331
+
332
+ }
333
+
@@ -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;