ring-client-api 14.3.0 → 15.0.0-beta.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
@@ -1,15 +1,15 @@
1
- import { clientApi, deviceApi, RingRestClient } from "./rest-client.js";
2
- import { Location } from "./location.js";
3
- import { PushNotificationAction, RingDeviceType } from "./ring-types.js";
4
- import { RingCamera } from "./ring-camera.js";
5
- import { RingChime } from "./ring-chime.js";
1
+ import { clientApi, deviceApi, RingRestClient } from './rest-client.js';
2
+ import { Location } from './location.js';
3
+ import { PushNotificationAction, RingDeviceType } from './ring-types.js';
4
+ import { RingCamera } from './ring-camera.js';
5
+ import { RingChime } from './ring-chime.js';
6
6
  import { combineLatest, EMPTY, merge, Subject } from 'rxjs';
7
7
  import { debounceTime, startWith, switchMap, throttleTime, } from 'rxjs/operators';
8
- import { clearTimeouts, enableDebug, logDebug, logError, logInfo, } from "./util.js";
9
- import { setFfmpegPath } from "./ffmpeg.js";
10
- import { Subscribed } from "./subscribed.js";
8
+ import { clearTimeouts, enableDebug, logDebug, logError, logInfo, } from './util.js';
9
+ import { setFfmpegPath } from './ffmpeg.js';
10
+ import { Subscribed } from './subscribed.js';
11
11
  import { PushReceiver } from '@eneris/push-receiver';
12
- import { RingIntercom } from "./ring-intercom.js";
12
+ import { RingIntercom } from './ring-intercom.js';
13
13
  import JSONbig from 'json-bigint';
14
14
  export class RingApi extends Subscribed {
15
15
  restClient;
@@ -1 +1,3 @@
1
+ export declare const redactedUuid = "[uuid]";
2
+ export declare function stripSensitiveFields(input: unknown): void;
1
3
  export declare function logDeviceData(): Promise<void>;
@@ -1,50 +1,100 @@
1
1
  /* eslint-disable no-console */
2
- import { RingApi } from "./api.js";
3
- import { acquireRefreshToken } from "./refresh-token.js";
4
- import { mapAsync } from "./util.js";
2
+ import { RingApi } from './api.js';
3
+ import { acquireRefreshToken } from './refresh-token.js';
4
+ import { mapAsync } from './util.js';
5
5
  const sensitiveFields = [
6
6
  'id',
7
7
  'device_id',
8
8
  'latitude',
9
9
  'longitude',
10
10
  'address',
11
+ 'address1',
12
+ 'address2',
11
13
  'email',
12
14
  'time_zone',
15
+ 'timezone',
13
16
  'location_id',
14
- 'serialNumber',
15
- 'catalogId',
16
- 'adapterZid',
17
+ 'serialnumber',
18
+ 'serial_number',
19
+ 'catalogid',
20
+ 'adapterzid',
17
21
  'fingerprint',
18
22
  'owner',
19
23
  'ssid',
24
+ 'wifi_name',
20
25
  'ap_address',
26
+ 'mac_address',
27
+ 'macaddress',
21
28
  'codes',
22
- 'groupId',
29
+ 'groupid',
23
30
  'group',
24
- 'groupMembers',
25
- ];
26
- function stripSensitiveFields(input) {
27
- if (typeof input === 'object') {
28
- if (Array.isArray(input)) {
29
- input.forEach((value) => stripSensitiveFields(value));
30
- return;
31
- }
32
- for (const key in input) {
33
- if (sensitiveFields.includes(key) || key.endsWith('_id')) {
34
- delete input[key];
31
+ 'groupmembers',
32
+ 'first_name',
33
+ 'last_name',
34
+ 'phone',
35
+ 'phone_number',
36
+ 'third_party_dsn',
37
+ 'amzn_dsn',
38
+ 'refresh_token',
39
+ 'access_token',
40
+ 'password',
41
+ 'token',
42
+ 'cookie',
43
+ 'city',
44
+ 'zip_code',
45
+ 'cross_street',
46
+ ], sensitiveFieldSet = new Set(sensitiveFields), uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
47
+ export const redactedUuid = '[uuid]';
48
+ function isUuid(value) {
49
+ return value.length === 36 && uuidPattern.test(value);
50
+ }
51
+ function isSensitiveKey(key) {
52
+ const lower = key.toLowerCase();
53
+ return (sensitiveFieldSet.has(lower) ||
54
+ lower.endsWith('_id') ||
55
+ // camelCase ids such as roomId / doorbotId (but not zid — keep key, redact value)
56
+ (key.endsWith('Id') && key !== 'Id'));
57
+ }
58
+ export function stripSensitiveFields(input) {
59
+ if (input === null || typeof input !== 'object') {
60
+ return;
61
+ }
62
+ if (Array.isArray(input)) {
63
+ for (let i = 0; i < input.length; i++) {
64
+ const value = input[i];
65
+ if (typeof value === 'string' && isUuid(value)) {
66
+ input[i] = redactedUuid;
35
67
  }
36
68
  else {
37
- const data = input[key];
38
- if (key.length === 36) {
39
- input[key.slice(0, 13) + '-uuid'] = data;
40
- delete input[key];
41
- }
42
- if (typeof data === 'string' && data.length === 36) {
43
- input[key] = data.slice(0, 13) + '-uuid';
44
- }
45
- stripSensitiveFields(data);
69
+ stripSensitiveFields(value);
46
70
  }
47
71
  }
72
+ return;
73
+ }
74
+ const record = input;
75
+ let uuidKeyIndex = 0;
76
+ // Snapshot keys so renames during iteration cannot re-introduce originals.
77
+ for (const key of Object.keys(record)) {
78
+ if (isSensitiveKey(key)) {
79
+ delete record[key];
80
+ continue;
81
+ }
82
+ let data = record[key];
83
+ if (isUuid(key)) {
84
+ delete record[key];
85
+ if (typeof data === 'string' && isUuid(data)) {
86
+ data = redactedUuid;
87
+ }
88
+ // Unique placeholder per key so sibling uuid keys do not clobber each other.
89
+ record[`${redactedUuid}:${uuidKeyIndex++}`] = data;
90
+ stripSensitiveFields(data);
91
+ continue;
92
+ }
93
+ if (typeof data === 'string' && isUuid(data)) {
94
+ record[key] = redactedUuid;
95
+ continue;
96
+ }
97
+ stripSensitiveFields(data);
48
98
  }
49
99
  }
50
100
  export async function logDeviceData() {
@@ -61,10 +111,12 @@ export async function logDeviceData() {
61
111
  intercoms: location.intercoms.map((intercom) => intercom.data),
62
112
  devices: devices.map((device) => device.data),
63
113
  };
64
- }), results = {
114
+ }),
115
+ // Clone so redaction does not mutate live RingApi device objects.
116
+ results = structuredClone({
65
117
  locations: locationsWithDevices,
66
118
  amazonKeyLocks,
67
- };
119
+ });
68
120
  stripSensitiveFields(results);
69
121
  console.log('\nPlease copy and paste everything AFTER THIS LINE:\n\n');
70
122
  console.log(JSON.stringify(results));
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
- export * from "./api.js";
2
- export * from "./location.js";
3
- export * from "./ring-camera.js";
4
- export * from "./ring-chime.js";
5
- export * from "./ring-device.js";
6
- export * from "./ring-intercom.js";
7
- export * from "./ring-types.js";
1
+ export * from './api.js';
2
+ export * from './location.js';
3
+ export * from './ring-camera.js';
4
+ export * from './ring-chime.js';
5
+ export * from './ring-device.js';
6
+ export * from './ring-intercom.js';
7
+ export * from './ring-types.js';
package/lib/location.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { WebSocket } from 'undici';
2
2
  import { BehaviorSubject, firstValueFrom, merge, ReplaySubject, Subject, } from 'rxjs';
3
3
  import { concatMap, debounceTime, distinctUntilChanged, filter, map, scan, shareReplay, skip, } from 'rxjs/operators';
4
- import { delay, generateUuid, logDebug, logError, logInfo } from "./util.js";
5
- import { DispatchSignalType, RingDeviceType, disabledLocationModes, isWebSocketSupportedAsset, } from "./ring-types.js";
6
- import { appApi, clientApi } from "./rest-client.js";
7
- import { getSearchQueryString } from "./ring-camera.js";
8
- import { RingDevice } from "./ring-device.js";
9
- import { Subscribed } from "./subscribed.js";
4
+ import { delay, generateUuid, logDebug, logError, logInfo } from './util.js';
5
+ import { DispatchSignalType, RingDeviceType, disabledLocationModes, isWebSocketSupportedAsset, } from './ring-types.js';
6
+ import { appApi, clientApi } from './rest-client.js';
7
+ import { getSearchQueryString } from './ring-camera.js';
8
+ import { RingDevice } from './ring-device.js';
9
+ import { Subscribed } from './subscribed.js';
10
10
  const deviceListMessageType = 'DeviceInfoDocGetList';
11
11
  function flattenDeviceData(data) {
12
12
  return Object.assign({}, data.general && data.general.v2, data.device && data.device.v1);
@@ -152,7 +152,7 @@ export class Location extends Subscribed {
152
152
  this.onDataUpdate.next(message);
153
153
  }
154
154
  }
155
- catch (e) {
155
+ catch {
156
156
  logError('Error parsing message from server: ' + event.data);
157
157
  }
158
158
  });
@@ -1,13 +1,13 @@
1
1
  /* eslint-disable no-console */
2
- import { RingRestClient } from "./rest-client.js";
3
- import { requestInput } from "./util.js";
2
+ import { RingRestClient } from './rest-client.js';
3
+ import { requestHiddenInput, requestInput } from './util.js';
4
4
  export async function acquireRefreshToken() {
5
- const email = await requestInput('Email: '), password = await requestInput('Password: '), restClient = new RingRestClient({ email, password }), getAuthWith2fa = async () => {
5
+ const email = await requestInput('Email: '), password = await requestHiddenInput('Password: '), restClient = new RingRestClient({ email, password }), getAuthWith2fa = async () => {
6
6
  const code = await requestInput('2fa Code: ');
7
7
  try {
8
8
  return await restClient.getAuth(code);
9
9
  }
10
- catch (_) {
10
+ catch {
11
11
  console.log('Incorrect 2fa code. Please try again.');
12
12
  return getAuthWith2fa();
13
13
  }
@@ -2,7 +2,7 @@ import type { AuthTokenResponse, SessionResponse } from './ring-types.ts';
2
2
  import { ReplaySubject } from 'rxjs';
3
3
  import type { Credentials } from '@eneris/push-receiver/dist/types.d.js';
4
4
  import { Agent } from 'undici';
5
- interface RequestOptions extends RequestInit {
5
+ interface RequestOptions extends Omit<RequestInit, 'dispatcher'> {
6
6
  responseType?: 'json' | 'buffer';
7
7
  timeout?: number;
8
8
  json?: object;
@@ -16,6 +16,9 @@ export interface ExtendedResponse {
16
16
  responseTimestamp: number;
17
17
  timeMillis: number;
18
18
  }
19
+ export declare class InvalidAuthResponseError extends Error {
20
+ name: string;
21
+ }
19
22
  export interface EmailAuth {
20
23
  email: string;
21
24
  password: string;
@@ -1,7 +1,11 @@
1
- import { delay, fromBase64, getHardwareId, logDebug, logError, logInfo, stringify, toBase64, } from "./util.js";
1
+ import { delay, fromBase64, getHardwareId, logDebug, logError, logInfo, stringify, toBase64, } from './util.js';
2
2
  import { ReplaySubject } from 'rxjs';
3
3
  import assert from 'assert';
4
- import { Agent } from 'undici';
4
+ // Import fetch from the same undici package as Agent. Node's global fetch is
5
+ // backed by a bundled undici that can be a different major than the npm
6
+ // dependency; passing an undici@8 Agent into global fetch then fails with
7
+ // UND_ERR_INVALID_ARG ("invalid onRequestStart method") on Node 22/24.
8
+ import { Agent, fetch as undiciFetch, } from 'undici';
5
9
  const fetchAgent = new Agent({
6
10
  connections: 6,
7
11
  pipelining: 1,
@@ -29,6 +33,9 @@ export function commandsApi(path) {
29
33
  export function appApi(path) {
30
34
  return appApiBaseUrl + path;
31
35
  }
36
+ export class InvalidAuthResponseError extends Error {
37
+ name = 'InvalidAuthResponseError';
38
+ }
32
39
  async function responseToError(response) {
33
40
  const error = new Error();
34
41
  error.response = {
@@ -51,9 +58,6 @@ async function responseToError(response) {
51
58
  return error;
52
59
  }
53
60
  async function requestWithRetry(requestOptions, retryCount = 0) {
54
- if (typeof fetch !== 'function') {
55
- throw new Error(`Your current NodeJS version (${process.version}) is too old to support this plugin. Please upgrade to the latest LTS version of NodeJS.`);
56
- }
57
61
  try {
58
62
  if (requestOptions.json || requestOptions.responseType === 'json') {
59
63
  requestOptions.headers = {
@@ -75,8 +79,8 @@ async function requestWithRetry(requestOptions, retryCount = 0) {
75
79
  if (options.timeout && !options.signal) {
76
80
  options.signal = AbortSignal.timeout(options.timeout);
77
81
  }
78
- // make the fetch request
79
- const response = await fetch(options.url, options), headers = response.headers;
82
+ // undici fetch + undici Agent must stay paired (see import comment)
83
+ const response = await undiciFetch(options.url, options), headers = response.headers;
80
84
  if (!response.ok) {
81
85
  const error = await responseToError(response);
82
86
  throw error;
@@ -223,7 +227,12 @@ export class RingRestClient {
223
227
  hardware_id: hardwareId,
224
228
  'User-Agent': 'android:com.ringapp',
225
229
  },
226
- }), oldRefreshToken = this.refreshToken;
230
+ }), oldRefreshToken = this.refreshToken, newRefreshToken = response?.refresh_token;
231
+ // Never replace the stored refresh token with one missing `rt` (e.g. an undecodable response body).
232
+ // Persisting it would guarantee `invalid_grant` on the next start, while the old token may still work.
233
+ if (typeof newRefreshToken !== 'string' || !newRefreshToken) {
234
+ throw new InvalidAuthResponseError('Received an oauth response from Ring without a refresh token. The existing refresh token has been kept. If this persists, make sure ring-client-api/homebridge-ring is up to date for your Node.js version.');
235
+ }
227
236
  // Store the new refresh token and auth config
228
237
  this.authConfig = {
229
238
  ...this.authConfig,
@@ -243,6 +252,10 @@ export class RingRestClient {
243
252
  };
244
253
  }
245
254
  catch (requestError) {
255
+ if (requestError instanceof InvalidAuthResponseError) {
256
+ // Ring did not reject the credentials, so keep them in memory rather than clearing and retrying
257
+ throw requestError;
258
+ }
246
259
  if (grantData.refresh_token) {
247
260
  // failed request with refresh token
248
261
  this.refreshToken = undefined;
@@ -250,9 +263,13 @@ export class RingRestClient {
250
263
  logError(requestError);
251
264
  return this.getAuth();
252
265
  }
253
- const response = requestError.response || {}, responseData = response.body || {}, responseError = 'error' in responseData && typeof responseData.error === 'string'
266
+ const response = requestError.response || {}, responseData = typeof response.body === 'object' && response.body !== null
267
+ ? response.body
268
+ : {}, responseError = 'error' in responseData && typeof responseData.error === 'string'
254
269
  ? responseData.error
255
- : '';
270
+ : response.status
271
+ ? `HTTP ${response.status}`
272
+ : '';
256
273
  if (response.status === 412 || // need 2fa code
257
274
  (response.status === 400 &&
258
275
  responseError.startsWith('Verification Code')) // invalid 2fa code entered
@@ -260,7 +277,7 @@ export class RingRestClient {
260
277
  this.using2fa = true;
261
278
  if (response.status === 400) {
262
279
  this.promptFor2fa = 'Invalid 2fa code entered. Please try again.';
263
- throw new Error(responseError);
280
+ throw new Error(responseError, { cause: requestError });
264
281
  }
265
282
  if ('tsv_state' in responseData) {
266
283
  const { tsv_state, phone } = responseData, prompt = tsv_state === 'totp'
@@ -271,7 +288,7 @@ export class RingRestClient {
271
288
  else {
272
289
  this.promptFor2fa = 'Please enter the code sent to your text/email';
273
290
  }
274
- throw new Error('Your Ring account is configured to use 2-factor authentication (2fa). See https://github.com/dgreif/ring/wiki/Refresh-Tokens for details.');
291
+ throw new Error('Your Ring account is configured to use 2-factor authentication (2fa). See https://github.com/dgreif/ring/wiki/Refresh-Tokens for details.', { cause: requestError });
275
292
  }
276
293
  const authTypeMessage = 'refreshToken' in this.authOptions
277
294
  ? 'refresh token is'
@@ -284,7 +301,7 @@ export class RingRestClient {
284
301
  ` (error: ${responseError})`;
285
302
  logError(requestError.response || requestError);
286
303
  logError(errorMessage);
287
- throw new Error(errorMessage);
304
+ throw new Error(errorMessage, { cause: requestError });
288
305
  }
289
306
  }
290
307
  async fetchNewSession(authToken) {
@@ -396,7 +413,9 @@ export class RingRestClient {
396
413
  }
397
414
  return this.request(options);
398
415
  }
399
- throw new Error('Not found with response: ' + stringify(response.body));
416
+ throw new Error('Not found with response: ' + stringify(response.body), {
417
+ cause: e,
418
+ });
400
419
  }
401
420
  if (response.status) {
402
421
  logError(`Request to ${url} failed with status ${response.status}. Response body: ${stringify(response.body)}`);
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { logRefreshToken } from "./refresh-token.js";
2
+ import { logRefreshToken } from './refresh-token.js';
3
3
  logRefreshToken().catch((e) => {
4
4
  // eslint-disable-next-line no-console
5
5
  console.error(e);
@@ -1,13 +1,13 @@
1
- import { RingCameraKind } from "./ring-types.js";
2
- import { DoorbellType, PushNotificationAction, RingCameraModel, } from "./ring-types.js";
3
- import { appApi, clientApi, deviceApi } from "./rest-client.js";
1
+ import { RingCameraKind } from './ring-types.js';
2
+ import { DoorbellType, PushNotificationAction, RingCameraModel, } from './ring-types.js';
3
+ import { appApi, clientApi, deviceApi } from './rest-client.js';
4
4
  import { BehaviorSubject, firstValueFrom, ReplaySubject, Subject } from 'rxjs';
5
5
  import { distinctUntilChanged, filter, map, share, startWith, throttleTime, } from 'rxjs/operators';
6
- import { buildSearchString, delay, logDebug, logError, } from "./util.js";
7
- import { Subscribed } from "./subscribed.js";
8
- import { WebrtcConnection } from "./streaming/webrtc-connection.js";
9
- import { StreamingSession } from "./streaming/streaming-session.js";
10
- import { SimpleWebRtcSession } from "./streaming/simple-webrtc-session.js";
6
+ import { buildSearchString, delay, logDebug, logError, } from './util.js';
7
+ import { Subscribed } from './subscribed.js';
8
+ import { WebrtcConnection } from './streaming/webrtc-connection.js';
9
+ import { StreamingSession } from './streaming/streaming-session.js';
10
+ import { SimpleWebRtcSession } from './streaming/simple-webrtc-session.js';
11
11
  const maxSnapshotRefreshSeconds = 15, fullDayMs = 24 * 60 * 60 * 1000;
12
12
  function parseBatteryLife(batteryLife) {
13
13
  if (batteryLife === null || batteryLife === undefined) {
package/lib/ring-chime.js CHANGED
@@ -1,5 +1,5 @@
1
- import { ChimeModel } from "./ring-types.js";
2
- import { clientApi, deviceApi } from "./rest-client.js";
1
+ import { ChimeModel } from './ring-types.js';
2
+ import { clientApi, deviceApi } from './rest-client.js';
3
3
  import { BehaviorSubject, Subject } from 'rxjs';
4
4
  const settingsWhichRequireReboot = [
5
5
  'ding_audio_id',
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { logDeviceData } from "./device-data.js";
2
+ import { logDeviceData } from './device-data.js';
3
3
  logDeviceData().catch((e) => {
4
4
  // eslint-disable-next-line no-console
5
5
  console.error(e);
@@ -1,8 +1,8 @@
1
1
  import { BehaviorSubject } from 'rxjs';
2
- import { deviceTypesWithVolume } from "./ring-types.js";
2
+ import { deviceTypesWithVolume } from './ring-types.js';
3
3
  import { filter, map } from 'rxjs/operators';
4
- import { Subscribed } from "./subscribed.js";
5
- import { logError } from "./util.js";
4
+ import { Subscribed } from './subscribed.js';
5
+ import { logError } from './util.js';
6
6
  export class RingDevice extends Subscribed {
7
7
  onData;
8
8
  zid;
@@ -1,9 +1,9 @@
1
- import { PushNotificationAction } from "./ring-types.js";
2
- import { clientApi, commandsApi } from "./rest-client.js";
1
+ import { PushNotificationAction } from './ring-types.js';
2
+ import { clientApi, commandsApi } from './rest-client.js';
3
3
  import { BehaviorSubject, Subject } from 'rxjs';
4
4
  import { distinctUntilChanged, map } from 'rxjs/operators';
5
- import { getBatteryLevel } from "./ring-camera.js";
6
- import { logError } from "./util.js";
5
+ import { getBatteryLevel } from './ring-camera.js';
6
+ import { logError } from './util.js';
7
7
  export class RingIntercom {
8
8
  id;
9
9
  deviceType;
@@ -1,51 +1,51 @@
1
1
  type Firmware = string | 'Up To Date';
2
2
  export declare const RingDeviceType: {
3
- readonly BaseStation: "hub.redsky";
4
- readonly BaseStationPro: "hub.kili";
5
- readonly Keypad: "security-keypad";
6
- readonly SecurityPanel: "security-panel";
7
- readonly ContactSensor: "sensor.contact";
8
- readonly MotionSensor: "sensor.motion";
9
- readonly FloodFreezeSensor: "sensor.flood-freeze";
10
- readonly FreezeSensor: "sensor.freeze";
11
- readonly TemperatureSensor: "sensor.temperature";
12
- readonly WaterSensor: "sensor.water";
13
- readonly TiltSensor: "sensor.tilt";
14
- readonly GlassbreakSensor: "sensor.glassbreak";
15
- readonly RangeExtender: "range-extender.zwave";
16
- readonly ZigbeeAdapter: "adapter.zigbee";
17
- readonly AccessCodeVault: "access-code.vault";
18
- readonly AccessCode: "access-code";
19
- readonly SmokeAlarm: "alarm.smoke";
20
- readonly CoAlarm: "alarm.co";
21
- readonly SmokeCoListener: "listener.smoke-co";
22
- readonly MultiLevelSwitch: "switch.multilevel";
23
- readonly Fan: "switch.multilevel";
24
- readonly MultiLevelBulb: "switch.multilevel.bulb";
25
- readonly Switch: "switch";
26
- readonly BeamsMotionSensor: "motion-sensor.beams";
27
- readonly BeamsSwitch: "switch.beams";
28
- readonly BeamsMultiLevelSwitch: "switch.multilevel.beams";
29
- readonly BeamsLightGroupSwitch: "group.light-group.beams";
30
- readonly BeamsTransformerSwitch: "switch.transformer.beams";
31
- readonly BeamsDevice: "device.beams";
32
- readonly RetrofitBridge: "bridge.flatline";
33
- readonly RetrofitZone: "sensor.zone";
34
- readonly Thermostat: "temperature-control.thermostat";
35
- readonly Sensor: "sensor";
36
- readonly RingNetAdapter: "adapter.ringnet";
37
- readonly CodeVault: "access-code.vault";
38
- readonly SecurityAccessCode: "access-code";
39
- readonly ZWaveAdapter: "adapter.zwave";
40
- readonly ZWaveExtender: "range-extender.zwave";
41
- readonly PanicButton: "security-panic";
42
- readonly UnknownZWave: "unknown.zwave";
43
- readonly OnvifCamera: "onvif_camera";
44
- readonly ThirdPartyGarageDoorOpener: "third_party_gdo";
45
- readonly IntercomHandsetAudio: "intercom_handset_audio";
46
- readonly IntercomHandsetVideo: "intercom_handset_video";
47
- readonly WaterValve: "valve.water";
48
- readonly KiddeSmokeCoAlarm: "comp.bluejay.sensor_bluejay_wsc";
3
+ readonly BaseStation: 'hub.redsky';
4
+ readonly BaseStationPro: 'hub.kili';
5
+ readonly Keypad: 'security-keypad';
6
+ readonly SecurityPanel: 'security-panel';
7
+ readonly ContactSensor: 'sensor.contact';
8
+ readonly MotionSensor: 'sensor.motion';
9
+ readonly FloodFreezeSensor: 'sensor.flood-freeze';
10
+ readonly FreezeSensor: 'sensor.freeze';
11
+ readonly TemperatureSensor: 'sensor.temperature';
12
+ readonly WaterSensor: 'sensor.water';
13
+ readonly TiltSensor: 'sensor.tilt';
14
+ readonly GlassbreakSensor: 'sensor.glassbreak';
15
+ readonly RangeExtender: 'range-extender.zwave';
16
+ readonly ZigbeeAdapter: 'adapter.zigbee';
17
+ readonly AccessCodeVault: 'access-code.vault';
18
+ readonly AccessCode: 'access-code';
19
+ readonly SmokeAlarm: 'alarm.smoke';
20
+ readonly CoAlarm: 'alarm.co';
21
+ readonly SmokeCoListener: 'listener.smoke-co';
22
+ readonly MultiLevelSwitch: 'switch.multilevel';
23
+ readonly Fan: 'switch.multilevel';
24
+ readonly MultiLevelBulb: 'switch.multilevel.bulb';
25
+ readonly Switch: 'switch';
26
+ readonly BeamsMotionSensor: 'motion-sensor.beams';
27
+ readonly BeamsSwitch: 'switch.beams';
28
+ readonly BeamsMultiLevelSwitch: 'switch.multilevel.beams';
29
+ readonly BeamsLightGroupSwitch: 'group.light-group.beams';
30
+ readonly BeamsTransformerSwitch: 'switch.transformer.beams';
31
+ readonly BeamsDevice: 'device.beams';
32
+ readonly RetrofitBridge: 'bridge.flatline';
33
+ readonly RetrofitZone: 'sensor.zone';
34
+ readonly Thermostat: 'temperature-control.thermostat';
35
+ readonly Sensor: 'sensor';
36
+ readonly RingNetAdapter: 'adapter.ringnet';
37
+ readonly CodeVault: 'access-code.vault';
38
+ readonly SecurityAccessCode: 'access-code';
39
+ readonly ZWaveAdapter: 'adapter.zwave';
40
+ readonly ZWaveExtender: 'range-extender.zwave';
41
+ readonly PanicButton: 'security-panic';
42
+ readonly UnknownZWave: 'unknown.zwave';
43
+ readonly OnvifCamera: 'onvif_camera';
44
+ readonly ThirdPartyGarageDoorOpener: 'third_party_gdo';
45
+ readonly IntercomHandsetAudio: 'intercom_handset_audio';
46
+ readonly IntercomHandsetVideo: 'intercom_handset_video';
47
+ readonly WaterValve: 'valve.water';
48
+ readonly KiddeSmokeCoAlarm: 'comp.bluejay.sensor_bluejay_wsc';
49
49
  };
50
50
  export type RingDeviceType = (typeof RingDeviceType)[keyof typeof RingDeviceType];
51
51
  export declare const RingDeviceCategory: {
@@ -70,40 +70,40 @@ export declare const RingDeviceCategory: {
70
70
  };
71
71
  export type RingDeviceCategory = (typeof RingDeviceCategory)[keyof typeof RingDeviceCategory];
72
72
  export declare const RingCameraKind: {
73
- readonly doorbot: "doorbot";
74
- readonly doorbell: "doorbell";
75
- readonly doorbell_v3: "doorbell_v3";
76
- readonly doorbell_v4: "doorbell_v4";
77
- readonly doorbell_v5: "doorbell_v5";
78
- readonly doorbell_oyster: "doorbell_oyster";
79
- readonly doorbell_portal: "doorbell_portal";
80
- readonly doorbell_scallop: "doorbell_scallop";
81
- readonly doorbell_scallop_lite: "doorbell_scallop_lite";
82
- readonly doorbell_graham_cracker: "doorbell_graham_cracker";
83
- readonly lpd_v1: "lpd_v1";
84
- readonly lpd_v2: "lpd_v2";
85
- readonly lpd_v4: "lpd_v4";
86
- readonly jbox_v1: "jbox_v1";
87
- readonly stickup_cam: "stickup_cam";
88
- readonly stickup_cam_v3: "stickup_cam_v3";
89
- readonly stickup_cam_elite: "stickup_cam_elite";
90
- readonly stickup_cam_longfin: "stickup_cam_longfin";
91
- readonly stickup_cam_lunar: "stickup_cam_lunar";
92
- readonly spotlightw_v2: "spotlightw_v2";
93
- readonly hp_cam_v1: "hp_cam_v1";
94
- readonly hp_cam_v2: "hp_cam_v2";
95
- readonly stickup_cam_v4: "stickup_cam_v4";
96
- readonly floodlight_v1: "floodlight_v1";
97
- readonly floodlight_v2: "floodlight_v2";
98
- readonly floodlight_pro: "floodlight_pro";
99
- readonly cocoa_camera: "cocoa_camera";
100
- readonly cocoa_doorbell: "cocoa_doorbell";
101
- readonly cocoa_doorbell_v2: "cocoa_doorbell_v2";
102
- readonly cocoa_doorbell_v3: "cocoa_doorbell_v3";
103
- readonly cocoa_floodlight: "cocoa_floodlight";
104
- readonly cocoa_spotlight: "cocoa_spotlight";
105
- readonly stickup_cam_mini: "stickup_cam_mini";
106
- readonly onvif_camera: "onvif_camera";
73
+ readonly doorbot: 'doorbot';
74
+ readonly doorbell: 'doorbell';
75
+ readonly doorbell_v3: 'doorbell_v3';
76
+ readonly doorbell_v4: 'doorbell_v4';
77
+ readonly doorbell_v5: 'doorbell_v5';
78
+ readonly doorbell_oyster: 'doorbell_oyster';
79
+ readonly doorbell_portal: 'doorbell_portal';
80
+ readonly doorbell_scallop: 'doorbell_scallop';
81
+ readonly doorbell_scallop_lite: 'doorbell_scallop_lite';
82
+ readonly doorbell_graham_cracker: 'doorbell_graham_cracker';
83
+ readonly lpd_v1: 'lpd_v1';
84
+ readonly lpd_v2: 'lpd_v2';
85
+ readonly lpd_v4: 'lpd_v4';
86
+ readonly jbox_v1: 'jbox_v1';
87
+ readonly stickup_cam: 'stickup_cam';
88
+ readonly stickup_cam_v3: 'stickup_cam_v3';
89
+ readonly stickup_cam_elite: 'stickup_cam_elite';
90
+ readonly stickup_cam_longfin: 'stickup_cam_longfin';
91
+ readonly stickup_cam_lunar: 'stickup_cam_lunar';
92
+ readonly spotlightw_v2: 'spotlightw_v2';
93
+ readonly hp_cam_v1: 'hp_cam_v1';
94
+ readonly hp_cam_v2: 'hp_cam_v2';
95
+ readonly stickup_cam_v4: 'stickup_cam_v4';
96
+ readonly floodlight_v1: 'floodlight_v1';
97
+ readonly floodlight_v2: 'floodlight_v2';
98
+ readonly floodlight_pro: 'floodlight_pro';
99
+ readonly cocoa_camera: 'cocoa_camera';
100
+ readonly cocoa_doorbell: 'cocoa_doorbell';
101
+ readonly cocoa_doorbell_v2: 'cocoa_doorbell_v2';
102
+ readonly cocoa_doorbell_v3: 'cocoa_doorbell_v3';
103
+ readonly cocoa_floodlight: 'cocoa_floodlight';
104
+ readonly cocoa_spotlight: 'cocoa_spotlight';
105
+ readonly stickup_cam_mini: 'stickup_cam_mini';
106
+ readonly onvif_camera: 'onvif_camera';
107
107
  };
108
108
  export type RingCameraKind = (typeof RingCameraKind)[keyof typeof RingCameraKind];
109
109
  export declare const RingCameraModel: {
@@ -884,23 +884,23 @@ export interface RingDeviceHistoryEvent {
884
884
  }
885
885
  export type DingState = 'ringing' | 'connected' | 'timed_out' | 'completed';
886
886
  export declare const NotificationDetectionType: {
887
- readonly Human: "human";
888
- readonly Loitering: "loitering";
889
- readonly Motion: "motion";
890
- readonly OtherMotion: "other_motion";
891
- readonly NotAvailable: "null";
892
- readonly StreamBroken: "stream_broken";
887
+ readonly Human: 'human';
888
+ readonly Loitering: 'loitering';
889
+ readonly Motion: 'motion';
890
+ readonly OtherMotion: 'other_motion';
891
+ readonly NotAvailable: 'null';
892
+ readonly StreamBroken: 'stream_broken';
893
893
  };
894
894
  export type NotificationDetectionType = (typeof NotificationDetectionType)[keyof typeof NotificationDetectionType];
895
895
  export declare const PushNotificationAction: {
896
- readonly Ding: "com.ring.pn.live-event.ding";
897
- readonly Motion: "com.ring.pn.live-event.motion";
898
- readonly IntercomDing: "com.ring.pn.live-event.intercom";
899
- readonly IntercomUnlock: "com.ring.push.INTERCOM_UNLOCK_FROM_APP";
900
- readonly AlarmModeNone: "com.ring.push.HANDLE_NEW_SECURITY_PANEL_MODE_NONE_NOTICE";
901
- readonly AlarmModeSome: "com.ring.push.HANDLE_NEW_SECURITY_PANEL_MODE_SOME_NOTICE";
902
- readonly AlarmSoundSiren: "com.ring.push.HANDLE_NEW_USER_SOUND_SIREN";
903
- readonly AlarmSilenceSiren: "com.ring.push.HANDLE_NEW_NON_ALARM_SIREN_SILENCED";
896
+ readonly Ding: 'com.ring.pn.live-event.ding';
897
+ readonly Motion: 'com.ring.pn.live-event.motion';
898
+ readonly IntercomDing: 'com.ring.pn.live-event.intercom';
899
+ readonly IntercomUnlock: 'com.ring.push.INTERCOM_UNLOCK_FROM_APP';
900
+ readonly AlarmModeNone: 'com.ring.push.HANDLE_NEW_SECURITY_PANEL_MODE_NONE_NOTICE';
901
+ readonly AlarmModeSome: 'com.ring.push.HANDLE_NEW_SECURITY_PANEL_MODE_SOME_NOTICE';
902
+ readonly AlarmSoundSiren: 'com.ring.push.HANDLE_NEW_USER_SOUND_SIREN';
903
+ readonly AlarmSilenceSiren: 'com.ring.push.HANDLE_NEW_NON_ALARM_SIREN_SILENCED';
904
904
  };
905
905
  export type PushNotificationAction = (typeof PushNotificationAction)[keyof typeof PushNotificationAction];
906
906
  export interface PushNotificationDingV2 {
@@ -1051,8 +1051,8 @@ export interface AccountMonitoringStatus {
1051
1051
  dispatchSetupComplete: boolean;
1052
1052
  }
1053
1053
  export declare const DispatchSignalType: {
1054
- readonly Burglar: "user-verified-burglar-xa";
1055
- readonly Fire: "user-verified-fire-xa";
1054
+ readonly Burglar: 'user-verified-burglar-xa';
1055
+ readonly Fire: 'user-verified-fire-xa';
1056
1056
  };
1057
1057
  export type DispatchSignalType = (typeof DispatchSignalType)[keyof typeof DispatchSignalType];
1058
1058
  export type LocationModeInput = 'home' | 'away' | 'disarmed';
@@ -24,7 +24,7 @@ export declare class WeriftPeerConnection extends Subscribed implements BasicPee
24
24
  onVideoRtp: Subject<RtpPacket>;
25
25
  onVideoRtcp: Subject<RtcpPacket>;
26
26
  onIceCandidate: Subject<RTCIceCandidate>;
27
- onConnectionState: ReplaySubject<"closed" | "connected" | "failed" | "disconnected" | "new" | "connecting">;
27
+ onConnectionState: ReplaySubject<"closed" | "connected" | "connecting" | "disconnected" | "failed" | "new">;
28
28
  returnAudioTrack: MediaStreamTrack;
29
29
  private onRequestKeyFrame;
30
30
  constructor();
@@ -33,7 +33,7 @@ export declare class WeriftPeerConnection extends Subscribed implements BasicPee
33
33
  type: 'answer';
34
34
  sdp: string;
35
35
  }): Promise<void>;
36
- addIceCandidate(candidate: RTCIceCandidate): Promise<void>;
36
+ addIceCandidate(candidate: Partial<RTCIceCandidate>): Promise<void>;
37
37
  requestKeyFrame(): void;
38
38
  close(): void;
39
39
  }
@@ -1,7 +1,7 @@
1
1
  import { MediaStreamTrack, RTCPeerConnection, RTCRtpCodecParameters, } from 'werift';
2
2
  import { interval, merge, ReplaySubject, Subject } from 'rxjs';
3
- import { logDebug, logError, logInfo } from "../util.js";
4
- import { Subscribed } from "../subscribed.js";
3
+ import { logDebug, logError, logInfo } from '../util.js';
4
+ import { Subscribed } from '../subscribed.js';
5
5
  const ringIceServers = [
6
6
  'stun:stun.kinesisvideo.us-east-1.amazonaws.com:443',
7
7
  'stun:stun.kinesisvideo.us-east-2.amazonaws.com:443',
@@ -1,4 +1,4 @@
1
- import { generateUuid } from "../util.js";
1
+ import { generateUuid } from '../util.js';
2
2
  function liveViewUrl(path) {
3
3
  return `https://api.ring.com/integrations/v1/liveview/${path}`;
4
4
  }
@@ -24,6 +24,10 @@ interface SessionStartedMessage {
24
24
  method: 'session_started';
25
25
  body: SessionBody;
26
26
  }
27
+ interface PingMessage {
28
+ method: 'ping';
29
+ body: SessionBody;
30
+ }
27
31
  interface PongMessage {
28
32
  method: 'pong';
29
33
  body: SessionBody;
@@ -46,12 +50,12 @@ interface StreamInfoMessage {
46
50
  transcoding_reason: 'codec_mismatch' | string;
47
51
  };
48
52
  }
49
- declare const CloseReasonCode: {
50
- readonly NormalClose: 0;
51
- readonly AuthenticationFailed: 5;
52
- readonly Timeout: 6;
53
- };
54
- type CloseReasonCode = (typeof CloseReasonCode)[keyof typeof CloseReasonCode];
53
+ interface CloseReasonCodes {
54
+ NormalClose: 0;
55
+ AuthenticationFailed: 5;
56
+ Timeout: 6;
57
+ }
58
+ type CloseReasonCode = CloseReasonCodes[keyof CloseReasonCodes];
55
59
  interface CloseMessage {
56
60
  method: 'close';
57
61
  body: {
@@ -61,5 +65,5 @@ interface CloseMessage {
61
65
  };
62
66
  } & SessionBody;
63
67
  }
64
- export type IncomingMessage = AnswerMessage | IceCandidateMessage | SessionCreatedMessage | SessionStartedMessage | PongMessage | CloseMessage | NotificationMessage | CameraStartedMessage | StreamInfoMessage;
68
+ export type IncomingMessage = AnswerMessage | IceCandidateMessage | SessionCreatedMessage | SessionStartedMessage | PingMessage | PongMessage | CloseMessage | NotificationMessage | CameraStartedMessage | StreamInfoMessage;
65
69
  export {};
@@ -1,10 +1 @@
1
- const CloseReasonCode = {
2
- NormalClose: 0,
3
- // reason: { code: 5, text: '[rsl-apps/webrtc-liveview-server/Session.cpp:429] [Auth] [0xd540]: [rsl-apps/session-manager/Manager.cpp:227] [AppAuth] Unauthorized: invalid or expired token' }
4
- // reason: { code: 5, text: 'Authentication failed: -1' }
5
- // reason: { code: 5, text: 'Sessions with the provided ID not found' }
6
- AuthenticationFailed: 5,
7
- // reason: { code: 6, text: 'Timeout waiting for ping' }
8
- Timeout: 6,
9
- };
10
1
  export {};
@@ -1,10 +1,10 @@
1
1
  import { RtpPacket } from 'werift';
2
2
  import { FfmpegProcess, reservePorts, RtpSplitter, } from '@homebridge/camera-utils';
3
3
  import { firstValueFrom, ReplaySubject, Subject } from 'rxjs';
4
- import { getFfmpegPath } from "../ffmpeg.js";
5
- import { logDebug, logError } from "../util.js";
4
+ import { getFfmpegPath } from '../ffmpeg.js';
5
+ import { logDebug, logError } from '../util.js';
6
6
  import { concatMap, map, mergeWith, take } from 'rxjs/operators';
7
- import { Subscribed } from "../subscribed.js";
7
+ import { Subscribed } from '../subscribed.js';
8
8
  function getCleanSdp(sdp, includeVideo) {
9
9
  return sdp
10
10
  .split('\nm=')
@@ -1,9 +1,9 @@
1
1
  import { WebSocket } from 'undici';
2
2
  import { firstValueFrom, fromEvent, interval, ReplaySubject, Subject, } from 'rxjs';
3
3
  import { concatMap, take } from 'rxjs/operators';
4
- import { generateUuid, logDebug, logError, logInfo } from "../util.js";
5
- import { WeriftPeerConnection, } from "./peer-connection.js";
6
- import { Subscribed } from "../subscribed.js";
4
+ import { generateUuid, logDebug, logError, logInfo } from '../util.js';
5
+ import { WeriftPeerConnection, } from './peer-connection.js';
6
+ import { Subscribed } from '../subscribed.js';
7
7
  export class WebrtcConnection extends Subscribed {
8
8
  onSessionId = new ReplaySubject(1);
9
9
  onOfferSent = new ReplaySubject(1);
@@ -143,6 +143,9 @@ export class WebrtcConnection extends Subscribed {
143
143
  sdpMLineIndex: message.body.mlineindex,
144
144
  });
145
145
  return;
146
+ case 'ping':
147
+ this.sendSessionMessage('pong');
148
+ return;
146
149
  case 'pong':
147
150
  return;
148
151
  case 'notification': {
@@ -239,7 +242,7 @@ export class WebrtcConnection extends Subscribed {
239
242
  });
240
243
  this.ws.close();
241
244
  }
242
- catch (_) {
245
+ catch {
243
246
  // ignore any errors since we are stopping the call
244
247
  }
245
248
  this.hasEnded = true;
package/lib/util.d.ts CHANGED
@@ -12,6 +12,7 @@ export declare function enableDebug(): void;
12
12
  export declare function generateUuid(seed?: string): string;
13
13
  export declare function getHardwareId(systemId?: string): Promise<string>;
14
14
  export declare function requestInput(question: string): Promise<string>;
15
+ export declare function requestHiddenInput(question: string): Promise<string>;
15
16
  export declare function stringify(data: any): string;
16
17
  export declare function mapAsync<T, U>(records: T[], asyncMapper: (record: T) => Promise<U>): Promise<U[]>;
17
18
  export declare function randomInteger(): number;
package/lib/util.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import debug from 'debug';
2
2
  import colors from 'colors';
3
3
  import { createInterface } from 'readline';
4
+ import { Writable } from 'stream';
4
5
  import { v4 as generateRandomUuid, v5 as generateUuidFromNamespace } from 'uuid';
5
6
  import { uuid as getSystemUuid } from 'systeminformation';
6
7
  import pushReceiverLogger from '@eneris/push-receiver/dist/utils/logger.js';
@@ -80,6 +81,27 @@ export async function requestInput(question) {
80
81
  lineReader.close();
81
82
  return answer.trim();
82
83
  }
84
+ export async function requestHiddenInput(question) {
85
+ const lineReader = createInterface({
86
+ input: process.stdin,
87
+ // Discard readline's echo of each keystroke so the input stays hidden
88
+ output: new Writable({
89
+ write(_chunk, _encoding, callback) {
90
+ callback();
91
+ },
92
+ }),
93
+ // Puts a TTY stdin in raw mode so the terminal doesn't echo the input either
94
+ terminal: true,
95
+ });
96
+ process.stdout.write(question);
97
+ const answer = await new Promise((resolve) => {
98
+ lineReader.question('', resolve);
99
+ });
100
+ lineReader.close();
101
+ // The newline readline writes when enter is pressed was discarded with the echo
102
+ process.stdout.write('\n');
103
+ return answer.trim();
104
+ }
83
105
  export function stringify(data) {
84
106
  if (typeof data === 'string') {
85
107
  return data;
@@ -107,7 +129,7 @@ function logPushReceiver(...args) {
107
129
  return;
108
130
  }
109
131
  }
110
- catch (_) {
132
+ catch {
111
133
  // proceed to log error
112
134
  }
113
135
  logDebug('[Push Receiver]');
package/package.json CHANGED
@@ -1,48 +1,68 @@
1
1
  {
2
2
  "name": "ring-client-api",
3
- "version": "14.3.0",
3
+ "version": "15.0.0-beta.0",
4
4
  "description": "Unofficial API for Ring doorbells, cameras, security alarm system and smart lighting",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
7
7
  "exports": {
8
- ".": "./lib/index.js",
9
- "./ffmpeg": "./lib/ffmpeg.js",
10
- "./util": "./lib/util.js",
11
- "./rest-client": "./lib/rest-client.js",
12
- "./streaming/streaming-session": "./lib/streaming/streaming-session.js"
8
+ ".": {
9
+ "types": "./lib/index.d.ts",
10
+ "ring-dev": "./index.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./ffmpeg": {
14
+ "types": "./lib/ffmpeg.d.ts",
15
+ "ring-dev": "./ffmpeg.ts",
16
+ "default": "./lib/ffmpeg.js"
17
+ },
18
+ "./util": {
19
+ "types": "./lib/util.d.ts",
20
+ "ring-dev": "./util.ts",
21
+ "default": "./lib/util.js"
22
+ },
23
+ "./rest-client": {
24
+ "types": "./lib/rest-client.d.ts",
25
+ "ring-dev": "./rest-client.ts",
26
+ "default": "./lib/rest-client.js"
27
+ },
28
+ "./streaming/streaming-session": {
29
+ "types": "./lib/streaming/streaming-session.d.ts",
30
+ "ring-dev": "./streaming/streaming-session.ts",
31
+ "default": "./lib/streaming/streaming-session.js"
32
+ }
13
33
  },
14
34
  "scripts": {
15
35
  "build": "rm -rf lib && tsc --declaration && chmod +x ./lib/ring-*-cli.js && rm -rf ./lib/test",
16
- "dev": "tsc --declaration --watch --preserveWatchOutput",
36
+ "auth-cli": "node --conditions=ring-dev --env-file-if-exists=../../.env ./ring-auth-cli.ts",
37
+ "device-data-cli": "node --conditions=ring-dev --env-file-if-exists=../../.env ./ring-device-data-cli.ts",
17
38
  "lint": "eslint .",
18
39
  "test": "vitest run"
19
40
  },
20
41
  "dependencies": {
21
- "@eneris/push-receiver": "4.3.0",
42
+ "@eneris/push-receiver": "4.4.0",
22
43
  "@homebridge/camera-utils": "^3.0.0",
23
44
  "colors": "1.4.0",
24
45
  "debug": "^4.4.3",
25
46
  "json-bigint": "^1.0.0",
26
47
  "rxjs": "^7.8.2",
27
- "systeminformation": "^5.30.7",
28
- "undici": "^7.19.2",
29
- "uuid": "^13.0.0",
30
- "werift": "0.22.4"
48
+ "systeminformation": "^5.33.19",
49
+ "undici": "^8.12.3",
50
+ "uuid": "^14.0.3",
51
+ "werift": "0.26.0"
31
52
  },
32
53
  "devDependencies": {
33
- "@types/debug": "4.1.12",
54
+ "@types/debug": "4.1.13",
34
55
  "@types/json-bigint": "^1.0.4",
35
- "@types/node": "25.1.0",
36
- "@types/uuid": "11.0.0",
37
- "@types/ws": "^8.18.1",
56
+ "@types/node": "26.6.6",
57
+ "@types/ws": "^8.18.2",
38
58
  "eslint-config-shared": "*",
39
- "msw": "^2.12.7",
59
+ "msw": "^3.0.3",
40
60
  "tsconfig": "*",
41
- "typescript": "5.9.3",
42
- "vitest": "^4.0.18"
61
+ "typescript": "7.0.2",
62
+ "vitest": "^5.1.0"
43
63
  },
44
64
  "engines": {
45
- "node": "^20 || ^22 || ^24"
65
+ "node": "^22.19.0 || ^24 || ^26"
46
66
  },
47
67
  "repository": {
48
68
  "type": "git",