@phystack/hub-client 4.4.51 → 4.4.53

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/src/index.ts CHANGED
@@ -7,9 +7,11 @@ import {
7
7
  EdgeTwinResponse,
8
8
  EdgeTwinDesiredPropertiesResponse,
9
9
  PeripheralTwinResponse,
10
- TwinTypeEnum,
11
10
  Instance,
12
11
  PeripheralInstance,
12
+ IPeripheralTwinInstance,
13
+ TwinMessageResult,
14
+ TwinMessageResultStatus,
13
15
  } from './types/twin.types';
14
16
  import {
15
17
  PhygridDataChannel,
@@ -27,8 +29,11 @@ import {
27
29
  createWebRTCMediaStreamConnection,
28
30
  WebRTCMediaStreamResult,
29
31
  } from './services/webrtc/mediastream';
32
+ import { TwinRegistry } from './twin-registry';
30
33
  // import { createWebRTCMediaStreamConnection, WebRTCMediaStreamResult } from './helpers/webrtc-mediastream';
31
34
 
35
+ export * from './types';
36
+
32
37
  // Define WebRTC types that might not be available in all environments
33
38
  declare global {
34
39
  // In browsers, these are standard WebRTC interfaces
@@ -91,7 +96,7 @@ declare global {
91
96
  }
92
97
  })().catch(err => console.error('Error in WebRTC globals initialization:', err));
93
98
 
94
- interface EventPayload<T = any> {
99
+ export interface EventPayload<T = any> {
95
100
  twinId?: string;
96
101
  sourceTwinId?: string;
97
102
  sourceDeviceId?: string;
@@ -115,8 +120,8 @@ export class PhyHubClient {
115
120
  private subscribedTwins: Set<string> = new Set();
116
121
  private twinMessageListeners: { [key: string]: Set<(message: any) => void> } = {};
117
122
  private instances: Map<string, Instance> = new Map();
118
- private peripheralInstances: Map<string, PeripheralInstance> = new Map();
119
123
  private twinUpdateListeners: { [key: string]: Set<(twin: TwinResponse) => void> } = {};
124
+ private twinInstancesRegistry: TwinRegistry | null = null;
120
125
 
121
126
  private readonly EVENTS = {
122
127
  PING: 'ping',
@@ -262,30 +267,36 @@ export class PhyHubClient {
262
267
  this.socketConnected = false;
263
268
  });
264
269
 
265
- this.socket.on(this.EVENTS.TWIN_MESSAGE, (payload: EventPayload) => {
270
+ this.socket.on(this.EVENTS.TWIN_MESSAGE, (payload: EventPayload, callback: (response: TwinMessageResult | null) => void) => {
266
271
  // console.log('Received TWIN_MESSAGE payload:', {
267
272
  // JSON: JSON.stringify(payload, null, 2),
268
273
  // });
269
274
 
275
+ let result: TwinMessageResult | null = null;
270
276
  if (payload.twinId && payload.data) {
271
277
  // console.log('Received TWIN_MESSAGE event:', {
272
278
  // hasListeners: !!this.twinMessageListeners[payload.twinId],
273
279
  // listenerCount: this.twinMessageListeners[payload.twinId]?.size
274
280
  // });
275
281
  const listeners = this.twinMessageListeners[payload.twinId];
276
- if (listeners) {
282
+ if (listeners?.size) {
277
283
  // console.log(`Executing ${listeners.size} listeners for twin ${payload.twinId}`);
278
- listeners.forEach(callback => {
279
- // console.log('Executing listener for twin:', callback);
284
+ listeners.forEach(listener => {
285
+ // console.log('Executing listener for twin:', listener);
280
286
  try {
281
- callback(payload);
287
+ listener(payload);
288
+ result = { status: TwinMessageResultStatus.Success, message: 'Action completed' };
282
289
  } catch (error) {
283
290
  console.error(`Error in twin message listener for ${payload.twinId}:`, error);
291
+ result = { status: TwinMessageResultStatus.Error, message: (error as Error)?.message || 'An error occurred' };
284
292
  }
285
293
  });
286
294
  } else {
287
- // console.log(`No listeners found for twin ${payload.twinId}`, JSON.stringify(payload, null, 2));
295
+ // even though there are no errors, still send acknowledgement to the socket server
296
+ result = { status: TwinMessageResultStatus.Warning, message: `No listeners found for twin ${payload.twinId} on this socket` };
288
297
  }
298
+
299
+ callback(result);
289
300
  } else {
290
301
  // console.log('Invalid twin message payload:', payload);
291
302
  }
@@ -336,6 +347,10 @@ export class PhyHubClient {
336
347
  if (!PhyHubClient.instance) {
337
348
  PhyHubClient.instance = new PhyHubClient(params);
338
349
  await PhyHubClient.instance.initializeConnection();
350
+
351
+ // create twin registry to store instances
352
+ PhyHubClient.instance.twinInstancesRegistry = new TwinRegistry(PhyHubClient.instance);
353
+
339
354
  console.info(`connect(): Connection to phyhub initialized`);
340
355
  }
341
356
  return PhyHubClient.instance;
@@ -707,303 +722,15 @@ export class PhyHubClient {
707
722
  }
708
723
 
709
724
  // TODO properties should automatically be updated when the edge twin is updated
710
- public async getPeripheralInstance(peripheralTwinId: string): Promise<PeripheralInstance> {
711
- if (this.peripheralInstances.has(peripheralTwinId)) {
712
- return this.peripheralInstances.get(peripheralTwinId)!;
725
+ /**
726
+ * provides instance of PeripheralTwinInstance class
727
+ */
728
+ public async getPeripheralInstance(peripheralTwinId: string): Promise<IPeripheralTwinInstance | null> {
729
+ if (!this.twinInstancesRegistry) {
730
+ throw new Error('PhyHubClient instance is not initialized yet');
713
731
  }
714
- // console.log(`Getting peripheral instance for twin ID: ${peripheralTwinId}`);
715
- let peripheralInstance: PeripheralInstance | undefined;
716
- const edgeInstance = await this.getInstance();
717
- if (!edgeInstance) {
718
- console.error('Edge instance not found');
719
- throw new Error('Edge instance not found');
720
- }
721
-
722
- // console.log('getting peripheral twin', peripheralTwinId);
723
-
724
- const peripheralTwin = await this.getTwinById(peripheralTwinId);
725
- // console.log('peripheralTwinp', peripheralTwin);
726
- if (!peripheralTwin) {
727
- console.error(`Peripheral twin with id ${peripheralTwinId} not found`);
728
- throw new Error(`Peripheral twin with id ${peripheralTwinId} not found`);
729
- }
730
-
731
- if (peripheralTwin.type !== TwinTypeEnum.Peripheral) {
732
- throw new Error(`Twin ${peripheralTwinId} is not a peripheral twin`);
733
- }
734
-
735
- // Auto-subscribe if peripheral belongs to a different device
736
- // if (peripheralTwin.deviceId !== edgeInstance.deviceId) {
737
- // console.log('Auto-subscribing to peripheral twin', peripheralTwinId);
738
- // const response =
739
- await this.subscribeTwin(peripheralTwinId);
740
- // console.log('subscribeTwin response', response);
741
- // }
742
-
743
- const instanceTypePrefix = 'peripheralInstance';
744
-
745
- const peripheralTwinEmit = (type: string, payload: any) => {
746
- // console.log(`Peripheral ${peripheralTwinId} emitting event:`, {
747
- // type,
748
- // payload
749
- // });
750
-
751
- const messagePayload = {
752
- type: `${instanceTypePrefix}:${peripheralTwin.id}:${type}`,
753
- sourceTwinId: peripheralTwin.id,
754
- sourceDeviceId: edgeInstance.deviceId,
755
- data: payload,
756
- };
757
-
758
- // console.log('Sending twin message:', JSON.stringify(messagePayload, null, 2));
759
- this.sendTwinMessage(peripheralTwin.id, messagePayload);
760
- };
761
-
762
- const peripheralTwinOn = (type: string, callback: (message: any) => void) => {
763
- console.log(`Setting up listener for peripheral ${peripheralTwinId} event: ${type}`);
764
-
765
- const onMessage = (payload: any) => {
766
- // console.log(`Received message for peripheral ${peripheralTwinId}:`, payload);
767
- const messageType = payload.data?.type || payload.type;
768
- if (messageType === `${instanceTypePrefix}:${peripheralTwin.id}:${type}`) {
769
- // console.log('Processing matching message type');
770
- callback(payload.data?.data || payload.data);
771
- } else {
772
- // console.log('Ignoring non-matching message type:', messageType);
773
- }
774
- };
775
-
776
- this.onTwinMessage(peripheralTwin.id, onMessage);
777
- };
778
-
779
- const peripheralTwinTo = (targetTwinId: string) => ({
780
- emit: (type: string, payload: any) => {
781
- const messagePayload = {
782
- type: `${instanceTypePrefix}:${peripheralTwin.id}:${type}`,
783
- sourceTwinId: edgeInstance.id,
784
- sourceDeviceId: edgeInstance.deviceId,
785
- data: payload,
786
- };
787
- this.sendTwinMessage(targetTwinId, messagePayload);
788
- },
789
- });
790
-
791
- const getDataChannel = async () => {
792
- return await this.getDataChannel(peripheralTwin.id);
793
- };
794
-
795
- const onDataChannel = async (callback: (dc: PhygridDataChannel) => void) => {
796
- return await this.onDataChannel(peripheralTwin.id, callback);
797
- };
798
-
799
- const getMediaStream = async () => {
800
- if (!peripheralInstance) {
801
- throw new Error('Peripheral instance not initialized');
802
- }
803
- return await this.getMediaStream(peripheralInstance.id);
804
- };
805
-
806
- const onMediaStream = async (callback: (stream: PhygridMediaStream) => void): Promise<void> => {
807
- if (!peripheralInstance) {
808
- throw new Error('Peripheral instance not initialized');
809
- }
810
- await this.onMediaStream(peripheralInstance.id, callback);
811
- // Return void to match the expected return type
812
- };
813
-
814
- const updateReported = async (properties: Record<string, any>) => {
815
- const newReported = {
816
- ...properties,
817
- };
818
- const result = await this.updateReportedProperties(peripheralTwin.id, newReported);
819
- // console.log('hub-client updateReported result', result);
820
- Object.assign(peripheralInstance!, result);
821
- return result;
822
- };
823
-
824
- const onUpdateReported = (callback: (reportedProperties: Record<string, any>) => void) => {
825
- let previousProperties: Record<string, any> | undefined = undefined;
826
-
827
- console.log(`[onUpdateReported] Registering handler for all reported properties`);
828
-
829
- this.onTwinUpdate(peripheralTwin.id, (twin: TwinResponse) => {
830
- console.log(`[onUpdateReported] Twin update received for ${peripheralTwin.id}`);
831
-
832
- // Only process updates for this specific peripheral
833
- if (twin.id !== peripheralTwin.id) {
834
- console.log(`[onUpdateReported] Twin ID mismatch: ${twin.id} !== ${peripheralTwin.id}`);
835
- return;
836
- }
837
-
838
- const reported = twin.properties.reported as Record<string, any>;
839
-
840
- // Check if reported properties exist
841
- if (reported) {
842
- // If this is the first update, always trigger
843
- if (previousProperties === undefined) {
844
- console.log(`[onUpdateReported] First update - calling callback`);
845
- previousProperties = JSON.parse(JSON.stringify(reported)); // Deep copy
846
- callback(reported);
847
- return;
848
- }
849
732
 
850
- const currentPropertiesStr = JSON.stringify(reported);
851
- const previousPropertiesStr = JSON.stringify(previousProperties);
852
-
853
- // Debug by printing parts of the strings - last 100 chars to avoid excessive logs
854
- console.log(
855
- `[onUpdateReported] Current (end): ...${currentPropertiesStr.substring(currentPropertiesStr.length - 100)}`
856
- );
857
- console.log(
858
- `[onUpdateReported] Previous (end): ...${previousPropertiesStr.substring(previousPropertiesStr.length - 100)}`
859
- );
860
-
861
- // Additional check - compare string lengths first
862
- if (currentPropertiesStr.length !== previousPropertiesStr.length) {
863
- console.log(
864
- `[onUpdateReported] String length changed: ${currentPropertiesStr.length} vs ${previousPropertiesStr.length}`
865
- );
866
- }
867
-
868
- const hasChanged = currentPropertiesStr !== previousPropertiesStr;
869
-
870
- console.log(
871
- `[onUpdateReported] Properties comparison:`,
872
- hasChanged ? 'CHANGED' : 'UNCHANGED'
873
- );
874
-
875
- if (hasChanged) {
876
- previousProperties = JSON.parse(currentPropertiesStr); // Deep copy
877
- console.log(`[onUpdateReported] Calling callback with updated reported properties`);
878
- callback(reported);
879
- } else {
880
- // Force a callback call at least every 5 updates to ensure clients get updates
881
- // This is a safety mechanism
882
- console.log(`[onUpdateReported] No change detected in properties`);
883
- }
884
- } else {
885
- console.log(`[onUpdateReported] No reported properties found`);
886
- }
887
- });
888
- };
889
-
890
- const onUpdateDesired = (callback: (desiredProperties: Record<string, any>) => void) => {
891
- let previousProperties: Record<string, any> | undefined = undefined;
892
-
893
- console.log(`[onUpdateDesired] Registering handler for all desired properties`);
894
-
895
- this.onTwinUpdate(peripheralTwin.id, (twin: TwinResponse) => {
896
- console.log(`[onUpdateDesired] Twin update received for ${peripheralTwin.id}`);
897
-
898
- // Only process updates for this specific peripheral
899
- if (twin.id !== peripheralTwin.id) {
900
- console.log(`[onUpdateDesired] Twin ID mismatch: ${twin.id} !== ${peripheralTwin.id}`);
901
- return;
902
- }
903
-
904
- const desired = twin.properties.desired as Record<string, any>;
905
-
906
- // Check if desired properties exist
907
- if (desired) {
908
- // If this is the first update, always trigger
909
- if (previousProperties === undefined) {
910
- console.log(`[onUpdateDesired] First update - calling callback`);
911
- previousProperties = JSON.parse(JSON.stringify(desired)); // Deep copy
912
- callback(desired);
913
- return;
914
- }
915
-
916
- const currentPropertiesStr = JSON.stringify(desired);
917
- const previousPropertiesStr = JSON.stringify(previousProperties);
918
-
919
- // Debug by printing parts of the strings - last 100 chars to avoid excessive logs
920
- console.log(
921
- `[onUpdateDesired] Current (end): ...${currentPropertiesStr.substring(currentPropertiesStr.length - 100)}`
922
- );
923
- console.log(
924
- `[onUpdateDesired] Previous (end): ...${previousPropertiesStr.substring(previousPropertiesStr.length - 100)}`
925
- );
926
-
927
- // Additional check - compare string lengths first
928
- if (currentPropertiesStr.length !== previousPropertiesStr.length) {
929
- console.log(
930
- `[onUpdateDesired] String length changed: ${currentPropertiesStr.length} vs ${previousPropertiesStr.length}`
931
- );
932
- }
933
-
934
- const hasChanged = currentPropertiesStr !== previousPropertiesStr;
935
-
936
- console.log(
937
- `[onUpdateDesired] Properties comparison:`,
938
- hasChanged ? 'CHANGED' : 'UNCHANGED'
939
- );
940
-
941
- if (hasChanged) {
942
- previousProperties = JSON.parse(currentPropertiesStr); // Deep copy
943
- console.log(`[onUpdateDesired] Calling callback with updated desired properties`);
944
- callback(desired);
945
- } else {
946
- // Force a callback call at least every 5 updates to ensure clients get updates
947
- // This is a safety mechanism
948
- console.log(`[onUpdateDesired] No change detected in properties`);
949
- }
950
- } else {
951
- console.log(`[onUpdateDesired] No desired properties found`);
952
- }
953
- });
954
- };
955
-
956
- const remove = async (): Promise<TwinResponse | void> => {
957
- const payload: EventPayload = {
958
- data: { twinId: peripheralTwin.id },
959
- };
960
-
961
- return new Promise((resolve, reject) => {
962
- this.emit('deletePeripheralTwin', payload, (response: any) => {
963
- const { twin } = response;
964
- resolve(twin);
965
- });
966
-
967
- this.socket?.on('error', (error: any) => {
968
- reject(error);
969
- });
970
- });
971
- };
972
-
973
- const updateDesired = async (properties: Record<string, any>): Promise<TwinResponse> => {
974
- const payload: EventPayload = {
975
- twinId: peripheralTwin.id,
976
- data: properties,
977
- };
978
-
979
- return new Promise((resolve, reject) => {
980
- this.emit('updatePeripheralTwinDesired', payload, (response: any) => {
981
- const { twin } = response;
982
- resolve(twin || {});
983
- });
984
-
985
- this.socket?.on('error', (error: any) => {
986
- reject(error);
987
- });
988
- });
989
- };
990
-
991
- peripheralInstance = {
992
- ...peripheralTwin,
993
- emit: peripheralTwinEmit,
994
- on: peripheralTwinOn,
995
- to: peripheralTwinTo,
996
- updateReported,
997
- updateDesired,
998
- onUpdateReported,
999
- onUpdateDesired,
1000
- remove,
1001
- getDataChannel,
1002
- onDataChannel,
1003
- getMediaStream,
1004
- onMediaStream,
1005
- };
1006
- this.peripheralInstances.set(peripheralTwinId, peripheralInstance);
733
+ const peripheralInstance = await this.twinInstancesRegistry.getPeripheralInstance(peripheralTwinId);
1007
734
  return peripheralInstance;
1008
735
  }
1009
736
 
@@ -1191,7 +918,7 @@ export class PhyHubClient {
1191
918
  targetTwinId: string,
1192
919
  data: any,
1193
920
  callback?: (response: any) => void
1194
- ) {
921
+ ) : Promise<TwinMessageResult | undefined> {
1195
922
  // console.log('sendTwinMessage called with:', {
1196
923
  // targetTwinId,
1197
924
  // data,
@@ -1229,9 +956,30 @@ export class PhyHubClient {
1229
956
  } else {
1230
957
  // console.log('sendTwinMessage emitting with promise');
1231
958
  return new Promise((resolve, reject) => {
959
+
960
+ const twinMessageTimeout = 10 * 1000;
961
+ const twinMessageTimeoutId = setTimeout(() => {
962
+ if (this.socket) {
963
+ this.socket?.off(payload.data.type);
964
+ }
965
+
966
+ reject({ status: TwinMessageResultStatus.Error, message: `twinMessage failed to complete in specified time (${twinMessageTimeout} ms)`});
967
+ }, twinMessageTimeout);
968
+
969
+ if (this.socket) {
970
+ this.socket.on(payload.data.type, (response: TwinMessageResult) => {
971
+ if (response && response.status !== TwinMessageResultStatus.Warning) {
972
+ // clear timeout on receiving twinMessage result when response status is not warning
973
+ clearTimeout(twinMessageTimeoutId);
974
+
975
+ this.socket?.off(payload.data.type);
976
+ resolve(response);
977
+ }
978
+ });
979
+ }
980
+
1232
981
  this.emit(this.EVENTS.TWIN_MESSAGE, payload, (response: any) => {
1233
- // console.log('sendTwinMessage promise received response:', response);
1234
- resolve(response);
982
+ console.log('sendTwinMessage promise received response:', response);
1235
983
  });
1236
984
 
1237
985
  this.socket?.on('error', (error: any) => {
@@ -1390,7 +1138,7 @@ export class PhyHubClient {
1390
1138
  // });
1391
1139
  // }
1392
1140
 
1393
- private async updateReportedProperties(
1141
+ public async updateReportedProperties(
1394
1142
  twinId: string,
1395
1143
  reportedProperties: Record<string, any>
1396
1144
  ): Promise<TwinResponse> {