From 9de2b480ff2688ac6595407686d40dce95b72af2 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Thu, 28 Aug 2025 11:31:37 -0700 Subject: [PATCH] webrtc: wip connectRPCObject --- packages/client/package-lock.json | 8 +- packages/client/package.json | 2 +- packages/client/src/index.ts | 6 - plugins/webrtc/package-lock.json | 107 +++++++++++++- plugins/webrtc/package.json | 1 + plugins/webrtc/src/datachannel-debouncer.ts | 44 ------ plugins/webrtc/src/datachannel-serializer.ts | 58 ++++++++ plugins/webrtc/src/ffmpeg-to-wrtc.ts | 132 +----------------- plugins/webrtc/src/main.ts | 24 +++- sdk/package-lock.json | 4 +- sdk/package.json | 2 +- sdk/types/package-lock.json | 4 +- sdk/types/package.json | 2 +- .../scrypted_python/scrypted_sdk/types.py | 2 +- sdk/types/src/types.input.ts | 4 +- server/package-lock.json | 8 +- server/package.json | 2 +- server/src/rpc-serializer.ts | 2 +- 18 files changed, 207 insertions(+), 205 deletions(-) delete mode 100644 plugins/webrtc/src/datachannel-debouncer.ts create mode 100644 plugins/webrtc/src/datachannel-serializer.ts diff --git a/packages/client/package-lock.json b/packages/client/package-lock.json index fd2d308e7..48c050a1c 100644 --- a/packages/client/package-lock.json +++ b/packages/client/package-lock.json @@ -21,7 +21,7 @@ "typescript": "^5.8.3" }, "peerDependencies": { - "@scrypted/types": "^0.5.36" + "@scrypted/types": "^0.5.37" } }, "node_modules/@cspotcode/source-map-support": { @@ -83,9 +83,9 @@ } }, "node_modules/@scrypted/types": { - "version": "0.5.36", - "resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.5.36.tgz", - "integrity": "sha512-0pYlauijsDGQ8SyO8ca5sWiv1VPUgF73dv4ktjm7J9m3KIjImP7WfF1Y5YBdi5PHqo8JIt+SAFqhZE18ZxQcug==", + "version": "0.5.37", + "resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.5.37.tgz", + "integrity": "sha512-RksclqyAN4c5Mm1hndeGIXVm1p9o80VPoYspGatkSGznoG6I2TMklm8uyiZCvdTYAbeH9GWffEmf5u631xcN8g==", "license": "ISC", "peer": true, "dependencies": { diff --git a/packages/client/package.json b/packages/client/package.json index 61149eeea..6921262fd 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -19,7 +19,7 @@ "typescript": "^5.8.3" }, "peerDependencies": { - "@scrypted/types": "^0.5.36" + "@scrypted/types": "^0.5.37" }, "dependencies": { "engine.io-client": "^6.6.3", diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 7ed0b9d3a..b4eef4acd 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,7 +1,6 @@ import { MediaObjectCreateOptions, ScryptedStatic } from "@scrypted/types"; import * as eio from 'engine.io-client'; import { SocketOptions } from 'engine.io-client'; -import { Deferred } from "../../../common/src/deferred"; import { timeoutPromise } from "../../../common/src/promise-utils"; import type { ClusterObject, ConnectRPCObject } from '../../../server/src/cluster/connect-rpc-object'; import type { IOSocket } from '../../../server/src/io'; @@ -397,10 +396,6 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro const explicitBaseUrl = baseUrl || `${globalThis.location.protocol}//${globalThis.location.host}`; - // underlying webrtc rpc transport may queue up messages before its ready to be to be handled. - // watch for this flush. - const flush = new Deferred(); - const addresses: string[] = []; const localAddressDefault = isNotChromeOrIsInstalledApp; @@ -537,7 +532,6 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro serializer.setupRpcPeer(rpcPeer); } - setTimeout(() => flush.resolve(undefined), 0); const scrypted = await attachPluginRemote(rpcPeer, undefined); const { serverVersion, diff --git a/plugins/webrtc/package-lock.json b/plugins/webrtc/package-lock.json index ea74591ca..4a7efa50c 100644 --- a/plugins/webrtc/package-lock.json +++ b/plugins/webrtc/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@scrypted/common": "file:../../common", "@scrypted/sdk": "file:../../sdk", + "@scrypted/server": "file:../../server", "ip": "^2.0.1" }, "devDependencies": { @@ -84,7 +85,7 @@ }, "../../sdk": { "name": "@scrypted/sdk", - "version": "0.5.33", + "version": "0.5.38", "license": "ISC", "dependencies": { "@babel/preset-typescript": "^7.27.1", @@ -124,6 +125,60 @@ "typedoc": "^0.28.5" } }, + "../../server": { + "name": "@scrypted/server", + "version": "0.142.8", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "@scrypted/ffmpeg-static": "^6.1.0-build3", + "@scrypted/node-pty": "^1.0.24", + "@scrypted/types": "^0.5.36", + "adm-zip": "^0.5.16", + "body-parser": "^2.2.0", + "cookie-parser": "^1.4.7", + "dotenv": "^16.5.0", + "engine.io": "^6.6.4", + "express": "^5.1.0", + "follow-redirects": "^1.15.9", + "http-auth": "^4.2.1", + "level": "^10.0.0", + "lodash": "^4.17.21", + "mime-types": "^3.0.1", + "node-dijkstra": "^2.5.0", + "node-forge": "^1.3.1", + "node-gyp": "^11.2.0", + "py": "npm:@bjia56/portable-python@^0.1.141", + "semver": "^7.7.2", + "sharp": "^0.34.2", + "source-map-support": "^0.5.21", + "tar": "^7.4.3", + "tslib": "^2.8.1", + "typescript": "^5.8.3", + "whatwg-mimetype": "^4.0.0", + "ws": "^8.18.2" + }, + "bin": { + "scrypted-serve": "bin/scrypted-serve" + }, + "devDependencies": { + "@types/adm-zip": "^0.5.7", + "@types/cookie-parser": "^1.4.9", + "@types/express": "^5.0.3", + "@types/follow-redirects": "^1.14.4", + "@types/http-auth": "^4.1.4", + "@types/lodash": "^4.17.17", + "@types/mime-types": "^3.0.1", + "@types/node": "^24.0.3", + "@types/node-dijkstra": "^2.5.6", + "@types/node-forge": "^1.3.11", + "@types/semver": "^7.7.0", + "@types/source-map-support": "^0.5.10", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "rimraf": "^6.0.1" + } + }, "node_modules/@scrypted/common": { "resolved": "../../common", "link": true @@ -132,6 +187,10 @@ "resolved": "../../sdk", "link": true }, + "node_modules/@scrypted/server": { + "resolved": "../../server", + "link": true + }, "node_modules/@types/ip": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@types/ip/-/ip-1.1.3.tgz", @@ -206,6 +265,52 @@ "webpack-bundle-analyzer": "^4.10.2" } }, + "@scrypted/server": { + "version": "file:../../server", + "requires": { + "@scrypted/ffmpeg-static": "^6.1.0-build3", + "@scrypted/node-pty": "^1.0.24", + "@scrypted/types": "^0.5.36", + "@types/adm-zip": "^0.5.7", + "@types/cookie-parser": "^1.4.9", + "@types/express": "^5.0.3", + "@types/follow-redirects": "^1.14.4", + "@types/http-auth": "^4.1.4", + "@types/lodash": "^4.17.17", + "@types/mime-types": "^3.0.1", + "@types/node": "^24.0.3", + "@types/node-dijkstra": "^2.5.6", + "@types/node-forge": "^1.3.11", + "@types/semver": "^7.7.0", + "@types/source-map-support": "^0.5.10", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "adm-zip": "^0.5.16", + "body-parser": "^2.2.0", + "cookie-parser": "^1.4.7", + "dotenv": "^16.5.0", + "engine.io": "^6.6.4", + "express": "^5.1.0", + "follow-redirects": "^1.15.9", + "http-auth": "^4.2.1", + "level": "^10.0.0", + "lodash": "^4.17.21", + "mime-types": "^3.0.1", + "node-dijkstra": "^2.5.0", + "node-forge": "^1.3.1", + "node-gyp": "^11.2.0", + "py": "npm:@bjia56/portable-python@^0.1.141", + "rimraf": "^6.0.1", + "semver": "^7.7.2", + "sharp": "^0.34.2", + "source-map-support": "^0.5.21", + "tar": "^7.4.3", + "tslib": "^2.8.1", + "typescript": "^5.8.3", + "whatwg-mimetype": "^4.0.0", + "ws": "^8.18.2" + } + }, "@types/ip": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@types/ip/-/ip-1.1.3.tgz", diff --git a/plugins/webrtc/package.json b/plugins/webrtc/package.json index bbc18efea..26e9b1be8 100644 --- a/plugins/webrtc/package.json +++ b/plugins/webrtc/package.json @@ -30,6 +30,7 @@ ] }, "dependencies": { + "@scrypted/server": "file:../../server", "@scrypted/common": "file:../../common", "@scrypted/sdk": "file:../../sdk", "ip": "^2.0.1" diff --git a/plugins/webrtc/src/datachannel-debouncer.ts b/plugins/webrtc/src/datachannel-debouncer.ts deleted file mode 100644 index 948df19b4..000000000 --- a/plugins/webrtc/src/datachannel-debouncer.ts +++ /dev/null @@ -1,44 +0,0 @@ -const maxPacketSize = 1 << 16; - -export class DataChannelDebouncer { - timeout: NodeJS.Timeout; - pending: Buffer; - maxWait = 10; - - constructor(public dc: { send: (buffer: Buffer) => void }, public kill: (e: Error) => void) { - } - - send(data: Buffer) { - // if this buffer would exceed the max packet size, flush now to ensure only large packets are flushed. - if (this.pending?.length + data.length >= maxPacketSize) - this.flush(); - - if (!this.pending) - this.pending = data; - else - this.pending = Buffer.concat([this.pending, data]); - clearTimeout(this.timeout); - this.timeout = setTimeout(() => this.flush(), this.maxWait); - - // if this buffer exceeds the max packet size, flush now to send the entire message immediately. - if (this.pending?.length + data.length >= maxPacketSize) - this.flush(); - } - - flush() { - try { - let offset = 0; - while (offset < this.pending.length) { - this.dc.send(this.pending.slice(offset, offset + maxPacketSize)); - offset += maxPacketSize; - } - - this.pending = undefined; - clearTimeout(this.timeout); - this.timeout = undefined; - } - catch (e) { - this.kill(e as Error); - } - } -} diff --git a/plugins/webrtc/src/datachannel-serializer.ts b/plugins/webrtc/src/datachannel-serializer.ts new file mode 100644 index 000000000..f3551dcfa --- /dev/null +++ b/plugins/webrtc/src/datachannel-serializer.ts @@ -0,0 +1,58 @@ +import { createRpcDuplexSerializer } from "@scrypted/server/src/rpc-serializer"; + +export function createDataChannelSerializer(dc: { send: (data: Buffer) => void }) { + // Chunking and debouncing state + let pending: Buffer[]; + + // Max packet size for data channels is 16KB + const MAX_PACKET_SIZE = 16384; + + // Flush pending chunks with proper chunking + function flushPending() { + if (!pending || pending.length === 0) + return; + + const chunks = pending; + pending = undefined; + + // Process all pending chunks + for (const data of chunks) { + let offset = 0; + + // Split data into chunks that fit within MAX_PACKET_SIZE + while (offset < data.length) { + const remaining = data.length - offset; + const chunkSize = Math.min(remaining, MAX_PACKET_SIZE); + const chunkData = data.subarray(offset, offset + chunkSize); + + dc.send(chunkData); + offset += chunkSize; + } + } + } + + // Queue data for sending with next-tick debouncing + function queuePending(data: Buffer) { + const hadPending = !!pending; + if (!pending) + pending = []; + pending.push(data); + + // Schedule flush for next tick if not already scheduled + if (!hadPending) { + setTimeout(() => flushPending(), 0); + } + } + + // Create a wrapper around the data channel send method for chunking + const chunkingDataChannel = { + write: (data: Buffer) => { + queuePending(data); + } + }; + + // Create the duplex serializer which handles all RPC serialization + const duplexSerializer = createRpcDuplexSerializer(chunkingDataChannel); + + return duplexSerializer; +} diff --git a/plugins/webrtc/src/ffmpeg-to-wrtc.ts b/plugins/webrtc/src/ffmpeg-to-wrtc.ts index fabe03e8b..487291424 100644 --- a/plugins/webrtc/src/ffmpeg-to-wrtc.ts +++ b/plugins/webrtc/src/ffmpeg-to-wrtc.ts @@ -707,116 +707,9 @@ export class WebRTCConnectionManagement implements RTCConnectionManagement { return ret; } - async createRPCGeneratorDataChannel(label: string, generator: AsyncGenerator, options?: { - initialWindowSize?: number, - }) { - - generator = await sdk.connectRPCObject(generator); - - let windowSize = options?.initialWindowSize; - - - let windowUpdate: Deferred; - if (typeof windowSize === 'number' && windowSize <= 0) { - windowUpdate = new Deferred(); - } - - const dcDeferred = new Deferred(); - - const dcStatus = new Deferred(); - waitClosed(this.pc).finally(() => dcStatus.reject(new Error('data channel closed'))); - - this.negotiation.then(async () => { - const createdDc = this.pc.createDataChannel(label, { - ordered: true, - }); - createdDc.onmessage = (event) => { - const data = event.data; - if (typeof data === 'string') { - try { - const msg = JSON.parse(data); - const u = windowUpdate; - if (typeof msg.windowUpdate === 'number') { - windowSize += msg.windowUpdate; - if (windowSize > 0) { - windowUpdate = undefined; - u?.resolve(); - } - } - else if (msg.windowUpdate === null) { - windowSize = undefined; - u?.resolve(); - } - } - catch (e) { - this.console.error('Error processing WebRTC datachannel control message.', data); - } - } - }; - - createdDc.onopen = () => dcDeferred.resolve(createdDc); - createdDc.onclose = () => { - dcStatus.reject(new Error('data channel closed')); - dcDeferred.reject(new Error('data channel closed')); - }; - createdDc.onerror = (e) => { - dcStatus.reject(e.error); - dcDeferred.reject(e.error); - }; - - await dcDeferred.promise; - // await sleep(1000); - }); - - Promise.resolve().then(async () => { - try { - await windowUpdate?.promise; - - for await (const chunk of generator) { - if (dcStatus.finished) { - break; - } - - const dc = await dcDeferred.promise; - if (dc.readyState !== 'open') - break; - // split into 32k segments and send, webrtc or sctp has chunk size limitation - let preamble = Buffer.alloc(4); - preamble.writeUint32BE(chunk.length); - for (let i = 0; i < chunk.length; i += 32 * 1024) { - let sending = chunk.subarray(i, i + 32 * 1024); - if (preamble) { - sending = Buffer.concat([preamble, sending]); - preamble = undefined; - } - dc.send(sending); - } - - if (typeof windowSize === 'number') { - windowSize -= chunk.length; - if (windowSize <= 0) { - windowUpdate = new Deferred(); - this.console.log('waiting for window update'); - await Promise.any([windowUpdate.promise, dcStatus.promise]); - } - } - else { - if (dc.bufferedAmount > dc.bufferedAmountLowThreshold) { - this.console.log('waiting for buffered amount low', dc.bufferedAmount); - await Promise.any([dc.bufferedAmountLow.asPromise(), dcStatus.promise]); - } - } - } - } - catch (e) { - const dc = await dcDeferred.promise; - if (dc.readyState === 'open') { - dc.send(e.toString()); - } - } - }); - - return new RTCGeneratorDataChannelWrapper(this, dcDeferred.promise, dcStatus.promise); + async connectRPCObject(o: any) { + // this should never actually be called as the client side should call the getParam version. + return sdk.connectRPCObject(o); } async close(): Promise { @@ -837,25 +730,6 @@ export class WebRTCConnectionManagement implements RTCConnectionManagement { } } -class RTCGeneratorDataChannelWrapper implements RTCGeneratorDataChannel { - constructor(public conn: WebRTCConnectionManagement, public dc: Promise, public dcStatus: Promise) { - } - - async close() { - this.conn.negotiation.then(async () => { - try { - const channel = await this.dc; - if (channel.readyState === 'closed') - return; - channel.close(); - await this.dcStatus.catch(() => {}); - } - catch (e) { - } - }); - } -} - export async function createRTCPeerConnectionSink( clientSignalingSession: RTCSignalingSession, console: Console, diff --git a/plugins/webrtc/src/main.ts b/plugins/webrtc/src/main.ts index 3fecdbdff..703568efa 100644 --- a/plugins/webrtc/src/main.ts +++ b/plugins/webrtc/src/main.ts @@ -4,14 +4,13 @@ import { timeoutPromise } from '@scrypted/common/src/promise-utils'; import { legacyGetSignalingSessionOptions } from '@scrypted/common/src/rtc-signaling'; import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from '@scrypted/common/src/settings-mixin'; import { createZygote } from '@scrypted/common/src/zygote'; -import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceProvider, FFmpegInput, ForkWorker, Intercom, MediaConverter, MediaObject, MediaObjectOptions, MixinProvider, RTCSessionControl, RTCSignalingChannel, RTCSignalingClient, RTCSignalingOptions, RTCSignalingSession, RequestMediaStream, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, SettingValue, Settings, VideoCamera, WritableDeviceState } from '@scrypted/sdk'; +import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceProvider, FFmpegInput, ForkWorker, Intercom, MediaConverter, MediaObject, MediaObjectOptions, MixinProvider, RTCSessionControl, RTCSignalingChannel, RTCSignalingClient, RTCSignalingOptions, RTCSignalingSession, RequestMediaStream, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, SettingValue, Settings, VideoCamera, WritableDeviceState } from '@scrypted/sdk'; import { StorageSettings } from '@scrypted/sdk/storage-settings'; +import { RpcPeer } from '@scrypted/server/src/rpc'; import crypto from 'crypto'; import ip from 'ip'; -import net from 'net'; import os from 'os'; -import worker_threads from 'worker_threads'; -import { DataChannelDebouncer } from './datachannel-debouncer'; +import { createDataChannelSerializer } from './datachannel-serializer'; import { WebRTCConnectionManagement, createRTCPeerConnectionSink, createTrackForwarder } from "./ffmpeg-to-wrtc"; import { stunServers, turnServers, weriftStunServers, weriftTurnServers } from './ice-servers'; import { waitClosed } from './peerconnection-util'; @@ -681,6 +680,23 @@ async function createConnection( const dc = pc.createDataChannel('rpc'); + const serializer = createDataChannelSerializer(dc); + const rpcPeer = new RpcPeer('webrtc-plugin', 'webrtc-client', serializer.sendMessage); + serializer.setupRpcPeer(rpcPeer); + dc.onmessage = (event) => { + if (event.data instanceof Buffer) { + serializer.onData(event.data); + } + }; + + // connect webrtc plugin directly to another plugin's object and proxy it over the datachannel. + // useful for generators. + const connectRPCObject: typeof sdk.connectRPCObject = async (o) => { + const ret = await sdk.connectRPCObject(o); + return ret; + }; + rpcPeer.params.connectRPCObject = connectRPCObject; + return connection; } export async function fork() { diff --git a/sdk/package-lock.json b/sdk/package-lock.json index f3f7ae5c2..f93c726fe 100644 --- a/sdk/package-lock.json +++ b/sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/sdk", - "version": "0.5.38", + "version": "0.5.39", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@scrypted/sdk", - "version": "0.5.38", + "version": "0.5.39", "license": "ISC", "dependencies": { "@babel/preset-typescript": "^7.27.1", diff --git a/sdk/package.json b/sdk/package.json index 792d7eadd..10263365e 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/sdk", - "version": "0.5.38", + "version": "0.5.39", "description": "", "main": "dist/src/index.js", "exports": { diff --git a/sdk/types/package-lock.json b/sdk/types/package-lock.json index bd1cd0bc4..8f676012c 100644 --- a/sdk/types/package-lock.json +++ b/sdk/types/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/types", - "version": "0.5.36", + "version": "0.5.37", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/types", - "version": "0.5.36", + "version": "0.5.37", "license": "ISC", "dependencies": { "openai": "^5.3.0" diff --git a/sdk/types/package.json b/sdk/types/package.json index d376dc48b..493b1854e 100644 --- a/sdk/types/package.json +++ b/sdk/types/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/types", - "version": "0.5.36", + "version": "0.5.37", "description": "", "main": "dist/index.js", "author": "", diff --git a/sdk/types/scrypted_python/scrypted_sdk/types.py b/sdk/types/scrypted_python/scrypted_sdk/types.py index e8849b395..8ecdd344c 100644 --- a/sdk/types/scrypted_python/scrypted_sdk/types.py +++ b/sdk/types/scrypted_python/scrypted_sdk/types.py @@ -1087,7 +1087,7 @@ class TamperState(TypedDict): pass -TYPES_VERSION = "0.5.36" +TYPES_VERSION = "0.5.37" class AirPurifier: diff --git a/sdk/types/src/types.input.ts b/sdk/types/src/types.input.ts index f400e3283..65f5fc6b8 100644 --- a/sdk/types/src/types.input.ts +++ b/sdk/types/src/types.input.ts @@ -2589,9 +2589,7 @@ export interface RTCConnectionManagement { videoDirection?: 'sendrecv' | 'sendonly' | 'recvonly', audioDirection?: 'sendrecv' | 'sendonly' | 'recvonly', }): Promise; - createRPCGeneratorDataChannel(label: string, generator: AsyncGenerator, options?: { - initialWindowSize?: number, - }): Promise; + connectRPCObject(value: T): Promise; close(): Promise; probe(): Promise; } diff --git a/server/package-lock.json b/server/package-lock.json index e737cefc1..a7f4fea4d 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -12,7 +12,7 @@ "dependencies": { "@scrypted/ffmpeg-static": "^6.1.0-build3", "@scrypted/node-pty": "^1.0.24", - "@scrypted/types": "^0.5.36", + "@scrypted/types": "^0.5.37", "adm-zip": "^0.5.16", "body-parser": "^2.2.0", "cookie-parser": "^1.4.7", @@ -594,9 +594,9 @@ } }, "node_modules/@scrypted/types": { - "version": "0.5.36", - "resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.5.36.tgz", - "integrity": "sha512-0pYlauijsDGQ8SyO8ca5sWiv1VPUgF73dv4ktjm7J9m3KIjImP7WfF1Y5YBdi5PHqo8JIt+SAFqhZE18ZxQcug==", + "version": "0.5.37", + "resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.5.37.tgz", + "integrity": "sha512-RksclqyAN4c5Mm1hndeGIXVm1p9o80VPoYspGatkSGznoG6I2TMklm8uyiZCvdTYAbeH9GWffEmf5u631xcN8g==", "license": "ISC", "dependencies": { "openai": "^5.3.0" diff --git a/server/package.json b/server/package.json index 79d39c9f3..cce6e7fa8 100644 --- a/server/package.json +++ b/server/package.json @@ -5,7 +5,7 @@ "dependencies": { "@scrypted/ffmpeg-static": "^6.1.0-build3", "@scrypted/node-pty": "^1.0.24", - "@scrypted/types": "^0.5.36", + "@scrypted/types": "^0.5.37", "adm-zip": "^0.5.16", "body-parser": "^2.2.0", "cookie-parser": "^1.4.7", diff --git a/server/src/rpc-serializer.ts b/server/src/rpc-serializer.ts index 2e1fb568d..58e3d59e6 100644 --- a/server/src/rpc-serializer.ts +++ b/server/src/rpc-serializer.ts @@ -35,7 +35,7 @@ export function createRpcSerializer(options: { rpcPeer.kill('connection closed.'); } - const sendMessage = (message: any, reject: (e: Error) => void, serializationContext: any,) => { + const sendMessage = (message: any, reject?: (e: Error) => void, serializationContext?: any) => { if (!connected) { reject?.(new Error('peer disconnected')); return;