@phystack/hub-client 4.4.52 → 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.
@@ -0,0 +1,356 @@
1
+ import { EventPayload, PhygridDataChannel, PhygridMediaStream, PhyHubClient } from ".";
2
+ import { Instance, TwinResponse, TwinTypeEnum } from "./types/twin.types";
3
+
4
+ export class PeripheralTwinInstance {
5
+ public phyHubClient: PhyHubClient;
6
+ public twinId: string;
7
+ public edgeInstance: Instance | null = null;
8
+ public peripheralTwinResponse: TwinResponse | null = null;
9
+
10
+ private instanceTypePrefix = 'peripheralInstance';
11
+
12
+ constructor(phyHubClient: PhyHubClient, twinId: string) {
13
+ this.phyHubClient = phyHubClient;
14
+ this.twinId = twinId;
15
+ }
16
+
17
+ // initialize the instance by performing sanity checks
18
+ async initialize() {
19
+ const edgeInstance = await this.phyHubClient.getInstance();
20
+ if (!edgeInstance) {
21
+ console.error('Edge instance not found');
22
+ throw new Error('Edge instance not found');
23
+ }
24
+ this.edgeInstance = edgeInstance;
25
+
26
+ const peripheralTwinResponse = await this.phyHubClient.getTwinById(this.twinId);
27
+ if (!peripheralTwinResponse) {
28
+ console.error(`Peripheral twin with id ${this.twinId} not found`);
29
+ throw new Error(`Peripheral twin with id ${this.twinId} not found`);
30
+ }
31
+
32
+ if (peripheralTwinResponse.type !== TwinTypeEnum.Peripheral) {
33
+ console.error(`Twin ${this.twinId} is not a peripheral twin`);
34
+ throw new Error(`Twin ${this.twinId} is not a peripheral twin`);
35
+ }
36
+
37
+ this.peripheralTwinResponse = peripheralTwinResponse;
38
+
39
+ await this.phyHubClient.subscribeTwin(this.twinId);
40
+ }
41
+
42
+ // alias to sendEvent()
43
+ async emit(eventType: string, payload: any) {
44
+ return await this.sendEvent(eventType, payload);
45
+ }
46
+
47
+ async sendEvent(eventType: string, payload: any) {
48
+ return await this.peripheralTwinEmit(eventType, payload);
49
+ }
50
+
51
+ private async peripheralTwinEmit(eventType: string, payload: any) {
52
+ if (!this.edgeInstance || !this.peripheralTwinResponse) {
53
+ throw new Error('Object is not initialized correctly');
54
+ }
55
+
56
+ const messagePayload = {
57
+ type: `${this.instanceTypePrefix}:${this.peripheralTwinResponse.id}:${eventType}`,
58
+ sourceTwinId: this.peripheralTwinResponse.id,
59
+ sourceDeviceId: this.edgeInstance.deviceId,
60
+ data: payload,
61
+ };
62
+
63
+ const result = await this.phyHubClient.sendTwinMessage(this.peripheralTwinResponse.id, messagePayload);
64
+ return result;
65
+ };
66
+
67
+ // alias to onEvent()
68
+ on(eventType: string, callback: (message: any) => void) {
69
+ return this.onEvent(eventType, callback);
70
+ }
71
+
72
+ onEvent(eventType: string, callback: (message: any) => void) {
73
+ return this.peripheralTwinOn(eventType, callback);
74
+ }
75
+
76
+ private peripheralTwinOn(eventType: string, callback: (message: any) => void) {
77
+ console.log(`Setting up listener for peripheral ${this.twinId} event: ${eventType}`);
78
+
79
+ if (!this.peripheralTwinResponse) {
80
+ console.error('Object is not initialized correctly');
81
+ return;
82
+ }
83
+
84
+ const onMessage = (payload: any) => {
85
+ // console.log(`Received message for peripheral ${peripheralTwinId}:`, payload);
86
+ const messageType = payload.data?.type || payload.type;
87
+ if (messageType === `${this.instanceTypePrefix}:${this.peripheralTwinResponse!.id}:${eventType}`) {
88
+ // console.log('Processing matching message type');
89
+ callback(payload.data?.data || payload.data);
90
+ } else {
91
+ // console.log('Ignoring non-matching message type:', messageType);
92
+ }
93
+ };
94
+
95
+ this.phyHubClient.onTwinMessage(this.peripheralTwinResponse.id, onMessage);
96
+ };
97
+
98
+ to(targetTwinId: string) {
99
+ return {
100
+ emit: async (eventType: string, payload: any) => {
101
+ if (!this.edgeInstance || !this.peripheralTwinResponse) {
102
+ throw new Error('Object is not initialized correctly');
103
+ }
104
+
105
+ const messagePayload = {
106
+ type: `${this.instanceTypePrefix}:${this.peripheralTwinResponse.id}:${eventType}`,
107
+ sourceTwinId: this.edgeInstance.id,
108
+ sourceDeviceId: this.edgeInstance.deviceId,
109
+ data: payload,
110
+ };
111
+
112
+ return await this.phyHubClient.sendTwinMessage(targetTwinId, messagePayload);
113
+ }
114
+ }
115
+ }
116
+
117
+ async getDataChannel() {
118
+ if (!this.peripheralTwinResponse) {
119
+ console.error('Object is not initialized correctly');
120
+ return;
121
+ }
122
+
123
+ return await this.phyHubClient.getDataChannel(this.peripheralTwinResponse.id);
124
+ };
125
+
126
+ async onDataChannel(callback: (dc: PhygridDataChannel) => void) {
127
+ if (!this.peripheralTwinResponse) {
128
+ console.error('Object is not initialized correctly');
129
+ return;
130
+ }
131
+
132
+ await this.phyHubClient.onDataChannel(this.peripheralTwinResponse.id, callback);
133
+ };
134
+
135
+ async getMediaStream() {
136
+ if (!this.peripheralTwinResponse) {
137
+ console.error('Object is not initialized correctly');
138
+ return;
139
+ }
140
+
141
+ return await this.phyHubClient.getMediaStream(this.peripheralTwinResponse.id);
142
+ };
143
+
144
+ async onMediaStream(callback: (stream: PhygridMediaStream) => void): Promise<void> {
145
+ if (!this.peripheralTwinResponse) {
146
+ throw new Error('Peripheral instance not initialized');
147
+ }
148
+
149
+ await this.phyHubClient.onMediaStream(this.peripheralTwinResponse.id, callback);
150
+ };
151
+
152
+ async updateReported(properties: Record<string, any>) {
153
+ if (!this.peripheralTwinResponse) {
154
+ throw new Error('Peripheral instance not initialized');
155
+ }
156
+
157
+ const newReported = {
158
+ ...properties,
159
+ };
160
+
161
+ const result = await this.phyHubClient.updateReportedProperties(this.peripheralTwinResponse.id, newReported);
162
+
163
+ this.peripheralTwinResponse = Object.assign({}, this.peripheralTwinResponse, result);
164
+
165
+ return result;
166
+ };
167
+
168
+ onUpdateReported(callback: (reportedProperties: Record<string, any>) => void) {
169
+ if (!this.peripheralTwinResponse) {
170
+ throw new Error('Peripheral instance not initialized');
171
+ }
172
+
173
+ let previousProperties: Record<string, any> | undefined = undefined;
174
+
175
+ console.log(`[onUpdateReported] Registering handler for all reported properties`);
176
+
177
+ this.phyHubClient.onTwinUpdate(this.peripheralTwinResponse.id, (twin: TwinResponse) => {
178
+ console.log(`[onUpdateReported] Twin update received for ${this.peripheralTwinResponse!.id}`);
179
+
180
+ // Only process updates for this specific peripheral
181
+ if (twin.id !== this.peripheralTwinResponse!.id) {
182
+ console.log(`[onUpdateReported] Twin ID mismatch: ${twin.id} !== ${this.peripheralTwinResponse!.id}`);
183
+ return;
184
+ }
185
+
186
+ const reported = twin.properties.reported as Record<string, any>;
187
+
188
+ // Check if reported properties exist
189
+ if (reported) {
190
+ // If this is the first update, always trigger
191
+ if (previousProperties === undefined) {
192
+ console.log(`[onUpdateReported] First update - calling callback`);
193
+ previousProperties = JSON.parse(JSON.stringify(reported)); // Deep copy
194
+ callback(reported);
195
+ return;
196
+ }
197
+
198
+ const currentPropertiesStr = JSON.stringify(reported);
199
+ const previousPropertiesStr = JSON.stringify(previousProperties);
200
+
201
+ // Debug by printing parts of the strings - last 100 chars to avoid excessive logs
202
+ console.log(
203
+ `[onUpdateReported] Current (end): ...${currentPropertiesStr.substring(currentPropertiesStr.length - 100)}`
204
+ );
205
+ console.log(
206
+ `[onUpdateReported] Previous (end): ...${previousPropertiesStr.substring(previousPropertiesStr.length - 100)}`
207
+ );
208
+
209
+ // Additional check - compare string lengths first
210
+ if (currentPropertiesStr.length !== previousPropertiesStr.length) {
211
+ console.log(
212
+ `[onUpdateReported] String length changed: ${currentPropertiesStr.length} vs ${previousPropertiesStr.length}`
213
+ );
214
+ }
215
+
216
+ const hasChanged = currentPropertiesStr !== previousPropertiesStr;
217
+
218
+ console.log(
219
+ `[onUpdateReported] Properties comparison:`,
220
+ hasChanged ? 'CHANGED' : 'UNCHANGED'
221
+ );
222
+
223
+ if (hasChanged) {
224
+ previousProperties = JSON.parse(currentPropertiesStr); // Deep copy
225
+ console.log(`[onUpdateReported] Calling callback with updated reported properties`);
226
+ callback(reported);
227
+ } else {
228
+ // Force a callback call at least every 5 updates to ensure clients get updates
229
+ // This is a safety mechanism
230
+ console.log(`[onUpdateReported] No change detected in properties`);
231
+ }
232
+ } else {
233
+ console.log(`[onUpdateReported] No reported properties found`);
234
+ }
235
+ });
236
+ };
237
+
238
+ async updateDesired(properties: Record<string, any>): Promise<TwinResponse>{
239
+ if (!this.peripheralTwinResponse) {
240
+ throw new Error('Peripheral instance not initialized');
241
+ }
242
+
243
+ const payload: EventPayload = {
244
+ twinId: this.peripheralTwinResponse.id,
245
+ data: properties,
246
+ };
247
+
248
+ const phyClientSocket = this.phyHubClient.getSocket();
249
+
250
+ return new Promise((resolve, reject) => {
251
+ this.phyHubClient.emit('updatePeripheralTwinDesired', payload, (response: any) => {
252
+ const { twin } = response;
253
+ resolve(twin || {});
254
+ });
255
+
256
+ phyClientSocket?.on('error', (error: any) => {
257
+ reject(error);
258
+ });
259
+ });
260
+ };
261
+
262
+ onUpdateDesired(callback: (desiredProperties: Record<string, any>) => void) {
263
+ if (!this.peripheralTwinResponse) {
264
+ throw new Error('Peripheral instance not initialized');
265
+ }
266
+
267
+ let previousProperties: Record<string, any> | undefined = undefined;
268
+
269
+ console.log(`[onUpdateDesired] Registering handler for all desired properties`);
270
+
271
+ this.phyHubClient.onTwinUpdate(this.peripheralTwinResponse.id, (twin: TwinResponse) => {
272
+ console.log(`[onUpdateDesired] Twin update received for ${this.peripheralTwinResponse!.id}`);
273
+
274
+ // Only process updates for this specific peripheral
275
+ if (twin.id !== this.peripheralTwinResponse!.id) {
276
+ console.log(`[onUpdateDesired] Twin ID mismatch: ${twin.id} !== ${this.peripheralTwinResponse!.id}`);
277
+ return;
278
+ }
279
+
280
+ const desired = twin.properties.desired as Record<string, any>;
281
+
282
+ // Check if desired properties exist
283
+ if (desired) {
284
+ // If this is the first update, always trigger
285
+ if (previousProperties === undefined) {
286
+ console.log(`[onUpdateDesired] First update - calling callback`);
287
+ previousProperties = JSON.parse(JSON.stringify(desired)); // Deep copy
288
+ callback(desired);
289
+ return;
290
+ }
291
+
292
+ const currentPropertiesStr = JSON.stringify(desired);
293
+ const previousPropertiesStr = JSON.stringify(previousProperties);
294
+
295
+ // Debug by printing parts of the strings - last 100 chars to avoid excessive logs
296
+ console.log(
297
+ `[onUpdateDesired] Current (end): ...${currentPropertiesStr.substring(currentPropertiesStr.length - 100)}`
298
+ );
299
+ console.log(
300
+ `[onUpdateDesired] Previous (end): ...${previousPropertiesStr.substring(previousPropertiesStr.length - 100)}`
301
+ );
302
+
303
+ // Additional check - compare string lengths first
304
+ if (currentPropertiesStr.length !== previousPropertiesStr.length) {
305
+ console.log(
306
+ `[onUpdateDesired] String length changed: ${currentPropertiesStr.length} vs ${previousPropertiesStr.length}`
307
+ );
308
+ }
309
+
310
+ const hasChanged = currentPropertiesStr !== previousPropertiesStr;
311
+
312
+ console.log(
313
+ `[onUpdateDesired] Properties comparison:`,
314
+ hasChanged ? 'CHANGED' : 'UNCHANGED'
315
+ );
316
+
317
+ if (hasChanged) {
318
+ previousProperties = JSON.parse(currentPropertiesStr); // Deep copy
319
+ console.log(`[onUpdateDesired] Calling callback with updated desired properties`);
320
+ callback(desired);
321
+ } else {
322
+ // Force a callback call at least every 5 updates to ensure clients get updates
323
+ // This is a safety mechanism
324
+ console.log(`[onUpdateDesired] No change detected in properties`);
325
+ }
326
+ } else {
327
+ console.log(`[onUpdateDesired] No desired properties found`);
328
+ }
329
+ });
330
+ };
331
+
332
+ async remove() : Promise<TwinResponse | void> {
333
+ if (!this.peripheralTwinResponse) {
334
+ throw new Error('Peripheral instance not initialized');
335
+ }
336
+
337
+ const payload: EventPayload = {
338
+ data: { twinId: this.peripheralTwinResponse.id },
339
+ };
340
+
341
+ const phyClientSocket = this.phyHubClient.getSocket();
342
+
343
+ return new Promise((resolve, reject) => {
344
+ this.phyHubClient.emit('deletePeripheralTwin', payload, (response: any) => {
345
+ const { twin } = response;
346
+ resolve(twin);
347
+ });
348
+
349
+ phyClientSocket?.on('error', (error: any) => {
350
+ reject(error);
351
+ });
352
+ });
353
+ };
354
+
355
+ }
356
+
@@ -0,0 +1,39 @@
1
+ import { PhyHubClient } from ".";
2
+ import { PeripheralTwinInstance } from "./peripheral-twin";
3
+ import { IPeripheralTwinInstance } from "./types/twin.types";
4
+
5
+ /**
6
+ * registry to create, store and retrieve instances of different types of twins e.g. Device, Screen, Peripheral
7
+ */
8
+
9
+ export class TwinRegistry {
10
+ private peripheralInstances: Map<string, IPeripheralTwinInstance> = new Map();
11
+
12
+ constructor(private phyHubClient: PhyHubClient) {
13
+ }
14
+
15
+ // create an instance of PeripheralTwinInstance class
16
+ async getPeripheralInstance(twinId: string) : Promise<IPeripheralTwinInstance | null> {
17
+ try {
18
+
19
+ // check whether we've already created instance for this twinId
20
+ if (this.peripheralInstances.has(twinId)) {
21
+ return this.peripheralInstances.get(twinId)!;
22
+ }
23
+
24
+ // create an object of PeripheralTwinInstance class
25
+ const peripheralTwinInstance = new PeripheralTwinInstance(this.phyHubClient, twinId);
26
+
27
+ // class object is created, but perform sanity checks such as whether PhyHubClient is valid, twinId is valid etc.
28
+ // to make sure we are able to correct and return a valid instance
29
+ await peripheralTwinInstance.initialize();
30
+
31
+ // set the newly created instance in the map for future use
32
+ this.peripheralInstances.set(twinId, peripheralTwinInstance);
33
+
34
+ return peripheralTwinInstance;
35
+ } catch(error) {
36
+ throw error;
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,3 @@
1
+ export * from './signal.types';
2
+ export * from './twin.types';
3
+ export * from './webrtc.types';
@@ -1,3 +1,4 @@
1
+ import { PhyHubClient } from '..';
1
2
  import { PhygridDataChannel, PhygridMediaStream } from './webrtc.types';
2
3
 
3
4
  export enum TwinTypeEnum {
@@ -801,3 +802,35 @@ export interface MediaOptions {
801
802
  isMediaConnection?: boolean;
802
803
  mediaDirection?: string;
803
804
  }
805
+
806
+ export enum TwinMessageResultStatus {
807
+ Success = 'success',
808
+ Error = 'error',
809
+ Warning = 'warning',
810
+ };
811
+
812
+ export interface TwinMessageResult {
813
+ status: TwinMessageResultStatus;
814
+ message: string;
815
+ }
816
+
817
+ export interface IPeripheralTwinInstance {
818
+ phyHubClient: PhyHubClient;
819
+ twinId: string;
820
+ edgeInstance: Instance | null;
821
+ peripheralTwinResponse: TwinResponse | null;
822
+ emit: (eventType: string, payload: any) => Promise<TwinMessageResult | undefined>;
823
+ sendEvent: (eventType: string, payload: any) => Promise<TwinMessageResult | undefined>;
824
+ on: (eventType: string, callback: (message: any) => void) => void;
825
+ onEvent: (eventType: string, callback: (message: any) => void) => void;
826
+ to: (targetTwinId: string) => { emit: (eventType: string, payload: any) => Promise<TwinMessageResult | undefined>};
827
+ getDataChannel: () => Promise<PhygridDataChannel | undefined>;
828
+ onDataChannel: (callback: (dc: PhygridDataChannel) => void) => Promise<void>;
829
+ getMediaStream: () => Promise<{stream: PhygridMediaStream; close: () => void} | undefined>;
830
+ onMediaStream: (callback: (stream: PhygridMediaStream) => void) => Promise<void>;
831
+ updateReported: (properties: Record<string, any>) => Promise<TwinResponse>;
832
+ onUpdateReported: (callback: (reportedProperties: Record<string, any>) => void) => void;
833
+ updateDesired: (properties: Record<string, any>) => Promise<TwinResponse>;
834
+ onUpdateDesired: (callback: (reportedProperties: Record<string, any>) => void) => void;
835
+ remove : () => Promise<TwinResponse | void>;
836
+ }