ring-client-api 11.7.7 → 11.8.0

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/lib/api.js CHANGED
@@ -123,7 +123,8 @@ class RingApi extends subscribed_1.Subscribed {
123
123
  }
124
124
  }
125
125
  async registerPushReceiver(cameras, intercoms) {
126
- const pushReceiver = new push_receiver_1.default({
126
+ const credentials = this.restClient._internalOnly_pushNotificationCredentials, pushReceiver = new push_receiver_1.default({
127
+ credentials,
127
128
  logLevel: 'NONE',
128
129
  senderId: '876313859327', // for Ring android app. 703521446232 for ring-site
129
130
  }), devicesById = {}, sendToDevice = (id, notification) => {
@@ -135,10 +136,15 @@ class RingApi extends subscribed_1.Subscribed {
135
136
  for (const intercom of intercoms) {
136
137
  devicesById[intercom.id] = intercom;
137
138
  }
138
- pushReceiver.onCredentialsChanged(({ newCredentials: { fcm: { token }, }, }) => onPushNotificationToken.next(token));
139
+ pushReceiver.onCredentialsChanged(({ newCredentials }) => {
140
+ // Store the new credentials in the rest client so that it can be used for subsequent restarts
141
+ this.restClient._internalOnly_pushNotificationCredentials = newCredentials;
142
+ // Send the new credentials to the server
143
+ onPushNotificationToken.next(newCredentials.fcm.token);
144
+ });
139
145
  this.addSubscriptions((0, rxjs_1.combineLatest)([
140
146
  onPushNotificationToken,
141
- this.restClient.onSession, // combined but not used here, just to trigger another request when session is updated
147
+ this.restClient.onSession.pipe((0, operators_1.startWith)(undefined)), // combined but not used here, just to trigger another request when session is updated
142
148
  ]).subscribe(async ([token]) => {
143
149
  try {
144
150
  await this.restClient.request({
@@ -160,7 +166,21 @@ class RingApi extends subscribed_1.Subscribed {
160
166
  (0, util_1.logError)(e);
161
167
  }
162
168
  }));
169
+ try {
170
+ await pushReceiver.connect();
171
+ }
172
+ catch (e) {
173
+ (0, util_1.logError)('Failed to connect push notification receiver');
174
+ (0, util_1.logError)(e);
175
+ }
176
+ const startTime = Date.now();
163
177
  pushReceiver.onNotification(({ message }) => {
178
+ // Ignore messages received in the first two seconds after connecting
179
+ // These are likely duplicates, and we aren't currently storying persistent ids anywhere to avoid re-processing them
180
+ if (Date.now() - startTime < 2000) {
181
+ (0, util_1.logInfo)('Ignoring push notification received in first two seconds after starting up');
182
+ return;
183
+ }
164
184
  const dataJson = message.data?.gcmData;
165
185
  try {
166
186
  const notification = (0, json_bigint_1.default)({ storeAsString: true }).parse(dataJson);
@@ -176,12 +196,9 @@ class RingApi extends subscribed_1.Subscribed {
176
196
  (0, util_1.logError)(e);
177
197
  }
178
198
  });
179
- try {
180
- await pushReceiver.connect();
181
- }
182
- catch (e) {
183
- (0, util_1.logError)('Failed to connect push notification receiver');
184
- (0, util_1.logError)(e);
199
+ // If we already have credentials, use them immediately
200
+ if (credentials) {
201
+ onPushNotificationToken.next(credentials.fcm.token);
185
202
  }
186
203
  }
187
204
  async fetchRawLocations() {
@@ -1,6 +1,7 @@
1
1
  import { Options as RequestOptions } from 'got';
2
2
  import { AuthTokenResponse, SessionResponse } from './ring-types';
3
3
  import { ReplaySubject } from 'rxjs';
4
+ import type { Credentials } from '@eneris/push-receiver/dist/types';
4
5
  export declare function clientApi(path: string): string;
5
6
  export declare function deviceApi(path: string): string;
6
7
  export declare function commandsApi(path: string): string;
@@ -24,6 +25,7 @@ export interface SessionOptions {
24
25
  export declare class RingRestClient {
25
26
  private authOptions;
26
27
  refreshToken: string | undefined;
28
+ private authConfig;
27
29
  private hardwareIdPromise;
28
30
  private _authPromise;
29
31
  private timeouts;
@@ -54,4 +56,6 @@ export declare class RingRestClient {
54
56
  }): Promise<T & ExtendedResponse>;
55
57
  getCurrentAuth(): Promise<AuthTokenResponse>;
56
58
  clearTimeouts(): void;
59
+ get _internalOnly_pushNotificationCredentials(): Credentials | undefined;
60
+ set _internalOnly_pushNotificationCredentials(credentials: Credentials | undefined);
57
61
  }
@@ -7,6 +7,7 @@ exports.RingRestClient = exports.appApi = exports.commandsApi = exports.deviceAp
7
7
  const got_1 = __importDefault(require("got"));
8
8
  const util_1 = require("./util");
9
9
  const rxjs_1 = require("rxjs");
10
+ const assert_1 = __importDefault(require("assert"));
10
11
  const defaultRequestOptions = {
11
12
  responseType: 'json',
12
13
  method: 'GET',
@@ -64,6 +65,22 @@ async function requestWithRetry(requestOptions) {
64
65
  throw e;
65
66
  }
66
67
  }
68
+ function parseAuthConfig(rawRefreshToken) {
69
+ if (!rawRefreshToken) {
70
+ return;
71
+ }
72
+ try {
73
+ const config = JSON.parse((0, util_1.fromBase64)(rawRefreshToken));
74
+ (0, assert_1.default)(config);
75
+ (0, assert_1.default)(config.rt);
76
+ return config;
77
+ }
78
+ catch (_) {
79
+ return {
80
+ rt: rawRefreshToken,
81
+ };
82
+ }
83
+ }
67
84
  class RingRestClient {
68
85
  clearPreviousAuth() {
69
86
  this._authPromise = undefined;
@@ -90,6 +107,11 @@ class RingRestClient {
90
107
  }
91
108
  constructor(authOptions) {
92
109
  this.authOptions = authOptions;
110
+ this.refreshToken = 'refreshToken' in this.authOptions
111
+ ? this.authOptions.refreshToken
112
+ : undefined;
113
+ this.authConfig = parseAuthConfig(this.refreshToken);
114
+ this.hardwareIdPromise = this.authConfig?.hid || (0, util_1.getHardwareId)(this.authOptions.systemId);
93
115
  this.timeouts = [];
94
116
  this.sessionPromise = undefined;
95
117
  this.using2fa = false;
@@ -99,17 +121,12 @@ class RingRestClient {
99
121
  api_version: apiVersion,
100
122
  device_model: this.authOptions.controlCenterDisplayName ?? 'ring-client-api',
101
123
  };
102
- this.refreshToken =
103
- 'refreshToken' in this.authOptions
104
- ? this.authOptions.refreshToken
105
- : undefined;
106
- this.hardwareIdPromise = (0, util_1.getHardwareId)(this.authOptions.systemId);
107
124
  }
108
125
  getGrantData(twoFactorAuthCode) {
109
- if (this.refreshToken && !twoFactorAuthCode) {
126
+ if (this.authConfig?.rt && !twoFactorAuthCode) {
110
127
  return {
111
128
  grant_type: 'refresh_token',
112
- refresh_token: this.refreshToken,
129
+ refresh_token: this.authConfig.rt,
113
130
  };
114
131
  }
115
132
  const { authOptions } = this;
@@ -125,7 +142,7 @@ class RingRestClient {
125
142
  async getAuth(twoFactorAuthCode) {
126
143
  const grantData = this.getGrantData(twoFactorAuthCode);
127
144
  try {
128
- const response = await requestWithRetry({
145
+ const hardwareId = await this.hardwareIdPromise, response = await requestWithRetry({
129
146
  url: 'https://oauth.ring.com/oauth/token',
130
147
  json: {
131
148
  client_id: 'ring_official_android',
@@ -136,21 +153,33 @@ class RingRestClient {
136
153
  headers: {
137
154
  '2fa-support': 'true',
138
155
  '2fa-code': twoFactorAuthCode || '',
139
- hardware_id: await this.hardwareIdPromise,
156
+ hardware_id: hardwareId,
140
157
  'User-Agent': 'android:com.ringapp',
141
158
  },
142
- });
159
+ }), oldRefreshToken = this.refreshToken;
160
+ // Store the new refresh token and auth config
161
+ this.authConfig = {
162
+ ...this.authConfig,
163
+ rt: response.refresh_token,
164
+ hid: hardwareId,
165
+ };
166
+ this.refreshToken = (0, util_1.toBase64)(JSON.stringify(this.authConfig));
167
+ // Emit an event with the new token
143
168
  this.onRefreshTokenUpdated.next({
144
- oldRefreshToken: this.refreshToken,
145
- newRefreshToken: response.refresh_token,
169
+ oldRefreshToken,
170
+ newRefreshToken: this.refreshToken,
146
171
  });
147
- this.refreshToken = response.refresh_token;
148
- return response;
172
+ return {
173
+ ...response,
174
+ // Override the refresh token in the response so that consumers of this data get the wrapped version
175
+ refresh_token: this.refreshToken,
176
+ };
149
177
  }
150
178
  catch (requestError) {
151
179
  if (grantData.refresh_token) {
152
180
  // failed request with refresh token
153
181
  this.refreshToken = undefined;
182
+ this.authConfig = undefined;
154
183
  (0, util_1.logError)(requestError);
155
184
  return this.getAuth();
156
185
  }
@@ -250,9 +279,6 @@ class RingRestClient {
250
279
  });
251
280
  }
252
281
  async request(options) {
253
- if (!this.sessionPromise) {
254
- this.refreshSession();
255
- }
256
282
  const hardwareId = await this.hardwareIdPromise, url = options.url, initialSessionPromise = this.sessionPromise;
257
283
  try {
258
284
  await initialSessionPromise;
@@ -319,5 +345,30 @@ class RingRestClient {
319
345
  clearTimeouts() {
320
346
  this.timeouts.forEach(clearTimeout);
321
347
  }
348
+ get _internalOnly_pushNotificationCredentials() {
349
+ return this.authConfig?.pnc;
350
+ }
351
+ set _internalOnly_pushNotificationCredentials(credentials) {
352
+ if (!this.refreshToken || !this.authConfig) {
353
+ throw new Error('Cannot set push notification credentials without a refresh token');
354
+ }
355
+ const oldRefreshToken = this.refreshToken;
356
+ this.authConfig = {
357
+ ...this.authConfig,
358
+ pnc: credentials,
359
+ };
360
+ // SOMEDAY: refactor the conversion from auth config to refresh token - DRY from above
361
+ const newRefreshToken = (0, util_1.toBase64)(JSON.stringify(this.authConfig));
362
+ if (newRefreshToken === oldRefreshToken) {
363
+ // No change, so we don't need to emit an updated refresh token
364
+ return;
365
+ }
366
+ // Save and emit the updated refresh token
367
+ this.refreshToken = newRefreshToken;
368
+ this.onRefreshTokenUpdated.next({
369
+ oldRefreshToken,
370
+ newRefreshToken,
371
+ });
372
+ }
322
373
  }
323
374
  exports.RingRestClient = RingRestClient;
@@ -99,9 +99,13 @@ class RingCamera extends subscribed_1.Subscribed {
99
99
  this.onInHomeDoorbellStatus = this.onData.pipe((0, operators_1.map)(({ settings: { chime_settings } }) => {
100
100
  return Boolean(chime_settings?.enable);
101
101
  }), (0, operators_1.distinctUntilChanged)());
102
- this.addSubscriptions(this.restClient.onSession.subscribe(() => {
102
+ this.addSubscriptions(this.restClient.onSession
103
+ .pipe((0, operators_1.startWith)(undefined), (0, operators_1.throttleTime)(1000)) // Force this to run immediately, but don't double run if a session is created due to these api calls
104
+ .subscribe(() => {
103
105
  this.subscribeToDingEvents().catch((e) => {
104
- (0, util_1.logError)('Failed to subscribe ' + initialData.description + ' to ding events');
106
+ (0, util_1.logError)('Failed to subscribe ' +
107
+ initialData.description +
108
+ ' to ding events');
105
109
  (0, util_1.logError)(e);
106
110
  });
107
111
  this.subscribeToMotionEvents().catch((e) => {
package/lib/ring-types.js CHANGED
@@ -47,7 +47,7 @@ var RingDeviceType;
47
47
  RingDeviceType["OnvifCamera"] = "onvif_camera";
48
48
  RingDeviceType["ThirdPartyGarageDoorOpener"] = "third_party_gdo";
49
49
  RingDeviceType["IntercomHandsetAudio"] = "intercom_handset_audio";
50
- })(RingDeviceType = exports.RingDeviceType || (exports.RingDeviceType = {}));
50
+ })(RingDeviceType || (exports.RingDeviceType = RingDeviceType = {}));
51
51
  // eslint-disable-next-line no-shadow
52
52
  var RingDeviceCategory;
53
53
  (function (RingDeviceCategory) {
@@ -68,7 +68,7 @@ var RingDeviceCategory;
68
68
  RingDeviceCategory[RingDeviceCategory["Keypads"] = 33] = "Keypads";
69
69
  RingDeviceCategory[RingDeviceCategory["Sirens"] = 34] = "Sirens";
70
70
  RingDeviceCategory[RingDeviceCategory["PanicButtons"] = 35] = "PanicButtons";
71
- })(RingDeviceCategory = exports.RingDeviceCategory || (exports.RingDeviceCategory = {}));
71
+ })(RingDeviceCategory || (exports.RingDeviceCategory = RingDeviceCategory = {}));
72
72
  // eslint-disable-next-line no-shadow
73
73
  var RingCameraKind;
74
74
  (function (RingCameraKind) {
@@ -102,7 +102,7 @@ var RingCameraKind;
102
102
  RingCameraKind["cocoa_floodlight"] = "cocoa_floodlight";
103
103
  RingCameraKind["stickup_cam_mini"] = "stickup_cam_mini";
104
104
  RingCameraKind["onvif_camera"] = "onvif_camera";
105
- })(RingCameraKind = exports.RingCameraKind || (exports.RingCameraKind = {}));
105
+ })(RingCameraKind || (exports.RingCameraKind = RingCameraKind = {}));
106
106
  exports.RingCameraModel = {
107
107
  doorbot: 'Doorbell',
108
108
  doorbell: 'Doorbell',
@@ -162,7 +162,7 @@ var DoorbellType;
162
162
  DoorbellType[DoorbellType["Mechanical"] = 0] = "Mechanical";
163
163
  DoorbellType[DoorbellType["Digital"] = 1] = "Digital";
164
164
  DoorbellType[DoorbellType["None"] = 2] = "None";
165
- })(DoorbellType = exports.DoorbellType || (exports.DoorbellType = {}));
165
+ })(DoorbellType || (exports.DoorbellType = DoorbellType = {}));
166
166
  // eslint-disable-next-line no-shadow
167
167
  var NotificationDetectionType;
168
168
  (function (NotificationDetectionType) {
@@ -173,7 +173,7 @@ var NotificationDetectionType;
173
173
  NotificationDetectionType["OtherMotion"] = "other_motion";
174
174
  NotificationDetectionType["NotAvailable"] = "null";
175
175
  NotificationDetectionType["StreamBroken"] = "stream_broken";
176
- })(NotificationDetectionType = exports.NotificationDetectionType || (exports.NotificationDetectionType = {}));
176
+ })(NotificationDetectionType || (exports.NotificationDetectionType = NotificationDetectionType = {}));
177
177
  // eslint-disable-next-line no-shadow
178
178
  var PushNotificationAction;
179
179
  (function (PushNotificationAction) {
@@ -181,13 +181,13 @@ var PushNotificationAction;
181
181
  PushNotificationAction["Motion"] = "com.ring.push.HANDLE_NEW_motion";
182
182
  PushNotificationAction["LowBattery"] = "com.ring.push.LOW_BATTERY_ALERT";
183
183
  PushNotificationAction["IntercomUnlock"] = "com.ring.push.INTERCOM_UNLOCK_FROM_APP";
184
- })(PushNotificationAction = exports.PushNotificationAction || (exports.PushNotificationAction = {}));
184
+ })(PushNotificationAction || (exports.PushNotificationAction = PushNotificationAction = {}));
185
185
  // eslint-disable-next-line no-shadow
186
186
  var DispatchSignalType;
187
187
  (function (DispatchSignalType) {
188
188
  DispatchSignalType["Burglar"] = "user-verified-burglar-xa";
189
189
  DispatchSignalType["Fire"] = "user-verified-fire-xa";
190
- })(DispatchSignalType = exports.DispatchSignalType || (exports.DispatchSignalType = {}));
190
+ })(DispatchSignalType || (exports.DispatchSignalType = DispatchSignalType = {}));
191
191
  exports.disabledLocationModes = ['disabled', 'unset'];
192
192
  function isWebSocketSupportedAsset({ kind }) {
193
193
  return kind.startsWith('base_station') || kind.startsWith('beams_bridge');
@@ -5,7 +5,7 @@ const ws_1 = require("ws");
5
5
  const streaming_connection_base_1 = require("./streaming-connection-base");
6
6
  const util_1 = require("../util");
7
7
  function parseLiveCallSession(sessionId) {
8
- const encodedSession = sessionId.split('.')[1], buff = Buffer.from(encodedSession, 'base64'), text = buff.toString('ascii');
8
+ const encodedSession = sessionId.split('.')[1], text = (0, util_1.fromBase64)(encodedSession);
9
9
  return JSON.parse(text);
10
10
  }
11
11
  class WebrtcConnection extends streaming_connection_base_1.StreamingConnectionBase {
@@ -106,6 +106,12 @@ const email = 'some@one.com', password = 'abc123!', phone = '+1xxxxxxxx89', twoF
106
106
  },
107
107
  }));
108
108
  }));
109
+ async function wrapRefreshToken(rt) {
110
+ return (0, util_1.toBase64)(JSON.stringify({
111
+ rt,
112
+ hid: await hardwareIdPromise,
113
+ }));
114
+ }
109
115
  beforeEach(() => {
110
116
  sessionCreatedCount = 0;
111
117
  });
@@ -143,9 +149,9 @@ describe('getAuth', () => {
143
149
  const auth = await client.getAuth(twoFactorAuthCode);
144
150
  expect(auth).toMatchObject({
145
151
  access_token: accessToken,
146
- refresh_token: refreshToken,
152
+ refresh_token: await wrapRefreshToken(refreshToken),
147
153
  });
148
- expect(client.refreshToken).toEqual(refreshToken);
154
+ expect(client.refreshToken).toEqual(await wrapRefreshToken(refreshToken));
149
155
  });
150
156
  test('it should handle invalid credentials', async () => {
151
157
  client = new rest_client_1.RingRestClient({
@@ -171,9 +177,9 @@ describe('getAuth', () => {
171
177
  });
172
178
  expect(await client.getCurrentAuth()).toMatchObject({
173
179
  access_token: accessToken,
174
- refresh_token: secondRefreshToken,
180
+ refresh_token: await wrapRefreshToken(secondRefreshToken),
175
181
  });
176
- expect(client.refreshToken).toEqual(secondRefreshToken);
182
+ expect(client.refreshToken).toEqual(await wrapRefreshToken(secondRefreshToken));
177
183
  });
178
184
  test('it should emit an event when a new refresh token is created', async () => {
179
185
  client = new rest_client_1.RingRestClient({
@@ -182,11 +188,11 @@ describe('getAuth', () => {
182
188
  const refreshedPromise = (0, rxjs_1.firstValueFrom)(client.onRefreshTokenUpdated), auth = await client.getAuth();
183
189
  expect(auth).toMatchObject({
184
190
  access_token: accessToken,
185
- refresh_token: secondRefreshToken,
191
+ refresh_token: await wrapRefreshToken(secondRefreshToken),
186
192
  });
187
193
  expect(await refreshedPromise).toEqual({
188
194
  oldRefreshToken: refreshToken,
189
- newRefreshToken: secondRefreshToken,
195
+ newRefreshToken: await wrapRefreshToken(secondRefreshToken),
190
196
  });
191
197
  });
192
198
  });
@@ -194,7 +200,7 @@ describe('fetch', () => {
194
200
  let invalidateFirstAccessToken = false;
195
201
  beforeEach(() => {
196
202
  invalidateFirstAccessToken = false;
197
- server.use(msw_1.rest.get('https://api.ring.com/devices/v1/locations', (req, res, ctx) => {
203
+ server.use(msw_1.rest.get('https://api.ring.com/clients_api/some_endpoint', (req, res, ctx) => {
198
204
  const authHeader = req.headers.get('Authorization');
199
205
  if (invalidateFirstAccessToken &&
200
206
  authHeader === `Bearer ${accessToken}`) {
@@ -207,10 +213,10 @@ describe('fetch', () => {
207
213
  return res(ctx.status(401));
208
214
  }
209
215
  if (sessionCreatedCount === 0) {
210
- // eslint-disable-next-line no-console
211
- console.error('Request received before session was created');
212
216
  // Session not created yet
213
- return res(ctx.status(404), ctx.text('Session not created yet'));
217
+ return res(ctx.status(404), ctx.json({
218
+ error: 'Session not found for ' + req.headers.get('hardware_id'),
219
+ }));
214
220
  }
215
221
  return res(ctx.json([]));
216
222
  }));
@@ -220,7 +226,7 @@ describe('fetch', () => {
220
226
  refreshToken,
221
227
  });
222
228
  const response = await client.request({
223
- url: 'https://api.ring.com/devices/v1/locations',
229
+ url: 'https://api.ring.com/clients_api/some_endpoint',
224
230
  });
225
231
  expect(response).toEqual([]);
226
232
  });
@@ -230,7 +236,7 @@ describe('fetch', () => {
230
236
  });
231
237
  invalidateFirstAccessToken = true;
232
238
  const response = await client.request({
233
- url: 'https://api.ring.com/devices/v1/locations',
239
+ url: 'https://api.ring.com/clients_api/some_endpoint',
234
240
  });
235
241
  expect(response).toEqual([]);
236
242
  });
package/lib/util.d.ts CHANGED
@@ -19,4 +19,6 @@ export declare function randomString(length: number): string;
19
19
  export type DeepPartial<T> = {
20
20
  [K in keyof T]?: T[K] extends Array<infer U> ? Array<DeepPartial<U>> : T[K] extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : DeepPartial<T[K]>;
21
21
  };
22
+ export declare function fromBase64(encodedInput: string): string;
23
+ export declare function toBase64(input: string): string;
22
24
  export {};
package/lib/util.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.randomString = exports.randomInteger = exports.mapAsync = exports.stringify = exports.requestInput = exports.getHardwareId = exports.generateUuid = exports.enableDebug = exports.useLogger = exports.logError = exports.logInfo = exports.logDebug = exports.delay = exports.clearTimeouts = void 0;
6
+ exports.toBase64 = exports.fromBase64 = exports.randomString = exports.randomInteger = exports.mapAsync = exports.stringify = exports.requestInput = exports.getHardwareId = exports.generateUuid = exports.enableDebug = exports.useLogger = exports.logError = exports.logInfo = exports.logDebug = exports.delay = exports.clearTimeouts = void 0;
7
7
  const debug_1 = __importDefault(require("debug"));
8
8
  const colors_1 = require("colors");
9
9
  const readline_1 = require("readline");
@@ -134,3 +134,13 @@ function logPushReceiver(...args) {
134
134
  logDebug(args[0]);
135
135
  }
136
136
  logger_1.default.error = logPushReceiver;
137
+ function fromBase64(encodedInput) {
138
+ const buff = Buffer.from(encodedInput, 'base64');
139
+ return buff.toString('ascii');
140
+ }
141
+ exports.fromBase64 = fromBase64;
142
+ function toBase64(input) {
143
+ const buff = Buffer.from(input);
144
+ return buff.toString('base64');
145
+ }
146
+ exports.toBase64 = toBase64;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ring-client-api",
3
- "version": "11.7.7",
3
+ "version": "11.8.0",
4
4
  "description": "Unofficial API for Ring doorbells, cameras, security alarm system and smart lighting",
5
5
  "main": "lib/index.js",
6
6
  "exports": {
@@ -27,29 +27,29 @@
27
27
  "got": "^11.8.5",
28
28
  "json-bigint": "^1.0.0",
29
29
  "msw": "^1.2.1",
30
- "rxjs": "^7.8.0",
30
+ "rxjs": "^7.8.1",
31
31
  "socket.io-client": "^2.5.0",
32
- "systeminformation": "^5.17.12",
32
+ "systeminformation": "^5.18.2",
33
33
  "uuid": "^9.0.0",
34
34
  "webcrypto-core": "^1.7.7",
35
- "werift": "0.18.2",
35
+ "werift": "0.18.3",
36
36
  "ws": "^8.13.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@types/debug": "4.1.7",
40
- "@types/jest": "29.5.0",
39
+ "@types/debug": "4.1.8",
40
+ "@types/jest": "29.5.2",
41
41
  "@types/json-bigint": "^1.0.1",
42
- "@types/node": "18.15.9",
43
- "@types/uuid": "9.0.1",
44
- "@types/ws": "^8.5.4",
42
+ "@types/node": "20.2.5",
43
+ "@types/uuid": "9.0.2",
44
+ "@types/ws": "^8.5.5",
45
45
  "eslint-config-shared": "*",
46
46
  "jest": "29.5.0",
47
- "ts-jest": "29.0.5",
47
+ "ts-jest": "29.1.0",
48
48
  "tsconfig": "*",
49
- "typescript": "5.0.2"
49
+ "typescript": "5.1.3"
50
50
  },
51
51
  "engines": {
52
- "node": "^16 || ^18"
52
+ "node": "^16 || ^18 || ^20"
53
53
  },
54
54
  "repository": {
55
55
  "type": "git",