Compare commits

..

19 Commits

Author SHA1 Message Date
Koushik Dutta
c8e94c0386 Merge branch 'main' of github.com:koush/scrypted 2024-01-18 13:15:41 -08:00
Koushik Dutta
8c6e7b997a ui: implement backup/restore 2024-01-18 13:15:37 -08:00
Johannes Bosecker
9abc7ca139 amcrest: Implemented other intercom codec for Dahua doorbells (G.711A). (#1273) 2024-01-18 12:36:11 -08:00
Koushik Dutta
2a943eb5e0 postbeta 2024-01-18 09:33:13 -08:00
Koushik Dutta
a4fe78a48b ha: publish 2024-01-17 22:07:56 -08:00
Koushik Dutta
50ff0833c9 server: new node min verison 2024-01-17 10:26:30 -08:00
Koushik Dutta
c94085a6c7 zwave: smoke alarm support 2024-01-17 10:25:48 -08:00
Koushik Dutta
c477437456 server: add hook for restart 2024-01-14 15:47:25 -08:00
Koushik Dutta
0da96130fe Merge branch 'main' of github.com:koush/scrypted 2024-01-14 15:36:17 -08:00
Koushik Dutta
fdbf7ab60b server: implement backup/restore 2024-01-14 15:36:08 -08:00
Long Zheng
0cecfb86ff npm-install.sh install auth-fetch package (#1267) 2024-01-14 14:46:23 -08:00
Koushik Dutta
9a195c6207 homekit: fix prune crash 2024-01-14 14:25:30 -08:00
Koushik Dutta
47021a7743 server: reduce deps 2024-01-14 14:25:19 -08:00
Koushik Dutta
01400cf206 core: prep for server fakefs removal 2024-01-14 14:24:43 -08:00
Koushik Dutta
99da29a738 postrelease 2024-01-14 08:06:50 -08:00
Koushik Dutta
6378c5953a server: bump core 2024-01-14 08:06:42 -08:00
Koushik Dutta
846034d7c8 core: fix login 2024-01-14 08:06:25 -08:00
Koushik Dutta
ad47f14922 ha: publish 2024-01-13 22:45:05 -08:00
Koushik Dutta
0066379b1e postrelease 2024-01-13 22:18:49 -08:00
28 changed files with 276 additions and 268 deletions

View File

@@ -3,6 +3,7 @@ import fs from 'fs';
import type { TranspileOptions } from "typescript";
import vm from "vm";
import { ScriptDevice } from "./monaco/script-device";
import path from 'path';
const { systemManager, deviceManager, mediaManager, endpointManager } = sdk;
@@ -22,9 +23,13 @@ export async function tsCompile(source: string, options: TranspileOptions = null
return ts.transpileModule(source, options).outputText;
}
export function readFileAsString(f: string) {
return fs.readFileSync(f).toString();;
}
function getTypeDefs() {
const scryptedTypesDefs = fs.readFileSync('@types/sdk/types.d.ts').toString();
const scryptedIndexDefs = fs.readFileSync('@types/sdk/index.d.ts').toString();
const scryptedTypesDefs = readFileAsString('@types/sdk/types.d.ts');
const scryptedIndexDefs = readFileAsString('@types/sdk/index.d.ts');
return {
scryptedIndexDefs,
scryptedTypesDefs,
@@ -104,7 +109,7 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
}
export function createMonacoEvalDefaults(extraLibs: { [lib: string]: string }) {
const bufferTypeDefs = fs.readFileSync('@types/node/buffer.d.ts').toString();
const bufferTypeDefs= readFileAsString('@types/node/buffer.d.ts');
const safeLibs = {
bufferTypeDefs,

View File

@@ -1,6 +1,6 @@
# Home Assistant Addon Configuration
name: Scrypted
version: "18-jammy-full.s6-v0.80.0"
version: "18-jammy-full.s6-v0.85.0"
slug: scrypted
description: Scrypted is a high performance home video integration and automation platform
url: "https://github.com/koush/scrypted"

View File

@@ -14,7 +14,7 @@ cd $(dirname $0)
git submodule init
git submodule update
for directory in sdk common server packages/client
for directory in sdk common server packages/client packages/auth-fetch
do
echo "$directory > npm install"
pushd $directory

View File

@@ -19,6 +19,8 @@ import { httpFetch } from '../../../server/src/fetch/http-fetch';
let fetcher: typeof httpFetch | typeof domFetch;
try {
if (process.arch === 'browser' as any)
throw new Error();
require('net');
require('events');
fetcher = httpFetch;

View File

@@ -495,6 +495,8 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
return this.onvifIntercom.startIntercom(media);
}
const doorbellType = this.storage.getItem('doorbellType');
// not sure if this all works, since i don't actually have a doorbell.
// good luck!
const channel = this.getRtspChannel() || '1';
@@ -505,12 +507,29 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
const args = ffmpegInput.inputArguments.slice();
args.unshift('-hide_banner');
args.push(
"-vn",
'-acodec', 'aac',
'-f', 'adts',
'pipe:3',
);
let contentType: string;
if (doorbellType == DAHUA_DOORBELL_TYPE) {
args.push(
"-vn",
'-acodec', 'pcm_alaw',
'-ac', '1',
'-ar', '8000',
'-sample_fmt', 's16',
'-f', 'alaw',
'pipe:3',
);
contentType = 'Audio/G.711A';
}
else {
args.push(
"-vn",
'-acodec', 'aac',
'-f', 'adts',
'pipe:3',
);
contentType = 'Audio/AAC';
}
this.console.log('ffmpeg intercom', args);
@@ -533,7 +552,7 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
url,
method: 'POST',
headers: {
'Content-Type': 'Audio/AAC',
'Content-Type': contentType,
'Content-Length': '9999999'
},
responseType: 'readable',

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/core",
"version": "0.2.4",
"version": "0.3.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/core",
"version": "0.2.4",
"version": "0.3.1",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/core",
"version": "0.2.4",
"version": "0.3.1",
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -1,4 +1,4 @@
import { tsCompile } from '@scrypted/common/src/eval/scrypted-eval';
import { readFileAsString, tsCompile } from '@scrypted/common/src/eval/scrypted-eval';
import sdk, { DeviceProvider, EngineIOHandler, HttpRequest, HttpRequestHandler, HttpResponse, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, Settings, SettingValue } from '@scrypted/sdk';
import { StorageSettings } from "@scrypted/sdk/storage-settings";
import fs from 'fs';
@@ -64,8 +64,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
constructor() {
super();
this.indexHtml = fs.readFileSync('dist/index.html').toString();
this.indexHtml = readFileAsString('dist/index.html');
(async () => {
await deviceManager.onDeviceDiscovered(

View File

@@ -3,7 +3,6 @@ import { scryptedEval } from "./scrypted-eval";
import { monacoEvalDefaults } from "./monaco";
import { createScriptDevice, ScriptDeviceImpl } from "@scrypted/common/src/eval/scrypted-eval";
import { ScriptCoreNativeId } from "./script-core";
import { PluginAPIProxy } from "../../../server/src/plugin/plugin-api";
const { deviceManager } = sdk;

View File

@@ -71,6 +71,9 @@
><v-toolbar-title>Server Management</v-toolbar-title></v-toolbar
>
<v-card-actions>
<v-btn text href="/web/component/backup" color="info">Backup</v-btn>
<v-btn text color="info" @click="restoreClick">Restore</v-btn>
<input type="file" ref="restoreFile" style="display: none;" @change="restore"/>
<v-spacer></v-spacer>
<v-dialog v-model="restart" width="500">
<template v-slot:activator="{ on }">
@@ -126,6 +129,22 @@ export default {
this.checkUpdateAvailable();
},
methods: {
async restoreClick() {
const restoreFile = this.$refs.restoreFile;
restoreFile.click();
},
async restore() {
const restoreFile = this.$refs.restoreFile;
const file = restoreFile.files[0];
if (!file)
return;
console.log(file);
const fileBlob = new Blob([file]);
await fetch('/web/component/restore', {
method: 'POST',
body: fileBlob,
});
},
async checkUpdateAvailable() {
const info = await this.$scrypted.systemManager.getComponent("info");
const version = await info.getVersion();

View File

@@ -42,15 +42,28 @@ export function createSystemSettingsDevice(systemManager: SystemManager): Scrypt
const results = systemSettings.map(async d => {
const settings = await d.getSettings();
for (const setting of settings) {
const subgroup = setting.group;
if (d.pluginId === '@scrypted/core')
setting.group = 'General';
else
setting.group = d.name;
setting.subgroup = subgroup;
setting.key = d.id + ':' + setting.key;
}
return settings;
});
return (await Promise.all(results)).flat();
const ret = (await Promise.all(results)).flat();
ret.sort((a, b) => {
if (a.group === 'General') {
if (b.group === 'General')
return 0;
return -1;
}
if (b.group === 'General')
return 1;
return 0;
});
return ret;
},
async putSetting(key, value) {
const [id, realKey] = key.split(':');

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/homekit",
"version": "1.2.35",
"version": "1.2.36",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/homekit",
"version": "1.2.35",
"version": "1.2.36",
"dependencies": {
"@koush/werift-src": "file:../../external/werift",
"check-disk-space": "^3.4.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/homekit",
"version": "1.2.35",
"version": "1.2.36",
"description": "HomeKit Plugin for Scrypted",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",

View File

@@ -1,8 +1,8 @@
import sdk, { FFmpegInput, MediaObject, VideoClip, VideoClipOptions } from '@scrypted/sdk';
import path from 'path';
import fs from 'fs';
import mkdirp from 'mkdirp';
import { mkdirp } from 'mkdirp';
import path from 'path';
const { mediaManager } = sdk;
export const VIDEO_CLIPS_NATIVE_ID = 'save-video-clips';
@@ -98,16 +98,6 @@ export async function getVideoClips(options?: VideoClipOptions, id?: string): Pr
if (options?.endTime)
ret = ret.filter(clip => clip.startTime + clip.duration < options.endTime);
if (options?.reverseOrder)
ret = ret.reverse();
if (options?.startId) {
const startIndex = ret.findIndex(c => c.id === options.startId);
if (startIndex === -1)
throw new Error('startIndex not found');
ret = ret.slice(startIndex);
}
if (options?.count)
ret = ret.slice(0, options.count);

View File

@@ -7,17 +7,17 @@ import { timeoutPromise } from "@scrypted/common/src/promise-utils";
import sdk, { AudioSensor, FFmpegInput, MotionSensor, ScryptedDevice, ScryptedInterface, ScryptedMimeTypes, VideoCamera } from '@scrypted/sdk';
import child_process from "child_process";
import fs from 'fs';
import mkdirp from 'mkdirp';
import { mkdirp } from 'mkdirp';
import net from 'net';
import path from 'path';
import { Duplex, Readable, Writable } from 'stream';
import { } from '../../common';
import { AudioRecordingCodecType, CameraRecordingConfiguration, DataStreamConnection, RecordingPacket } from '../../hap';
import { AudioRecordingCodecType, CameraRecordingConfiguration, RecordingPacket } from '../../hap';
import type { HomeKitPlugin } from "../../main";
import { getCameraRecordingFiles, HksvVideoClip, VIDEO_CLIPS_NATIVE_ID } from './camera-recording-files';
import { checkCompatibleCodec, FORCE_OPUS, transcodingDebugModeWarning } from './camera-utils';
import { NAL_TYPE_DELIMITER, NAL_TYPE_FU_A, NAL_TYPE_IDR, NAL_TYPE_PPS, NAL_TYPE_SEI, NAL_TYPE_SPS, NAL_TYPE_STAP_A } from "./h264-packetizer";
import path from 'path';
import { getDebugMode } from "./camera-debug-mode-storage";
import { HksvVideoClip, VIDEO_CLIPS_NATIVE_ID, getCameraRecordingFiles } from './camera-recording-files';
import { FORCE_OPUS, checkCompatibleCodec, transcodingDebugModeWarning } from './camera-utils';
import { NAL_TYPE_DELIMITER, NAL_TYPE_FU_A, NAL_TYPE_IDR, NAL_TYPE_PPS, NAL_TYPE_SEI, NAL_TYPE_SPS, NAL_TYPE_STAP_A } from "./h264-packetizer";
const { log, mediaManager, deviceManager } = sdk;

View File

@@ -1,4 +1,4 @@
{
"scrypted.debugHost": "koushik-ubuntu",
"scrypted.debugHost": "scrypted-server",
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/zwave",
"version": "0.1.2",
"version": "0.1.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/zwave",
"version": "0.1.2",
"version": "0.1.5",
"license": "Apache",
"dependencies": {
"@scrypted/sdk": "file:../../sdk",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/zwave",
"version": "0.1.2",
"version": "0.1.5",
"description": "Z-Wave USB Controller for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -0,0 +1,22 @@
import { CO2Sensor } from "@scrypted/sdk";
import type { ValueID } from "@zwave-js/core";
import { ZWaveNode, ZWaveNodeValueUpdatedArgs } from "zwave-js";
import { Notification } from "./Notification";
import { ZwaveDeviceBase } from "./ZwaveDeviceBase";
export class SmokeAlarmToCO2Sensor extends Notification implements CO2Sensor {
static getInterfaces(node: ZWaveNode, valueId: ValueID): string[] {
if (Notification.checkInterface(node, valueId, 'Smoke detected')
|| Notification.checkInterface(node, valueId, 'Smoke detected (location provided)')) {
return ['CO2Sensor'];
}
return null;
}
static onValueChanged(zwaveDevice: ZwaveDeviceBase, valueId: ZWaveNodeValueUpdatedArgs) {
if (valueId.propertyKey === 'Alarm status') {
const notification = Notification.lookupNotification(zwaveDevice, 'Smoke Alarm');
zwaveDevice.co2ppm = notification.lookupValue(valueId.newValue as number) ? 50 : 0;
}
}
}

View File

@@ -65,6 +65,7 @@ export class ZwaveDeviceBase extends ScryptedDeviceBase implements Refresh, Sett
}
onValueChanged(valueId: ZWaveNodeValueUpdatedArgs) {
this.console.log('value changed', valueId);
var cc = getCommandClassIndex(valueId.commandClass, valueId.property as number);
if (!cc) {
cc = getCommandClass(valueId.commandClass);

View File

@@ -1,26 +1,26 @@
import OnOffToSwitch from './OnOffToSwitch';
import BrightnessToSwitchMultilevel from './BrightnessToSwitchMultilevel';
import { CommandClassHandler as CommandClassHandlerClass } from './ZwaveDeviceBase';
import BinarySensorToStateSensor from './BinarySensorToStateSensor';
import LockToDoorLock from './LockToDoorLock';
import BatteryToBattery from './BatteryToBattery';
import ThermometerToSensorMultilevel from './ThermometerToSensorMultilevel';
import HumidityToSensorMultilevel from './HumiditySensorToSensorMultilevel';
import LuminanceSensorToSensorMultilevel from './LuminanceSensorToSensorMultilevel';
import UltravioletSensorMultilevel from './UltravioletSensorToSensorMultilevel';
import SettingsToConfiguration from './SettingsToConfiguration';
import EntryToBarrierOperator from './EntryToBarrierOperator';
import EntrySensorToBarriorOperator from './EntrySensorToBarrierOperator';
import ColorSettingRgbToColor from './ColorSettingRgbToColor';
import { NotificationType } from './Notification';
import { EntrySensorToAccessControl } from './EntrySensorToAccessControl';
import { FloodSensorToWaterAlarm } from './FloodSensorToWaterAlarm';
import { PasswordStoreToUserCode } from './PasswordStoreToUserCode';
import { TamperSensorToHomeSecurity } from './TamperSensorToHomeSecurity';
import { PowerSensorToPowerManagement } from './PowerSensorToPowerManagement';
import { ZWaveNode } from 'zwave-js';
import {CommandClasses, ValueID} from '@zwave-js/core'
import { ScryptedInterface } from '@scrypted/sdk';
import { CommandClasses, ValueID } from '@zwave-js/core';
import { ZWaveNode } from 'zwave-js';
import BatteryToBattery from './BatteryToBattery';
import BinarySensorToStateSensor from './BinarySensorToStateSensor';
import BrightnessToSwitchMultilevel from './BrightnessToSwitchMultilevel';
import ColorSettingRgbToColor from './ColorSettingRgbToColor';
import { EntrySensorToAccessControl } from './EntrySensorToAccessControl';
import EntrySensorToBarriorOperator from './EntrySensorToBarrierOperator';
import EntryToBarrierOperator from './EntryToBarrierOperator';
import { FloodSensorToWaterAlarm } from './FloodSensorToWaterAlarm';
import HumidityToSensorMultilevel from './HumiditySensorToSensorMultilevel';
import LockToDoorLock from './LockToDoorLock';
import LuminanceSensorToSensorMultilevel from './LuminanceSensorToSensorMultilevel';
import OnOffToSwitch from './OnOffToSwitch';
import { PasswordStoreToUserCode } from './PasswordStoreToUserCode';
import { PowerSensorToPowerManagement } from './PowerSensorToPowerManagement';
import SettingsToConfiguration from './SettingsToConfiguration';
import { SmokeAlarmToCO2Sensor } from './SmokeAlarmToBinarySensor';
import { TamperSensorToHomeSecurity } from './TamperSensorToHomeSecurity';
import ThermometerToSensorMultilevel from './ThermometerToSensorMultilevel';
import UltravioletSensorMultilevel from './UltravioletSensorToSensorMultilevel';
import { CommandClassHandler as CommandClassHandlerClass } from './ZwaveDeviceBase';
var CommandClassMap: {[ccId: string]: CommandClassInfo} = {};
@@ -72,25 +72,26 @@ export function getCommandClassIndex(commandClass: number, index: number): Comma
return CommandClassMap[`${commandClass}#${index}`];
}
addCommandClassIndex(CommandClasses['Binary Switch'], 'currentValue', OnOffToSwitch, 'OnOff');
addCommandClassIndex(CommandClasses['Multilevel Switch'], 'currentValue', BrightnessToSwitchMultilevel, 'Brightness', 'OnOff');
addCommandClassIndex(CommandClasses['Color'], 'currentValue', ColorSettingRgbToColor, 'ColorSettingRgb', 'ColorSettingTemperature');
addCommandClassIndex(CommandClasses['Binary Sensor'], 'Any', BinarySensorToStateSensor, 'BinarySensor');
addCommandClassIndex(CommandClasses['Door Lock'], 'currentMode', LockToDoorLock, 'Lock');
addCommandClassIndex(CommandClasses['Battery'], 'level', BatteryToBattery, 'Battery');
addCommandClassIndex(CommandClasses['Entry Control'], 'currentValue', EntryToBarrierOperator, 'Entry');
addCommandClassIndex(CommandClasses['Multilevel Sensor'], 'Air temperature', ThermometerToSensorMultilevel, 'Thermometer');
addCommandClassIndex(CommandClasses['Multilevel Sensor'], 'Humidity', HumidityToSensorMultilevel, 'HumiditySensor');
addCommandClassIndex(CommandClasses['Multilevel Sensor'], 'Illuminance', LuminanceSensorToSensorMultilevel, 'LuminanceSensor');
addCommandClassIndex(CommandClasses['Multilevel Sensor'], 'Ultraviolet', UltravioletSensorMultilevel, 'UltravioletSensor');
addCommandClassIndex(CommandClasses['Binary Switch'], 'currentValue', OnOffToSwitch, ScryptedInterface.OnOff);
addCommandClassIndex(CommandClasses['Multilevel Switch'], 'currentValue', BrightnessToSwitchMultilevel, ScryptedInterface.Brightness, ScryptedInterface.OnOff);
addCommandClassIndex(CommandClasses['Color'], 'currentValue', ColorSettingRgbToColor, ScryptedInterface.ColorSettingRgb, ScryptedInterface.ColorSettingTemperature);
addCommandClassIndex(CommandClasses['Binary Sensor'], 'Any', BinarySensorToStateSensor, ScryptedInterface.BinarySensor);
addCommandClassIndex(CommandClasses['Door Lock'], 'currentMode', LockToDoorLock, ScryptedInterface.Lock);
addCommandClassIndex(CommandClasses['Battery'], 'level', BatteryToBattery, ScryptedInterface.Battery);
addCommandClassIndex(CommandClasses['Entry Control'], 'currentValue', EntryToBarrierOperator, ScryptedInterface.Entry);
addCommandClassIndex(CommandClasses['Multilevel Sensor'], 'Air temperature', ThermometerToSensorMultilevel, ScryptedInterface.Thermometer);
addCommandClassIndex(CommandClasses['Multilevel Sensor'], 'Humidity', HumidityToSensorMultilevel, ScryptedInterface.HumiditySensor);
addCommandClassIndex(CommandClasses['Multilevel Sensor'], 'Illuminance', LuminanceSensorToSensorMultilevel, ScryptedInterface.LuminanceSensor);
addCommandClassIndex(CommandClasses['Multilevel Sensor'], 'Ultraviolet', UltravioletSensorMultilevel, ScryptedInterface.UltravioletSensor);
addCommandClassIndex(CommandClasses['Notification'], 'Access Control', EntrySensorToAccessControl, 'EntrySensor');
addCommandClassIndex(CommandClasses['Notification'], 'Water Alarm', FloodSensorToWaterAlarm, 'FloodSensor');
addCommandClassIndex(CommandClasses['Notification'], 'Access Control', EntrySensorToAccessControl, ScryptedInterface.EntrySensor);
addCommandClassIndex(CommandClasses['Notification'], 'Water Alarm', FloodSensorToWaterAlarm, ScryptedInterface.FloodSensor);
addCommandClassIndex(CommandClasses['Notification'], 'Home Security', TamperSensorToHomeSecurity, ScryptedInterface.TamperSensor);
addCommandClassIndex(CommandClasses['Notification'], 'Power Management', PowerSensorToPowerManagement, 'PowerSensor');
addCommandClassIndex(CommandClasses['Notification'], 'Power Management', PowerSensorToPowerManagement, ScryptedInterface.PowerSensor);
addCommandClassIndex(CommandClasses['Notification'], 'Smoke Alarm', SmokeAlarmToCO2Sensor, ScryptedInterface.CO2Sensor);
addCommandClassIndex(CommandClasses['Barrier Operator'], 'currentState', EntryToBarrierOperator, 'Entry');
addCommandClassIndex(CommandClasses['Barrier Operator'], 'position', EntrySensorToBarriorOperator, 'EntrySensor');
addCommandClassIndex(CommandClasses['Barrier Operator'], 'currentState', EntryToBarrierOperator, ScryptedInterface.Entry);
addCommandClassIndex(CommandClasses['Barrier Operator'], 'position', EntrySensorToBarriorOperator, ScryptedInterface.EntrySensor);
addCommandClass(CommandClasses['Configuration'], SettingsToConfiguration, 'Settings');
addCommandClass(CommandClasses['User Code'], PasswordStoreToUserCode, 'PasswordStore');
addCommandClass(CommandClasses['Configuration'], SettingsToConfiguration, ScryptedInterface.Settings);
addCommandClass(CommandClasses['User Code'], PasswordStoreToUserCode, ScryptedInterface.PasswordStore);

View File

@@ -27,6 +27,7 @@
"${workspaceFolder}/**/*.js"
],
"env": {
"SCRYPTED_CAN_RESTART": "true",
// "SCRYPTED_DEFAULT_AUTHENTICATION": "demo"
// force usage of system python because brew python is 3.11
// which has no wheels for coreml tools or tflite-runtime

152
server/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/server",
"version": "0.83.0",
"version": "0.87.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/server",
"version": "0.83.0",
"version": "0.87.0",
"license": "ISC",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
@@ -14,7 +14,6 @@
"adm-zip": "^0.5.10",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"debug": "^4.3.4",
"engine.io": "^6.5.4",
"express": "^4.18.2",
"ffmpeg-static": "^5.2.0",
@@ -22,16 +21,13 @@
"http-auth": "^4.2.0",
"ip": "^1.1.8",
"level": "^8.0.0",
"linkfs": "^2.1.0",
"lodash": "^4.17.21",
"memfs": "^4.6.0",
"nan": "^2.18.0",
"node-dijkstra": "^2.5.0",
"node-forge": "^1.3.1",
"node-gyp": "^10.0.1",
"router": "^1.3.8",
"semver": "^7.5.4",
"send": "^0.18.0",
"sharp": "^0.33.2",
"source-map-support": "^0.5.21",
"tar": "^6.2.0",
@@ -46,7 +42,6 @@
"devDependencies": {
"@types/adm-zip": "^0.5.5",
"@types/cookie-parser": "^1.4.6",
"@types/debug": "^4.1.12",
"@types/express": "^4.17.21",
"@types/follow-redirects": "^1.14.4",
"@types/http-auth": "^4.1.4",
@@ -54,7 +49,6 @@
"@types/lodash": "^4.14.202",
"@types/node-dijkstra": "^2.5.6",
"@types/node-forge": "^1.3.11",
"@types/pem": "^1.14.4",
"@types/semver": "^7.5.6",
"@types/source-map-support": "^0.5.10",
"@types/tar": "^6.1.10",
@@ -713,15 +707,6 @@
"@types/node": "*"
}
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
"dev": true,
"dependencies": {
"@types/ms": "*"
}
},
"node_modules/@types/express": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
@@ -785,12 +770,6 @@
"integrity": "sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==",
"dev": true
},
"node_modules/@types/ms": {
"version": "0.7.31",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==",
"dev": true
},
"node_modules/@types/node": {
"version": "20.2.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz",
@@ -811,15 +790,6 @@
"@types/node": "*"
}
},
"node_modules/@types/pem": {
"version": "1.14.4",
"resolved": "https://registry.npmjs.org/@types/pem/-/pem-1.14.4.tgz",
"integrity": "sha512-Xt6qY6kX1RD4UmYNhWCCf3OSJrRcwbQIaJ/mQSjjAHxIjXMHx/vHNPOgEU3HdVKS1k/U5CZ6ClQlRo8egkl8xg==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
@@ -1018,11 +988,6 @@
"node": ">=10"
}
},
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@@ -1812,12 +1777,6 @@
"node": ">= 0.8"
}
},
"node_modules/fast-diff": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"peer": true
},
"node_modules/ffmpeg-static": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.2.0.tgz",
@@ -2145,14 +2104,6 @@
"node": ">= 6"
}
},
"node_modules/hyperdyperid": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz",
"integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==",
"engines": {
"node": ">=10.18"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -2303,35 +2254,6 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/json-joy": {
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/json-joy/-/json-joy-9.4.0.tgz",
"integrity": "sha512-qSWB6VlyQGOdzhjP5eKABYTqAzNlzFaR+uYPYzYijfbhcOSuqWP9Q6bfU7AVvNMFPnaU79vqFqezHeqFtCPXDA==",
"dependencies": {
"arg": "^5.0.2",
"hyperdyperid": "^1.2.0"
},
"bin": {
"json-pack": "bin/json-pack.js",
"json-pack-test": "bin/json-pack-test.js",
"json-patch": "bin/json-patch.js",
"json-patch-test": "bin/json-patch-test.js",
"json-pointer": "bin/json-pointer.js",
"json-pointer-test": "bin/json-pointer-test.js",
"json-unpack": "bin/json-unpack.js"
},
"engines": {
"node": ">=10.0"
},
"funding": {
"url": "https://github.com/sponsors/streamich"
},
"peerDependencies": {
"quill-delta": "^5",
"rxjs": "7",
"tslib": "2"
}
},
"node_modules/level": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz",
@@ -2368,28 +2290,11 @@
"node": ">=12"
}
},
"node_modules/linkfs": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/linkfs/-/linkfs-2.1.0.tgz",
"integrity": "sha512-kmsGcmpvjStZ0ATjuHycBujtNnXiZR28BTivEu0gAMDTT7GEyodcK6zSRtu6xsrdorrPZEIN380x7BD7xEYkew=="
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
"peer": true
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"peer": true
},
"node_modules/lru-cache": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.3.tgz",
@@ -2457,25 +2362,6 @@
"node": ">= 0.6"
}
},
"node_modules/memfs": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-4.6.0.tgz",
"integrity": "sha512-I6mhA1//KEZfKRQT9LujyW6lRbX7RkC24xKododIDO3AGShcaFAMKElv1yFGWX8fD4UaSiwasr3NeQ5TdtHY1A==",
"dependencies": {
"json-joy": "^9.2.0",
"thingies": "^1.11.1"
},
"engines": {
"node": ">= 4.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/streamich"
},
"peerDependencies": {
"tslib": "2"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -3294,20 +3180,6 @@
}
]
},
"node_modules/quill-delta": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
"integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
"peer": true,
"dependencies": {
"fast-diff": "^1.3.0",
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.5.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -3437,15 +3309,6 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -3906,17 +3769,6 @@
"node": ">=8"
}
},
"node_modules/thingies": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/thingies/-/thingies-1.12.0.tgz",
"integrity": "sha512-AiGqfYC1jLmJagbzQGuoZRM48JPsr9yB734a7K6wzr34NMhjUPrWSQrkF7ZBybf3yCerCL2Gcr02kMv4NmaZfA==",
"engines": {
"node": ">=10.18"
},
"peerDependencies": {
"tslib": "^2"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/server",
"version": "0.84.0",
"version": "0.87.0",
"description": "",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
@@ -8,7 +8,6 @@
"adm-zip": "^0.5.10",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"debug": "^4.3.4",
"engine.io": "^6.5.4",
"express": "^4.18.2",
"ffmpeg-static": "^5.2.0",
@@ -16,16 +15,13 @@
"http-auth": "^4.2.0",
"ip": "^1.1.8",
"level": "^8.0.0",
"linkfs": "^2.1.0",
"lodash": "^4.17.21",
"memfs": "^4.6.0",
"nan": "^2.18.0",
"node-dijkstra": "^2.5.0",
"node-forge": "^1.3.1",
"node-gyp": "^10.0.1",
"router": "^1.3.8",
"semver": "^7.5.4",
"send": "^0.18.0",
"sharp": "^0.33.2",
"source-map-support": "^0.5.21",
"tar": "^6.2.0",
@@ -37,7 +33,6 @@
"devDependencies": {
"@types/adm-zip": "^0.5.5",
"@types/cookie-parser": "^1.4.6",
"@types/debug": "^4.1.12",
"@types/express": "^4.17.21",
"@types/follow-redirects": "^1.14.4",
"@types/http-auth": "^4.1.4",
@@ -45,7 +40,6 @@
"@types/lodash": "^4.14.202",
"@types/node-dijkstra": "^2.5.6",
"@types/node-forge": "^1.3.11",
"@types/pem": "^1.14.4",
"@types/semver": "^7.5.6",
"@types/source-map-support": "^0.5.10",
"@types/tar": "^6.1.10",

View File

@@ -84,9 +84,11 @@ export class PluginHost {
}
async upsertDevice(upsert: Device) {
const newDevice = !this.scrypted.findPluginDevice(this.pluginId, upsert.nativeId);
const { pluginDevicePromise, interfacesChanged } = this.scrypted.upsertDevice(this.pluginId, upsert);
const pi = await pluginDevicePromise;
await this.remote.setNativeId(pi.nativeId, pi._id, pi.storage || {});
if (newDevice)
await this.remote.setNativeId(pi.nativeId, pi._id, pi.storage || {});
// fetch a new device instance if the descriptor changed.
// plugin may return the same instance.
// this avoids device and mixin churn.

View File

@@ -2,7 +2,6 @@ import { ScryptedStatic, SystemManager } from '@scrypted/types';
import AdmZip from 'adm-zip';
import { once } from 'events';
import fs from 'fs';
import { Volume } from 'memfs';
import net from 'net';
import path from 'path';
import { install as installSourceMapSupport } from 'source-map-support';
@@ -19,7 +18,7 @@ import { DeviceManagerImpl, PluginReader, attachPluginRemote, setupPluginRemote
import { PluginStats, startStatsUpdater } from './plugin-remote-stats';
import { createREPLServer } from './plugin-repl';
import { NodeThreadWorker } from './runtime/node-thread-worker';
const { link } = require('linkfs');
import worker_threads from 'worker_threads';
const serverVersion = require('../../package.json').version;
@@ -196,10 +195,18 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
}
}
let volume: any;
// let volume: any;
let pluginReader: PluginReader;
if (zipOptions?.unzippedPath && fs.existsSync(zipOptions?.unzippedPath)) {
volume = link(fs, ['', path.join(zipOptions.unzippedPath, 'fs')]);
if (worker_threads.isMainThread) {
const fsDir = path.join(zipOptions.unzippedPath, 'fs')
if (fs.existsSync(fsDir))
process.chdir(fsDir);
else
process.chdir(zipOptions.unzippedPath);
}
// volume = link(fs, ['', path.join(zipOptions.unzippedPath, 'fs')]);
pluginReader = name => {
const filename = path.join(zipOptions.unzippedPath, name);
if (!fs.existsSync(filename))
@@ -208,18 +215,20 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
};
}
else {
// this code path was used in testing and should be unreachable.
const admZip = new AdmZip(zipData);
volume = new Volume();
for (const entry of admZip.getEntries()) {
if (entry.isDirectory)
continue;
if (!entry.entryName.startsWith('fs/'))
continue;
const name = entry.entryName.substring('fs/'.length);
volume.mkdirpSync(path.dirname(name));
const data = entry.getData();
volume.writeFileSync(name, data);
}
// volume = new Volume();
// for (const entry of admZip.getEntries()) {
// if (entry.isDirectory)
// continue;
// if (!entry.entryName.startsWith('fs/'))
// continue;
// const name = entry.entryName.substring('fs/'.length);
// volume.mkdirpSync(path.dirname(name));
// const data = entry.getData();
// volume.writeFileSync(name, data);
// }
pluginReader = name => {
const entry = admZip.getEntry(name);
@@ -235,9 +244,6 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
const pnp = getPluginNodePath(pluginId);
pluginConsole?.log('node modules', pnp);
params.require = (name: string) => {
if (name === 'fakefs' || (name === 'fs' && !packageJson.scrypted.realfs)) {
return volume;
}
if (name === 'realfs') {
return require('fs');
}

View File

@@ -52,7 +52,7 @@ interface DeviceProxyPair {
proxy: ScryptedDevice;
}
const MIN_SCRYPTED_CORE_VERSION = 'v0.2.4';
const MIN_SCRYPTED_CORE_VERSION = 'v0.2.6';
const PLUGIN_DEVICE_STATE_VERSION = 2;
interface HttpPluginData {
@@ -65,7 +65,6 @@ export type RuntimeHost = (mainFilename: string, pluginId: string, options: Runt
export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
clusterId = crypto.randomBytes(3).toString('hex');
clusterSecret = crypto.randomBytes(16).toString('hex');
datastore: Level;
plugins: { [id: string]: PluginHost } = {};
pluginDevices: { [id: string]: PluginDevice } = {};
devices: { [id: string]: DeviceProxyPair } = {};
@@ -105,10 +104,8 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
info = new Info();
pluginHosts = new Map<string, RuntimeHost>();
constructor(public mainFilename: string, datastore: Level, insecure: http.Server, secure: https.Server, app: express.Application) {
constructor(public mainFilename: string, public datastore: Level, insecure: http.Server, secure: https.Server, app: express.Application) {
super(app);
this.datastore = datastore;
this.app = app;
// ensure that all the users are loaded from the db.
this.usersService.getAllUsers();

View File

@@ -26,11 +26,12 @@ import { getNpmPackageInfo } from './services/plugin';
import { setScryptedUserPassword, UsersService } from './services/users';
import { sleep } from './sleep';
import { ONE_DAY_MILLISECONDS, UserToken } from './usertoken';
import AdmZip from 'adm-zip';
export type Runtime = ScryptedRuntime;
if (!semver.gte(process.version, '16.0.0')) {
throw new Error('"node" version out of date. Please update node to v16 or higher.')
if (!semver.gte(process.version, '18.0.0')) {
throw new Error('"node" version out of date. Please update node to v18 or higher.')
}
process.on('unhandledRejection', error => {
@@ -112,6 +113,7 @@ app.use(bodyParser.raw({ type: 'application/zip', limit: 100000000 }) as any)
async function start(mainFilename: string, options?: {
onRuntimeCreated?: (runtime: ScryptedRuntime) => Promise<void>,
restart?: () => void,
}) {
const volumeDir = getScryptedVolume();
await fs.promises.mkdir(volumeDir, {
@@ -335,6 +337,90 @@ async function start(mainFilename: string, options?: {
await options?.onRuntimeCreated?.(scrypted);
await scrypted.start();
app.post('/web/component/restore', async (req, res) => {
const buffers: Buffer[] = [];
let zip: AdmZip;
req.on('data', b => buffers.push(b));
try {
await once(req, 'end');
zip = new AdmZip(Buffer.concat(buffers));
if (!zip.test())
throw new Error('backup zip test failed.');
}
catch (e) {
res.send({
error: "Error during restore.",
});
return;
}
try {
scrypted.kill();
await sleep(5000);
await db.close();
await fs.promises.rm(volumeDir, {
recursive: true,
force: true,
});
await fs.promises.mkdir(volumeDir, {
recursive: true
});
zip.extractAllTo(dbPath, true);
res.send({
success: true,
});
}
catch (e) {
res.send({
error: "Error during restore.",
});
}
if (options?.restart)
options.restart();
else
process.exit();
});
app.get('/web/component/backup', async (req, res) => {
try {
const backupDbPath = path.join(volumeDir, 'backup.db');
await fs.promises.rm(backupDbPath, {
recursive: true,
force: true,
});
const backupDb = new Level(backupDbPath);
await backupDb.open();
for await (const [key, value] of db.iterator()) {
await backupDb.put(key, value);
}
await backupDb.close();
const backupZip = path.join(volumeDir, 'backup.zip');
await fs.promises.rm(backupZip, {
recursive: true,
force: true,
});
const zip = new AdmZip();
await zip.addLocalFolderPromise(backupDbPath, {});
const zipBuffer = await zip.toBufferPromise();
// the file is a normal zip file, but an extension is added to prevent safari, etc, from unzipping it automatically.
res.header('Content-Disposition', 'attachment; filename="scrypted.zip.backup"')
res.send(zipBuffer);
}
catch (e) {
console.error('Backup error', e);
res.status(500);
res.send('Internal Error');
}
});
app.get(['/web/component/script/npm/:pkg', '/web/component/script/npm/@:owner/:pkg'], async (req, res) => {
const { owner, pkg } = req.params;
let endpoint = pkg;