mirror of
https://github.com/koush/scrypted.git
synced 2026-02-07 16:02:13 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e33cacd15d | ||
|
|
e225b746c4 | ||
|
|
1e1fda6b9a | ||
|
|
e23c9c22dc | ||
|
|
28f1ce9d4e | ||
|
|
5f4e2793ff | ||
|
|
1e1755fa7e | ||
|
|
ebd56b86e4 | ||
|
|
4607bec07c | ||
|
|
a26cdfea2a | ||
|
|
6d0da449ad | ||
|
|
ad15fe3324 | ||
|
|
fbe3e83884 | ||
|
|
0d4cf34930 | ||
|
|
3a3e15cd74 | ||
|
|
8b9cfebbfa | ||
|
|
b3087058a7 | ||
|
|
4e923a78da | ||
|
|
b5593d6251 | ||
|
|
40b7b621a0 | ||
|
|
7104ad6378 | ||
|
|
51f893ef63 | ||
|
|
29c5dfd73b | ||
|
|
1615f79a0b | ||
|
|
dc5a6126b9 | ||
|
|
0fbe3e4686 | ||
|
|
c9641568f8 | ||
|
|
dceea38eb8 | ||
|
|
cd1fce71e2 |
@@ -1,6 +1,6 @@
|
||||
# Home Assistant Addon Configuration
|
||||
name: Scrypted
|
||||
version: "v0.114.0-jammy-full"
|
||||
version: "v0.116.0-jammy-full"
|
||||
slug: scrypted
|
||||
description: Scrypted is a high performance home video integration and automation platform
|
||||
url: "https://github.com/koush/scrypted"
|
||||
|
||||
@@ -33,20 +33,20 @@ rm -rf /tmp/npu && mkdir -p /tmp/npu && cd /tmp/npu
|
||||
# different npu downloads for ubuntu versions
|
||||
if [ -n "$UBUNTU_22_04" ]
|
||||
then
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.5.1/intel-driver-compiler-npu_1.5.1.20240708-9842236399_ubuntu22.04_amd64.deb
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-driver-compiler-npu_1.6.0.20240814-10390978568_ubuntu22.04_amd64.deb
|
||||
# firmware can only be installed on host. will cause problems inside container.
|
||||
if [ -n "$INTEL_FW_NPU" ]
|
||||
then
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.5.1/intel-fw-npu_1.5.1.20240708-9842236399_ubuntu22.04_amd64.deb
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-fw-npu_1.6.0.20240814-10390978568_ubuntu22.04_amd64.deb
|
||||
fi
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.5.1/intel-level-zero-npu_1.5.1.20240708-9842236399_ubuntu22.04_amd64.deb
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-level-zero-npu_1.6.0.20240814-10390978568_ubuntu22.04_amd64.deb
|
||||
else
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.5.1/intel-driver-compiler-npu_1.5.1.20240708-9842236399_ubuntu24.04_amd64.deb
|
||||
if [ -n "$INTEL_FW_NPU" ]
|
||||
then
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.5.1/intel-fw-npu_1.5.1.20240708-9842236399_ubuntu24.04_amd64.deb
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-fw-npu_1.6.0.20240814-10390978568_ubuntu24.04_amd64.deb
|
||||
fi
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.5.1/intel-level-zero-npu_1.5.1.20240708-9842236399_ubuntu24.04_amd64.deb
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-level-zero-npu_1.6.0.20240814-10390978568_ubuntu24.04_amd64.deb
|
||||
fi
|
||||
|
||||
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v1.17.6/level-zero_1.17.6+u22.04_amd64.deb
|
||||
|
||||
@@ -10,7 +10,7 @@ function readyn() {
|
||||
}
|
||||
|
||||
cd /tmp
|
||||
SCRYPTED_VERSION=v0.96.0
|
||||
SCRYPTED_VERSION=v0.116.0
|
||||
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
|
||||
if [ -z "$VMID" ]
|
||||
then
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<details>
|
||||
<summary>Changelog</summary>
|
||||
|
||||
### 0.3.2
|
||||
|
||||
alexa: fix syncedDevices being undefined
|
||||
|
||||
|
||||
### 0.3.1
|
||||
|
||||
alexa/google-home: fix potential vulnerability. do not allow local network control using cloud tokens belonging to a different user. the plugins are now locked to a specific scrypted cloud account once paired.
|
||||
|
||||
4
plugins/alexa/package-lock.json
generated
4
plugins/alexa/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.3",
|
||||
"dependencies": {
|
||||
"axios": "^1.3.4",
|
||||
"uuid": "^9.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.3",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
|
||||
4
plugins/amcrest/package-lock.json
generated
4
plugins/amcrest/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.161",
|
||||
"version": "0.0.162",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.161",
|
||||
"version": "0.0.162",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.161",
|
||||
"version": "0.0.162",
|
||||
"description": "Amcrest Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { OnvifIntercom } from "../../onvif/src/onvif-intercom";
|
||||
import { createRtspMediaStreamOptions, RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp";
|
||||
import { AmcrestCameraClient, AmcrestEvent, AmcrestEventData } from "./amcrest-api";
|
||||
import { amcrestAutoConfigureSettings, autoconfigureSettings } from "./amcrest-configure";
|
||||
import { group } from "console";
|
||||
|
||||
const { mediaManager } = sdk;
|
||||
|
||||
@@ -336,10 +337,14 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
|
||||
const ac = {
|
||||
...automaticallyConfigureSettings,
|
||||
subgroup: 'Advanced',
|
||||
};
|
||||
ac.type = 'button';
|
||||
ret.push(ac);
|
||||
ret.push({ ...amcrestAutoConfigureSettings });
|
||||
ret.push({
|
||||
...amcrestAutoConfigureSettings,
|
||||
subgroup: 'Advanced',
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
1
plugins/cloud/.vscode/launch.json
vendored
1
plugins/cloud/.vscode/launch.json
vendored
@@ -16,7 +16,6 @@
|
||||
"ts-node/register"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"protocol": "inspector",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
|
||||
2
plugins/cloud/.vscode/settings.json
vendored
2
plugins/cloud/.vscode/settings.json
vendored
@@ -1,4 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
}
|
||||
38
plugins/cloud/package-lock.json
generated
38
plugins/cloud/package-lock.json
generated
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@scrypted/cloud",
|
||||
"version": "0.2.32",
|
||||
"version": "0.2.37",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/cloud",
|
||||
"version": "0.2.32",
|
||||
"version": "0.2.37",
|
||||
"dependencies": {
|
||||
"@eneris/push-receiver": "^4.1.6",
|
||||
"@eneris/push-receiver": "^4.2.0",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"bpmux": "^8.2.1",
|
||||
@@ -21,7 +21,7 @@
|
||||
"@types/http-proxy": "^1.17.15",
|
||||
"@types/ip": "^1.1.3",
|
||||
"@types/nat-upnp": "^1.1.5",
|
||||
"@types/node": "^22.1.0",
|
||||
"@types/node": "^22.5.2",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
@@ -79,7 +79,7 @@
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.50",
|
||||
"version": "0.3.61",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
@@ -2416,14 +2416,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eneris/push-receiver": {
|
||||
"version": "4.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@eneris/push-receiver/-/push-receiver-4.1.6.tgz",
|
||||
"integrity": "sha512-M5EZK7s3AujCuWDrqbEHeVRyyeK145hs1FtWkrOKtEbD0lSZgLfhoHIr09adc+hvcNDOAy8awVh/64XsprWVLw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@eneris/push-receiver/-/push-receiver-4.2.0.tgz",
|
||||
"integrity": "sha512-OGc4dUcy9yvKTIShOpBVHsYGEC7qRo6JNRRlsL7kaa3/uJSJyRoLOUdKsTqH8q76qldViXb8mEdOzCGv8wwK4A==",
|
||||
"dependencies": {
|
||||
"http_ece": "^1.2.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"long": "^5.2.3",
|
||||
"protobufjs": "^7.3.2"
|
||||
"protobufjs": "^7.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
@@ -2568,11 +2568,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz",
|
||||
"integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==",
|
||||
"version": "22.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.2.tgz",
|
||||
"integrity": "sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.13.0"
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
@@ -2813,9 +2813,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/protobufjs": {
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.2.tgz",
|
||||
"integrity": "sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==",
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
|
||||
"integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
@@ -2932,9 +2932,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz",
|
||||
"integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg=="
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
|
||||
@@ -30,15 +30,14 @@
|
||||
"realfs": true,
|
||||
"interfaces": [
|
||||
"SystemSettings",
|
||||
"BufferConverter",
|
||||
"MediaConverter",
|
||||
"OauthClient",
|
||||
"Settings",
|
||||
"DeviceProvider",
|
||||
"HttpRequestHandler"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@eneris/push-receiver": "^4.1.6",
|
||||
"@eneris/push-receiver": "^4.2.0",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"bpmux": "^8.2.1",
|
||||
@@ -51,8 +50,8 @@
|
||||
"@types/http-proxy": "^1.17.15",
|
||||
"@types/ip": "^1.1.3",
|
||||
"@types/nat-upnp": "^1.1.5",
|
||||
"@types/node": "^22.1.0",
|
||||
"@types/node": "^22.5.2",
|
||||
"ts-node": "^10.9.2"
|
||||
},
|
||||
"version": "0.2.32"
|
||||
"version": "0.2.37"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ function runLog(bin: string, args: string[]) {
|
||||
return cp;
|
||||
}
|
||||
|
||||
async function runLogWait(bin: string, args: string[], timeout: number, signal?: AbortSignal) {
|
||||
async function runLogWait(bin: string, args: string[], timeout: number, signal?: AbortSignal, outputChanged?: (output: string) => void) {
|
||||
const cp = runLog(bin, args);
|
||||
|
||||
signal?.addEventListener('abort', () => {
|
||||
@@ -37,9 +37,11 @@ async function runLogWait(bin: string, args: string[], timeout: number, signal?:
|
||||
let output: string = '';
|
||||
cp.stdio[1].on('data', (data) => {
|
||||
output += data.toString();
|
||||
outputChanged?.(output);
|
||||
});
|
||||
cp.stdio[2].on('data', (data) => {
|
||||
output += data.toString();
|
||||
outputChanged?.(output);
|
||||
});
|
||||
|
||||
await timeoutPromise(timeout, once(cp, 'exit'));
|
||||
@@ -49,15 +51,23 @@ async function runLogWait(bin: string, args: string[], timeout: number, signal?:
|
||||
return output;
|
||||
}
|
||||
|
||||
async function login(bin: string, signal?: AbortSignal) {
|
||||
async function login(bin: string, signal?: AbortSignal, urlCallback?: (url: string) => void) {
|
||||
const userHome = process.env.HOME || process.env.USERPROFILE;
|
||||
const certPem = path.join(userHome, '.cloudflared', 'cert.pem');
|
||||
rmSync(certPem, { force: true, recursive: true });
|
||||
|
||||
await runLogWait(bin, ['tunnel', 'login'], 300000, signal);
|
||||
await runLogWait(bin, ['tunnel', 'login'], 300000, signal, output => {
|
||||
const match = output.match(/Please open the following URL and log in with your Cloudflare account:(?<url>.*?)Leave/s);
|
||||
if (match) {
|
||||
const url = match.groups.url.trim();
|
||||
if (url)
|
||||
urlCallback(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function createTunnel(bin: string, domain: string) {
|
||||
await runLogWait(bin, ['tunnel', 'cleanup', domain], 30000).catch(() => { });
|
||||
await runLogWait(bin, ['tunnel', 'delete', domain], 30000).catch(() => { });
|
||||
return runLogWait(bin, ['tunnel', 'create', domain], 30000);
|
||||
}
|
||||
@@ -103,10 +113,10 @@ async function ensureBin(bin: string) {
|
||||
return bin;
|
||||
}
|
||||
|
||||
export async function createLocallyManagedTunnel(domain: string, bin?: string, signal?: AbortSignal) {
|
||||
export async function createLocallyManagedTunnel(domain: string, bin?: string, signal?: AbortSignal, urlCallback?: (url: string) => void) {
|
||||
bin = await ensureBin(bin);
|
||||
|
||||
await login(bin, signal);
|
||||
await login(bin, signal, urlCallback);
|
||||
const createOutput = await createTunnel(bin, domain);
|
||||
const jsonFilePath = extractJsonFilePath(createOutput);
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Deferred } from "@scrypted/common/src/deferred";
|
||||
import sdk, { BufferConverter, DeviceProvider, HttpRequest, HttpRequestHandler, HttpResponse, OauthClient, PushHandler, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings } from "@scrypted/sdk";
|
||||
import sdk, { HttpRequest, HttpRequestHandler, HttpResponse, MediaConverter, MediaObject, MediaObjectOptions, OauthClient, PushHandler, ScryptedDeviceBase, ScryptedInterface, ScryptedMimeTypes, Setting, Settings } from "@scrypted/sdk";
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
import bpmux from 'bpmux';
|
||||
import { ChildProcess } from "child_process";
|
||||
import * as cloudflared from 'cloudflared';
|
||||
import crypto from 'crypto';
|
||||
import { once } from 'events';
|
||||
@@ -20,12 +21,9 @@ import { sleep } from '../../../common/src/sleep';
|
||||
import { createSelfSignedCertificate } from '../../../server/src/cert';
|
||||
import { httpFetch } from '../../../server/src/fetch/http-fetch';
|
||||
import { installCloudflared } from "./cloudflared-install";
|
||||
import { createLocallyManagedTunnel, runLocallyManagedTunnel } from "./cloudflared-local-managed";
|
||||
import { PushManager } from './push';
|
||||
import { qsparse, qsstringify } from "./qs";
|
||||
import { createLocallyManagedTunnel, runLocallyManagedTunnel } from "./cloudflared-local-managed";
|
||||
import { ChildProcess } from "child_process";
|
||||
|
||||
// import { registerDuckDns } from "./greenlock";
|
||||
|
||||
const { deviceManager, endpointManager, systemManager } = sdk;
|
||||
|
||||
@@ -34,25 +32,7 @@ const SCRYPTED_SERVER = localStorage.getItem('scrypted-server') || 'home.scrypte
|
||||
|
||||
const SCRYPTED_CLOUD_MESSAGE_PATH = '/_punch/cloudmessage';
|
||||
|
||||
class ScryptedPush extends ScryptedDeviceBase implements BufferConverter {
|
||||
constructor(public cloud: ScryptedCloud) {
|
||||
super('push');
|
||||
|
||||
this.fromMimeType = ScryptedMimeTypes.PushEndpoint;
|
||||
this.toMimeType = ScryptedMimeTypes.Url;
|
||||
}
|
||||
|
||||
async convert(data: Buffer | string, fromMimeType: string): Promise<Buffer> {
|
||||
const validDomain = this.cloud.getSSLHostname();
|
||||
if (validDomain)
|
||||
return Buffer.from(`https://${validDomain}${await this.cloud.getCloudMessagePath()}/${data}`);
|
||||
|
||||
const url = `http://127.0.0.1/push/${data}`;
|
||||
return this.cloud.whitelist(url, 10 * 365 * 24 * 60 * 60 * 1000, `https://${this.cloud.getHostname()}${SCRYPTED_CLOUD_MESSAGE_PATH}`);
|
||||
}
|
||||
}
|
||||
|
||||
class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings, BufferConverter, DeviceProvider, HttpRequestHandler {
|
||||
class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings, MediaConverter, HttpRequestHandler {
|
||||
cloudflareTunnel: string;
|
||||
cloudflared: {
|
||||
url: Promise<string>;
|
||||
@@ -62,7 +42,6 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
server: http.Server;
|
||||
secureServer: https.Server;
|
||||
proxy: HttpProxy;
|
||||
push: ScryptedPush;
|
||||
whitelisted = new Map<string, string>();
|
||||
reregisterTimer: NodeJS.Timeout;
|
||||
storageSettings = new StorageSettings(this, {
|
||||
@@ -85,15 +64,16 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
persistedDefaultValue: crypto.randomBytes(8).toString('hex'),
|
||||
},
|
||||
forwardingMode: {
|
||||
title: "Port Forwarding Mode",
|
||||
description: "The port forwarding mode used to expose the HTTPS port. If port forwarding is disabled or unavailable, Scrypted Cloud will fall back to push to initiate connections with this Scrypted server. Port Forwarding and UPNP are optional but will significantly speed up cloud connections.",
|
||||
title: "Connection Mode",
|
||||
description: "The connection mode that exposes this server to the internet.",
|
||||
choices: [
|
||||
"Default",
|
||||
"UPNP",
|
||||
"Router Forward",
|
||||
"Custom Domain",
|
||||
"Disabled",
|
||||
],
|
||||
defaultValue: 'UPNP',
|
||||
defaultValue: 'Default',
|
||||
onPut: () => this.scheduleRefreshPortForward(),
|
||||
},
|
||||
hostname: {
|
||||
@@ -189,10 +169,27 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
title: 'Cloudflare Tunnel Custom Domain',
|
||||
placeholder: 'scrypted.example.com',
|
||||
description: 'Optional: Host a custom domain with Cloudflare. After setting the domain, complete the Cloudflare browser login link shown in Scrypted Cloud Plugin Console.',
|
||||
mapPut: (ov, nv) => {
|
||||
try {
|
||||
const url = new URL(nv);
|
||||
return url.hostname;
|
||||
}
|
||||
catch (e) {
|
||||
return nv;
|
||||
}
|
||||
},
|
||||
onPut: (_, nv) => {
|
||||
if (!nv)
|
||||
this.storageSettings.values.cloudflaredTunnelCredentials = undefined;
|
||||
this.doCloudflaredLogin(nv);
|
||||
},
|
||||
},
|
||||
cloudflaredTunnelLoginUrl: {
|
||||
group: 'Cloudflare',
|
||||
type: 'html',
|
||||
title: 'Cloudflare Tunnel Login',
|
||||
hide: true,
|
||||
},
|
||||
cloudflaredTunnelUrl: {
|
||||
group: 'Cloudflare',
|
||||
title: 'Cloudflare Tunnel URL',
|
||||
@@ -245,6 +242,10 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
reverseConnections = new Set<Duplex>();
|
||||
cloudflaredLoginController?: AbortController;
|
||||
|
||||
get portForwardingDisabled() {
|
||||
return this.storageSettings.values.forwardingMode === 'Disabled' || this.storageSettings.values.forwardingMode === 'Default';
|
||||
}
|
||||
|
||||
get cloudflareTunnelHost() {
|
||||
if (!this.cloudflareTunnel)
|
||||
return;
|
||||
@@ -254,6 +255,17 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.converters = [
|
||||
[ScryptedMimeTypes.LocalUrl, ScryptedMimeTypes.Url],
|
||||
[ScryptedMimeTypes.PushEndpoint, ScryptedMimeTypes.Url],
|
||||
];
|
||||
// legacy cleanup
|
||||
this.fromMimeType = undefined;
|
||||
this.toMimeType = undefined;
|
||||
deviceManager.onDevicesChanged({
|
||||
devices: [],
|
||||
});
|
||||
|
||||
this.storageSettings.settings.register.onPut = async () => {
|
||||
await this.sendRegistrationId(await this.manager.registrationId);
|
||||
}
|
||||
@@ -281,8 +293,9 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
};
|
||||
|
||||
this.storageSettings.settings.securePort.onGet = async () => {
|
||||
const hide = this.portForwardingDisabled;
|
||||
return {
|
||||
hide: this.storageSettings.values.forwardingMode === 'Disabled',
|
||||
hide,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -292,20 +305,6 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
};
|
||||
|
||||
// this.storageSettings.settings.duckDnsToken.onGet = async () => {
|
||||
// return {
|
||||
// hide: this.storageSettings.values.forwardingMode === 'Custom Domain'
|
||||
// || this.storageSettings.values.forwardingMode === 'Disabled',
|
||||
// }
|
||||
// };
|
||||
|
||||
// this.storageSettings.settings.duckDnsHostname.onGet = async () => {
|
||||
// return {
|
||||
// hide: this.storageSettings.values.forwardingMode === 'Custom Domain'
|
||||
// || this.storageSettings.values.forwardingMode === 'Disabled',
|
||||
// }
|
||||
// };
|
||||
|
||||
this.storageSettings.settings.cloudflaredTunnelCustomDomain.onGet =
|
||||
this.storageSettings.settings.cloudflaredTunnelUrl.onGet = async () => {
|
||||
return {
|
||||
@@ -313,21 +312,21 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
};
|
||||
|
||||
this.log.clearAlerts();
|
||||
|
||||
this.storageSettings.settings.securePort.onPut = (ov, nv) => {
|
||||
if (ov && ov !== nv)
|
||||
this.log.a('Reload the Scrypted Cloud Plugin to apply the port change.');
|
||||
};
|
||||
|
||||
this.fromMimeType = ScryptedMimeTypes.LocalUrl;
|
||||
this.toMimeType = ScryptedMimeTypes.Url;
|
||||
|
||||
if (!this.storageSettings.values.certificate)
|
||||
this.storageSettings.values.certificate = createSelfSignedCertificate();
|
||||
|
||||
if (this.storageSettings.values.cloudflaredTunnelCustomDomain && !this.storageSettings.values.cloudflaredTunnelCredentials)
|
||||
this.storageSettings.values.cloudflaredTunnelCustomDomain = undefined;
|
||||
|
||||
this.log.clearAlerts();
|
||||
|
||||
const proxy = this.setupProxyServer();
|
||||
this.setupCloudPush();
|
||||
this.updateCors();
|
||||
|
||||
const observeRegistrations = () => {
|
||||
@@ -445,7 +444,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
|
||||
async testPortForward() {
|
||||
try {
|
||||
if (this.storageSettings.values.forwardingMode === 'Disabled')
|
||||
if (this.portForwardingDisabled)
|
||||
throw new Error('Port forwarding is disabled.');
|
||||
|
||||
const pluginPath = await endpointManager.getPath(undefined, {
|
||||
@@ -480,7 +479,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
if (!upnpPort)
|
||||
upnpPort = Math.round(Math.random() * 20000 + 40000);
|
||||
|
||||
if (this.storageSettings.values.forwardingMode === 'Disabled') {
|
||||
if (this.portForwardingDisabled) {
|
||||
this.updatePortForward(upnpPort);
|
||||
return;
|
||||
}
|
||||
@@ -602,10 +601,11 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
|
||||
getAuthority() {
|
||||
const { forwardingMode } = this.storageSettings.values;
|
||||
if (forwardingMode === 'Disabled')
|
||||
if (this.portForwardingDisabled)
|
||||
return {};
|
||||
|
||||
const { forwardingMode } = this.storageSettings.values;
|
||||
|
||||
const upnp_port = forwardingMode === 'Custom Domain' ? 443 : this.storageSettings.values.upnpPort;
|
||||
const hostname = forwardingMode === 'Custom Domain'
|
||||
? this.storageSettings.values.hostname
|
||||
@@ -687,18 +687,6 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
}
|
||||
|
||||
async setupCloudPush() {
|
||||
await deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
name: 'Cloud Push Endpoint',
|
||||
type: ScryptedDeviceType.API,
|
||||
nativeId: 'push',
|
||||
interfaces: [ScryptedInterface.BufferConverter],
|
||||
},
|
||||
);
|
||||
this.push = new ScryptedPush(this);
|
||||
}
|
||||
|
||||
async onRequest(request: HttpRequest, response: HttpResponse): Promise<void> {
|
||||
if (request.url.endsWith('/testPortForward')) {
|
||||
response.send(this.randomBytes);
|
||||
@@ -725,10 +713,6 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
}
|
||||
|
||||
async getDevice(nativeId: string) {
|
||||
return this.push;
|
||||
}
|
||||
|
||||
async releaseDevice(id: string, nativeId: string): Promise<void> {
|
||||
}
|
||||
|
||||
@@ -744,19 +728,34 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
return this.getSSLHostname() || SCRYPTED_SERVER;
|
||||
}
|
||||
|
||||
async convert(data: Buffer, fromMimeType: string, toMimeType: string): Promise<Buffer> {
|
||||
// if cloudflare is enabled and the plugin isn't set up as a custom domain, try to use the cloudflare url for
|
||||
// short lived urls.
|
||||
if (this.cloudflareTunnel && this.storageSettings.values.forwardingMode !== 'Custom Domain') {
|
||||
const params = new URLSearchParams(toMimeType.split(';')[1] || '');
|
||||
if (params.get('short-lived') === 'true') {
|
||||
const u = new URL(data.toString(), this.cloudflareTunnel);
|
||||
u.host = this.cloudflareTunnelHost;
|
||||
u.port = '';
|
||||
return Buffer.from(u.toString());
|
||||
async convertMedia(data: string | Buffer | any, fromMimeType: string, toMimeType: string, options?: MediaObjectOptions): Promise<MediaObject | Buffer | any> {
|
||||
if (!toMimeType.startsWith(ScryptedMimeTypes.Url))
|
||||
throw new Error('unsupported cloud url conversion');
|
||||
|
||||
if (fromMimeType.startsWith(ScryptedMimeTypes.LocalUrl)) {
|
||||
// if cloudflare is enabled and the plugin isn't set up as a custom domain, try to use the cloudflare url for
|
||||
// short lived urls.
|
||||
if (this.cloudflareTunnel && this.storageSettings.values.forwardingMode !== 'Custom Domain') {
|
||||
const params = new URLSearchParams(toMimeType.split(';')[1] || '');
|
||||
if (params.get('short-lived') === 'true') {
|
||||
const u = new URL(data.toString(), this.cloudflareTunnel);
|
||||
u.host = this.cloudflareTunnelHost;
|
||||
u.port = '';
|
||||
return Buffer.from(u.toString());
|
||||
}
|
||||
}
|
||||
return this.whitelist(data.toString(), 10 * 365 * 24 * 60 * 60 * 1000, `https://${this.getHostname()}`);
|
||||
}
|
||||
return this.whitelist(data.toString(), 10 * 365 * 24 * 60 * 60 * 1000, `https://${this.getHostname()}`);
|
||||
else if (fromMimeType.startsWith(ScryptedMimeTypes.PushEndpoint)) {
|
||||
const validDomain = this.getSSLHostname();
|
||||
if (validDomain)
|
||||
return Buffer.from(`https://${validDomain}${await this.getCloudMessagePath()}/${data}`);
|
||||
|
||||
const url = `http://127.0.0.1/push/${data}`;
|
||||
return this.whitelist(url, 10 * 365 * 24 * 60 * 60 * 1000, `https://${this.getHostname()}${SCRYPTED_CLOUD_MESSAGE_PATH}`);
|
||||
}
|
||||
|
||||
throw new Error('unsupported cloud url conversion');
|
||||
}
|
||||
|
||||
async getSettings(): Promise<Setting[]> {
|
||||
@@ -961,6 +960,9 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
|
||||
socket.pipe(local).pipe(socket);
|
||||
});
|
||||
mux.on('error', () => {
|
||||
client.destroy();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1216,18 +1218,25 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
return;
|
||||
}
|
||||
|
||||
this.log.a('Visit the URL printed in the Scrypted Cloud plugin console to log into Cloudflare.');
|
||||
// this.log.a('Visit the URL printed in the Scrypted Cloud plugin console to log into Cloudflare.');
|
||||
const customDomain = this.storageSettings.values.cloudflaredTunnelCustomDomain;
|
||||
try {
|
||||
this.cloudflaredLoginController?.abort();
|
||||
this.cloudflaredLoginController = new AbortController();
|
||||
const { bin } = await installCloudflared();
|
||||
const jsonContents = await createLocallyManagedTunnel(domain, bin, this.cloudflaredLoginController.signal);
|
||||
const jsonContents = await createLocallyManagedTunnel(domain, bin, this.cloudflaredLoginController.signal, url => {
|
||||
this.console.warn('Cloudflare login URL:', url);
|
||||
this.storageSettings.values.cloudflaredTunnelLoginUrl = `<div style="padding-bottom: 16px"><a href="${url}" target="_blank" >Click here to log into Cloudflare</a></div>`;
|
||||
this.storageSettings.settings.cloudflaredTunnelLoginUrl.hide = false;
|
||||
this.onDeviceEvent(ScryptedInterface.Settings, undefined);
|
||||
});
|
||||
this.storageSettings.values.cloudflaredTunnelCredentials = jsonContents;
|
||||
this.storageSettings.values.cloudflaredTunnelToken = undefined;
|
||||
this.cloudflared?.child.kill();
|
||||
}
|
||||
catch (e) {
|
||||
this.storageSettings.values.cloudflaredTunnelCustomDomain = undefined;
|
||||
if (customDomain)
|
||||
this.storageSettings.values.cloudflaredTunnelCustomDomain = undefined;
|
||||
this.console.error('cloudflared login error', e);
|
||||
this.log.a('Cloudflare login error. See console logs.');
|
||||
}
|
||||
|
||||
@@ -29,11 +29,14 @@ export class PushManager extends EventEmitter {
|
||||
const instance = new PushReceiver({
|
||||
...savedConfig,
|
||||
firebase: {
|
||||
messagingSenderId: senderId,
|
||||
projectId: 'scrypted-app',
|
||||
apiKey: 'AIzaSyDI0bgFuVPIqKZoNpB-iTOU7ijIeepxOXE',
|
||||
appId: '1:827888101440:web:6ff9f8ada107e9cc0097a5',
|
||||
} ,
|
||||
apiKey: "AIzaSyDI0bgFuVPIqKZoNpB-iTOU7ijIeepxOXE",
|
||||
authDomain: "scrypted-app.firebaseapp.com",
|
||||
databaseURL: "https://scrypted-app.firebaseio.com",
|
||||
projectId: "scrypted-app",
|
||||
storageBucket: "scrypted-app.appspot.com",
|
||||
messagingSenderId: "827888101440",
|
||||
appId: "1:827888101440:web:6ff9f8ada107e9cc0097a5"
|
||||
},
|
||||
heartbeatIntervalMs: 15 * 60 * 1000,
|
||||
});
|
||||
|
||||
@@ -57,7 +60,12 @@ export class PushManager extends EventEmitter {
|
||||
this.emit('message', message.data);
|
||||
});
|
||||
|
||||
await instance.connect();
|
||||
try {
|
||||
await instance.connect();
|
||||
}
|
||||
catch (e) {
|
||||
console.error('failed to connect to push server', e);
|
||||
}
|
||||
|
||||
return savedConfig.credentials?.fcm?.token || deferred.promise;
|
||||
})();
|
||||
|
||||
4
plugins/core/package-lock.json
generated
4
plugins/core/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.65",
|
||||
"version": "0.3.68",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.65",
|
||||
"version": "0.3.68",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.65",
|
||||
"version": "0.3.68",
|
||||
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function checkLxcDependencies() {
|
||||
// the current workaround is to install the release manually.
|
||||
// https://github.com/intel/compute-runtime/releases/tag/24.13.29138.7
|
||||
const output = await new Promise<string>((r,f)=> child_process.exec("sh -c 'apt show versions intel-opencl-icd'", (err, stdout, stderr) => {
|
||||
if (err)
|
||||
if (err && !stdout && !stderr)
|
||||
f(err);
|
||||
else
|
||||
r(stdout + '\n' + stderr);
|
||||
@@ -73,6 +73,30 @@ export async function checkLxcDependencies() {
|
||||
sdk.log.a('Failed to verify/install intel-opencl-icd version.');
|
||||
}
|
||||
|
||||
try {
|
||||
const output = await new Promise<string>((r,f)=> child_process.exec("sh -c 'apt show versions intel-driver-compiler-npu'", (err, stdout, stderr) => {
|
||||
if (err && !stdout && !stderr)
|
||||
f(err);
|
||||
else
|
||||
r(stdout + '\n' + stderr);
|
||||
}));
|
||||
|
||||
if (
|
||||
// apt
|
||||
output.includes('No packages found')
|
||||
) {
|
||||
const cp = child_process.spawn('sh', ['-c', 'curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash']);
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
if (exitCode !== 0)
|
||||
sdk.log.a('Failed to install intel-driver-compiler-npu.');
|
||||
else
|
||||
needRestart = true;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
sdk.log.a('Failed to verify/install intel-driver-compiler-npu.');
|
||||
}
|
||||
|
||||
if (needRestart)
|
||||
sdk.log.a('A system update is pending. Please restart Scrypted to apply changes.');
|
||||
}
|
||||
|
||||
4
plugins/google-home/package-lock.json
generated
4
plugins/google-home/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/google-home",
|
||||
"version": "0.0.60",
|
||||
"version": "0.0.61",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/google-home",
|
||||
"version": "0.0.60",
|
||||
"version": "0.0.61",
|
||||
"dependencies": {
|
||||
"@googleapis/homegraph": "^2.0.0",
|
||||
"@homebridge/ciao": "^1.1.5",
|
||||
|
||||
@@ -49,5 +49,5 @@
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@types/url-parse": "^1.4.3"
|
||||
},
|
||||
"version": "0.0.60"
|
||||
"version": "0.0.61"
|
||||
}
|
||||
|
||||
4
plugins/hikvision/package-lock.json
generated
4
plugins/hikvision/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/hikvision",
|
||||
"version": "0.0.159",
|
||||
"version": "0.0.160",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/hikvision",
|
||||
"version": "0.0.159",
|
||||
"version": "0.0.160",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/hikvision",
|
||||
"version": "0.0.159",
|
||||
"version": "0.0.160",
|
||||
"description": "Hikvision Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -465,10 +465,14 @@ export class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom
|
||||
|
||||
const ac = {
|
||||
...automaticallyConfigureSettings,
|
||||
subgroup: 'Advanced',
|
||||
};
|
||||
ac.type = 'button';
|
||||
ret.push(ac);
|
||||
ret.push({ ...hikvisionAutoConfigureSettings });
|
||||
ret.push({
|
||||
...hikvisionAutoConfigureSettings,
|
||||
subgroup: 'Advanced',
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
4
plugins/onnx/package-lock.json
generated
4
plugins/onnx/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/onnx",
|
||||
"version": "0.1.109",
|
||||
"version": "0.1.110",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/onnx",
|
||||
"version": "0.1.109",
|
||||
"version": "0.1.110",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -42,5 +42,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.109"
|
||||
"version": "0.1.110"
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ class ONNXPlugin(
|
||||
if sys.platform == 'darwin':
|
||||
providers.append("CoreMLExecutionProvider")
|
||||
|
||||
if ('linux' in sys.platform or 'win' in sys.platform) and platform.machine() == 'x86_64':
|
||||
if ('linux' in sys.platform or 'win' in sys.platform) and (platform.machine() == 'x86_64' or platform.machine() == 'AMD64'):
|
||||
deviceId = int(deviceId)
|
||||
providers.append(("CUDAExecutionProvider", { "device_id": deviceId }))
|
||||
|
||||
|
||||
4
plugins/onvif/package-lock.json
generated
4
plugins/onvif/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/onvif",
|
||||
"version": "0.1.22",
|
||||
"version": "0.1.23",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/onvif",
|
||||
"version": "0.1.22",
|
||||
"version": "0.1.23",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/onvif",
|
||||
"version": "0.1.22",
|
||||
"version": "0.1.23",
|
||||
"description": "ONVIF Camera Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -227,9 +227,14 @@ class OnvifCamera extends RtspSmartCamera implements ObjectDetector, Intercom, V
|
||||
|
||||
const ac = {
|
||||
...automaticallyConfigureSettings,
|
||||
subgroup: 'Advanced',
|
||||
};
|
||||
ac.type = 'button';
|
||||
ret.push(ac);
|
||||
ret.push({
|
||||
...onvifAutoConfigureSettings,
|
||||
subgroup: 'Advanced',
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
2
plugins/openvino/.vscode/launch.json
vendored
2
plugins/openvino/.vscode/launch.json
vendored
@@ -21,7 +21,7 @@
|
||||
},
|
||||
{
|
||||
"localRoot": "${workspaceFolder}/src",
|
||||
"remoteRoot": "${config:scrypted.pythonRemoteRoot}"
|
||||
"remoteRoot": "."
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
10
plugins/openvino/.vscode/settings.json
vendored
10
plugins/openvino/.vscode/settings.json
vendored
@@ -1,12 +1,12 @@
|
||||
|
||||
{
|
||||
// docker installation
|
||||
"scrypted.debugHost": "scrypted-demo",
|
||||
"scrypted.serverRoot": "/server",
|
||||
// "scrypted.debugHost": "scrypted-demo",
|
||||
// "scrypted.serverRoot": "/server",
|
||||
|
||||
// proxmox installation
|
||||
// "scrypted.debugHost": "scrypted-server",
|
||||
// "scrypted.serverRoot": "/root/.scrypted",
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
"scrypted.serverRoot": "/root/.scrypted",
|
||||
|
||||
// pi local installation
|
||||
// "scrypted.debugHost": "192.168.2.119",
|
||||
@@ -18,7 +18,7 @@
|
||||
// "scrypted.debugHost": "koushik-winvm",
|
||||
// "scrypted.serverRoot": "C:\\Users\\koush\\.scrypted",
|
||||
|
||||
"scrypted.pythonRemoteRoot": "${config:scrypted.serverRoot}/volume/plugin.zip",
|
||||
"scrypted.pythonRemoteRoot": "${config:scrypted.serverRoot}/volume",
|
||||
"python.analysis.extraPaths": [
|
||||
"./node_modules/@scrypted/sdk/types/scrypted_python"
|
||||
]
|
||||
|
||||
4
plugins/openvino/package-lock.json
generated
4
plugins/openvino/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.109",
|
||||
"version": "0.1.111",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.109",
|
||||
"version": "0.1.111",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -42,5 +42,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.109"
|
||||
"version": "0.1.111"
|
||||
}
|
||||
|
||||
@@ -101,6 +101,8 @@ class OpenVINOPlugin(
|
||||
nvidia = False
|
||||
iris_xe = False
|
||||
arc = False
|
||||
npu = False
|
||||
gpu = False
|
||||
|
||||
dgpus = []
|
||||
# search for NVIDIA dGPU, as that is not preferred by AUTO for some reason?
|
||||
@@ -116,6 +118,10 @@ class OpenVINOPlugin(
|
||||
if "NVIDIA" in full_device_name and "dGPU" in full_device_name:
|
||||
dgpus.append(device)
|
||||
nvidia = True
|
||||
if "NPU" in device:
|
||||
npu = True
|
||||
if "GPU" in device:
|
||||
gpu = True
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -123,8 +129,15 @@ class OpenVINOPlugin(
|
||||
if mode == "Default":
|
||||
mode = "AUTO"
|
||||
|
||||
if len(dgpus):
|
||||
if npu:
|
||||
if gpu:
|
||||
mode = f"AUTO:NPU,GPU,CPU"
|
||||
else:
|
||||
mode = f"AUTO:NPU,CPU"
|
||||
elif len(dgpus):
|
||||
mode = f"AUTO:{','.join(dgpus)},CPU"
|
||||
elif gpu:
|
||||
mode = f"GPU"
|
||||
|
||||
mode = mode or "AUTO"
|
||||
self.mode = mode
|
||||
@@ -137,7 +150,7 @@ class OpenVINOPlugin(
|
||||
if model == "Default" or model not in availableModels:
|
||||
if model != "Default":
|
||||
self.storage.setItem("model", "Default")
|
||||
if arc or nvidia:
|
||||
if arc or nvidia or npu:
|
||||
model = "scrypted_yolov9c_320"
|
||||
elif iris_xe:
|
||||
model = "scrypted_yolov9s_320"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# must ensure numpy is pinned to prevent dependencies with an unpinned numpy from pulling numpy>=2.0.
|
||||
numpy==1.26.4
|
||||
openvino==2024.2.0
|
||||
openvino==2024.3.0
|
||||
Pillow==10.3.0
|
||||
opencv-python==4.10.0.84
|
||||
|
||||
4
plugins/reolink/package-lock.json
generated
4
plugins/reolink/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/reolink",
|
||||
"version": "0.0.93",
|
||||
"version": "0.0.94",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/reolink",
|
||||
"version": "0.0.93",
|
||||
"version": "0.0.94",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/reolink",
|
||||
"version": "0.0.93",
|
||||
"version": "0.0.94",
|
||||
"description": "Reolink Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -797,6 +797,7 @@ class ReolinkProvider extends RtspProvider {
|
||||
placeholder: '192.168.2.222',
|
||||
},
|
||||
{
|
||||
subgroup: 'Advanced',
|
||||
key: 'rtspChannel',
|
||||
title: 'Channel Number Override',
|
||||
description: "Optional: The channel number to use for snapshots and video. E.g., 0, 1, 2, etc.",
|
||||
@@ -804,12 +805,14 @@ class ReolinkProvider extends RtspProvider {
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
subgroup: 'Advanced',
|
||||
key: 'httpPort',
|
||||
title: 'HTTP Port',
|
||||
description: 'Optional: Override the HTTP Port from the default value of 80.',
|
||||
placeholder: '80',
|
||||
},
|
||||
{
|
||||
subgroup: 'Advanced',
|
||||
key: 'skipValidate',
|
||||
title: 'Skip Validation',
|
||||
description: 'Add the device without verifying the credentials and network settings.',
|
||||
|
||||
4
sdk/package-lock.json
generated
4
sdk/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.60",
|
||||
"version": "0.3.61",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.60",
|
||||
"version": "0.3.61",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.60",
|
||||
"version": "0.3.61",
|
||||
"description": "",
|
||||
"main": "dist/src/index.js",
|
||||
"exports": {
|
||||
|
||||
4
sdk/types/package-lock.json
generated
4
sdk/types/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/types",
|
||||
"version": "0.3.56",
|
||||
"version": "0.3.57",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/types",
|
||||
"version": "0.3.56",
|
||||
"version": "0.3.57",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/types",
|
||||
"version": "0.3.56",
|
||||
"version": "0.3.57",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"author": "",
|
||||
|
||||
@@ -648,7 +648,7 @@ class NotifierOptions(TypedDict):
|
||||
dir: NotificationDirection
|
||||
image: str
|
||||
lang: str
|
||||
recordedEvent: RecordedEvent
|
||||
recordedEvent: Union[RecordedEvent, Any]
|
||||
renotify: bool
|
||||
requireInteraction: bool
|
||||
silent: bool
|
||||
|
||||
@@ -234,7 +234,7 @@ export interface NotifierOptions {
|
||||
tag?: string;
|
||||
timestamp?: number;
|
||||
vibrate?: VibratePattern;
|
||||
recordedEvent?: RecordedEvent;
|
||||
recordedEvent?: RecordedEvent & { id: string };
|
||||
|
||||
// removed from typescript dom?
|
||||
actions?: NotificationAction[];
|
||||
|
||||
36
server/package-lock.json
generated
36
server/package-lock.json
generated
@@ -1,25 +1,25 @@
|
||||
{
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.115.45",
|
||||
"version": "0.117.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.115.45",
|
||||
"version": "0.117.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/ffmpeg-static": "^6.1.0-build1",
|
||||
"@scrypted/node-pty": "^1.0.18",
|
||||
"@scrypted/types": "^0.3.56",
|
||||
"adm-zip": "^0.5.15",
|
||||
"@scrypted/types": "^0.3.57",
|
||||
"adm-zip": "^0.5.16",
|
||||
"body-parser": "^1.20.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"dotenv": "^16.4.5",
|
||||
"engine.io": "^6.6.0",
|
||||
"express": "^4.19.2",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"follow-redirects": "^1.15.9",
|
||||
"http-auth": "^4.2.0",
|
||||
"level": "^8.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -46,7 +46,7 @@
|
||||
"@types/follow-redirects": "^1.14.4",
|
||||
"@types/http-auth": "^4.1.4",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@types/node": "^22.5.0",
|
||||
"@types/node": "^22.5.4",
|
||||
"@types/node-dijkstra": "^2.5.6",
|
||||
"@types/node-forge": "^1.3.11",
|
||||
"@types/semver": "^7.5.8",
|
||||
@@ -557,9 +557,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/types": {
|
||||
"version": "0.3.56",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.56.tgz",
|
||||
"integrity": "sha512-SYP73isVSgVnFFokLKi4E5i3qXubSDMUjwUA4f4ZmLs0e2wSCP48NupGhKPavVay/k1E5PXjv8svTkMnAcNQOw=="
|
||||
"version": "0.3.57",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.57.tgz",
|
||||
"integrity": "sha512-XHE8RL2r8m5NGp+bsoUbeZ1zTHkGh5exXnrQFj2grUivZ9RY/D5wWxWieO+KHLSLstL7H71gMAuMxw6QQpnDXg=="
|
||||
},
|
||||
"node_modules/@types/adm-zip": {
|
||||
"version": "0.5.5",
|
||||
@@ -666,9 +666,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.0.tgz",
|
||||
"integrity": "sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==",
|
||||
"version": "22.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
|
||||
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
@@ -786,9 +786,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/adm-zip": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.15.tgz",
|
||||
"integrity": "sha512-jYPWSeOA8EFoZnucrKCNihqBjoEGQSU4HKgHYQgKNEQ0pQF9a/DYuo/+fAxY76k4qe75LUlLWpAM1QWcBMTOKw==",
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
|
||||
"integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
}
|
||||
@@ -1619,9 +1619,9 @@
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.115.45",
|
||||
"version": "0.117.4",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@scrypted/ffmpeg-static": "^6.1.0-build1",
|
||||
"@scrypted/node-pty": "^1.0.18",
|
||||
"@scrypted/types": "^0.3.56",
|
||||
"adm-zip": "^0.5.15",
|
||||
"@scrypted/types": "^0.3.57",
|
||||
"adm-zip": "^0.5.16",
|
||||
"body-parser": "^1.20.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"dotenv": "^16.4.5",
|
||||
"engine.io": "^6.6.0",
|
||||
"express": "^4.19.2",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"follow-redirects": "^1.15.9",
|
||||
"http-auth": "^4.2.0",
|
||||
"level": "^8.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -36,7 +36,7 @@
|
||||
"@types/follow-redirects": "^1.14.4",
|
||||
"@types/http-auth": "^4.1.4",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@types/node": "^22.5.0",
|
||||
"@types/node": "^22.5.4",
|
||||
"@types/node-dijkstra": "^2.5.6",
|
||||
"@types/node-forge": "^1.3.11",
|
||||
"@types/semver": "^7.5.8",
|
||||
|
||||
@@ -26,12 +26,15 @@ import rpc_reader
|
||||
import scrypted_python.scrypted_sdk.types
|
||||
from plugin_pip import install_with_pip, need_requirements, remove_pip_dirs
|
||||
from scrypted_python.scrypted_sdk import PluginFork, ScryptedStatic
|
||||
from scrypted_python.scrypted_sdk.types import (Device, DeviceManifest,
|
||||
EventDetails,
|
||||
ScryptedInterface,
|
||||
ScryptedInterfaceMethods,
|
||||
ScryptedInterfaceProperty,
|
||||
Storage)
|
||||
from scrypted_python.scrypted_sdk.types import (
|
||||
Device,
|
||||
DeviceManifest,
|
||||
EventDetails,
|
||||
ScryptedInterface,
|
||||
ScryptedInterfaceMethods,
|
||||
ScryptedInterfaceProperty,
|
||||
Storage,
|
||||
)
|
||||
|
||||
SCRYPTED_REQUIREMENTS = """
|
||||
ptpython
|
||||
@@ -509,19 +512,28 @@ class PluginRemote:
|
||||
)
|
||||
return base64.b64encode(m.digest()).decode("utf-8")
|
||||
|
||||
def onProxySerialization(value: Any, sourceKey: str = None):
|
||||
def isClusterAddress(address: str):
|
||||
return not address or address == SCRYPTED_CLUSTER_ADDRESS
|
||||
|
||||
def onProxySerialization(peer: rpc.RpcPeer, value: Any, sourceKey: str = None):
|
||||
properties: dict = rpc.RpcPeer.prepareProxyProperties(value) or {}
|
||||
clusterEntry = properties.get("__cluster", None)
|
||||
proxyId: str = (
|
||||
clusterEntry and clusterEntry.get("proxyId", None)
|
||||
) or rpc.RpcPeer.generateId()
|
||||
proxyId: str
|
||||
existing = peer.localProxied.get(value, None)
|
||||
if existing:
|
||||
proxyId = existing["id"]
|
||||
else:
|
||||
proxyId = (
|
||||
clusterEntry and clusterEntry.get("proxyId", None)
|
||||
) or rpc.RpcPeer.generateId()
|
||||
|
||||
if (
|
||||
clusterEntry
|
||||
and clusterPort == clusterEntry["port"]
|
||||
and sourceKey != clusterEntry.get("sourceKey", None)
|
||||
):
|
||||
clusterEntry = None
|
||||
if clusterEntry:
|
||||
if (
|
||||
isClusterAddress(clusterEntry.get("address", None))
|
||||
and clusterPort == clusterEntry["port"]
|
||||
and sourceKey != clusterEntry.get("sourceKey", None)
|
||||
):
|
||||
clusterEntry = None
|
||||
|
||||
if not clusterEntry:
|
||||
clusterEntry: ClusterObject = {
|
||||
@@ -536,7 +548,9 @@ class PluginRemote:
|
||||
|
||||
return proxyId, properties
|
||||
|
||||
self.peer.onProxySerialization = onProxySerialization
|
||||
self.peer.onProxySerialization = lambda value: onProxySerialization(
|
||||
self.peer, value, None
|
||||
)
|
||||
|
||||
async def resolveObject(id: str, sourceKey: str):
|
||||
sourcePeer: rpc.RpcPeer = (
|
||||
@@ -564,7 +578,7 @@ class PluginRemote:
|
||||
self.loop, rpcTransport
|
||||
)
|
||||
peer.onProxySerialization = lambda value: onProxySerialization(
|
||||
value, clusterPeerPort
|
||||
peer, value, clusterPeerKey
|
||||
)
|
||||
future: asyncio.Future[rpc.RpcPeer] = asyncio.Future()
|
||||
future.set_result(peer)
|
||||
@@ -593,7 +607,7 @@ class PluginRemote:
|
||||
clusterPort = clusterRpcServer.sockets[0].getsockname()[1]
|
||||
|
||||
def ensureClusterPeer(address: str, port: int):
|
||||
if not address or address == SCRYPTED_CLUSTER_ADDRESS:
|
||||
if isClusterAddress(address):
|
||||
address = "127.0.0.1"
|
||||
clusterPeerKey = getClusterPeerKey(address, port)
|
||||
clusterPeerPromise = clusterPeers.get(clusterPeerKey)
|
||||
@@ -601,21 +615,26 @@ class PluginRemote:
|
||||
return clusterPeerPromise
|
||||
|
||||
async def connectClusterPeer():
|
||||
reader, writer = await asyncio.open_connection(address, port)
|
||||
sourceAddress, sourcePort = writer.get_extra_info("sockname")
|
||||
if (
|
||||
sourceAddress != SCRYPTED_CLUSTER_ADDRESS
|
||||
and sourceAddress != "127.0.0.1"
|
||||
):
|
||||
print("source address mismatch", sourceAddress)
|
||||
sourceKey = getClusterPeerKey(sourceAddress, sourcePort)
|
||||
rpcTransport = rpc_reader.RpcStreamTransport(reader, writer)
|
||||
clusterPeer, peerReadLoop = await rpc_reader.prepare_peer_readloop(
|
||||
self.loop, rpcTransport
|
||||
)
|
||||
clusterPeer.onProxySerialization = lambda value: onProxySerialization(
|
||||
value, sourceKey
|
||||
)
|
||||
try:
|
||||
reader, writer = await asyncio.open_connection(address, port)
|
||||
sourceAddress, sourcePort = writer.get_extra_info("sockname")
|
||||
if (
|
||||
sourceAddress != SCRYPTED_CLUSTER_ADDRESS
|
||||
and sourceAddress != "127.0.0.1"
|
||||
):
|
||||
print("source address mismatch", sourceAddress)
|
||||
rpcTransport = rpc_reader.RpcStreamTransport(reader, writer)
|
||||
clusterPeer, peerReadLoop = await rpc_reader.prepare_peer_readloop(
|
||||
self.loop, rpcTransport
|
||||
)
|
||||
clusterPeer.onProxySerialization = (
|
||||
lambda value: onProxySerialization(
|
||||
clusterPeer, value, clusterPeerKey
|
||||
)
|
||||
)
|
||||
except:
|
||||
clusterPeers.pop(clusterPeerKey)
|
||||
raise
|
||||
|
||||
async def run_loop():
|
||||
try:
|
||||
|
||||
@@ -268,12 +268,21 @@ class RpcPeer:
|
||||
|
||||
proxiedEntry = self.localProxied.get(value, None)
|
||||
if proxiedEntry:
|
||||
if self.onProxySerialization:
|
||||
proxyId, __remote_proxy_props = self.onProxySerialization(value)
|
||||
else:
|
||||
__remote_proxy_props = RpcPeer.prepareProxyProperties(value)
|
||||
proxyId = proxiedEntry['id']
|
||||
|
||||
if proxyId != proxiedEntry['id']:
|
||||
raise Exception('onProxySerialization proxy id mismatch')
|
||||
|
||||
proxiedEntry['finalizerId'] = RpcPeer.generateId()
|
||||
ret = {
|
||||
'__remote_proxy_id': proxiedEntry['id'],
|
||||
'__remote_proxy_id': proxyId,
|
||||
'__remote_proxy_finalizer_id': proxiedEntry['finalizerId'],
|
||||
'__remote_constructor_name': __remote_constructor_name,
|
||||
'__remote_proxy_props': RpcPeer.prepareProxyProperties(value),
|
||||
'__remote_proxy_props': __remote_proxy_props,
|
||||
'__remote_proxy_oneway_methods': getattr(value, '__proxy_oneway_methods', None),
|
||||
}
|
||||
return ret
|
||||
|
||||
@@ -110,19 +110,25 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
||||
|
||||
const SCRYPTED_CLUSTER_ADDRESS = process.env.SCRYPTED_CLUSTER_ADDRESS;
|
||||
|
||||
const onProxySerialization = (value: any, sourceKey?: string) => {
|
||||
function isClusterAddress(address: string) {
|
||||
return !address || address === SCRYPTED_CLUSTER_ADDRESS;
|
||||
}
|
||||
|
||||
const onProxySerialization = (peer: RpcPeer, value: any, sourceKey: string) => {
|
||||
const properties = RpcPeer.prepareProxyProperties(value) || {};
|
||||
let clusterEntry: ClusterObject = properties.__cluster;
|
||||
|
||||
// ensure globally stable proxyIds.
|
||||
// worker threads will embed their pid and tid in the proxy id for cross worker fast path.
|
||||
const proxyId = clusterEntry?.proxyId || (worker_threads.isMainThread ? RpcPeer.generateId() : `n-${process.pid}-${worker_threads.threadId}-${RpcPeer.generateId()}`);
|
||||
const proxyId = peer.localProxied.get(value)?.id || clusterEntry?.proxyId || `n-${process.pid}-${worker_threads.threadId}-${RpcPeer.generateId()}`;
|
||||
|
||||
// if the cluster entry already exists, check if it belongs to this node.
|
||||
// if it belongs to this node, the entry must also be for this peer.
|
||||
// relying on the liveness/gc of a different peer may cause race conditions.
|
||||
if (clusterEntry && clusterPort === clusterEntry.port && sourceKey !== clusterEntry.sourceKey)
|
||||
clusterEntry = undefined;
|
||||
if (clusterEntry) {
|
||||
if (isClusterAddress(clusterEntry?.address) && clusterPort === clusterEntry.port && sourceKey !== clusterEntry.sourceKey)
|
||||
clusterEntry = undefined;
|
||||
}
|
||||
|
||||
if (!clusterEntry) {
|
||||
clusterEntry = {
|
||||
@@ -142,13 +148,21 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
||||
properties,
|
||||
};
|
||||
}
|
||||
peer.onProxySerialization = onProxySerialization;
|
||||
|
||||
peer.onProxySerialization = value => onProxySerialization(peer, value, undefined);
|
||||
|
||||
const resolveObject = async (id: string, sourceKey: string) => {
|
||||
const sourcePeer = sourceKey
|
||||
? await clusterPeers.get(sourceKey)
|
||||
: peer;
|
||||
return sourcePeer?.localProxyMap.get(id);
|
||||
if (!sourcePeer)
|
||||
console.error('source peer not found', sourceKey);
|
||||
const ret = sourcePeer?.localProxyMap.get(id);
|
||||
if (!ret) {
|
||||
console.error('source key not found', sourceKey, id);
|
||||
return;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// all cluster clients, incoming and outgoing, connect with random ports which can be used as peer ids
|
||||
@@ -161,13 +175,13 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
||||
}
|
||||
|
||||
const clusterRpcServer = net.createServer(client => {
|
||||
const clusterPeer = createDuplexRpcPeer(peer.selfName, 'cluster-client', client, client);
|
||||
const clusterPeerAddress = client.remoteAddress;
|
||||
const clusterPeerPort = client.remotePort;
|
||||
const clusterPeerKey = getClusterPeerKey(clusterPeerAddress, clusterPeerPort);
|
||||
const clusterPeer = createDuplexRpcPeer(peer.selfName, clusterPeerKey, client, client);
|
||||
// the listening peer sourceKey (client address/port) is used by the OTHER peer (the client)
|
||||
// to determine if it is already connected to THIS peer (the server).
|
||||
clusterPeer.onProxySerialization = (value) => onProxySerialization(value, clusterPeerKey);
|
||||
clusterPeer.onProxySerialization = (value) => onProxySerialization(clusterPeer, value, clusterPeerKey);
|
||||
clusterPeers.set(clusterPeerKey, Promise.resolve(clusterPeer));
|
||||
startPluginRemoteOptions?.onClusterPeer?.(clusterPeer);
|
||||
const connectRPCObject: ConnectRPCObject = async (o) => {
|
||||
@@ -189,7 +203,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
||||
const clusterPort = await listenZero(clusterRpcServer, listenAddress);
|
||||
|
||||
const ensureClusterPeer = (address: string, connectPort: number) => {
|
||||
if (!address || address === SCRYPTED_CLUSTER_ADDRESS)
|
||||
if (isClusterAddress(address))
|
||||
address = '127.0.0.1';
|
||||
|
||||
const clusterPeerKey = getClusterPeerKey(address, connectPort);
|
||||
@@ -203,16 +217,12 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
||||
|
||||
try {
|
||||
await once(socket, 'connect');
|
||||
|
||||
// this connecting peer sourceKey (server address/port) is used by the OTHER peer (the server)
|
||||
// to determine if it is already connected to THIS peer (the client).
|
||||
const { address: sourceAddress, port: sourcePort } = (socket.address() as net.AddressInfo);
|
||||
const { address: sourceAddress } = (socket.address() as net.AddressInfo);
|
||||
if (sourceAddress !== SCRYPTED_CLUSTER_ADDRESS && sourceAddress !== '127.0.0.1')
|
||||
console.warn("source address mismatch", sourceAddress);
|
||||
const sourcePeerKey = getClusterPeerKey(sourceAddress, sourcePort);
|
||||
|
||||
const clusterPeer = createDuplexRpcPeer(peer.selfName, 'cluster-server', socket, socket);
|
||||
clusterPeer.onProxySerialization = (value) => onProxySerialization(value, sourcePeerKey);
|
||||
const clusterPeer = createDuplexRpcPeer(peer.selfName, clusterPeerKey, socket, socket);
|
||||
clusterPeer.onProxySerialization = (value) => onProxySerialization(clusterPeer, value, clusterPeerKey);
|
||||
return clusterPeer;
|
||||
}
|
||||
catch (e) {
|
||||
@@ -239,30 +249,6 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
||||
const tidChannels = new Map<number, Deferred<worker_threads.MessagePort>>();
|
||||
const tidPeers = new Map<number, Promise<RpcPeer>>();
|
||||
|
||||
function finishTidPeerConnection(tid: number, port: worker_threads.MessagePort) {
|
||||
const threadPeer = NodeThreadWorker.createRpcPeer(peer.selfName, 'thread-server', port);
|
||||
// this connecting peer sourceKey (thread id) is used by the OTHER peer (the server)
|
||||
// to determine if it is already connected to THIS peer (the client).
|
||||
const threadPeerKey = `thread:${worker_threads.threadId}-${tid}`;
|
||||
threadPeer.onProxySerialization = value => onProxySerialization(value, threadPeerKey);
|
||||
|
||||
const connectRPCObject: ConnectRPCObject = async (o) => {
|
||||
const sha256 = computeClusterObjectHash(o, clusterSecret);
|
||||
if (sha256 !== o.sha256)
|
||||
throw new Error('secret incorrect');
|
||||
return resolveObject(o.proxyId, o.sourceKey);
|
||||
}
|
||||
threadPeer.params['connectRPCObject'] = connectRPCObject;
|
||||
function cleanup(message: string) {
|
||||
tidChannels.delete(tid);
|
||||
tidPeers.delete(tid);
|
||||
threadPeer.kill(message);
|
||||
}
|
||||
port.on('close', () => cleanup('connection closed.'));
|
||||
port.on('messageerror', () => cleanup('message error.'));
|
||||
return threadPeer;
|
||||
}
|
||||
|
||||
function connectTidPeer(tid: number) {
|
||||
let peerPromise = tidPeers.get(tid);
|
||||
if (peerPromise)
|
||||
@@ -281,17 +267,33 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
||||
}
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
clusterPeers.delete(threadPeerKey);
|
||||
const threadPeerKey = `thread:${tid}`;
|
||||
function peerCleanup() {
|
||||
clusterPeers.delete(threadPeerKey);
|
||||
}
|
||||
peerPromise = tidDeferred.promise.then(port => {
|
||||
port.on('close', () => cleanup());
|
||||
port.on('messageerror', () => cleanup());
|
||||
return finishTidPeerConnection(tid, port);
|
||||
const threadPeer = NodeThreadWorker.createRpcPeer(peer.selfName, threadPeerKey, port);
|
||||
threadPeer.onProxySerialization = value => onProxySerialization(threadPeer, value, threadPeerKey);
|
||||
|
||||
const connectRPCObject: ConnectRPCObject = async (o) => {
|
||||
const sha256 = computeClusterObjectHash(o, clusterSecret);
|
||||
if (sha256 !== o.sha256)
|
||||
throw new Error('secret incorrect');
|
||||
return resolveObject(o.proxyId, o.sourceKey);
|
||||
}
|
||||
threadPeer.params['connectRPCObject'] = connectRPCObject;
|
||||
|
||||
function cleanup(message: string) {
|
||||
peerCleanup();
|
||||
tidChannels.delete(tid);
|
||||
tidPeers.delete(tid);
|
||||
threadPeer.kill(message);
|
||||
}
|
||||
port.on('close', () => cleanup('connection closed.'));
|
||||
port.on('messageerror', () => cleanup('message error.'));
|
||||
return threadPeer;
|
||||
});
|
||||
peerPromise.catch(() => cleanup());
|
||||
const threadPeerKey = `thread:${worker_threads.threadId}-${tid}`;
|
||||
peerPromise.catch(() => peerCleanup());
|
||||
clusterPeers.set(threadPeerKey, peerPromise);
|
||||
tidPeers.set(tid, peerPromise);
|
||||
|
||||
@@ -414,7 +416,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
||||
return newValue;
|
||||
}
|
||||
catch (e) {
|
||||
console.error('failure rpc', e);
|
||||
console.error('failure rpc', clusterObject, e);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ export class RpcPeer {
|
||||
result.reject(error);
|
||||
}
|
||||
for (const y of this.yieldedAsyncIterators) {
|
||||
y.throw(error).catch(() => {});
|
||||
y.throw(error).catch(() => { });
|
||||
}
|
||||
this.yieldedAsyncIterators.clear();
|
||||
this.pendingResults = Object.freeze({});
|
||||
@@ -594,13 +594,25 @@ export class RpcPeer {
|
||||
|
||||
let proxiedEntry = this.localProxied.get(value);
|
||||
if (proxiedEntry) {
|
||||
const {
|
||||
proxyId: __remote_proxy_id,
|
||||
properties: __remote_proxy_props,
|
||||
} = this.onProxySerialization?.(value)
|
||||
|| {
|
||||
proxyId: proxiedEntry.id,
|
||||
properties: RpcPeer.prepareProxyProperties(value),
|
||||
};
|
||||
|
||||
if (__remote_proxy_id !== proxiedEntry.id)
|
||||
throw new Error('onProxySerialization proxy id mismatch');
|
||||
|
||||
const __remote_proxy_finalizer_id = RpcPeer.generateId();
|
||||
proxiedEntry.finalizerId = __remote_proxy_finalizer_id;
|
||||
const ret: RpcRemoteProxyValue = {
|
||||
__remote_proxy_id: proxiedEntry.id,
|
||||
__remote_proxy_id,
|
||||
__remote_proxy_finalizer_id,
|
||||
__remote_constructor_name,
|
||||
__remote_proxy_props: RpcPeer.prepareProxyProperties(value),
|
||||
__remote_proxy_props,
|
||||
__remote_proxy_oneway_methods: value?.[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS],
|
||||
}
|
||||
return ret;
|
||||
@@ -619,12 +631,11 @@ export class RpcPeer {
|
||||
const {
|
||||
proxyId: __remote_proxy_id,
|
||||
properties: __remote_proxy_props,
|
||||
} = this.onProxySerialization
|
||||
? this.onProxySerialization(value)
|
||||
: {
|
||||
proxyId: RpcPeer.generateId(),
|
||||
properties: RpcPeer.prepareProxyProperties(value),
|
||||
};
|
||||
} = this.onProxySerialization?.(value)
|
||||
|| {
|
||||
proxyId: RpcPeer.generateId(),
|
||||
properties: RpcPeer.prepareProxyProperties(value),
|
||||
};
|
||||
|
||||
proxiedEntry = {
|
||||
id: __remote_proxy_id,
|
||||
@@ -742,7 +753,7 @@ export class RpcPeer {
|
||||
}
|
||||
else {
|
||||
if (Object.isFrozen(this.pendingResults)) {
|
||||
(target as AsyncGenerator).throw(new RPCResultError(this, 'RpcPeer has been killed (yield)')).catch(() => {});
|
||||
(target as AsyncGenerator).throw(new RPCResultError(this, 'RpcPeer has been killed (yield)')).catch(() => { });
|
||||
}
|
||||
else {
|
||||
this.yieldedAsyncIterators.add(target);
|
||||
|
||||
@@ -740,7 +740,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
||||
return pluginHost;
|
||||
}
|
||||
|
||||
findPluginDevice?(pluginId: string, nativeId?: ScryptedNativeId): PluginDevice {
|
||||
findPluginDevice(pluginId: string, nativeId?: ScryptedNativeId): PluginDevice {
|
||||
// JSON stringify over rpc turns undefined into null.
|
||||
if (nativeId === null)
|
||||
nativeId = undefined;
|
||||
|
||||
@@ -5,14 +5,14 @@ import { startPluginRemote } from "./plugin/plugin-remote-worker";
|
||||
import { SidebandSocketSerializer } from "./plugin/socket-serializer";
|
||||
import { RpcMessage } from "./rpc";
|
||||
import { NodeThreadWorker } from './plugin/runtime/node-thread-worker';
|
||||
import { isNodePluginThreadProcess } from './plugin/runtime/node-fork-worker';
|
||||
import { isNodePluginForkProcess, isNodePluginThreadProcess } from './plugin/runtime/node-fork-worker';
|
||||
|
||||
function start(mainFilename: string) {
|
||||
const pluginId = process.argv[3];
|
||||
module.paths.push(getPluginNodePath(pluginId));
|
||||
|
||||
if (isNodePluginThreadProcess()) {
|
||||
console.log('starting thread', pluginId);
|
||||
console.log('starting thread', pluginId, process.pid, worker_threads.threadId);
|
||||
const { port } = worker_threads.workerData as { port: worker_threads.MessagePort };
|
||||
const peer = startPluginRemote(mainFilename, pluginId, (message, reject, serializationContext) => NodeThreadWorker.send(message, port, reject, serializationContext));
|
||||
NodeThreadWorker.setupRpcPeer(peer, port);
|
||||
@@ -26,7 +26,10 @@ function start(mainFilename: string) {
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.log('starting plugin', pluginId);
|
||||
if (isNodePluginForkProcess())
|
||||
console.log('starting fork', pluginId, process.pid);
|
||||
else
|
||||
console.log('starting plugin', pluginId, process.pid);
|
||||
const peer = startPluginRemote(mainFilename, process.argv[3], (message, reject, serializationContext) => process.send(message, serializationContext?.sendHandle, {
|
||||
// what happened to this argument?
|
||||
// swallowErrors: !reject,
|
||||
|
||||
Reference in New Issue
Block a user