From 1c4b7c4dad3d51d1dc360035f1c9faaa17987f87 Mon Sep 17 00:00:00 2001 From: Eli Fayerman Date: Wed, 17 Jun 2026 12:26:32 -0400 Subject: [PATCH 1/5] Add initial Ring Camera Recorder codebase Self-hosted tool that records the live view from Ring cameras you own to local disk, without a Ring Protect subscription. Includes: - Interactive 2FA auth with automatic refresh-token rotation/persistence - Motion/ding-triggered recording, plus manual one-shot capture - Fragmented-MP4 output (interrupted clips stay playable) - Configurable retention, layered config, CLI (list/record/verify) - Hermetic tests (filename, retention, trigger state machine) - systemd + pm2 service units, CI workflow, LICENSE/NOTICE/SECURITY docs --- .github/workflows/ci.yml | 26 + .gitignore | 15 + LICENSE | 21 + NOTICE | 36 + README.md | 177 +++ SECURITY.md | 41 + assets/hero.jpg | Bin 0 -> 164427 bytes config.example.json | 11 + deploy/ecosystem.config.cjs | 23 + deploy/ring-camera-recorder.service | 34 + package-lock.json | 2009 +++++++++++++++++++++++++++ package.json | 34 + src/auth.ts | 84 ++ src/cli.ts | 93 ++ src/config.ts | 109 ++ src/events.ts | 83 ++ src/files.ts | 65 + src/index.ts | 48 + src/log.ts | 26 + src/recorder.ts | 101 ++ src/retention.ts | 28 + src/ring.ts | 79 ++ src/verify.ts | 156 +++ test/logic.test.mjs | 84 ++ tsconfig.json | 18 + 25 files changed, 3401 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 assets/hero.jpg create mode 100644 config.example.json create mode 100644 deploy/ecosystem.config.cjs create mode 100644 deploy/ring-camera-recorder.service create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/auth.ts create mode 100644 src/cli.ts create mode 100644 src/config.ts create mode 100644 src/events.ts create mode 100644 src/files.ts create mode 100644 src/index.ts create mode 100644 src/log.ts create mode 100644 src/recorder.ts create mode 100644 src/retention.ts create mode 100644 src/ring.ts create mode 100644 src/verify.ts create mode 100644 test/logic.test.mjs create mode 100644 tsconfig.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c576477 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + build-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Type-check / build + run: npm run build + + - name: Run hermetic tests + run: npm test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f1a4bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +node_modules/ +dist/ +recordings/ +*.log + +# Raw image-generator output (the finished hero lives in assets/). +nanobanana-output/ + +# Secrets — the refresh token grants full account access. Never commit it. +.ring-token.json +.env +config.local.json + +# Stray image-generator outputs in assets (keep only curated hero.jpg) +assets/*.png diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0156a19 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Eli Fayerman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..5238832 --- /dev/null +++ b/NOTICE @@ -0,0 +1,36 @@ +Ring Local Recorder +Copyright (c) 2026 Eli Fayerman +Licensed under the MIT License (see LICENSE). + +================================================================================ +NOT AFFILIATED WITH RING +================================================================================ + +This project is an independent, community-built tool. It is NOT affiliated with, +authorized, maintained, sponsored, or endorsed by Ring LLC, Amazon.com, Inc., or +any of their subsidiaries or affiliates. "Ring" and related marks are trademarks +of their respective owners and are used here only for identification purposes. + +================================================================================ +THIRD-PARTY COMPONENTS +================================================================================ + +This software depends on, but does not redistribute, the following packages. +They are installed from the npm registry at install time under their own +licenses: + + - ring-client-api (MIT): https://github.com/dgreif/ring + Provides the Ring account/streaming integration. Uses Ring's unofficial + API; see the legal notes in README.md. + - rxjs (Apache-2.0) + - werift (MIT): WebRTC transport + - @homebridge/camera-utils (MIT) + - @eneris/push-receiver (MIT): push notification delivery + +ffmpeg: + Recording relies on ffmpeg. By default the binary is provided at install time + by the "ffmpeg-for-homebridge" package; alternatively you may point the tool at + a system ffmpeg via the RING_FFMPEG environment variable. ffmpeg is licensed + under the LGPL/GPL depending on how it was built. This project does NOT bundle + or redistribute any ffmpeg binary; it invokes whatever ffmpeg is present on + the host. See https://ffmpeg.org/legal.html. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7abaec8 --- /dev/null +++ b/README.md @@ -0,0 +1,177 @@ +# Ring Camera Recorder + +

+ Ring Camera Recorder: self-hosted local recording for your Ring cameras +

+ +Self-hosted tool that **locally records the live view from Ring cameras you own**. It captures +the freely available live stream (not Ring's paid cloud storage), so it works **without a Ring +Protect subscription**. It records your own cameras and your own footage. Read the +[Legal & responsible use](#legal--responsible-use) section before using or sharing it. + +> **You are recording the live stream, not downloading cloud history.** Without a Ring +> Protect subscription, Ring stores nothing in the cloud, so there is no event history to +> retrieve. This tool grabs the *live view* feed (the same one the app shows you for free) +> and writes it to disk, either on demand or automatically when a motion/doorbell event +> fires. That is the only no-subscription path. + +## What it does + +- Authenticates to your Ring account once (email + password + 2FA) and stores a + **refresh token**. Your password is never saved or hardcoded. +- Lists your cameras. +- Records the live stream to `recordings/{camera}_{ISO8601}.mp4`. +- Auto-records a clip when a camera reports **motion** or a doorbell **ding**. +- Manual "record now for N seconds" and continuous modes. +- Optional retention: delete clips older than N days. + +## Requirements + +- Node.js >= 20 (tested on 22). +- `ffmpeg` available on `PATH` (`ffmpeg -version` should work). +- A Ring account with at least one camera. + +## Setup + +```bash +npm install + +# One-time interactive login. Prompts for email, password, and the 2FA code Ring +# texts/emails you, then writes the refresh token to .ring-token.json. +npm run auth + +# Optional: copy and edit the config. +cp config.example.json config.json + +# List your cameras (confirms auth works and shows names/ids for the `cameras` filter). +npm run list +``` + +## Usage + +```bash +# Long-running service: watches for motion/ding and auto-records per config.json. +npm run build && npm start +# (during development you can skip the build step with: npm run dev) + +# Record a single clip right now (default camera selection, default length): +npm run record -- --camera "Front Door" --seconds 30 + +# Full end-to-end verification (run after `npm run auth`): authenticates, lists +# cameras, captures a clip, probes it for video/audio, and (optionally) waits for +# a real motion/ding to confirm auto-recording. Trigger motion when prompted. +npm run verify -- --watch-motion 60 +``` + +## Tests + +```bash +npm test # builds, then runs hermetic logic tests (no Ring account needed) +``` + +Covers the filename convention, retention pruning, and the motion/ding trigger state +machine (rising-edge detection, no-overlap-while-recording, cooldown suppression, +doorbell-only-on-doorbots). The live network path is exercised separately by +`npm run verify` once you have a token. + +## Configuration + +Config is layered: built-in defaults < `config.json` < `config.local.json` < environment +variables. See `config.example.json`. Key fields: + +| Field | Default | Meaning | +|-------|---------|---------| +| `tokenPath` | `.ring-token.json` | Where the refresh token lives. | +| `outputDir` | `recordings` | Where clips are written. | +| `clipLengthSeconds` | `30` | Default clip length for events + manual record. | +| `cameras` | `"all"` | `"all"` or an array of name substrings / numeric ids. | +| `recordOnMotion` | `true` | Auto-record on motion events. | +| `recordOnDing` | `true` | Auto-record on doorbell press. | +| `motionCooldownSeconds` | `20` | Min gap between auto-recordings per camera. | +| `retentionDays` | `null` | Delete clips older than N days. `null` = keep all. | +| `retentionSweepMinutes` | `60` | How often retention runs while the service is up. | + +Environment overrides: `RING_TOKEN_PATH`, `RING_OUTPUT_DIR`, `RING_CLIP_SECONDS`, +`RING_RETENTION_DAYS`, `RING_DEBUG=1`. + +## Triggering policy + +When a motion/ding event fires, `src/events.ts` decides whether to start a clip. The +default `shouldTrigger()` policy: + +- **Skips** if a recording for that camera is already running (no overlapping ffmpeg jobs). +- Enforces `motionCooldownSeconds` between the *start* of consecutive clips. + +If you want "extend while motion persists" instead of fixed-length clips, or a different +cooldown, edit `shouldTrigger()`; it's deliberately isolated for that. One +no-subscription caveat: a clip can only begin *after* the event arrives, so there's no +pre-roll buffer (you lose the ~1-2s before the trigger). + +## Running as a background service + +- **systemd**: see `deploy/ring-camera-recorder.service` (header has install steps). +- **pm2**: `pm2 start deploy/ecosystem.config.cjs`. + +Run `npm run auth` manually first: the login is interactive and the service only reads +the saved token. + +## Limitations (no subscription) + +- **Live-only.** No access to past clips / cloud event history; those require Ring Protect. +- Motion/ding **push notifications are still delivered free**, which is what triggers + auto-recording. +- Battery-powered cameras spin up the radio for each live stream, so frequent + motion-triggered recording will drain the battery faster than passive monitoring. +- Ring may throttle accounts that open live streams very aggressively; keep clip lengths + and cooldowns reasonable for 24/7 use, or use a locally-streaming camera instead. + +## Security + +The refresh token in `.ring-token.json` grants full access to your Ring account. It is +gitignored, written with `0600` permissions, and rotated tokens are persisted +automatically. Treat that file like a password. + +`npm audit` reports a high-severity advisory in a transitive WebRTC dependency (`ip` via +`werift`). It is **not** in this project's code, is low-risk for personal use, and +**cannot be fixed without breaking streaming**. Do not run `npm audit fix --force`. See +[`SECURITY.md`](./SECURITY.md) for the full assessment and how to report issues. + +## Contributing + +Issues and pull requests are welcome. This is a small, best-effort project maintained in +spare time. Keep PRs focused, and note that the Ring integration depends on an +unofficial upstream API that can break without notice (see [`SECURITY.md`](./SECURITY.md)). +For anything security-related, follow `SECURITY.md` rather than opening a public issue. + +By contributing, you agree your contributions are licensed under the MIT License (see +[`LICENSE`](./LICENSE)). + +## Legal & responsible use + +**This is not legal advice.** Use this tool at your own risk. By using it you accept +responsibility for complying with the laws and agreements that apply to you. + +- **Your own cameras only.** Use this exclusively with cameras on a Ring account you + control, authenticating with your own credentials. It is a tool for recording *your own* + footage, not for accessing anyone else's account, cameras, or data. + +- **Audio recording / consent.** Clips include **audio**. Many jurisdictions have + two-party / all-party consent ("wiretapping") laws. In some U.S. states and other + countries, recording other people's audio without consent can carry **civil and even + criminal** liability, independent of Ring. Recording your own property is generally fine; + capturing other people's conversations may not be. Know your local laws before recording, + and consider disabling audio where required. + +- **Ring Terms of Service.** This relies on `ring-client-api`, which uses Ring's + **unofficial** API. Automated/unofficial access may violate Ring's / Amazon's Terms of + Service, and **Ring could suspend accounts** that use it. There is no guarantee of + continued access; Ring can change or block the API at any time (e.g. the 2026 + `/oauth/v2` auth migration; see the upstream `dgreif/ring` issues). + +- **Not affiliated with Ring.** This project is independent and is **not** affiliated with, + authorized, endorsed, or sponsored by Ring LLC or Amazon. "Ring" is a trademark of its + owner, used here only to describe interoperability. See [`NOTICE`](./NOTICE). + +- **No warranty.** Provided "as is" under the [MIT License](./LICENSE), without warranty of + any kind. The authors are not liable for account suspensions, lost recordings, missed + events, or any other damages arising from use. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..9526925 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ +# Security + +## Reporting a vulnerability + +Please report security issues privately via GitHub's "Report a vulnerability" +(Security Advisories) on this repository, or by opening a minimal issue that does +**not** include any secrets, tokens, or personal footage. We'll respond as soon as +we reasonably can. This is a volunteer, best-effort project (see the no-warranty +note in [LICENSE](./LICENSE)). + +## Handling of credentials + +- This tool never stores your Ring password. The interactive login exchanges it for + a **refresh token**, written to `.ring-token.json` with `0600` permissions. +- That token grants full access to your Ring account; treat it like a password. +- `.ring-token.json`, `.env`, and `config.local.json` are git-ignored. **Never commit + them.** Run `git status` before your first commit to confirm. +- Rotated tokens are persisted automatically; the token value is never written to logs. +- `RING_DEBUG=1` enables verbose `ring-client-api` logging, which may include sensitive + request details. Use it only for local troubleshooting, never in shared logs. + +## Known dependency advisory (`npm audit`) + +`npm audit` reports high-severity findings that all trace to a single transitive +dependency: the `ip` package's `isPublic` SSRF miscategorization +([GHSA-2p57-rm9w-gvfp](https://github.com/advisories/GHSA-2p57-rm9w-gvfp)), pulled in +via `werift` / `werift-ice` (the WebRTC NAT-traversal stack) beneath `ring-client-api`. + +- **Not in this project's code.** Nothing here calls `ip` directly; it lives in the + WebRTC ICE candidate layer. +- **Do NOT run `npm audit fix --force`.** Its only "fix" downgrades `ring-client-api` + to `9.13.0` (pre-WebRTC), which breaks streaming entirely. This project intentionally + pins `ring-client-api@14.3.0`. +- **Cannot be resolved downstream.** The current latest `ring-client-api` still depends + on the affected versions; it will clear once `werift` updates upstream. +- **Low practical risk for this use case.** The `ip` SSRF is relevant when an attacker + controls connection targets and the app relies on `ip.isPublic()` for SSRF protection. + Here, the WebRTC session connects to *your own* Ring account over authenticated, + DTLS-encrypted media; there is no untrusted-peer attack surface in a personal recorder. + +We track this and will bump `ring-client-api` when an upstream release clears the advisory. diff --git a/assets/hero.jpg b/assets/hero.jpg new file mode 100644 index 0000000000000000000000000000000000000000..11bdf31da273733da2a2d27d27a5369ca6ced6ce GIT binary patch literal 164427 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

K}h6M%-^u>qe!sTjk&Z2kOl=6r^ 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%0Aych8`tEHNq#IIwKX&*A+}oE)Mwu_ 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 z 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ΝUvBLSiaj7rQ6@w60VaqY7DPpt2^4KIelXVV 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

!x{Ci}-S@j> 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 apBg7@cQ!QK`^{(j9@_j`}BYROz2er literal 0 HcmV?d00001 diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..9c31783 --- /dev/null +++ b/config.example.json @@ -0,0 +1,11 @@ +{ + "tokenPath": ".ring-token.json", + "outputDir": "recordings", + "clipLengthSeconds": 30, + "cameras": "all", + "recordOnMotion": true, + "recordOnDing": true, + "motionCooldownSeconds": 20, + "retentionDays": 14, + "retentionSweepMinutes": 60 +} diff --git a/deploy/ecosystem.config.cjs b/deploy/ecosystem.config.cjs new file mode 100644 index 0000000..a95cab1 --- /dev/null +++ b/deploy/ecosystem.config.cjs @@ -0,0 +1,23 @@ +// pm2 process definition. Usage: +// npm install && npm run build +// npm run auth # interactive, once +// pm2 start deploy/ecosystem.config.cjs +// pm2 logs ring-camera-recorder +// pm2 save && pm2 startup # survive reboots +module.exports = { + apps: [ + { + name: 'ring-camera-recorder', + script: 'dist/index.js', + cwd: __dirname + '/..', + autorestart: true, + restart_delay: 10000, + max_restarts: 20, + env: { + // RING_OUTPUT_DIR: '/var/lib/ring-recordings', + // RING_RETENTION_DAYS: '14', + // RING_DEBUG: '1', + }, + }, + ], +}; diff --git a/deploy/ring-camera-recorder.service b/deploy/ring-camera-recorder.service new file mode 100644 index 0000000..67ff527 --- /dev/null +++ b/deploy/ring-camera-recorder.service @@ -0,0 +1,34 @@ +# systemd unit for Ring Camera Recorder. +# +# Install: +# 1. Build first: npm install && npm run build +# 2. Edit User/paths below to match your machine. +# 3. sudo cp deploy/ring-camera-recorder.service /etc/systemd/system/ +# 4. sudo systemctl daemon-reload +# 5. sudo systemctl enable --now ring-camera-recorder +# 6. journalctl -u ring-camera-recorder -f # follow logs +# +# Note: run `npm run auth` MANUALLY once (it is interactive) before enabling the +# service. The service only reads the already-saved refresh token. + +[Unit] +Description=Ring Camera Recorder (local live recording) +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +# Replace with the unprivileged user and the absolute path where you cloned this repo. +User=youruser +WorkingDirectory=/home/youruser/ring-camera-recorder +ExecStart=/usr/bin/node dist/index.js +Restart=on-failure +RestartSec=10 +# Token rotation writes back to the token file, so the working dir must be writable. +# Environment overrides (optional) — these win over config.json: +# Environment=RING_OUTPUT_DIR=/var/lib/ring-recordings +# Environment=RING_RETENTION_DAYS=14 +# Environment=RING_DEBUG=1 + +[Install] +WantedBy=multi-user.target diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4a6ce6f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2009 @@ +{ + "name": "ring-camera-recorder", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ring-camera-recorder", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "ring-client-api": "14.3.0" + }, + "bin": { + "ring-camera-recorder": "dist/cli.js" + }, + "devDependencies": { + "@types/node": "^22.9.0", + "tsx": "^4.19.2", + "typescript": "^5.6.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eneris/push-receiver": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@eneris/push-receiver/-/push-receiver-4.3.0.tgz", + "integrity": "sha512-COn7fZIh1P+3FYEpE87E2KoACW/gYTxwURykYqQ1lKMO1O/vdfrWP4ECsew/Kto9Mec34J46hvD5I/2nNa8kfA==", + "license": "MIT", + "dependencies": { + "http_ece": "^1.2.1", + "jsonwebtoken": "^9.0.2", + "long": "^5.2.3", + "protobufjs": "^7.4.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fidm/asn1": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@fidm/asn1/-/asn1-1.0.4.tgz", + "integrity": "sha512-esd1jyNvRb2HVaQGq2Gg8Z0kbQPXzV9Tq5Z14KNIov6KfFD6PTaRIO8UpcsYiTNzOqJpmyzWgVTrUwFV3UF4TQ==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@fidm/x509": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fidm/x509/-/x509-1.2.1.tgz", + "integrity": "sha512-nwc2iesjyc9hkuzcrMCBXQRn653XuAUKorfWM8PZyJawiy1QzLj4vahwzaI25+pfpwOLvMzbJ0uKpWLDNmo16w==", + "license": "MIT", + "dependencies": { + "@fidm/asn1": "^1.0.4", + "tweetnacl": "^1.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@homebridge/camera-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@homebridge/camera-utils/-/camera-utils-3.0.0.tgz", + "integrity": "sha512-lw+DflCBEgbcfeBfXrPGhbzYn63bj61hpETVwqTO3FCThIMa3VoFDUNEy7jQq6OBS+iaMeHSuv4gy6w8yxFK7A==", + "license": "MIT", + "dependencies": { + "execa": "^9.5.1", + "ffmpeg-for-homebridge": "^2.1.7", + "pick-port": "^2.1.0", + "rxjs": "^7.8.1", + "systeminformation": "^5.23.5" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, + "node_modules/@minhducsun2002/leb128": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@minhducsun2002/leb128/-/leb128-1.0.0.tgz", + "integrity": "sha512-eFrYUPDVHeuwWHluTG1kwNQUEUcFjVKYwPkU8z9DR1JH3AW7JtJsG9cRVGmwz809kKtGfwGJj58juCZxEvnI/g==", + "license": "MIT" + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.8.0.tgz", + "integrity": "sha512-NgekZOrSJFSBFLFoLfwePguAWAx7z1+f2TEsWFUMyiqqfntZ4+S/S5hzqME3q4pCA0iOsFKdwiQ35dwY24eVqA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "@peculiar/asn1-x509-attr": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.8.0.tgz", + "integrity": "sha512-akbF8+uvleHs8sejNPQxwmVFuInAg6FMNHOwMILXfP518YfFJwdR3jr6oNUPOaEJfuEhn/vkNOCIT6ASUd4mbg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.8.0.tgz", + "integrity": "sha512-ohwlk+u9Rv2NOAY1c6MfHj45ATVF8R1DUN/WCgABiRtLi2ZftlZWZX7KvpAbU8v9xPcmoILfELeEABj/rn18AQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.8.0.tgz", + "integrity": "sha512-5yof1ytoB++RQtaFbqSUJ8pxDJtZT6vbVqZ8XoJ61ph7UjNVvfFwAilnCodqkNsAodpy13gDhoxZXw00pghnyg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.8.0", + "@peculiar/asn1-pkcs8": "^2.8.0", + "@peculiar/asn1-rsa": "^2.8.0", + "@peculiar/asn1-schema": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.8.0.tgz", + "integrity": "sha512-qAKXtLpBEw9LqhKpjw3ajZSXlBur+ipW+y2ivVBQAG6F6qRx94yO+1ZR4mvw+YaCfKSaOzLeYEzsPaBp4SJELA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.8.0.tgz", + "integrity": "sha512-b5nDWCnkV60+cQ141D6sVVwK9nz64R5n3zSVnklGd+ECdkW2Ol3U1a6yYFlalpSOaD557yuJB64A+q42jG7lUQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.8.0", + "@peculiar/asn1-pfx": "^2.8.0", + "@peculiar/asn1-pkcs8": "^2.8.0", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "@peculiar/asn1-x509-attr": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.8.0.tgz", + "integrity": "sha512-zHEUlCqB2mk7x2lxDwHHJy7hWZOPdGHVlsmITWKB5/PbQo61atbu9PJ/0r9dQNMwFzbKPXZ8uK8/91eUhRznSg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.8.0.tgz", + "integrity": "sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==", + "license": "MIT", + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.8.0.tgz", + "integrity": "sha512-N0CMuhWUzsWEVq6F1q9X6+VKUnWzSW+cSVg+aPaGGwDdbFoFWTYgin5MHwXgpWd6y9COMBxnfy/Qc+Xc7F0Zwg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.8.0.tgz", + "integrity": "sha512-tHjkfS/qhMnmrlB2J9NhflQlQ7In3khO3CfmVrriOlpTeErY9ZIKOso1hQ5JQiyrJ7ShvqVPk7E5fQmbclkSKA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@shinyoshiaki/binary-data": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@shinyoshiaki/binary-data/-/binary-data-0.6.1.tgz", + "integrity": "sha512-7HDb/fQAop2bCmvDIzU5+69i+UJaFgIVp99h1VzK1mpg1JwSODOkjbqD7ilTYnqlnadF8C4XjpwpepxDsGY6+w==", + "license": "MIT", + "dependencies": { + "generate-function": "^2.3.1", + "is-plain-object": "^2.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@shinyoshiaki/jspack": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@shinyoshiaki/jspack/-/jspack-0.0.6.tgz", + "integrity": "sha512-SdsNhLjQh4onBlyPrn4ia1Pdx5bXT88G/LIEpOYAjx2u4xeY/m/HB5yHqlkJB1uQR3Zw4R3hBWLj46STRAN0rg==" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/node": { + "version": "22.19.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.21.tgz", + "integrity": "sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/aes-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz", + "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==", + "license": "MIT" + }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/ffmpeg-for-homebridge": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ffmpeg-for-homebridge/-/ffmpeg-for-homebridge-2.2.2.tgz", + "integrity": "sha512-A1bmqv9BTrpGZPCQ0XpWk4gmzheBGEv3eFQDKBKA74hwG/tJdt6JqcF+jlmlp5s/OqRdDrEsyveL2T+GvNf/fA==", + "hasInstallScript": true, + "dependencies": { + "tar": "^7.5.12" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/http_ece": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/http_ece/-/http_ece-1.2.1.tgz", + "integrity": "sha512-+tzLoMYgXvicu60sVFoswTiu6BiQ6EX3DORRJQ3W2dNpNWCyZ3tcmRFZZ3jgVyw8ziWUCeUARKCkYDY6JgFx+w==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/int64-buffer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-1.1.0.tgz", + "integrity": "sha512-94smTCQOvigN4d/2R/YDjz8YVG0Sufvv2aAh8P5m42gwhCsDAJqnbNOrxJsrADuAFAA69Q/ptGzxvNcNuIJcvw==", + "license": "MIT" + }, + "node_modules/ip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", + "license": "MIT" + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mp4box": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mp4box/-/mp4box-0.5.4.tgz", + "integrity": "sha512-GcCH0fySxBurJtvr0dfhz0IxHZjc1RP+F+I8xw+LIwkU1a+7HJx8NCDiww1I5u4Hz6g4eR1JlGADEGJ9r4lSfA==", + "license": "BSD-3-Clause" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pick-port": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/pick-port/-/pick-port-2.2.1.tgz", + "integrity": "sha512-uc29LvLYMTwN8chzRVwv8D9rkIa3hj7rvjX57JeBJ3VJpWK/3j0BxuZ5VwklAEgulpiDtPfxbwnaH952MyDBCQ==", + "license": "ISC", + "dependencies": { + "debug": "^4.4.3" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mediasoup" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/protobufjs": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", + "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/ring-client-api": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/ring-client-api/-/ring-client-api-14.3.0.tgz", + "integrity": "sha512-JUGDMvAv3Y5DbaYbYYvr9JIeHonrtCmS46C/vRahg0OshnsOyurH+wG7+3os4caXhnQnTlikL0+lc4xNNKE+Mg==", + "funding": [ + { + "type": "paypal", + "url": "https://www.paypal.me/dustingreif" + }, + { + "type": "github", + "url": "https://github.com/sponsors/dgreif" + } + ], + "license": "MIT", + "dependencies": { + "@eneris/push-receiver": "4.3.0", + "@homebridge/camera-utils": "^3.0.0", + "colors": "1.4.0", + "debug": "^4.4.3", + "json-bigint": "^1.0.0", + "rxjs": "^7.8.2", + "systeminformation": "^5.30.7", + "undici": "^7.19.2", + "uuid": "^13.0.0", + "werift": "0.22.4" + }, + "bin": { + "ring-auth-cli": "lib/ring-auth-cli.js", + "ring-device-data-cli": "lib/ring-device-data-cli.js" + }, + "engines": { + "node": "^20 || ^22 || ^24" + } + }, + "node_modules/rx.mini": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/rx.mini/-/rx.mini-1.4.0.tgz", + "integrity": "sha512-8w5cSc1mwNja7fl465DXOkVvIOkpvh2GW4jo31nAIvX4WTXCsRnKJGUfiDBzWtYRInEcHAUYIZfzusjIrea8gA==" + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/systeminformation": { + "version": "5.31.7", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.7.tgz", + "integrity": "sha512-/8NC53e5nP9nmhn42/ncdOkyJnOoue/Vy+tJOyUGd1Yv66G069wK4rrziwhrqDETgk78CudTQupw5z19S5uoZw==", + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, + "node_modules/tar": { + "version": "7.5.16", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", + "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/turbo-crc32": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/turbo-crc32/-/turbo-crc32-1.0.1.tgz", + "integrity": "sha512-8yyRd1ZdNp+AQLGqi3lTaA2k81JjlIZOyFQEsi7GQWBgirnQOxjqVtDEbYHM2Z4yFdJ5AQw0fxBLLnDCl6RXoQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/uuid": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.2.tgz", + "integrity": "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/werift": { + "version": "0.22.4", + "resolved": "https://registry.npmjs.org/werift/-/werift-0.22.4.tgz", + "integrity": "sha512-N/Jgh82cjhxTusQSJzAmgQhrqPcUrytlybrh45VoggusHEsw7Q8S8uhPD+5nvgDM8FYyjJ0BcMGg5vbvalWi0A==", + "license": "MIT", + "dependencies": { + "@fidm/x509": "^1.2.1", + "@minhducsun2002/leb128": "^1.0.0", + "@noble/curves": "^1.8.1", + "@peculiar/x509": "^1.12.3", + "@shinyoshiaki/binary-data": "^0.6.1", + "@shinyoshiaki/jspack": "^0.0.6", + "aes-js": "^3.1.2", + "buffer": "^6.0.3", + "buffer-crc32": "^1.0.0", + "debug": "4.4.0", + "fast-deep-equal": "^3.1.3", + "int64-buffer": "1.1.0", + "ip": "^2.0.1", + "mp4box": "^0.5.3", + "multicast-dns": "^7.2.5", + "turbo-crc32": "^1.0.1", + "tweetnacl": "^1.0.3", + "werift-common": "*", + "werift-dtls": "*", + "werift-ice": "*", + "werift-rtp": "*", + "werift-sctp": "*" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/werift-common": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/werift-common/-/werift-common-0.0.3.tgz", + "integrity": "sha512-ma3E4BqKTyZVLhrdfTVs2T1tg9seeUtKMRn5e64LwgrogWa62+3LAUoLBUSl1yPWhgSkXId7GmcHuWDen9IJeQ==", + "license": "MIT", + "dependencies": { + "@shinyoshiaki/jspack": "^0.0.6", + "debug": "^4.4.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/werift-dtls": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/werift-dtls/-/werift-dtls-0.5.7.tgz", + "integrity": "sha512-z2fjbP7fFUFmu/Ky4bCKXzdgPTtmSY1DYi0TUf3GG2zJT4jMQ3TQmGY8y7BSSNGetvL4h3pRZ5un0EcSOWpPog==", + "license": "MIT", + "dependencies": { + "@fidm/x509": "^1.2.1", + "@noble/curves": "^1.3.0", + "@peculiar/x509": "^1.9.2", + "@shinyoshiaki/binary-data": "^0.6.1", + "date-fns": "^2.29.3", + "lodash": "^4.17.21", + "rx.mini": "^1.2.2", + "tweetnacl": "^1.0.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/werift-ice": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/werift-ice/-/werift-ice-0.2.2.tgz", + "integrity": "sha512-td52pHp+JmFnUn5jfDr/SSNO0dMCbknhuPdN1tFp9cfRj5jaktN63qnAdUuZC20QCC3ETWdsOthcm+RalHpFCQ==", + "license": "MIT", + "dependencies": { + "@shinyoshiaki/jspack": "^0.0.6", + "buffer-crc32": "^1.0.0", + "debug": "^4.3.4", + "int64-buffer": "^1.0.1", + "ip": "^2.0.1", + "lodash": "^4.17.21", + "multicast-dns": "^7.2.5", + "p-cancelable": "^2.1.1", + "rx.mini": "^1.2.2" + } + }, + "node_modules/werift-rtp": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/werift-rtp/-/werift-rtp-0.8.8.tgz", + "integrity": "sha512-GiYMSdvCyScQaw5bnEsraSoHUVZpjfokJAiLV4R1FsiB06t6XiebPYPpkqB9nYNNKiA8Z/cYWsym7wISq1sYSQ==", + "license": "MIT", + "dependencies": { + "@minhducsun2002/leb128": "^1.0.0", + "@shinyoshiaki/jspack": "^0.0.6", + "aes-js": "^3.1.2", + "buffer": "^6.0.3", + "mp4box": "^0.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/werift-sctp": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/werift-sctp/-/werift-sctp-0.0.11.tgz", + "integrity": "sha512-7109yuI5U7NTEHjqjn0A8VeynytkgVaxM6lRr1Ziv0D8bPcaB8A7U/P88M7WaCpWDoELHoXiRUjQycMWStIgjQ==", + "license": "MIT", + "dependencies": { + "@shinyoshiaki/jspack": "^0.0.6" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/werift/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9d694e6 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "ring-camera-recorder", + "version": "0.1.0", + "private": true, + "description": "Self-hosted tool to locally record the live view from Ring cameras you own. Captures the free live stream (not paid cloud storage), so it works without a Ring Protect subscription.", + "license": "MIT", + "author": "Eli Fayerman", + "type": "module", + "engines": { + "node": ">=20" + }, + "bin": { + "ring-camera-recorder": "./dist/cli.js" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node dist/index.js", + "dev": "tsx src/index.ts", + "auth": "tsx src/auth.ts", + "record": "tsx src/cli.ts record", + "list": "tsx src/cli.ts list", + "verify": "tsx src/verify.ts", + "test": "npm run build && node test/logic.test.mjs", + "typecheck": "tsc -p tsconfig.json --noEmit" + }, + "dependencies": { + "ring-client-api": "14.3.0" + }, + "devDependencies": { + "tsx": "^4.19.2", + "typescript": "^5.6.3", + "@types/node": "^22.9.0" + } +} diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000..ea5f0af --- /dev/null +++ b/src/auth.ts @@ -0,0 +1,84 @@ +import { createInterface, type Interface } from 'node:readline'; +import { RingRestClient } from 'ring-client-api/rest-client'; +import { loadConfig } from './config.js'; +import { writeToken, tokenFileExists } from './ring.js'; +import { log } from './log.js'; + +/** + * Interactive one-time login. Prompts for email + password + 2FA code, acquires a + * refresh token via Ring's auth API, and writes it to the configured token file. + * + * The token (not the password) is what the service uses thereafter, and Ring + * rotates it on every connect — see `onRefreshTokenUpdated` handling in ring.ts. + */ +async function main(): Promise { + const cfg = loadConfig(); + + if (tokenFileExists(cfg) && !process.argv.includes('--force')) { + log.warn(`A token already exists at ${cfg.tokenPath}. Re-run with --force to overwrite.`); + process.exit(0); + } + + const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true }); + try { + const email = (await question(rl, 'Ring email: ')).trim(); + const password = await hiddenQuestion(rl, 'Ring password: '); + + const client = new RingRestClient({ email, password }); + + let ok = false; + try { + await client.getAuth(); + ok = true; + } catch { + // Expected when 2FA is enabled: the first getAuth triggers the code being + // sent and sets promptFor2fa with a human-readable instruction. + if (!client.promptFor2fa) { + throw new Error('Login failed (check email/password). Ring did not request a 2FA code.'); + } + log.info(client.promptFor2fa); + const code = (await question(rl, '2FA code: ')).trim(); + await client.getAuth(code); + ok = true; + } + + const token = client.refreshToken; + if (!ok || !token) throw new Error('Authentication succeeded but no refresh token was returned.'); + + writeToken(cfg, token); + log.info(`Refresh token saved to ${cfg.tokenPath} (mode 0600). You can now run \`npm start\`.`); + } finally { + rl.close(); + } +} + +function question(rl: Interface, query: string): Promise { + return new Promise((resolve) => rl.question(query, resolve)); +} + +/** Prompt without echoing the typed characters (masks with '*'). */ +function hiddenQuestion(rl: Interface, query: string): Promise { + return new Promise((resolve) => { + const iface = rl as Interface & { output: NodeJS.WritableStream; _writeToOutput?: (s: string) => void }; + let muted = false; + const original = iface._writeToOutput?.bind(iface); + iface._writeToOutput = (s: string) => { + if (!muted) { + iface.output.write(s); + return; + } + // Echo a mask for typed chars but let control sequences (newline) pass. + iface.output.write(s.includes('\n') ? '\n' : '*'); + }; + rl.question(query, (answer) => { + iface._writeToOutput = original; + resolve(answer); + }); + muted = true; + }); +} + +main().catch((err) => { + log.error((err as Error).message); + process.exit(1); +}); diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..e952c1d --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,93 @@ +import { loadConfig } from './config.js'; +import { createRingApi } from './ring.js'; +import { recordClip } from './recorder.js'; +import { log } from './log.js'; + +/** + * Small CLI: + * ring-recorder list + * ring-recorder record --camera "Front Door" --seconds 30 + */ +async function main(): Promise { + const [, , command, ...rest] = process.argv; + + if (command !== 'list' && command !== 'record') { + log.error('Usage: ring-recorder '); + process.exit(2); + } + + const cfg = loadConfig(); + const api = createRingApi(cfg); // reads token; throws a friendly error if missing + + try { + if (command === 'list') { + const cameras = await api.getCameras(); + if (cameras.length === 0) { + log.warn('No cameras found on this account.'); + return; + } + log.info(`Found ${cameras.length} camera(s):`); + for (const c of cameras) { + const kind = c.isDoorbot ? 'doorbell' : 'camera'; + const battery = c.batteryLevel != null ? `${c.batteryLevel}%` : 'wired'; + process.stdout.write(` #${c.id} ${c.name} [${kind}, ${c.model}, battery: ${battery}]\n`); + } + return; + } + + if (command === 'record') { + const args = parseArgs(rest); + const seconds = args.seconds ? Number(args.seconds) : cfg.clipLengthSeconds; + if (!Number.isFinite(seconds) || seconds <= 0) throw new Error('--seconds must be a positive number'); + + const cameras = await api.getCameras(); + const match = pickCamera(cameras, args.camera); + if (!match) { + throw new Error( + `No camera matched ${args.camera ? `"${args.camera}"` : '(none specified)'}. ` + + `Available: ${cameras.map((c) => `${c.name} (#${c.id})`).join(', ')}`, + ); + } + const result = await recordClip(match, cfg, seconds); + log.info(`Done: ${result.path}`); + return; + } + } finally { + api.disconnect(); + // ffmpeg/WebRTC teardown can leave handles open briefly; exit cleanly. + setTimeout(() => process.exit(process.exitCode ?? 0), 1500); + } +} + +function parseArgs(argv: string[]): Record { + const out: Record = {}; + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + if (a.startsWith('--')) { + const key = a.slice(2); + const next = argv[i + 1]; + if (next && !next.startsWith('--')) { + out[key] = next; + i++; + } else { + out[key] = 'true'; + } + } + } + return out; +} + +function pickCamera(cameras: Awaited['getCameras']>>, sel?: string) { + if (!sel) return cameras[0]; // default to the first camera if unspecified + const byId = Number(sel); + if (Number.isFinite(byId)) { + const hit = cameras.find((c) => c.id === byId); + if (hit) return hit; + } + return cameras.find((c) => c.name.toLowerCase().includes(sel.toLowerCase())); +} + +main().catch((err) => { + log.error((err as Error).message); + process.exit(1); +}); diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..33ea8f8 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,109 @@ +import { readFileSync, existsSync } from 'node:fs'; +import { resolve, isAbsolute } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const PROJECT_ROOT = resolve(__dirname, '..'); + +export interface AppConfig { + /** Path to the JSON file holding the Ring refresh token. Created by `npm run auth`. */ + tokenPath: string; + /** Directory where recorded clips are written. */ + outputDir: string; + /** Default clip length in seconds for event-triggered and manual recordings. */ + clipLengthSeconds: number; + /** + * Camera selection. Either the string "all", or an array of camera names + * (case-insensitive substring match) / numeric device ids to include. + */ + cameras: 'all' | Array; + /** Start a recording automatically when a camera reports motion. */ + recordOnMotion: boolean; + /** Start a recording automatically when a doorbell is pressed (ding). */ + recordOnDing: boolean; + /** + * Minimum seconds between two automatic recordings for the SAME camera. + * Prevents a burst of motion events from spawning overlapping ffmpeg jobs. + */ + motionCooldownSeconds: number; + /** + * Delete clips older than this many days. 0 or null disables retention + * cleanup (keep everything). + */ + retentionDays: number | null; + /** How often (minutes) to run the retention sweep while the service runs. */ + retentionSweepMinutes: number; +} + +const DEFAULTS: AppConfig = { + tokenPath: '.ring-token.json', + outputDir: 'recordings', + clipLengthSeconds: 30, + cameras: 'all', + recordOnMotion: true, + recordOnDing: true, + motionCooldownSeconds: 20, + retentionDays: null, + retentionSweepMinutes: 60, +}; + +/** Resolve a possibly-relative path against the project root. */ +export function resolveFromRoot(p: string): string { + return isAbsolute(p) ? p : resolve(PROJECT_ROOT, p); +} + +/** + * Load config by layering: built-in DEFAULTS < config.json < config.local.json + * < environment variable overrides. Paths are resolved to absolute. + */ +export function loadConfig(): AppConfig { + let merged: AppConfig = { ...DEFAULTS }; + + for (const name of ['config.json', 'config.local.json']) { + const file = resolve(PROJECT_ROOT, name); + if (existsSync(file)) { + try { + const parsed = JSON.parse(readFileSync(file, 'utf8')); + merged = { ...merged, ...parsed }; + } catch (err) { + throw new Error(`Failed to parse ${name}: ${(err as Error).message}`); + } + } + } + + // Environment overrides (handy for systemd unit files / containers). + if (process.env.RING_TOKEN_PATH) merged.tokenPath = process.env.RING_TOKEN_PATH; + if (process.env.RING_OUTPUT_DIR) merged.outputDir = process.env.RING_OUTPUT_DIR; + if (process.env.RING_CLIP_SECONDS) merged.clipLengthSeconds = num(process.env.RING_CLIP_SECONDS, merged.clipLengthSeconds); + if (process.env.RING_RETENTION_DAYS) merged.retentionDays = num(process.env.RING_RETENTION_DAYS, 0) || null; + + // Resolve paths to absolute so the rest of the app never guesses cwd. + merged.tokenPath = resolveFromRoot(merged.tokenPath); + merged.outputDir = resolveFromRoot(merged.outputDir); + + validate(merged); + return merged; +} + +function num(v: string, fallback: number): number { + const n = Number(v); + return Number.isFinite(n) ? n : fallback; +} + +function validate(c: AppConfig): void { + if (c.clipLengthSeconds <= 0) throw new Error('clipLengthSeconds must be > 0'); + if (c.motionCooldownSeconds < 0) throw new Error('motionCooldownSeconds must be >= 0'); + if (c.retentionDays !== null && c.retentionDays < 0) throw new Error('retentionDays must be >= 0 or null'); + if (c.cameras !== 'all' && !Array.isArray(c.cameras)) { + throw new Error('cameras must be "all" or an array of names/ids'); + } +} + +/** True if a camera (by name + id) is selected by the config filter. */ +export function cameraSelected(cfg: AppConfig, name: string, id: number): boolean { + if (cfg.cameras === 'all') return true; + return cfg.cameras.some((sel) => + typeof sel === 'number' ? sel === id : name.toLowerCase().includes(String(sel).toLowerCase()), + ); +} diff --git a/src/events.ts b/src/events.ts new file mode 100644 index 0000000..e7ca5d9 --- /dev/null +++ b/src/events.ts @@ -0,0 +1,83 @@ +import type { Subscription } from 'rxjs'; +import type { RingCamera } from 'ring-client-api'; +import type { AppConfig } from './config.js'; +import { recordClip, type RecordResult } from './recorder.js'; +import { log } from './log.js'; + +/** Recorder function shape — injectable so the trigger logic is unit-testable. */ +export type RecordFn = (camera: RingCamera, cfg: AppConfig, seconds: number) => Promise; + +/** Per-camera runtime state used to debounce overlapping triggers. */ +interface CameraState { + recording: boolean; + lastStartMs: number; +} + +/** + * Decide whether an incoming motion/ding event should start a new recording. + * + * This is the core triggering policy. The default below: + * - skips if a recording for this camera is already in progress, and + * - enforces `motionCooldownSeconds` between the START of consecutive clips. + * + * Trade-offs you may want to change (see README "Triggering policy"): + * - "extend" instead of "skip": keep recording while motion persists (better + * coverage of long events, but unbounded clip length + battery drain). + * - shorter/zero cooldown: more clips, more overlap, more account API load. + */ +function shouldTrigger(state: CameraState, cfg: AppConfig, nowMs: number): boolean { + if (state.recording) return false; + const sinceLastMs = nowMs - state.lastStartMs; + if (sinceLastMs < cfg.motionCooldownSeconds * 1000) return false; + return true; +} + +/** + * Wire motion + ding subscriptions for one camera. Returns the RxJS + * subscriptions so the caller can tear them down on shutdown. + */ +export function watchCamera(camera: RingCamera, cfg: AppConfig, recordFn: RecordFn = recordClip): Subscription[] { + const state: CameraState = { recording: false, lastStartMs: 0 }; + const subs: Subscription[] = []; + + const trigger = (reason: string) => { + const now = Date.now(); + if (!shouldTrigger(state, cfg, now)) { + log.debug(`Ignoring ${reason} on "${camera.name}" (busy or within cooldown).`); + return; + } + state.recording = true; + state.lastStartMs = now; + log.info(`Trigger: ${reason} on "${camera.name}".`); + recordFn(camera, cfg, cfg.clipLengthSeconds) + .catch((err) => log.error(`Recording failed for "${camera.name}": ${(err as Error).message}`)) + .finally(() => { + state.recording = false; + }); + }; + + if (cfg.recordOnMotion) { + subs.push( + // onMotionDetected emits a boolean; record on the rising edge (motion start). + camera.onMotionDetected.subscribe((active: boolean) => { + if (active) trigger('motion'); + }), + ); + } + + if (cfg.recordOnDing && camera.isDoorbot) { + subs.push( + camera.onDoorbellPressed.subscribe(() => trigger('doorbell ding')), + ); + } + + const triggers: string[] = []; + if (cfg.recordOnMotion) triggers.push('motion'); + if (cfg.recordOnDing && camera.isDoorbot) triggers.push('ding'); + log.info( + `Watching "${camera.name}" (#${camera.id})` + + (triggers.length ? ` for: ${triggers.join(', ')}` : ' (no auto-triggers enabled)'), + ); + + return subs; +} diff --git a/src/files.ts b/src/files.ts new file mode 100644 index 0000000..9d400c2 --- /dev/null +++ b/src/files.ts @@ -0,0 +1,65 @@ +import { mkdirSync, readdirSync, statSync, unlinkSync } from 'node:fs'; +import { join } from 'node:path'; + +/** + * Build a recording filename: {camera}_{ISO8601}.mp4 + * + * The camera name is slugified so it is always filesystem-safe, and the + * timestamp uses a colon-free ISO 8601 variant (colons are illegal on some + * filesystems and awkward in shells). Example: + * FrontDoor_2026-06-17T14-03-22Z.mp4 + */ +export function clipFilename(cameraName: string, when: Date): string { + const slug = slugify(cameraName); + const ts = when.toISOString().replace(/\.\d{3}Z$/, 'Z').replace(/:/g, '-'); + return `${slug}_${ts}.mp4`; +} + +export function slugify(name: string): string { + return ( + name + .normalize('NFKD') + .replace(/[^\w\s-]/g, '') + .trim() + .replace(/[\s_]+/g, '-') + .replace(/-+/g, '-') || 'camera' + ); +} + +/** Ensure the output directory exists; returns the absolute path passed in. */ +export function ensureDir(dir: string): string { + mkdirSync(dir, { recursive: true }); + return dir; +} + +/** + * Delete .mp4 files in `dir` whose mtime is older than `retentionDays`. + * Returns the list of deleted file paths. A null/0 retention is a no-op. + */ +export function pruneOldClips(dir: string, retentionDays: number | null, now: Date): string[] { + if (!retentionDays || retentionDays <= 0) return []; + const cutoff = now.getTime() - retentionDays * 24 * 60 * 60 * 1000; + const deleted: string[] = []; + + let entries: string[]; + try { + entries = readdirSync(dir); + } catch { + return []; // dir not created yet — nothing to prune + } + + for (const entry of entries) { + if (!entry.endsWith('.mp4')) continue; + const full = join(dir, entry); + try { + const st = statSync(full); + if (st.isFile() && st.mtimeMs < cutoff) { + unlinkSync(full); + deleted.push(full); + } + } catch { + // File vanished mid-sweep (e.g. a concurrent record finished/rotated). Skip. + } + } + return deleted; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..5905db7 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,48 @@ +import type { Subscription } from 'rxjs'; +import { loadConfig } from './config.js'; +import { createRingApi, getSelectedCameras } from './ring.js'; +import { watchCamera } from './events.js'; +import { startRetention } from './retention.js'; +import { log } from './log.js'; + +/** + * Long-running service: connect to Ring, watch the selected cameras for motion / + * doorbell events, and auto-record live clips. Also runs retention cleanup. + */ +async function main(): Promise { + const cfg = loadConfig(); + log.info('Starting Ring local recorder.'); + log.info(`Output: ${cfg.outputDir} | clip: ${cfg.clipLengthSeconds}s | cameras: ${JSON.stringify(cfg.cameras)}`); + + const api = createRingApi(cfg); + const cameras = await getSelectedCameras(api, cfg); + + if (cameras.length === 0) { + log.error('No cameras to watch. Check the `cameras` filter in config.json. Exiting.'); + process.exit(1); + } + + const subs: Subscription[] = []; + for (const camera of cameras) { + subs.push(...watchCamera(camera, cfg)); + } + + const stopRetention = startRetention(cfg); + log.info(`Ready. Watching ${cameras.length} camera(s). Press Ctrl-C to stop.`); + + const shutdown = (signal: string) => { + log.info(`Received ${signal}, shutting down.`); + for (const s of subs) s.unsubscribe(); + stopRetention(); + api.disconnect(); + // Give in-flight ffmpeg writes a moment to flush before exit. + setTimeout(() => process.exit(0), 1500); + }; + process.on('SIGINT', () => shutdown('SIGINT')); + process.on('SIGTERM', () => shutdown('SIGTERM')); +} + +main().catch((err) => { + log.error(`Fatal: ${(err as Error).message}`); + process.exit(1); +}); diff --git a/src/log.ts b/src/log.ts new file mode 100644 index 0000000..5bff97c --- /dev/null +++ b/src/log.ts @@ -0,0 +1,26 @@ +/** + * Minimal leveled logger. Writes a single line per event with an ISO timestamp + * so output is greppable and friendly to `journalctl`/pm2 log capture. + */ +type Level = 'info' | 'warn' | 'error' | 'debug'; + +const DEBUG = process.env.RING_DEBUG === '1' || process.env.RING_DEBUG === 'true'; + +function emit(level: Level, msg: string, extra?: unknown): void { + if (level === 'debug' && !DEBUG) return; + const stamp = new Date().toISOString(); + const line = `${stamp} [${level.toUpperCase()}] ${msg}`; + const stream = level === 'error' || level === 'warn' ? process.stderr : process.stdout; + if (extra !== undefined) { + stream.write(`${line} ${typeof extra === 'string' ? extra : JSON.stringify(extra)}\n`); + } else { + stream.write(`${line}\n`); + } +} + +export const log = { + info: (msg: string, extra?: unknown) => emit('info', msg, extra), + warn: (msg: string, extra?: unknown) => emit('warn', msg, extra), + error: (msg: string, extra?: unknown) => emit('error', msg, extra), + debug: (msg: string, extra?: unknown) => emit('debug', msg, extra), +}; diff --git a/src/recorder.ts b/src/recorder.ts new file mode 100644 index 0000000..4534820 --- /dev/null +++ b/src/recorder.ts @@ -0,0 +1,101 @@ +import { join } from 'node:path'; +import { statSync, rmSync } from 'node:fs'; +import { firstValueFrom } from 'rxjs'; +import type { RingCamera } from 'ring-client-api'; +import type { AppConfig } from './config.js'; +import { clipFilename, ensureDir } from './files.js'; +import { log } from './log.js'; + +/** + * ffmpeg output args for a single timestamped clip. + * + * `+frag_keyframe+empty_moov+default_base_moof` makes a *fragmented* MP4: the + * header is written up front and self-contained fragments are flushed as the + * stream arrives. If the process is killed mid-recording (Ctrl-C on the service, + * a crash, a dropped stream), the file remains playable up to the last whole + * fragment — instead of the "moov atom not found" corruption you get from a + * plain MP4 that only writes its index at the very end. + */ +function clipOutputArgs(seconds: number, outPath: string): string[] { + return [ + '-t', String(seconds), + '-movflags', '+frag_keyframe+empty_moov+default_base_moof', + '-f', 'mp4', + outPath, + ]; +} + +export interface RecordResult { + camera: string; + path: string; + bytes: number; + seconds: number; +} + +/** + * Record a single live clip from `camera` for `seconds` to the output dir. + * + * Uses the library's `recordToFile`, which opens a WebRTC live call, transcodes + * via ffmpeg, and resolves when the duration elapses (`-t `). Throws if + * the live call never starts (camera offline / throttled / auth lapsed). + */ +export async function recordClip( + camera: RingCamera, + cfg: AppConfig, + seconds: number, +): Promise { + ensureDir(cfg.outputDir); + const startedAt = new Date(); + const filename = clipFilename(camera.name, startedAt); + const outPath = join(cfg.outputDir, filename); + + log.info(`Recording ${seconds}s from "${camera.name}" → ${filename}`); + + // Open a live WebRTC call and transcode to a fragmented MP4 (see clipOutputArgs). + // The call resolves via onCallEnded when ffmpeg exits (duration reached). A hung + // call would otherwise hang forever, so cap the wait with a generous margin. + const hardTimeoutMs = (seconds + 30) * 1000; + const session = await camera.streamVideo({ output: clipOutputArgs(seconds, outPath) }); + try { + await withTimeout( + firstValueFrom(session.onCallEnded), + hardTimeoutMs, + `live call for "${camera.name}" exceeded ${hardTimeoutMs / 1000}s`, + ); + } catch (err) { + session.stop(); // ensure the WebRTC session + ffmpeg are torn down on timeout + throw err; + } + + let bytes = 0; + try { + bytes = statSync(outPath).size; + } catch { + throw new Error(`Recording finished but no file was written at ${outPath}`); + } + if (bytes === 0) { + // A 0-byte file means the live stream never produced data — clean it up so it + // doesn't masquerade as a real clip (and survive a retention sweep). + rmSync(outPath, { force: true }); + throw new Error(`Recording produced an empty file (stream never started) for "${camera.name}"`); + } + + log.info(`Saved ${filename} (${(bytes / 1024 / 1024).toFixed(2)} MB)`); + return { camera: camera.name, path: outPath, bytes, seconds }; +} + +function withTimeout(p: Promise, ms: number, message: string): Promise { + return new Promise((resolve, reject) => { + const t = setTimeout(() => reject(new Error(message)), ms); + p.then( + (v) => { + clearTimeout(t); + resolve(v); + }, + (e) => { + clearTimeout(t); + reject(e); + }, + ); + }); +} diff --git a/src/retention.ts b/src/retention.ts new file mode 100644 index 0000000..1224e50 --- /dev/null +++ b/src/retention.ts @@ -0,0 +1,28 @@ +import type { AppConfig } from './config.js'; +import { pruneOldClips } from './files.js'; +import { log } from './log.js'; + +/** + * Run one retention sweep now, then on an interval. Returns a stop() to clear + * the timer on shutdown. No-op (returns a no-op stop) if retention is disabled. + */ +export function startRetention(cfg: AppConfig): () => void { + if (!cfg.retentionDays || cfg.retentionDays <= 0) { + log.info('Retention disabled (keeping all clips).'); + return () => {}; + } + + const sweep = () => { + const deleted = pruneOldClips(cfg.outputDir, cfg.retentionDays, new Date()); + if (deleted.length) { + log.info(`Retention: deleted ${deleted.length} clip(s) older than ${cfg.retentionDays}d.`); + } + }; + + sweep(); // run immediately on startup + const handle = setInterval(sweep, cfg.retentionSweepMinutes * 60 * 1000); + handle.unref?.(); // don't keep the process alive solely for the sweep timer + log.info(`Retention: deleting clips older than ${cfg.retentionDays}d, sweeping every ${cfg.retentionSweepMinutes}m.`); + + return () => clearInterval(handle); +} diff --git a/src/ring.ts b/src/ring.ts new file mode 100644 index 0000000..472282d --- /dev/null +++ b/src/ring.ts @@ -0,0 +1,79 @@ +import { readFileSync, writeFileSync, existsSync, chmodSync } from 'node:fs'; +import { RingApi, type RingCamera } from 'ring-client-api'; +import type { AppConfig } from './config.js'; +import { cameraSelected } from './config.js'; +import { log } from './log.js'; + +interface TokenFile { + refreshToken: string; + /** ISO timestamp of the last time we wrote this file — informational only. */ + updatedAt?: string; +} + +export function tokenFileExists(cfg: AppConfig): boolean { + return existsSync(cfg.tokenPath); +} + +export function readToken(cfg: AppConfig): string { + if (!existsSync(cfg.tokenPath)) { + throw new Error( + `No refresh token at ${cfg.tokenPath}. Run \`npm run auth\` first to log in.`, + ); + } + const raw = JSON.parse(readFileSync(cfg.tokenPath, 'utf8')) as TokenFile; + if (!raw.refreshToken) throw new Error(`Token file ${cfg.tokenPath} is missing "refreshToken".`); + return raw.refreshToken; +} + +/** Write the token with 0600 perms (it grants full account access). */ +export function writeToken(cfg: AppConfig, refreshToken: string): void { + const body: TokenFile = { refreshToken, updatedAt: new Date().toISOString() }; + writeFileSync(cfg.tokenPath, JSON.stringify(body, null, 2), { mode: 0o600 }); + try { + chmodSync(cfg.tokenPath, 0o600); // enforce perms even if the file pre-existed + } catch { + /* best effort on filesystems without unix perms */ + } +} + +/** + * Construct a RingApi from the stored token and wire up rotation persistence. + * + * Ring rotates refresh tokens continuously and expires the old one shortly after + * use, so an unattended service MUST persist every rotation — otherwise it works + * once and then fails to re-auth on the next restart. That is what + * `onRefreshTokenUpdated` is for. + */ +export function createRingApi(cfg: AppConfig): RingApi { + const refreshToken = readToken(cfg); + + const api = new RingApi({ + refreshToken, + // Poll camera status so battery/online changes are reflected; cheap. + cameraStatusPollingSeconds: 20, + // ffmpeg: bundled ffmpeg-for-homebridge by default. Override via RING_FFMPEG. + ...(process.env.RING_FFMPEG ? { ffmpegPath: process.env.RING_FFMPEG } : {}), + debug: process.env.RING_DEBUG === '1' || process.env.RING_DEBUG === 'true', + }); + + api.onRefreshTokenUpdated.subscribe(({ newRefreshToken, oldRefreshToken }) => { + if (!oldRefreshToken) return; // initial emit on first connect — nothing to replace yet + log.info('Ring refresh token rotated; persisting new token.'); + writeToken(cfg, newRefreshToken); + }); + + return api; +} + +/** Get cameras filtered by the config `cameras` selector. */ +export async function getSelectedCameras(api: RingApi, cfg: AppConfig): Promise { + const all = await api.getCameras(); + const selected = all.filter((c) => cameraSelected(cfg, c.name, c.id)); + if (selected.length === 0) { + log.warn( + `No cameras matched the config filter ${JSON.stringify(cfg.cameras)}. ` + + `Found: ${all.map((c) => `${c.name} (#${c.id})`).join(', ') || '(none)'}`, + ); + } + return selected; +} diff --git a/src/verify.ts b/src/verify.ts new file mode 100644 index 0000000..ff926d7 --- /dev/null +++ b/src/verify.ts @@ -0,0 +1,156 @@ +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; +import type { Subscription } from 'rxjs'; +import type { RingCamera } from 'ring-client-api'; +import { loadConfig } from './config.js'; +import { createRingApi } from './ring.js'; +import { recordClip } from './recorder.js'; +import { log } from './log.js'; + +const execFileP = promisify(execFile); + +/** + * End-to-end verification harness. Run AFTER `npm run auth` has saved a token. + * + * npm run verify -- [--camera "Name"] [--seconds 10] [--watch-motion 60] + * + * Runs the goal's verify checklist against the real account: + * 1. authenticate + list real cameras + * 2. capture a live clip to disk + * 3. probe the clip for playable video (and report audio) + * 4. (optional) wait for a real motion/ding event and confirm auto-recording + */ +async function main(): Promise { + const args = parseArgs(process.argv.slice(2)); + const seconds = args.seconds ? Number(args.seconds) : 10; + const watchMotion = args['watch-motion'] ? Number(args['watch-motion']) : 0; + const cfg = loadConfig(); + const api = createRingApi(cfg); + + let failures = 0; + const step = (n: number, msg: string) => log.info(`[${n}] ${msg}`); + + try { + // 1. AUTH + LIST --------------------------------------------------------- + step(1, 'Authenticating and listing cameras…'); + const cameras = await api.getCameras(); + if (cameras.length === 0) throw new Error('Authenticated but found 0 cameras on the account.'); + for (const c of cameras) { + const battery = c.batteryLevel != null ? `${c.batteryLevel}%` : 'wired'; + log.info(` #${c.id} ${c.name} [${c.isDoorbot ? 'doorbell' : 'camera'}, ${c.model}, ${battery}]`); + } + log.info(` ✓ Auth OK, ${cameras.length} camera(s) listed.`); + + const target = pickCamera(cameras, args.camera); + if (!target) throw new Error(`No camera matched "${args.camera}".`); + + // 2. CAPTURE ------------------------------------------------------------- + step(2, `Capturing a ${seconds}s live clip from "${target.name}"…`); + const clip = await recordClip(target, cfg, seconds); + log.info(` ✓ Wrote ${clip.path} (${(clip.bytes / 1024 / 1024).toFixed(2)} MB).`); + + // 3. PROBE --------------------------------------------------------------- + step(3, 'Probing the clip for playable streams…'); + const streams = await probeStreams(clip.path); + const hasVideo = streams.includes('video'); + const hasAudio = streams.includes('audio'); + if (!hasVideo) { + failures++; + log.error(' ✗ No video stream found — clip is not playable.'); + } else { + log.info(' ✓ Video stream present, clip is playable.'); + } + if (hasAudio) log.info(' ✓ Audio stream present.'); + else log.warn(' ! No audio stream (Ring audio can be off in the app, or omitted for this device).'); + + // 4. MOTION TRIGGER (optional) ------------------------------------------ + if (watchMotion > 0) { + step(4, `Waiting up to ${watchMotion}s for a real motion/ding on "${target.name}" — trigger it now (walk in front / press the doorbell)…`); + const fired = await waitForTriggeredRecording(target, cfg, watchMotion); + if (fired) log.info(' ✓ Motion/ding fired and an auto-recording completed end-to-end.'); + else { + failures++; + log.error(` ✗ No motion/ding event within ${watchMotion}s. Re-run with a longer --watch-motion and trigger motion.`); + } + } else { + step(4, 'Skipping motion test (pass --watch-motion and trigger motion to verify it live).'); + } + + log.info(failures === 0 ? '\nVERIFY: all checks passed.' : `\nVERIFY: ${failures} check(s) failed.`); + } finally { + api.disconnect(); + setTimeout(() => process.exit(failures === 0 ? 0 : 1), 1500); + } +} + +/** Return the list of codec_types ("video"/"audio") present in a media file. */ +async function probeStreams(file: string): Promise { + const { stdout } = await execFileP('ffprobe', [ + '-v', 'error', + '-show_entries', 'stream=codec_type', + '-of', 'json', + file, + ]); + const parsed = JSON.parse(stdout) as { streams?: Array<{ codec_type?: string }> }; + return (parsed.streams ?? []).map((s) => s.codec_type ?? '').filter(Boolean); +} + +/** + * Subscribe to motion + ding for one camera; on the first event, record a short + * clip and resolve true once it's on disk. Resolves false on timeout. + */ +function waitForTriggeredRecording(camera: RingCamera, cfg: ReturnType, timeoutSec: number): Promise { + return new Promise((resolve) => { + const subs: Subscription[] = []; + let done = false; + const finish = (val: boolean) => { + if (done) return; + done = true; + subs.forEach((s) => s.unsubscribe()); + clearTimeout(timer); + resolve(val); + }; + const onTrigger = (reason: string) => { + log.info(` → ${reason} detected; recording a clip to prove the trigger path…`); + recordClip(camera, cfg, Math.min(cfg.clipLengthSeconds, 10)) + .then(() => finish(true)) + .catch((e) => { + log.error(` recording after trigger failed: ${(e as Error).message}`); + finish(false); + }); + }; + subs.push(camera.onMotionDetected.subscribe((active: boolean) => active && onTrigger('motion'))); + if (camera.isDoorbot) subs.push(camera.onDoorbellPressed.subscribe(() => onTrigger('doorbell ding'))); + const timer = setTimeout(() => finish(false), timeoutSec * 1000); + }); +} + +function pickCamera(cameras: RingCamera[], sel?: string): RingCamera | undefined { + if (!sel) return cameras[0]; + const id = Number(sel); + if (Number.isFinite(id)) { + const hit = cameras.find((c) => c.id === id); + if (hit) return hit; + } + return cameras.find((c) => c.name.toLowerCase().includes(sel.toLowerCase())); +} + +function parseArgs(argv: string[]): Record { + const out: Record = {}; + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + if (a.startsWith('--')) { + const next = argv[i + 1]; + if (next && !next.startsWith('--')) { + out[a.slice(2)] = next; + i++; + } else out[a.slice(2)] = 'true'; + } + } + return out; +} + +main().catch((err) => { + log.error((err as Error).message); + process.exit(1); +}); diff --git a/test/logic.test.mjs b/test/logic.test.mjs new file mode 100644 index 0000000..3f21506 --- /dev/null +++ b/test/logic.test.mjs @@ -0,0 +1,84 @@ +// Hermetic logic tests — no Ring account or network required. +// Run with: npm test (builds to dist/ first, then runs this against dist). +import { clipFilename, slugify, pruneOldClips, ensureDir } from '../dist/files.js'; +import { watchCamera } from '../dist/events.js'; +import { mkdtempSync, writeFileSync, utimesSync, readdirSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +const tick = () => new Promise((r) => setTimeout(r, 5)); +let pass = 0, fail = 0; +const ok = (c, m) => (c ? (pass++, console.log(' PASS', m)) : (fail++, console.log(' FAIL', m))); + +console.log('files: naming + retention'); +{ + const name = clipFilename('Front Door!', new Date('2026-06-17T14:03:22.500Z')); + ok(/^Front-Door_2026-06-17T14-03-22Z\.mp4$/.test(name), 'filename matches {camera}_{ISO8601}.mp4, colons stripped'); + ok(slugify(' Backyard / Garage ') === 'Backyard-Garage', 'slugify sanitizes unsafe chars'); + ok(slugify('***') === 'camera', 'slugify falls back to "camera" for empty result'); + + const dir = mkdtempSync(join(tmpdir(), 'ring-ret-')); + ensureDir(dir); + const old = join(dir, 'Cam_old.mp4'); writeFileSync(old, 'x'); + writeFileSync(join(dir, 'Cam_fresh.mp4'), 'x'); + writeFileSync(join(dir, 'notes.txt'), 'x'); + const tenDaysAgo = Date.now() / 1000 - 10 * 86400; + utimesSync(old, tenDaysAgo, tenDaysAgo); + const deleted = pruneOldClips(dir, 7, new Date()); + const left = readdirSync(dir).sort(); + ok(deleted.length === 1 && deleted[0].endsWith('Cam_old.mp4'), 'pruned exactly the >7d clip'); + ok(left.includes('Cam_fresh.mp4'), 'kept the fresh clip'); + ok(left.includes('notes.txt'), 'left non-mp4 file untouched'); + ok(pruneOldClips(dir, null, new Date()).length === 0, 'retention=null is a no-op'); +} + +console.log('events: motion/ding trigger state machine'); +function obs() { + const subs = []; + return { subscribe(fn) { subs.push(fn); return { unsubscribe() {} }; }, next(v) { subs.forEach((f) => f(v)); } }; +} +const fakeCamera = (isDoorbot) => ({ name: 'TestCam', id: 42, isDoorbot, onMotionDetected: obs(), onDoorbellPressed: obs() }); +const cfg = (over = {}) => ({ clipLengthSeconds: 10, recordOnMotion: true, recordOnDing: true, motionCooldownSeconds: 0, ...over }); +function spy() { + const calls = []; let resolveLast; + const fn = (camera, c, seconds) => { calls.push({ seconds }); return new Promise((res) => { resolveLast = () => res({ camera: camera.name, path: 'x', bytes: 1, seconds }); }); }; + return { fn, calls, finish: () => resolveLast && resolveLast() }; +} + +{ + const cam = fakeCamera(false); const s = spy(); + watchCamera(cam, cfg(), s.fn); + cam.onMotionDetected.next(false); await tick(); + ok(s.calls.length === 0, 'motion=false does not trigger (rising-edge only)'); + cam.onMotionDetected.next(true); await tick(); + ok(s.calls.length === 1 && s.calls[0].seconds === 10, 'motion=true records one clip at configured length'); + cam.onMotionDetected.next(true); await tick(); + ok(s.calls.length === 1, 'second motion while recording is skipped (no overlap)'); + s.finish(); await tick(); + cam.onMotionDetected.next(true); await tick(); + ok(s.calls.length === 2, 'after finish (cooldown 0) motion triggers again'); +} +{ + const cam = fakeCamera(false); const s = spy(); + watchCamera(cam, cfg({ motionCooldownSeconds: 3600 }), s.fn); + cam.onMotionDetected.next(true); await tick(); s.finish(); await tick(); + cam.onMotionDetected.next(true); await tick(); + ok(s.calls.length === 1, 'second motion within cooldown is skipped'); +} +{ + const cam = fakeCamera(true); const s = spy(); + watchCamera(cam, cfg({ recordOnMotion: false }), s.fn); + cam.onMotionDetected.next(true); await tick(); + ok(s.calls.length === 0, 'motion ignored when recordOnMotion=false'); + cam.onDoorbellPressed.next({}); await tick(); + ok(s.calls.length === 1, 'doorbell ding triggers a recording'); +} +{ + const cam = fakeCamera(false); const s = spy(); + watchCamera(cam, cfg(), s.fn); + cam.onDoorbellPressed.next({}); await tick(); + ok(s.calls.length === 0, 'ding on non-doorbot does nothing'); +} + +console.log(`\n${fail === 0 ? 'ALL PASS' : 'FAILURES'}: ${pass} passed, ${fail} failed`); +process.exit(fail === 0 ? 0 : 1); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a254abd --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "outDir": "dist", + "rootDir": "src", + "declaration": false, + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src/**/*.ts"] +} From f4c1bca3957ae988ad68a3c881a025f2436a6740 Mon Sep 17 00:00:00 2001 From: Eli Fayerman Date: Wed, 17 Jun 2026 12:36:32 -0400 Subject: [PATCH 2/5] Address bot review: harden config, token I/O, logging, recording - Atomic token write + create parent dir on rotation (no corrupt token file) - Clear error when the token file is unreadable or invalid JSON - Validate numeric config is finite; require retentionSweepMinutes > 0 - Ignore non-numeric RING_RETENTION_DAYS instead of silently disabling retention - Preserve the original auth error when the failure is not a 2FA prompt - Logger tolerates circular-reference objects (no crash from catch blocks) - verify: record once and unsubscribe on the first trigger - Filenames keep milliseconds to avoid same-second overwrite - Retention sweep surfaces real I/O errors instead of swallowing them --- src/auth.ts | 7 ++++--- src/config.ts | 17 +++++++++++++---- src/files.ts | 15 ++++++++++----- src/log.ts | 14 +++++++++++++- src/retention.ts | 10 +++++++--- src/ring.ts | 24 ++++++++++++++++++++---- src/verify.ts | 4 ++++ test/logic.test.mjs | 2 +- 8 files changed, 72 insertions(+), 21 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index ea5f0af..ded2519 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -30,11 +30,12 @@ async function main(): Promise { try { await client.getAuth(); ok = true; - } catch { + } catch (err) { // Expected when 2FA is enabled: the first getAuth triggers the code being - // sent and sets promptFor2fa with a human-readable instruction. + // sent and sets promptFor2fa with a human-readable instruction. Anything + // else (network, DNS, rate limit, bad credentials) should surface its cause. if (!client.promptFor2fa) { - throw new Error('Login failed (check email/password). Ring did not request a 2FA code.'); + throw new Error(`Login failed (check email/password): ${(err as Error).message}`); } log.info(client.promptFor2fa); const code = (await question(rl, '2FA code: ')).trim(); diff --git a/src/config.ts b/src/config.ts index 33ea8f8..eb04d18 100644 --- a/src/config.ts +++ b/src/config.ts @@ -76,7 +76,11 @@ export function loadConfig(): AppConfig { if (process.env.RING_TOKEN_PATH) merged.tokenPath = process.env.RING_TOKEN_PATH; if (process.env.RING_OUTPUT_DIR) merged.outputDir = process.env.RING_OUTPUT_DIR; if (process.env.RING_CLIP_SECONDS) merged.clipLengthSeconds = num(process.env.RING_CLIP_SECONDS, merged.clipLengthSeconds); - if (process.env.RING_RETENTION_DAYS) merged.retentionDays = num(process.env.RING_RETENTION_DAYS, 0) || null; + if (process.env.RING_RETENTION_DAYS) { + // Ignore non-numeric env input rather than silently disabling retention. + const parsed = Number(process.env.RING_RETENTION_DAYS); + if (Number.isFinite(parsed)) merged.retentionDays = parsed > 0 ? parsed : null; + } // Resolve paths to absolute so the rest of the app never guesses cwd. merged.tokenPath = resolveFromRoot(merged.tokenPath); @@ -92,9 +96,14 @@ function num(v: string, fallback: number): number { } function validate(c: AppConfig): void { - if (c.clipLengthSeconds <= 0) throw new Error('clipLengthSeconds must be > 0'); - if (c.motionCooldownSeconds < 0) throw new Error('motionCooldownSeconds must be >= 0'); - if (c.retentionDays !== null && c.retentionDays < 0) throw new Error('retentionDays must be >= 0 or null'); + if (!Number.isFinite(c.clipLengthSeconds) || c.clipLengthSeconds <= 0) + throw new Error('clipLengthSeconds must be a number > 0'); + if (!Number.isFinite(c.motionCooldownSeconds) || c.motionCooldownSeconds < 0) + throw new Error('motionCooldownSeconds must be a number >= 0'); + if (!Number.isFinite(c.retentionSweepMinutes) || c.retentionSweepMinutes <= 0) + throw new Error('retentionSweepMinutes must be a number > 0'); + if (c.retentionDays !== null && (!Number.isFinite(c.retentionDays) || c.retentionDays < 0)) + throw new Error('retentionDays must be a number >= 0 or null'); if (c.cameras !== 'all' && !Array.isArray(c.cameras)) { throw new Error('cameras must be "all" or an array of names/ids'); } diff --git a/src/files.ts b/src/files.ts index 9d400c2..2ab5021 100644 --- a/src/files.ts +++ b/src/files.ts @@ -6,12 +6,14 @@ import { join } from 'node:path'; * * The camera name is slugified so it is always filesystem-safe, and the * timestamp uses a colon-free ISO 8601 variant (colons are illegal on some - * filesystems and awkward in shells). Example: - * FrontDoor_2026-06-17T14-03-22Z.mp4 + * filesystems and awkward in shells). Milliseconds are kept so two recordings + * of the same camera started within the same second don't collide and overwrite + * each other. Example: + * FrontDoor_2026-06-17T14-03-22-500Z.mp4 */ export function clipFilename(cameraName: string, when: Date): string { const slug = slugify(cameraName); - const ts = when.toISOString().replace(/\.\d{3}Z$/, 'Z').replace(/:/g, '-'); + const ts = when.toISOString().replace(/[:.]/g, '-'); return `${slug}_${ts}.mp4`; } @@ -44,8 +46,11 @@ export function pruneOldClips(dir: string, retentionDays: number | null, now: Da let entries: string[]; try { entries = readdirSync(dir); - } catch { - return []; // dir not created yet — nothing to prune + } catch (err) { + // Output dir not created yet is fine; surface real failures (permissions, + // I/O) instead of silently disabling retention. + if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []; + throw err; } for (const entry of entries) { diff --git a/src/log.ts b/src/log.ts index 5bff97c..ec8c1b4 100644 --- a/src/log.ts +++ b/src/log.ts @@ -12,7 +12,19 @@ function emit(level: Level, msg: string, extra?: unknown): void { const line = `${stamp} [${level.toUpperCase()}] ${msg}`; const stream = level === 'error' || level === 'warn' ? process.stderr : process.stdout; if (extra !== undefined) { - stream.write(`${line} ${typeof extra === 'string' ? extra : JSON.stringify(extra)}\n`); + let extraStr: string; + if (typeof extra === 'string') { + extraStr = extra; + } else { + try { + extraStr = JSON.stringify(extra); + } catch { + // Circular refs (some Error objects, API responses) must not crash the + // logger — it is frequently called from catch blocks. + extraStr = String(extra); + } + } + stream.write(`${line} ${extraStr}\n`); } else { stream.write(`${line}\n`); } diff --git a/src/retention.ts b/src/retention.ts index 1224e50..3326fa2 100644 --- a/src/retention.ts +++ b/src/retention.ts @@ -13,9 +13,13 @@ export function startRetention(cfg: AppConfig): () => void { } const sweep = () => { - const deleted = pruneOldClips(cfg.outputDir, cfg.retentionDays, new Date()); - if (deleted.length) { - log.info(`Retention: deleted ${deleted.length} clip(s) older than ${cfg.retentionDays}d.`); + try { + const deleted = pruneOldClips(cfg.outputDir, cfg.retentionDays, new Date()); + if (deleted.length) { + log.info(`Retention: deleted ${deleted.length} clip(s) older than ${cfg.retentionDays}d.`); + } + } catch (err) { + log.warn(`Retention sweep failed: ${(err as Error).message}`); } }; diff --git a/src/ring.ts b/src/ring.ts index 472282d..7378b6b 100644 --- a/src/ring.ts +++ b/src/ring.ts @@ -1,4 +1,5 @@ -import { readFileSync, writeFileSync, existsSync, chmodSync } from 'node:fs'; +import { readFileSync, writeFileSync, existsSync, chmodSync, mkdirSync, renameSync } from 'node:fs'; +import { dirname } from 'node:path'; import { RingApi, type RingCamera } from 'ring-client-api'; import type { AppConfig } from './config.js'; import { cameraSelected } from './config.js'; @@ -20,15 +21,30 @@ export function readToken(cfg: AppConfig): string { `No refresh token at ${cfg.tokenPath}. Run \`npm run auth\` first to log in.`, ); } - const raw = JSON.parse(readFileSync(cfg.tokenPath, 'utf8')) as TokenFile; - if (!raw.refreshToken) throw new Error(`Token file ${cfg.tokenPath} is missing "refreshToken".`); + let raw: TokenFile; + try { + raw = JSON.parse(readFileSync(cfg.tokenPath, 'utf8')) as TokenFile; + } catch (err) { + throw new Error( + `Token file ${cfg.tokenPath} is unreadable or not valid JSON (${(err as Error).message}). Re-run \`npm run auth\`.`, + ); + } + if (!raw || !raw.refreshToken) { + throw new Error(`Token file ${cfg.tokenPath} is missing "refreshToken". Re-run \`npm run auth\`.`); + } return raw.refreshToken; } /** Write the token with 0600 perms (it grants full account access). */ export function writeToken(cfg: AppConfig, refreshToken: string): void { const body: TokenFile = { refreshToken, updatedAt: new Date().toISOString() }; - writeFileSync(cfg.tokenPath, JSON.stringify(body, null, 2), { mode: 0o600 }); + mkdirSync(dirname(cfg.tokenPath), { recursive: true }); + // Write to a temp file then atomically rename, so an interruption (or a + // concurrent rotation) can't leave a truncated, unparseable token file that + // would break the next startup. + const tmp = `${cfg.tokenPath}.tmp`; + writeFileSync(tmp, JSON.stringify(body, null, 2), { mode: 0o600 }); + renameSync(tmp, cfg.tokenPath); try { chmodSync(cfg.tokenPath, 0o600); // enforce perms even if the file pre-existed } catch { diff --git a/src/verify.ts b/src/verify.ts index ff926d7..5dd19f8 100644 --- a/src/verify.ts +++ b/src/verify.ts @@ -110,7 +110,11 @@ function waitForTriggeredRecording(camera: RingCamera, cfg: ReturnType { + if (triggered) return; // record once; ignore further events for this camera + triggered = true; + subs.forEach((s) => s.unsubscribe()); // stop the event streams immediately log.info(` → ${reason} detected; recording a clip to prove the trigger path…`); recordClip(camera, cfg, Math.min(cfg.clipLengthSeconds, 10)) .then(() => finish(true)) diff --git a/test/logic.test.mjs b/test/logic.test.mjs index 3f21506..586b2ed 100644 --- a/test/logic.test.mjs +++ b/test/logic.test.mjs @@ -13,7 +13,7 @@ const ok = (c, m) => (c ? (pass++, console.log(' PASS', m)) : (fail++, console. console.log('files: naming + retention'); { const name = clipFilename('Front Door!', new Date('2026-06-17T14:03:22.500Z')); - ok(/^Front-Door_2026-06-17T14-03-22Z\.mp4$/.test(name), 'filename matches {camera}_{ISO8601}.mp4, colons stripped'); + ok(/^Front-Door_2026-06-17T14-03-22-500Z\.mp4$/.test(name), 'filename keeps ms, colons stripped (no same-second collision)'); ok(slugify(' Backyard / Garage ') === 'Backyard-Garage', 'slugify sanitizes unsafe chars'); ok(slugify('***') === 'camera', 'slugify falls back to "camera" for empty result'); From 38ffcf6abe1a4d9c544a18551631fc0b54f76c30 Mon Sep 17 00:00:00 2001 From: Eli Fayerman Date: Wed, 17 Jun 2026 12:43:30 -0400 Subject: [PATCH 3/5] Address round-2 review: rising-edge motion, CI pinning, hardening - events: gate motion on the false->true rising edge (matches documented intent; sustained 'true' emissions no longer queue extra clips). CodeRabbit. - retention: per-file sweep surfaces non-ENOENT errors instead of swallowing all. CodeAnt (critical). - ci: pin actions/checkout and actions/setup-node to commit SHAs. CodeRabbit. - verify: validate numeric CLI args (--seconds / --watch-motion) to reject NaN. CodeRabbit. - tests updated for rising-edge semantics (15/15 pass). --- .github/workflows/ci.yml | 4 ++-- src/events.ts | 10 +++++++--- src/files.ts | 9 +++++++-- src/verify.ts | 12 ++++++++++-- test/logic.test.mjs | 12 +++++++----- 5 files changed, 33 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c576477..4869b55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,9 +9,9 @@ jobs: build-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: '22' cache: npm diff --git a/src/events.ts b/src/events.ts index e7ca5d9..954a7b9 100644 --- a/src/events.ts +++ b/src/events.ts @@ -11,6 +11,7 @@ export type RecordFn = (camera: RingCamera, cfg: AppConfig, seconds: number) => interface CameraState { recording: boolean; lastStartMs: number; + motionActive: boolean; } /** @@ -37,7 +38,7 @@ function shouldTrigger(state: CameraState, cfg: AppConfig, nowMs: number): boole * subscriptions so the caller can tear them down on shutdown. */ export function watchCamera(camera: RingCamera, cfg: AppConfig, recordFn: RecordFn = recordClip): Subscription[] { - const state: CameraState = { recording: false, lastStartMs: 0 }; + const state: CameraState = { recording: false, lastStartMs: 0, motionActive: false }; const subs: Subscription[] = []; const trigger = (reason: string) => { @@ -58,9 +59,12 @@ export function watchCamera(camera: RingCamera, cfg: AppConfig, recordFn: Record if (cfg.recordOnMotion) { subs.push( - // onMotionDetected emits a boolean; record on the rising edge (motion start). + // onMotionDetected emits a boolean. Trigger only on the rising edge + // (false -> true), so sustained motion that emits repeated `true`s does + // not queue extra clips once cooldown/no-overlap would otherwise allow it. camera.onMotionDetected.subscribe((active: boolean) => { - if (active) trigger('motion'); + if (active && !state.motionActive) trigger('motion'); + state.motionActive = active; }), ); } diff --git a/src/files.ts b/src/files.ts index 2ab5021..3d9071f 100644 --- a/src/files.ts +++ b/src/files.ts @@ -1,5 +1,6 @@ import { mkdirSync, readdirSync, statSync, unlinkSync } from 'node:fs'; import { join } from 'node:path'; +import { log } from './log.js'; /** * Build a recording filename: {camera}_{ISO8601}.mp4 @@ -62,8 +63,12 @@ export function pruneOldClips(dir: string, retentionDays: number | null, now: Da unlinkSync(full); deleted.push(full); } - } catch { - // File vanished mid-sweep (e.g. a concurrent record finished/rotated). Skip. + } catch (err) { + // A file vanishing mid-sweep (ENOENT) is expected; surface anything else + // (permissions, I/O) so expired clips aren't silently left in place. + if ((err as NodeJS.ErrnoException).code !== 'ENOENT') { + log.warn(`Retention: could not process ${full}: ${(err as Error).message}`); + } } } return deleted; diff --git a/src/verify.ts b/src/verify.ts index 5dd19f8..de93e34 100644 --- a/src/verify.ts +++ b/src/verify.ts @@ -22,8 +22,8 @@ const execFileP = promisify(execFile); */ async function main(): Promise { const args = parseArgs(process.argv.slice(2)); - const seconds = args.seconds ? Number(args.seconds) : 10; - const watchMotion = args['watch-motion'] ? Number(args['watch-motion']) : 0; + const seconds = numArg(args.seconds, 10, 1, '--seconds'); + const watchMotion = numArg(args['watch-motion'], 0, 0, '--watch-motion'); const cfg = loadConfig(); const api = createRingApi(cfg); @@ -139,6 +139,14 @@ function pickCamera(cameras: RingCamera[], sel?: string): RingCamera | undefined return cameras.find((c) => c.name.toLowerCase().includes(sel.toLowerCase())); } +/** Parse a numeric CLI arg, rejecting non-numeric input instead of letting NaN flow into ffmpeg/timeout math. */ +function numArg(raw: string | undefined, fallback: number, min: number, flag: string): number { + if (raw === undefined) return fallback; + const n = Number(raw); + if (!Number.isFinite(n) || n < min) throw new Error(`${flag} must be a number >= ${min} (got "${raw}")`); + return n; +} + function parseArgs(argv: string[]): Record { const out: Record = {}; for (let i = 0; i < argv.length; i++) { diff --git a/test/logic.test.mjs b/test/logic.test.mjs index 586b2ed..b3a98e2 100644 --- a/test/logic.test.mjs +++ b/test/logic.test.mjs @@ -49,21 +49,23 @@ function spy() { const cam = fakeCamera(false); const s = spy(); watchCamera(cam, cfg(), s.fn); cam.onMotionDetected.next(false); await tick(); - ok(s.calls.length === 0, 'motion=false does not trigger (rising-edge only)'); + ok(s.calls.length === 0, 'motion=false does not trigger'); cam.onMotionDetected.next(true); await tick(); - ok(s.calls.length === 1 && s.calls[0].seconds === 10, 'motion=true records one clip at configured length'); + ok(s.calls.length === 1 && s.calls[0].seconds === 10, 'rising edge (false->true) records one clip'); cam.onMotionDetected.next(true); await tick(); - ok(s.calls.length === 1, 'second motion while recording is skipped (no overlap)'); + ok(s.calls.length === 1, 'sustained true (no new rising edge) does not re-trigger'); s.finish(); await tick(); + cam.onMotionDetected.next(false); await tick(); cam.onMotionDetected.next(true); await tick(); - ok(s.calls.length === 2, 'after finish (cooldown 0) motion triggers again'); + ok(s.calls.length === 2, 'a new rising edge after motion ends triggers again'); } { const cam = fakeCamera(false); const s = spy(); watchCamera(cam, cfg({ motionCooldownSeconds: 3600 }), s.fn); cam.onMotionDetected.next(true); await tick(); s.finish(); await tick(); + cam.onMotionDetected.next(false); await tick(); cam.onMotionDetected.next(true); await tick(); - ok(s.calls.length === 1, 'second motion within cooldown is skipped'); + ok(s.calls.length === 1, 'new rising edge within cooldown is skipped'); } { const cam = fakeCamera(true); const s = spy(); From 3c54593f2426795124f70776844fca3c69fe8292 Mon Sep 17 00:00:00 2001 From: Eli Fayerman Date: Wed, 17 Jun 2026 12:46:02 -0400 Subject: [PATCH 4/5] Harden recording start + event dispatch (round-3 review) - recorder: add a start timeout around camera.streamVideo() so a hang during WebRTC signaling can't block forever (the onCallEnded timeout only covered the call after the session existed). gemini (high). - events: dispatch recordFn via Promise.resolve().then() so a synchronous throw becomes a rejection and never leaves state.recording stuck true. gemini. --- src/events.ts | 6 +++++- src/recorder.ts | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/events.ts b/src/events.ts index 954a7b9..4b42685 100644 --- a/src/events.ts +++ b/src/events.ts @@ -50,7 +50,11 @@ export function watchCamera(camera: RingCamera, cfg: AppConfig, recordFn: Record state.recording = true; state.lastStartMs = now; log.info(`Trigger: ${reason} on "${camera.name}".`); - recordFn(camera, cfg, cfg.clipLengthSeconds) + // Wrap in Promise.resolve().then(...) so a *synchronous* throw in recordFn + // still becomes a rejection (caught below) and can never leave state.recording + // stuck at true, which would permanently block this camera. + Promise.resolve() + .then(() => recordFn(camera, cfg, cfg.clipLengthSeconds)) .catch((err) => log.error(`Recording failed for "${camera.name}": ${(err as Error).message}`)) .finally(() => { state.recording = false; diff --git a/src/recorder.ts b/src/recorder.ts index 4534820..9b75606 100644 --- a/src/recorder.ts +++ b/src/recorder.ts @@ -54,8 +54,16 @@ export async function recordClip( // Open a live WebRTC call and transcode to a fragmented MP4 (see clipOutputArgs). // The call resolves via onCallEnded when ffmpeg exits (duration reached). A hung // call would otherwise hang forever, so cap the wait with a generous margin. + const startTimeoutMs = 30_000; const hardTimeoutMs = (seconds + 30) * 1000; - const session = await camera.streamVideo({ output: clipOutputArgs(seconds, outPath) }); + // Cap the call setup too: streamVideo() performs WebRTC signaling that can hang + // on flaky networks or the unofficial API, and the onCallEnded timeout below + // only starts counting once the session object exists. + const session = await withTimeout( + camera.streamVideo({ output: clipOutputArgs(seconds, outPath) }), + startTimeoutMs, + `live stream for "${camera.name}" did not start within ${startTimeoutMs / 1000}s`, + ); try { await withTimeout( firstValueFrom(session.onCallEnded), From 11e2dd1a829e4632dfc32b25408ac4bc7c42b214 Mon Sep 17 00:00:00 2001 From: Eli Fayerman Date: Wed, 17 Jun 2026 12:49:20 -0400 Subject: [PATCH 5/5] Use nodenext module resolution tsconfig used moduleResolution 'bundler' (intended for bundlers). This project is Node ESM run via `node dist/`, so 'nodenext' matches the runtime. Relative imports already use explicit .js extensions, so the switch builds cleanly. gemini. --- tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index a254abd..c1cd15c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { "target": "ES2022", - "module": "ES2022", - "moduleResolution": "bundler", + "module": "nodenext", + "moduleResolution": "nodenext", "lib": ["ES2022"], "outDir": "dist", "rootDir": "src",