Commit Graph

8518 Commits

Author SHA1 Message Date
Raman Gupta
c1ed7dff65 python-client: fix imports and extract a reusable scrypted_client library (#2094)
* python-client: link transitive server modules required by plugin_remote

plugin_remote now imports cluster_labels, cluster_setup, plugin_console,
plugin_pip, and plugin_volume (plus a lazy plugin_repl import), but
packages/python-client only symlinks plugin_remote, rpc, rpc_reader, and
scrypted_python. As a result the client cannot be imported at all:

    ModuleNotFoundError: No module named 'cluster_labels'

Add symlinks for the missing modules, matching the existing pattern of
sharing one implementation with server/python.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaSWgBV1XsZxWiYM16sWME

* python-client: extract reusable scrypted_client library from test.py

The only way to connect to Scrypted from Python has been to copy the
bootstrap out of test.py, which is not importable (module-level event
loop) and had drifted from the current rpc_reader/PluginRemote APIs
(writeJSON vs writeSerialized, missing ClusterSetup argument).

Move the transport and connection handshake into an importable
scrypted_client module:

- EioRpcTransport gains a close() method, an optional injectable
  aiohttp session for the engine.io connection, and queues its send
  loop on the running loop instead of via run_coroutine_threadsafe.
- connect_scrypted_client() performs login, the engine.io connect, and
  the getRemote handshake, with a connect timeout and consistent
  ScryptedConnectionError on failure. It also attaches the constructed
  SystemManager to remote.systemManager, the same wiring loadZip does
  for plugins, so PluginRemote.notify can dispatch events to
  systemManager.listen() callbacks.
- test.py becomes a small demo of the library and exits cleanly
  without os._exit(); the server URL is configurable via
  SCRYPTED_BASE_URL.

Verified live against a Scrypted server (device enumeration and OnOff
state reads).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaSWgBV1XsZxWiYM16sWME

* python-client: bound all connect phases and tear down tasks on close

Address review feedback:

- close() now cancels and awaits both the send loop and the peer read
  loop (previously the send task was cancelled but never awaited and
  the read task was untracked), so closing the event loop after
  close() no longer risks 'Task was destroyed but it is pending'
  warnings.
- connect_scrypted_client() stores the read-loop task on the transport
  and propagates read-loop failures into the pending handshake future,
  so a link that dies mid-handshake fails immediately with the
  underlying error instead of waiting out the timeout.
- The timeout parameter now bounds every phase: the login POST
  (aiohttp ClientTimeout), the engine.io connect (asyncio.wait_for),
  and the wait for initial system state.
- Document session ownership: login_session is borrowed and never
  closed; an http_session given to EioRpcTransport is owned by the
  transport and closed by close().

Verified live against a Scrypted server: clean run with empty stderr,
plus connection-refused and bad-credential paths both raising
ScryptedConnectionError.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaSWgBV1XsZxWiYM16sWME

* python-client: lazily import plugin host modules in plugin_remote

A client consuming plugin_remote (for SystemManager, DeviceManager,
MediaManager) only needs the types and the engine.io/rpc bits. The
plugin host modules (cluster_labels, plugin_console, plugin_pip,
plugin_volume) are only used inside loadZipWrapped, so import them
there -- importing plugin_remote no longer requires them, and the
client directory drops those symlinks (plugin_repl was already a lazy
import). cluster_setup stays: the client bootstrap constructs a
ClusterSetup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 13:36:55 -07:00
Raman Gupta
5945b17c5e python-client: fix test.py for current rpc_reader and PluginRemote APIs (#2087)
test.py has drifted from the server code it exercises: RpcTransport
implementations must provide writeSerialized (rpc.py no longer calls
writeJSON), and PluginRemote.__init__ now takes a ClusterSetup as its
first argument instead of the peer. Verified against a live scrypted
server (connects and enumerates devices).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:53:35 -07:00
Koushik Dutta
1c983f55bf rebroadcast: publish strict rtp parser 2026-07-22 10:26:28 -07:00
Koushik Dutta
0def4758b4 rebroadcast: fix potential settings crash 2026-07-22 09:47:07 -07:00
Raman Gupta
9a6fc6e810 server: dispatch python plugin events to systemManager listeners (#2086)
The python port of plugin-remote's notify() updates systemState but the
dispatch to systemManager.events was left as a commented-out TODO, so
systemManager.listen() callbacks never fire in python plugins/clients
(plugin-remote.ts dispatches via notifyEventDetails here). Port the
current typescript semantics: plain property changes update state and
notify listeners with the unwrapped value, everything else passes
through raw, and mixin property events no longer write device state.

Also fix EventRegistry.listenDevice to register callbacks in the
token-keyed listener set (it raised KeyError on the device id key,
diverging from event-registry.ts) so watch-mode device listeners work.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 11:14:13 -07:00
Koushik Dutta
2f4037f5ee cloud: publish 2026-07-14 09:09:16 -07:00
Koushik Dutta
e728d85b11 cloud: save only last 100 persistent ids 2026-07-13 16:49:33 -07:00
Koushik Dutta
c8194236e6 common: fix race condition in not awaiting AsyncUsingHolder 2026-07-09 10:42:37 -07:00
Koushik Dutta
a3c5fb0b76 Merge branch 'main' of github.com:koush/scrypted 2026-07-09 10:37:38 -07:00
Koushik Dutta
e14cac7b15 common: fix type checking 2026-07-09 10:37:33 -07:00
Nick Berardi
ade34b7da6 fix(alexa): v3 API compliance, fan speed, security panel, and camera streaming improvements (#2083)
* fix(alexa): v3 API compliance fixes, fan speed, and security panel control

Bring the Alexa Smart Home integration in line with the current v3 API.

Tier 1 (malformed responses / missing handlers):
- BrightnessController: report brightness under the Alexa.BrightnessController
  namespace instead of Alexa.PowerController so Set/AdjustBrightness responses
  match the advertised capability.
- SmartVision ObjectDetection: flatten the doubly-nested payload.events array.
- SecurityPanelController: add Arm/Disarm directive handlers (previously
  advertised but unhandled, silently falling through to a fake success), wired
  to armSecuritySystem/disarmSecuritySystem. Enforce the spec rule rejecting a
  direct ARMED_AWAY -> other armed-state transition with AUTHORIZATION_REQUIRED.
  Drop the advertised FOUR_DIGIT_PIN authorization, which Scrypted cannot honor.
- Auth failures now return an Alexa INVALID_AUTHORIZATION_CREDENTIAL
  ErrorResponse instead of a bare HTTP 500 that Alexa retries and surfaces as a
  generic skill error.

API modernization:
- EndpointHealth: downgrade to the documented v3.1 / connectivity-only shape and
  stop emitting the undocumented battery property (and 3.2 version), which risks
  the whole capability being rejected.
- Fan: add Alexa.RangeController (Fan.Speed, 0-100%) for devices implementing the
  Fan interface, alongside the existing PowerController on/off.

Cleanups:
- addAccessToken: initialize event.endpoint as an object, not an array.
- Hoist getArmState to a shared export instead of duplicating it.

Bump version to 0.4.0.

* fix(alexa): improve camera stream quality and connectivity

Tune the WebRTC negotiation for Alexa camera sessions.

- Resolution: raise the proxied stream cap from 720p to 1080p. The previous
  1280x720 screen hint forced the transcoder into medium-resolution mode for
  every endpoint, so even an Echo Show 15 or Fire TV only received 720p. 1080p
  lets larger displays render sharply while the transcoder still clamps width
  and falls back to 720p when H.264 High can't be negotiated. We cap rather
  than send the uncapped source, since Alexa's directive carries no display
  hint and a 4K stream would waste bandwidth on smaller Echo devices.

- Two-way audio: only advertise isFullDuplexAudioSupported when the camera
  implements the Intercom interface. Advertising full duplex on a one-way
  camera makes Alexa set up a return mic path that goes nowhere.

- TURN: stop unconditionally disabling TURN, and expose a "Use TURN Servers"
  plugin setting (on by default). Alexa sessions are proxied and often cross
  NATs where a TURN relay is the only path that connects; combined with
  disabled trickle ICE (all candidates in the initial SDP), omitting the relay
  candidate left NAT-blocked sessions with no fallback. When enabled, TURN
  usage defers to the WebRTC plugin's own setting, matching the Google Home
  integration; disabling it force-disables TURN for Alexa sessions only.

Bump version to 0.5.0.
2026-06-29 19:47:34 -07:00
Steven Wu
baef85054c fix(homekit): force tcp rtsp_transport for two-way audio talkback (#2081)
The two-way audio talkback loopback (RtspServer over listenZeroSingleClient)
only supports TCP interleaved transport. The talkback ffmpeg input was built
without -rtsp_transport, so ffmpeg attempts a UDP SETUP first and the loopback
responds 461 Unsupported Transport.

The equivalent webrtc talkback path (plugins/webrtc/src/session-control.ts)
has always specified -rtsp_transport tcp; the homekit path did not. Align it.

Before: talkback was silent and the log showed "method SETUP failed: 461
Unsupported Transport". After: the 461 is gone and talkback audio works.
2026-06-28 20:19:34 -07:00
Koushik Dutta
f7018ddd9f Merge branch 'main' of github.com:koush/scrypted 2026-06-01 15:58:09 -07:00
Koushik Dutta
86e7cced78 homekit: add mp4 fragment io timeout 2026-06-01 15:57:55 -07:00
George Talusan
3e94bdeb9a fix(homekit): bump to latest @homebridge/hap-nodejs and @homebridge/ciao to catch the loopback sender packet leak (#2029)
* fix(homekit): bump to latest @homebridge/hap-nodejs and @homebridge/ciao to catch the loopback sender packet leak

* remove dead .gitmodules
2026-06-01 15:40:41 -07:00
thllxb
c5fc35ed6d fix(rebroadcast): do not send getStreamLength before play in RTMP client (#2056)
ffmpeg's libavformat RTMP client (gen_get_stream_length in rtmpproto.c) only sends getStreamLength for seekable/VOD streams, never as an unconditional preamble to play. Some live RTMP servers - notably Reolink cameras - respond to an unsolicited getStreamLength on a live stream by sending TCP FIN ~100-150ms later, killing the connection before the play command can take effect. The rebroadcast prebuffer then enters a tight reconnect loop that never produces a usable stream.

Removing the unconditional getStreamLength send lets Reolink RTMP rebroadcast work indefinitely without disconnects, and should not regress other servers since ffmpeg already runs without it.

Fixes #2055

Co-authored-by: thllxb <223556219+Copilot@users.noreply.github.com>
2026-06-01 15:18:01 -07:00
thllxb
41f2d87a48 fix(reolink): do not percent-encode RTMP credential params (#2058)
The Reolink RTMP server does not percent-decode query parameter values - it compares the raw bytes from the URL against the stored password. URLSearchParams.set() percent-encodes values when the URL is serialised (WHATWG application/x-www-form-urlencoded), which corrupts passwords containing characters such as '!' (-> '%21'), '#' (-> '%23'), '+' (-> '%2B'), space, etc.

Affected: users whose firmware causes getLoginParameters to return {user, password} (instead of a Login-API token) and whose password contains any of those characters. Symptom: rebroadcast prebuffer fails with 'Socket received FIN' immediately after 'Sending play command'.

Append the RTMP credential pairs as raw bytes instead of going through URLSearchParams. Token-only path is unaffected (hex tokens contain no characters that require encoding). Mirrors the rationale of #1509 for the HTTP API path.

Fixes #2057

Co-authored-by: thllxb <223556219+Copilot@users.noreply.github.com>
2026-06-01 15:17:42 -07:00
Koushik Dutta
5606a95fb3 server: fix null toleratnce in getPluginInfo 2026-06-01 15:13:13 -07:00
Koushik Dutta
bd57be88fd server: fix compile error 2026-06-01 15:10:34 -07:00
merritt925
2c1ccfbca7 fix: restore null-tolerance lost in strictNullChecks migration (#2060)
Two runtime regressions from the strict-mode migration (range 125db2e..c6be722) where a real runtime guard was replaced by a non-null assertion:

- services/cluster-fork.ts: findPluginDevice(...)!._id threw and aborted fork() when the plugin device was not found. Restored ?._id so options.id stays undefined (it is only a worker-affinity hint).
- plugin/plugin-host-api.ts: onDevicesChanged() coerced a missing optional 'devices' field to [], turning a fail-safe throw into mass (cascading) removal of all of a provider's devices. Restored fail-fast on missing devices.

Both compile under strictNullChecks.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 15:09:47 -07:00
David Glandon
07f9bfacac server: Fix leaking fd due to cleanup (#2052) 2026-05-29 22:15:21 -07:00
Koushik Dutta
1545790cdb cloud: publish beta with explicit tunel address 2026-05-20 14:44:57 -07:00
Sergey Vilgelm
6d9a3876f5 server: guard plugin-repl against stale nativeId (#2024)
* server: guard plugin-repl against stale nativeId

The REPL handler destructured nativeIds.get(filter) directly, which
threw an unhandledRejection TypeError when filter was a nativeId for a
device that had been removed since the UI rendered the REPL link.
Break out of the chain walk when the lookup misses instead.

Assisted-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-02 09:24:06 -07:00
Koushik Dutta
57320b90ed fix using holder no-op release disposal 2026-04-29 10:50:59 -07:00
Koushik Dutta
0b62a7c1a5 Merge branch 'main' of github.com:koush/scrypted 2026-04-21 09:12:53 -07:00
Koushik Dutta
b72628c7b6 common: add tslib as dev dep 2026-04-21 09:12:46 -07:00
Pankaj Rupani
de65dffc7c docs: clarify avahi config for host vs bridge networking (#2014)
Some checks failed
Test / Test local installation on macos-13 (push) Has been cancelled
Test / Test local installation on macos-14 (push) Has been cancelled
Test / Test local installation on ubuntu-24.04-arm (push) Has been cancelled
Test / Test local installation on ubuntu-latest (push) Has been cancelled
Test / Test local installation on windows-latest (push) Has been cancelled
* docs: clarify avahi config for host vs bridge networking

The HomeKit plugin's MDNSServer binds UDP port 5353 directly on every
network interface. With network_mode: host (the default), this conflicts
with any system avahi-daemon running on the host, causing EADDRINUSE on
all interfaces and silently breaking HomeKit mDNS discovery.

The previous comments implied the host socket mount and
SCRYPTED_DOCKER_AVAHI=true were equivalent alternatives ("choose one or
the other"), which is misleading. Clarify the two distinct use cases:

- Option A (host networking): SCRYPTED_DOCKER_AVAHI=true is required.
  The host avahi-daemon must be stopped. The /var/run socket mounts are
  incompatible with this mode. Add a note about mounting
  /etc/avahi/services to preserve host service advertisements.

- Option B (bridge networking only): host socket mount works because
  Scrypted registers via D-Bus without binding 5353. Does not work with
  host networking.

Fixes #2013

* fix: use SCRYPTED_DOCKER_AVAHI=true for avahi setup with host networking

The install script was configuring the host avahi socket mount (Option B)
while the template uses network_mode: host. This is a broken combination:
the HomeKit plugin's MDNSServer binds UDP 5353 directly on every interface,
conflicting with the system avahi-daemon, causing EADDRINUSE on all
interfaces and silently breaking HomeKit mDNS discovery.

Fix: when the user opts in to avahi, use SCRYPTED_DOCKER_AVAHI=true
(Option A) instead. This makes Scrypted run its own internal avahi.
Also stop/disable the host avahi-daemon so it doesn't conflict, and
mount /etc/avahi/services:ro so Scrypted's avahi picks up existing host
service definitions (e.g. Time Machine, Samba).

Fixes #2013
2026-04-16 07:59:36 -07:00
Yuki MIZUNO
55369aeb88 rebroadcast: show stream selection options when synthetic streams are configured (#2012)
* fix: show stream selection options when synthetic streams are configured

* fix: show stream selection options and enforce FFmpeg parser for synthetic streams
2026-04-16 07:57:28 -07:00
Koushik Dutta
194acf15e8 common: use peerDependencies 2026-04-07 10:19:34 -07:00
Koushik Dutta
fbb4fc8fa4 common: update types 2026-04-07 10:17:40 -07:00
Koushik Dutta
c6be722989 various: strict fixes 2026-04-02 16:08:23 -07:00
Koushik Dutta
21ecfd85ef client: strict mode 2026-04-02 15:59:56 -07:00
Koushik Dutta
d0383c6503 server: match mediaobject constrcutor 2026-04-02 15:55:54 -07:00
Koushik Dutta
1c894561a0 tsconfig: remove redundant strict flags covered by 'strict: true'
Some checks failed
Build SDK / Build (push) Has been cancelled
The following flags are implied by 'strict: true' and are redundant:
- strictNullChecks
- strictFunctionTypes
- strictBindCallApply
- strictPropertyInitialization
- noImplicitThis
- alwaysStrict
- noImplicitAny

Kept explicitly:
- noFallthroughCasesInSwitch
- noUncheckedIndexedAccess
- noImplicitOverride
- noImplicitReturns
2026-04-02 14:59:37 -07:00
Koushik Dutta
d8b305dcdb fixup: services: update findPluginDevice call after return type change 2026-04-02 14:58:25 -07:00
Koushik Dutta
5a1c3d024b plugin: add type assertions for strictNullChecks in runtime workers
Fix strictNullChecks:
- child-process-worker.ts: add definite assignment for childProcess
- custom-worker.ts: add assertions for pluginDevice and options
- node-fork-worker.ts: add catch block type annotation
- node-thread-worker.ts: add catch block type annotation
- python-worker.ts: add assertions for worker properties
2026-04-02 14:57:53 -07:00
Koushik Dutta
2bd8354ead plugin: add type assertions for strictNullChecks in plugin core modules
Fix strictNullChecks:
- device.ts: add assertions for storage and nativeIds access
- endpoint.ts: add assertions for device and handler access
- plugin-api.ts: add definite assignment for callback properties
- plugin-host-api.ts: add assertions for findPluginDevice results,
  consolidate plugin assertions at declarations
- plugin-lazy-remote.ts: add assertion for getFile result
- system.ts: add definite assignment for manager properties,
  add assertions for state access
2026-04-02 14:57:14 -07:00
Koushik Dutta
34a9e698ae plugin: add type assertions for strictNullChecks in plugin-device and remote modules
Fix strictNullChecks:
- plugin-device.ts: consolidate entry/host assertions at declarations,
  use undefined! for proxy values, add definite assignment for mixinTable
- plugin-remote.ts: add assertions for callbacks and nativeIds access
- plugin-remote-worker.ts: fix clusterWorkerId as Promise<string | undefined>,
  add assertions for worker and options properties
2026-04-02 14:54:40 -07:00
Koushik Dutta
01aab01e46 plugin: add type assertions for strictNullChecks in plugin-host and media
Fix strictNullChecks:
- plugin-host.ts: consolidate logger assertion at declaration,
  extract handler variable to avoid repeated assertions,
  use undefined! for clusterWorkerId, add definite assignment
- media.ts: add assertions for converter and mediaObject access
2026-04-02 14:51:35 -07:00
Koushik Dutta
61d9345bf6 cluster: add type assertions for strictNullChecks compliance
Fix strictNullChecks in cluster modules:
- cluster-labels.ts: add assertions for label and weight access
- cluster.ts: keep getClusterWorkerId returning string (API contract)
- scrypted-cluster-main.ts: add assertions for socket properties,
  consolidate remoteAddress assertion, fix cluster worker lookups
- cluster-fork.ts: add assertions for worker and options properties,
  fix findPluginDevice call with assertion
2026-04-02 14:50:26 -07:00
Koushik Dutta
d0deb04588 runtime: add type assertions for strictNullChecks compliance
Fix strictNullChecks in runtime.ts and scrypted-server-main.ts:
- Change findPluginDevice return type to PluginDevice | undefined
- Add type assertions at call sites that expect valid results
- Consolidate assertions at variable declarations
- Use undefined! instead of undefined as any
- Use throw e instead of throw new Error(e as string)
- Add catch block type annotations (e: any)
- Fix socket property and plugin device lookups with assertions
2026-04-02 14:48:39 -07:00
Koushik Dutta
d69c949ec7 services: add type assertions for strictNullChecks compliance
Fix strictNullChecks in services:
- plugin.ts: assert findPluginDeviceById results at assignment point,
  clean up scattered assertions throughout methods
- users.ts: make aclId parameter optional in addUserToDatabase/addUserInternal
2026-04-02 14:19:26 -07:00
Koushik Dutta
519eb36368 utils: add type assertions for strictNullChecks compliance
Fix strictNullChecks in utility modules:
- rpc.ts: make onProxySerialization optional (used with ?.), add definite assignment for killedDeferred
- state.ts: cast catch block error to Error
- level.ts: add definite assignment for curId
- threading.ts: cast catch block errors to Error
- http-interfaces.ts: cast catch block error to Error
- plugin-http.ts: cast catch block errors to Error, fix abstract method return type
- usertoken.ts: make aclId optional in UserToken constructor
2026-04-02 14:15:28 -07:00
Koushik Dutta
6ece12e3cc sdk: change return type of getNativeIds 2026-04-02 13:34:39 -07:00
Koushik Dutta
896445ec80 strictNullChecks: fix plugin remote handlers
- Make PluginRemote.loadZip options parameter optional to match implementations
- Add undefined to _protocols type in WebSocket class
- Add non-null assertions for device lookup in REPL server
2026-04-02 13:33:12 -07:00
Koushik Dutta
85a30d7b87 strictNullChecks: fix plugin types
- Add non-null assertion for eventInterface in acl.ts
- Add non-null assertion for SCRYPTED_CLUSTER_ADDRESS env var
- Use .flat() instead of [].concat() for type safety
- Add non-null assertion for ScryptedDevice descriptor access
- Use options! and null! assertions for mediaObject properties
2026-04-02 13:33:12 -07:00
Koushik Dutta
f620c80589 strictNullChecks: fix plugin runtime workers
- Add non-null assertions for worker and stdio access
- Add non-null assertions for kill() undefined assignments
2026-04-02 13:33:12 -07:00
Koushik Dutta
5f040f5ff4 strictNullChecks: fix core utilities
- Change Express sendFile root from null to undefined
- Add undefined to RefreshPromise.promise type
- Add undefined to debouncer current promise type
2026-04-02 13:33:12 -07:00
Koushik Dutta
5bfb3e5675 strictNullChecks: fix services and notifyInterfaceEvent type
- Change notifyInterfaceEvent parameter to PluginDevice | undefined
- Pass undefined instead of null at call sites
- Add non-null assertion for webhookUpdateAuthorization
2026-04-02 13:33:12 -07:00
Koushik Dutta
056be96e4f strictNullChecks: fix cluster modules
- Initialize foundAny variable before use
- Add fallback for undefined env var in parseFloat
- Add non-null assertions for Map lookups and array indices
- Fix ClusterObject type annotation for undefined case
- Add non-null assertions for env vars and socket properties
2026-04-02 13:33:12 -07:00