mirror of
https://github.com/koush/scrypted.git
synced 2026-03-20 16:40:24 +00:00
server/various: scaffolding for multi-user and non-admin users
This commit is contained in:
@@ -240,9 +240,7 @@ class CastDevice extends ScryptedDeviceBase implements MediaPlayer, Refresh, Eng
|
||||
reject: any;
|
||||
};
|
||||
|
||||
async onConnection(request: HttpRequest, webSocketUrl: string) {
|
||||
const ws = new WebSocket(webSocketUrl);
|
||||
|
||||
async onConnection(request: HttpRequest, ws: WebSocket) {
|
||||
ws.onmessage = async (message) => {
|
||||
const json = JSON.parse(message.data as string);
|
||||
const { token } = json;
|
||||
|
||||
@@ -129,6 +129,9 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
return this.aggregateCore;
|
||||
}
|
||||
|
||||
async releaseDevice(id: string, nativeId: string, device: any): Promise<void> {
|
||||
}
|
||||
|
||||
checkEngineIoEndpoint(request: HttpRequest, name: string) {
|
||||
const check = `/endpoint/@scrypted/core/engine.io/${name}/`;
|
||||
if (!request.url.startsWith(check))
|
||||
@@ -154,9 +157,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
return true;
|
||||
}
|
||||
|
||||
async onConnection(request: HttpRequest, webSocketUrl: string): Promise<void> {
|
||||
const ws = new WebSocket(webSocketUrl);
|
||||
|
||||
async onConnection(request: HttpRequest, ws: WebSocket): Promise<void> {
|
||||
if (await this.checkService(request, ws, 'console') || await this.checkService(request, ws, 'repl')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -194,9 +194,7 @@ class GoogleHome extends ScryptedDeviceBase implements HttpRequestHandler, Engin
|
||||
this.throttleSync();
|
||||
}
|
||||
|
||||
async onConnection(request: HttpRequest, webSocketUrl: string) {
|
||||
const ws = new WebSocket(webSocketUrl);
|
||||
|
||||
async onConnection(request: HttpRequest, ws: WebSocket) {
|
||||
ws.onmessage = async (message) => {
|
||||
const json = JSON.parse(message.data as string);
|
||||
const { token } = json;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Deferred } from '@scrypted/common/src/deferred';
|
||||
import { listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
|
||||
import { createBrowserSignalingSession } from "@scrypted/common/src/rtc-connect";
|
||||
import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from '@scrypted/common/src/settings-mixin';
|
||||
import sdk, { BufferConverter, BufferConvertorOptions, DeviceCreator, DeviceCreatorSettings, DeviceProvider, FFmpegInput, HttpRequest, Intercom, MediaObject, MixinProvider, RequestMediaStream, RequestMediaStreamOptions, ResponseMediaStreamOptions, RTCSessionControl, RTCSignalingChannel, RTCSignalingClient, RTCSignalingSession, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera } from '@scrypted/sdk';
|
||||
import sdk, { BufferConverter, BufferConvertorOptions, ConnectOptions, DeviceCreator, DeviceCreatorSettings, DeviceProvider, FFmpegInput, HttpRequest, Intercom, MediaObject, MixinProvider, RequestMediaStream, RequestMediaStreamOptions, ResponseMediaStreamOptions, RTCSessionControl, RTCSignalingChannel, RTCSignalingClient, RTCSignalingSession, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera } from '@scrypted/sdk';
|
||||
import { StorageSettings } from '@scrypted/sdk/storage-settings';
|
||||
import crypto from 'crypto';
|
||||
import net from 'net';
|
||||
@@ -402,7 +402,7 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
|
||||
const message = await new Promise<{
|
||||
connectionManagementId: string,
|
||||
updateSessionId: string,
|
||||
}>((resolve, reject) => {
|
||||
} & ConnectOptions>((resolve, reject) => {
|
||||
const close = () => {
|
||||
const str = 'Connection closed while waiting for message';
|
||||
reject(new Error(str));
|
||||
@@ -416,6 +416,8 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
|
||||
}
|
||||
});
|
||||
|
||||
message.username = request.username;
|
||||
|
||||
const { connectionManagementId, updateSessionId } = message;
|
||||
if (connectionManagementId) {
|
||||
cleanup.promise.finally(async () => {
|
||||
@@ -457,7 +459,12 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
|
||||
|
||||
const cp = await client.clientPromise;
|
||||
cp.on('close', () => cleanup.resolve('socket client closed'));
|
||||
process.send(message, cp);
|
||||
// TODO: remove process.send hack
|
||||
// 12/16/2022
|
||||
if (sdk.connect)
|
||||
sdk.connect(cp, message);
|
||||
else
|
||||
process.send(message, cp);
|
||||
}
|
||||
catch (e) {
|
||||
console.error("error negotiating browser RTCC signaling", e);
|
||||
@@ -492,10 +499,10 @@ export async function fork() {
|
||||
if (port) {
|
||||
const socket = net.connect(port, '127.0.0.1');
|
||||
cleanup.promise.finally(() => socket.destroy());
|
||||
|
||||
|
||||
const dc = pc.createDataChannel('rpc');
|
||||
dc.message.subscribe(message => socket.write(message));
|
||||
|
||||
|
||||
const debouncer = new DataChannelDebouncer({
|
||||
send: u8 => dc.send(Buffer.from(u8)),
|
||||
}, e => {
|
||||
|
||||
4
sdk/package-lock.json
generated
4
sdk/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.28",
|
||||
"version": "0.2.33",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.28",
|
||||
"version": "0.2.33",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.28",
|
||||
"version": "0.2.33",
|
||||
"description": "",
|
||||
"main": "dist/src/index.js",
|
||||
"exports": {
|
||||
|
||||
3
sdk/src/acl.ts
Normal file
3
sdk/src/acl.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function mergeScryptedAccessControl() {
|
||||
|
||||
}
|
||||
4
sdk/types/package-lock.json
generated
4
sdk/types/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/types",
|
||||
"version": "0.2.25",
|
||||
"version": "0.2.30",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/types",
|
||||
"version": "0.2.25",
|
||||
"version": "0.2.30",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/rimraf": "^3.0.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/types",
|
||||
"version": "0.2.25",
|
||||
"version": "0.2.30",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"author": "",
|
||||
|
||||
@@ -73,7 +73,6 @@ class ScryptedInterface(Enum):
|
||||
AirQualitySensor = "AirQualitySensor"
|
||||
AmbientLightSensor = "AmbientLightSensor"
|
||||
AudioSensor = "AudioSensor"
|
||||
Authenticator = "Authenticator"
|
||||
Battery = "Battery"
|
||||
BinarySensor = "BinarySensor"
|
||||
Brightness = "Brightness"
|
||||
@@ -130,9 +129,9 @@ class ScryptedInterface(Enum):
|
||||
Scriptable = "Scriptable"
|
||||
ScryptedDevice = "ScryptedDevice"
|
||||
ScryptedPlugin = "ScryptedPlugin"
|
||||
ScryptedUser = "ScryptedUser"
|
||||
SecuritySystem = "SecuritySystem"
|
||||
Settings = "Settings"
|
||||
SoftwareUpdate = "SoftwareUpdate"
|
||||
StartStop = "StartStop"
|
||||
TamperSensor = "TamperSensor"
|
||||
TemperatureSetting = "TemperatureSetting"
|
||||
@@ -235,6 +234,12 @@ class PictureDimensions(TypedDict):
|
||||
width: float
|
||||
pass
|
||||
|
||||
class ScryptedDeviceAccessControl(TypedDict):
|
||||
id: str
|
||||
methods: list[str]
|
||||
properties: list[str]
|
||||
pass
|
||||
|
||||
class VideoStreamOptions(TypedDict):
|
||||
bitrate: float
|
||||
bitrateControl: Any | Any
|
||||
@@ -559,6 +564,10 @@ class ScriptSource(TypedDict):
|
||||
script: str
|
||||
pass
|
||||
|
||||
class ScryptedUserAccessControl(TypedDict):
|
||||
devicesAccessControls: list[ScryptedDeviceAccessControl]
|
||||
pass
|
||||
|
||||
class SecuritySystemState(TypedDict):
|
||||
mode: SecuritySystemMode
|
||||
obstruction: SecuritySystemObstruction
|
||||
@@ -582,6 +591,18 @@ class Setting(TypedDict):
|
||||
value: SettingValue
|
||||
pass
|
||||
|
||||
class TemperatureCommand(TypedDict):
|
||||
mode: ThermostatMode
|
||||
setpoint: float | tuple[float, float]
|
||||
pass
|
||||
|
||||
class TemperatureSettingStatus(TypedDict):
|
||||
activeMode: ThermostatMode
|
||||
availableModes: list[ThermostatMode]
|
||||
mode: ThermostatMode
|
||||
setpoint: float | tuple[float, float]
|
||||
pass
|
||||
|
||||
class VideoClip(TypedDict):
|
||||
description: str
|
||||
detectionClasses: list[str]
|
||||
@@ -616,11 +637,6 @@ class AudioSensor:
|
||||
audioDetected: bool
|
||||
pass
|
||||
|
||||
class Authenticator:
|
||||
async def checkPassword(self, password: str) -> bool:
|
||||
pass
|
||||
pass
|
||||
|
||||
class Battery:
|
||||
batteryLevel: float
|
||||
pass
|
||||
@@ -697,7 +713,7 @@ class DeviceProvider:
|
||||
class Display:
|
||||
async def startDisplay(self, media: MediaObject) -> None:
|
||||
pass
|
||||
async def stopDisplay(self, media: MediaObject) -> None:
|
||||
async def stopDisplay(self) -> None:
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -708,7 +724,7 @@ class Dock:
|
||||
pass
|
||||
|
||||
class EngineIOHandler:
|
||||
async def onConnection(self, request: HttpRequest, webSocketUrl: str) -> None:
|
||||
async def onConnection(self, request: HttpRequest, webScoket: WebSocket) -> None:
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -964,6 +980,11 @@ class ScryptedPlugin:
|
||||
pass
|
||||
pass
|
||||
|
||||
class ScryptedUser:
|
||||
async def getScryptedUserAccessControl(self) -> ScryptedUserAccessControl:
|
||||
pass
|
||||
pass
|
||||
|
||||
class SecuritySystem:
|
||||
securitySystemState: SecuritySystemState
|
||||
async def armSecuritySystem(self, mode: SecuritySystemMode) -> None:
|
||||
@@ -979,14 +1000,6 @@ class Settings:
|
||||
pass
|
||||
pass
|
||||
|
||||
class SoftwareUpdate:
|
||||
updateAvailable: bool
|
||||
async def checkForUpdate(self) -> bool:
|
||||
pass
|
||||
async def installUpdate(self) -> None:
|
||||
pass
|
||||
pass
|
||||
|
||||
class StartStop:
|
||||
running: bool
|
||||
async def start(self) -> None:
|
||||
@@ -1000,12 +1013,15 @@ class TamperSensor:
|
||||
pass
|
||||
|
||||
class TemperatureSetting:
|
||||
temperatureSetting: TemperatureSettingStatus
|
||||
thermostatActiveMode: ThermostatMode
|
||||
thermostatAvailableModes: list[ThermostatMode]
|
||||
thermostatMode: ThermostatMode
|
||||
thermostatSetpoint: float
|
||||
thermostatSetpointHigh: float
|
||||
thermostatSetpointLow: float
|
||||
async def setTemperature(self, command: TemperatureCommand) -> None:
|
||||
pass
|
||||
async def setThermostatMode(self, mode: ThermostatMode) -> None:
|
||||
pass
|
||||
async def setThermostatSetpoint(self, degrees: float) -> None:
|
||||
@@ -1215,6 +1231,7 @@ class ScryptedInterfaceProperty(Enum):
|
||||
running = "running"
|
||||
paused = "paused"
|
||||
docked = "docked"
|
||||
temperatureSetting = "temperatureSetting"
|
||||
thermostatActiveMode = "thermostatActiveMode"
|
||||
thermostatAvailableModes = "thermostatAvailableModes"
|
||||
thermostatMode = "thermostatMode"
|
||||
@@ -1229,7 +1246,6 @@ class ScryptedInterfaceProperty(Enum):
|
||||
entryOpen = "entryOpen"
|
||||
batteryLevel = "batteryLevel"
|
||||
online = "online"
|
||||
updateAvailable = "updateAvailable"
|
||||
fromMimeType = "fromMimeType"
|
||||
toMimeType = "toMimeType"
|
||||
binaryState = "binaryState"
|
||||
@@ -1407,6 +1423,13 @@ class DeviceState:
|
||||
def docked(self, value: bool):
|
||||
self.setScryptedProperty("docked", value)
|
||||
|
||||
@property
|
||||
def temperatureSetting(self) -> TemperatureSettingStatus:
|
||||
return self.getScryptedProperty("temperatureSetting")
|
||||
@temperatureSetting.setter
|
||||
def temperatureSetting(self, value: TemperatureSettingStatus):
|
||||
self.setScryptedProperty("temperatureSetting", value)
|
||||
|
||||
@property
|
||||
def thermostatActiveMode(self) -> ThermostatMode:
|
||||
return self.getScryptedProperty("thermostatActiveMode")
|
||||
@@ -1505,13 +1528,6 @@ class DeviceState:
|
||||
def online(self, value: bool):
|
||||
self.setScryptedProperty("online", value)
|
||||
|
||||
@property
|
||||
def updateAvailable(self) -> bool:
|
||||
return self.getScryptedProperty("updateAvailable")
|
||||
@updateAvailable.setter
|
||||
def updateAvailable(self, value: bool):
|
||||
self.setScryptedProperty("updateAvailable", value)
|
||||
|
||||
@property
|
||||
def fromMimeType(self) -> str:
|
||||
return self.getScryptedProperty("fromMimeType")
|
||||
@@ -1794,12 +1810,14 @@ ScryptedInterfaceDescriptors = {
|
||||
"TemperatureSetting": {
|
||||
"name": "TemperatureSetting",
|
||||
"methods": [
|
||||
"setTemperature",
|
||||
"setThermostatMode",
|
||||
"setThermostatSetpoint",
|
||||
"setThermostatSetpointHigh",
|
||||
"setThermostatSetpointLow"
|
||||
],
|
||||
"properties": [
|
||||
"temperatureSetting",
|
||||
"thermostatActiveMode",
|
||||
"thermostatAvailableModes",
|
||||
"thermostatMode",
|
||||
@@ -1926,13 +1944,6 @@ ScryptedInterfaceDescriptors = {
|
||||
],
|
||||
"properties": []
|
||||
},
|
||||
"Authenticator": {
|
||||
"name": "Authenticator",
|
||||
"methods": [
|
||||
"checkPassword"
|
||||
],
|
||||
"properties": []
|
||||
},
|
||||
"Scene": {
|
||||
"name": "Scene",
|
||||
"methods": [
|
||||
@@ -2013,16 +2024,6 @@ ScryptedInterfaceDescriptors = {
|
||||
"online"
|
||||
]
|
||||
},
|
||||
"SoftwareUpdate": {
|
||||
"name": "SoftwareUpdate",
|
||||
"methods": [
|
||||
"checkForUpdate",
|
||||
"installUpdate"
|
||||
],
|
||||
"properties": [
|
||||
"updateAvailable"
|
||||
]
|
||||
},
|
||||
"BufferConverter": {
|
||||
"name": "BufferConverter",
|
||||
"methods": [
|
||||
@@ -2285,6 +2286,13 @@ ScryptedInterfaceDescriptors = {
|
||||
"properties": [
|
||||
"applicationInfo"
|
||||
]
|
||||
},
|
||||
"ScryptedUser": {
|
||||
"name": "ScryptedUser",
|
||||
"methods": [
|
||||
"getScryptedUserAccessControl"
|
||||
],
|
||||
"properties": []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ for (const name of Object.values(ScryptedInterface)) {
|
||||
}
|
||||
|
||||
const properties = Object.values(ScryptedInterfaceDescriptors).map(d => d.properties).flat();
|
||||
const methods = Object.values(ScryptedInterfaceDescriptors).map(d => d.methods).flat();
|
||||
|
||||
const deviceStateContents = `
|
||||
export interface DeviceState {
|
||||
@@ -48,11 +49,18 @@ ${properties.map(property => ' ' + property + ' = \"' + property + '",\n').join
|
||||
}
|
||||
`;
|
||||
|
||||
const methodContents = `
|
||||
export enum ScryptedInterfaceMethod {
|
||||
${methods.map(method => ' ' + method + ' = \"' + method + '",\n').join('')}
|
||||
}
|
||||
`;
|
||||
|
||||
const contents = `
|
||||
export const TYPES_VERSION = "${typesVersion}";
|
||||
|
||||
${deviceStateContents}
|
||||
${propertyContents}
|
||||
${methodContents}
|
||||
|
||||
export const ScryptedInterfaceDescriptors: { [scryptedInterface: string]: ScryptedInterfaceDescriptor } = ${stringifyObject(ScryptedInterfaceDescriptors, { indent: ' ' })}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Worker as NodeWorker } from 'worker_threads';
|
||||
import type { Socket as NodeNetSocket } from 'net';
|
||||
|
||||
export type ScryptedNativeId = string | undefined;
|
||||
|
||||
@@ -253,23 +254,77 @@ export interface Dock {
|
||||
|
||||
docked?: boolean;
|
||||
}
|
||||
|
||||
export interface TemperatureCommand {
|
||||
mode?: ThermostatMode;
|
||||
setpoint?: number | [number, number];
|
||||
}
|
||||
export interface TemperatureSettingStatus {
|
||||
availableModes?: ThermostatMode[];
|
||||
mode?: ThermostatMode;
|
||||
activeMode?: ThermostatMode;
|
||||
setpoint?: number | [number, number];
|
||||
}
|
||||
/**
|
||||
* TemperatureSetting represents a thermostat device.
|
||||
*/
|
||||
export interface TemperatureSetting {
|
||||
temperatureSetting?: TemperatureSettingStatus;
|
||||
setTemperature(command: TemperatureCommand): Promise<void>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param mode
|
||||
*/
|
||||
setThermostatMode(mode: ThermostatMode): Promise<void>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param mode
|
||||
*/
|
||||
setThermostatSetpoint(degrees: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param mode
|
||||
*/
|
||||
setThermostatSetpointHigh(high: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param mode
|
||||
*/
|
||||
setThermostatSetpointLow(low: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param mode
|
||||
*/
|
||||
thermostatAvailableModes?: ThermostatMode[];
|
||||
/**
|
||||
* @deprecated
|
||||
* @param mode
|
||||
*/
|
||||
thermostatMode?: ThermostatMode;
|
||||
/**
|
||||
* @deprecated
|
||||
* @param mode
|
||||
*/
|
||||
thermostatActiveMode?: ThermostatMode;
|
||||
/**
|
||||
* @deprecated
|
||||
* @param mode
|
||||
*/
|
||||
thermostatSetpoint?: number;
|
||||
/**
|
||||
* @deprecated
|
||||
* @param mode
|
||||
*/
|
||||
thermostatSetpointHigh?: number;
|
||||
/**
|
||||
* @deprecated
|
||||
* @param mode
|
||||
*/
|
||||
thermostatSetpointLow?: number;
|
||||
}
|
||||
export enum HumidityMode {
|
||||
@@ -683,7 +738,7 @@ export interface PanTiltZoom {
|
||||
*/
|
||||
export interface Display {
|
||||
startDisplay(media: MediaObject): Promise<void>;
|
||||
stopDisplay(media: MediaObject): Promise<void>;
|
||||
stopDisplay(): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -712,13 +767,7 @@ export interface PasswordStore {
|
||||
removePassword(password: string): Promise<void>;
|
||||
|
||||
}
|
||||
/**
|
||||
* Authenticator can be used to require a password before allowing interaction with a security device.
|
||||
*/
|
||||
export interface Authenticator {
|
||||
checkPassword(password: string): Promise<boolean>;
|
||||
|
||||
}
|
||||
/**
|
||||
* Scenes control multiple different devices into a given state.
|
||||
*/
|
||||
@@ -947,16 +996,6 @@ export interface Scriptable {
|
||||
loadScripts(): Promise<{ [filename: string]: ScriptSource }>;
|
||||
eval(source: ScriptSource, variables?: { [name: string]: any }): Promise<any>;
|
||||
}
|
||||
/**
|
||||
* SoftwareUpdate provides a way to check for updates and install them. This may be a Scrypted Plugin or device firmware.
|
||||
*/
|
||||
export interface SoftwareUpdate {
|
||||
checkForUpdate(): Promise<boolean>;
|
||||
|
||||
installUpdate(): Promise<void>;
|
||||
|
||||
updateAvailable?: boolean;
|
||||
}
|
||||
|
||||
export interface BufferConvertorOptions {
|
||||
sourceId?: string;
|
||||
@@ -1566,7 +1605,7 @@ export interface HttpResponseOptions {
|
||||
headers?: object;
|
||||
}
|
||||
export interface EngineIOHandler {
|
||||
onConnection(request: HttpRequest, webSocketUrl: string): Promise<void>;
|
||||
onConnection(request: HttpRequest, webScoket: WebSocket): Promise<void>;
|
||||
|
||||
}
|
||||
/**
|
||||
@@ -1664,7 +1703,6 @@ export enum ScryptedInterface {
|
||||
Intercom = "Intercom",
|
||||
Lock = "Lock",
|
||||
PasswordStore = "PasswordStore",
|
||||
Authenticator = "Authenticator",
|
||||
Scene = "Scene",
|
||||
Entry = "Entry",
|
||||
EntrySensor = "EntrySensor",
|
||||
@@ -1675,7 +1713,6 @@ export enum ScryptedInterface {
|
||||
Refresh = "Refresh",
|
||||
MediaPlayer = "MediaPlayer",
|
||||
Online = "Online",
|
||||
SoftwareUpdate = "SoftwareUpdate",
|
||||
BufferConverter = "BufferConverter",
|
||||
Settings = "Settings",
|
||||
BinarySensor = "BinarySensor",
|
||||
@@ -1711,6 +1748,7 @@ export enum ScryptedInterface {
|
||||
RTCSignalingChannel = "RTCSignalingChannel",
|
||||
RTCSignalingClient = "RTCSignalingClient",
|
||||
LauncherApplication = "LauncherApplication",
|
||||
ScryptedUser = "ScryptedUser",
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1869,6 +1907,62 @@ export interface PluginFork<T> {
|
||||
worker: NodeWorker;
|
||||
}
|
||||
|
||||
export declare interface DeviceState {
|
||||
id?: string;
|
||||
setState?(property: string, value: any): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ScryptedInterfaceDescriptor {
|
||||
name: string;
|
||||
properties: string[];
|
||||
methods: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* ScryptedDeviceAccessControl describes the methods and properties on a device
|
||||
* that will be visible to the user.
|
||||
* If methods is null, the user will be granted full access to all methods.
|
||||
* If properties is null, the user will be granted full access to all properties.
|
||||
*/
|
||||
export interface ScryptedDeviceAccessControl {
|
||||
id: string;
|
||||
methods?: string[];
|
||||
properties?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* ScryptedUserAccessControl describes the list of devices that
|
||||
* may be accessed by the user.
|
||||
*/
|
||||
export interface ScryptedUserAccessControl {
|
||||
/**
|
||||
* If devicesAccessControls is null, the user has full access to all devices.
|
||||
*/
|
||||
devicesAccessControls: ScryptedDeviceAccessControl[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ScryptedUser represents a user managed by Scrypted.
|
||||
* This interface can not be implemented, only extended by Mixins.
|
||||
*/
|
||||
export interface ScryptedUser {
|
||||
/**
|
||||
* Retrieve the ScryptedUserAccessControl for a user. If no access control object is returned
|
||||
* the user has full access to all devices. This differs from an admin user that can also
|
||||
* access admin related system services.
|
||||
*/
|
||||
getScryptedUserAccessControl(): Promise<ScryptedUserAccessControl | null>;
|
||||
}
|
||||
|
||||
export interface APIOptions {
|
||||
username?: string;
|
||||
accessControls?: ScryptedUserAccessControl;
|
||||
}
|
||||
|
||||
export interface ConnectOptions extends APIOptions {
|
||||
pluginId: string;
|
||||
}
|
||||
|
||||
export interface ScryptedStatic {
|
||||
/**
|
||||
* @deprecated
|
||||
@@ -1883,16 +1977,15 @@ export interface ScryptedStatic {
|
||||
pluginHostAPI: any;
|
||||
pluginRemoteAPI: any;
|
||||
|
||||
/**
|
||||
* Start a new instance of the plugin, returning an instance of the new process
|
||||
* and the result of the fork method.
|
||||
*/
|
||||
fork?<T>(): PluginFork<T>;
|
||||
}
|
||||
|
||||
export declare interface DeviceState {
|
||||
id?: string;
|
||||
setState?(property: string, value: any): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ScryptedInterfaceDescriptor {
|
||||
name: string;
|
||||
properties: string[];
|
||||
methods: string[];
|
||||
/**
|
||||
* Initiate the Scrypted RPC wire protocol on a socket.
|
||||
* @param socket
|
||||
* @param options
|
||||
*/
|
||||
connect?(socket: NodeNetSocket, options?: ConnectOptions): void;
|
||||
}
|
||||
|
||||
14
server/package-lock.json
generated
14
server/package-lock.json
generated
@@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"@mapbox/node-pre-gyp": "^1.0.10",
|
||||
"@scrypted/types": "^0.2.18",
|
||||
"@scrypted/types": "^0.2.29",
|
||||
"adm-zip": "^0.5.9",
|
||||
"axios": "^0.21.4",
|
||||
"body-parser": "^1.19.0",
|
||||
@@ -245,9 +245,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/types": {
|
||||
"version": "0.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.2.18.tgz",
|
||||
"integrity": "sha512-LEhdAgpWZbVMDt74zM/jqBQr42xQl4fDaGwAGtwz0XJ1xnx/hXBXVLs+SdP+Gtnoujjqd1kWTdZCDrnPP4/luw=="
|
||||
"version": "0.2.29",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.2.29.tgz",
|
||||
"integrity": "sha512-l6BCe+2jHPnLaKbUfNxjtgfBkzzIVDhUo8iTjAKyJUfdWZsHZ8IFkzP9GTwenUHmOapDwYDboRum9NCxnjt7AA=="
|
||||
},
|
||||
"node_modules/@tootallnate/once": {
|
||||
"version": "1.1.2",
|
||||
@@ -3281,9 +3281,9 @@
|
||||
}
|
||||
},
|
||||
"@scrypted/types": {
|
||||
"version": "0.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.2.18.tgz",
|
||||
"integrity": "sha512-LEhdAgpWZbVMDt74zM/jqBQr42xQl4fDaGwAGtwz0XJ1xnx/hXBXVLs+SdP+Gtnoujjqd1kWTdZCDrnPP4/luw=="
|
||||
"version": "0.2.29",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.2.29.tgz",
|
||||
"integrity": "sha512-l6BCe+2jHPnLaKbUfNxjtgfBkzzIVDhUo8iTjAKyJUfdWZsHZ8IFkzP9GTwenUHmOapDwYDboRum9NCxnjt7AA=="
|
||||
},
|
||||
"@tootallnate/once": {
|
||||
"version": "1.1.2",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"dependencies": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"@mapbox/node-pre-gyp": "^1.0.10",
|
||||
"@scrypted/types": "^0.2.18",
|
||||
"@scrypted/types": "^0.2.29",
|
||||
"adm-zip": "^0.5.9",
|
||||
"axios": "^0.21.4",
|
||||
"body-parser": "^1.19.0",
|
||||
|
||||
@@ -28,7 +28,7 @@ export class ScryptedAlert extends ScryptedDocument {
|
||||
title: string;
|
||||
path: string;
|
||||
message: string;
|
||||
}``
|
||||
}
|
||||
|
||||
export class PluginDevice extends ScryptedDocument {
|
||||
constructor(id?: string) {
|
||||
|
||||
@@ -22,9 +22,6 @@ export interface PluginAPI {
|
||||
listen(EventListener: (id: string, eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister>;
|
||||
listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister>;
|
||||
|
||||
ioClose(id: string): Promise<void>;
|
||||
ioSend(id: string, message: string): Promise<void>;
|
||||
|
||||
deliverPush(endpoint: string, request: HttpRequest): Promise<void>;
|
||||
|
||||
getLogger(nativeId: ScryptedNativeId): Promise<PluginLogger>;
|
||||
@@ -113,12 +110,6 @@ export class PluginAPIProxy extends PluginAPIManagedListeners implements PluginA
|
||||
async listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister> {
|
||||
return this.manageListener(await this.api.listenDevice(id, event, callback));
|
||||
}
|
||||
ioClose(id: string): Promise<void> {
|
||||
return this.api.ioClose(id);
|
||||
}
|
||||
ioSend(id: string, message: string): Promise<void> {
|
||||
return this.api.ioSend(id, message);
|
||||
}
|
||||
deliverPush(endpoint: string, request: HttpRequest): Promise<void> {
|
||||
return this.api.deliverPush(endpoint, request);
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
|
||||
'onMixinEvent',
|
||||
'onDeviceEvent',
|
||||
'setStorage',
|
||||
'ioSend',
|
||||
'ioClose',
|
||||
'setDeviceProperty',
|
||||
'deliverPush',
|
||||
'requestRestart',
|
||||
@@ -105,18 +103,6 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
|
||||
}
|
||||
}
|
||||
|
||||
async ioClose(id: string) {
|
||||
// @ts-expect-error
|
||||
this.pluginHost.io.clients[id]?.close();
|
||||
this.pluginHost.ws[id]?.close();
|
||||
}
|
||||
|
||||
async ioSend(id: string, message: string) {
|
||||
// @ts-expect-error
|
||||
this.pluginHost.io.clients[id]?.send(message);
|
||||
this.pluginHost.ws[id]?.send(message);
|
||||
}
|
||||
|
||||
async setState(nativeId: ScryptedNativeId, key: string, value: any) {
|
||||
checkProperty(key, value);
|
||||
this.scrypted.stateManager.setPluginState(this.pluginId, nativeId, this.propertyInterfaces?.[key], key, value);
|
||||
|
||||
@@ -168,7 +168,14 @@ export class PluginHost {
|
||||
});
|
||||
|
||||
// @ts-expect-error
|
||||
await handler.onConnection(endpointRequest, new WebSocketConnection(`io://${id}`));
|
||||
await handler.onConnection(endpointRequest, new WebSocketConnection(`io://${id}`, {
|
||||
send(message) {
|
||||
socket.send(message);
|
||||
},
|
||||
close(message) {
|
||||
socket.close();
|
||||
},
|
||||
}));
|
||||
}
|
||||
catch (e) {
|
||||
console.error('engine.io plugin error', e);
|
||||
@@ -304,7 +311,6 @@ export class PluginHost {
|
||||
});
|
||||
|
||||
this.worker.setupRpcPeer(this.peer);
|
||||
this.peer.addSerializer(WebSocketConnection, WebSocketConnection.name, new WebSocketSerializer());
|
||||
|
||||
this.worker.stdout.on('data', data => console.log(data.toString()));
|
||||
this.worker.stderr.on('data', data => console.error(data.toString()));
|
||||
@@ -373,7 +379,6 @@ export class PluginHost {
|
||||
}
|
||||
});
|
||||
serializer.setupRpcPeer(rpcPeer);
|
||||
rpcPeer.addSerializer(WebSocketConnection, WebSocketConnection.name, new WebSocketSerializer());
|
||||
|
||||
// wrap the host api with a connection specific api that can be torn down on disconnect
|
||||
const createMediaManager = await this.peer.getParam('createMediaManager');
|
||||
@@ -390,7 +395,6 @@ export class PluginHost {
|
||||
|
||||
async createRpcPeer(duplex: Duplex) {
|
||||
const rpcPeer = createDuplexRpcPeer(`api/${this.pluginId}`, 'duplex', duplex, duplex);
|
||||
rpcPeer.addSerializer(WebSocketConnection, WebSocketConnection.name, new WebSocketSerializer());
|
||||
|
||||
// wrap the host api with a connection specific api that can be torn down on disconnect
|
||||
const createMediaManager = await this.peer.getParam('createMediaManager');
|
||||
|
||||
@@ -62,7 +62,7 @@ export interface WebSocketConnectCallbacks {
|
||||
}
|
||||
|
||||
export interface WebSocketConnect {
|
||||
(url: string, callbacks: WebSocketConnectCallbacks): void;
|
||||
(connection: WebSocketConnection, callbacks: WebSocketConnectCallbacks): void;
|
||||
}
|
||||
|
||||
export interface WebSocketMethods {
|
||||
@@ -76,15 +76,14 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect): any
|
||||
_url: string;
|
||||
_protocols: string[];
|
||||
readyState: number;
|
||||
_ws: WebSocketMethods;
|
||||
|
||||
constructor(url: string, protocols?: string[]) {
|
||||
constructor(public connection: WebSocketConnection, protocols?: string[]) {
|
||||
super();
|
||||
this._url = url;
|
||||
this._url = connection.url;
|
||||
this._protocols = protocols;
|
||||
this.readyState = 0;
|
||||
|
||||
__websocketConnect(url, {
|
||||
__websocketConnect(connection, {
|
||||
connect: (e, ws) => {
|
||||
// connect
|
||||
if (e != null) {
|
||||
@@ -95,7 +94,6 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect): any
|
||||
return;
|
||||
}
|
||||
|
||||
this._ws = ws;
|
||||
this.readyState = 1;
|
||||
this.dispatchEvent({
|
||||
type: 'open',
|
||||
@@ -129,7 +127,7 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect): any
|
||||
}
|
||||
|
||||
send(message: string | ArrayBufferLike) {
|
||||
this._ws.send(message);
|
||||
this.connection.send(message);
|
||||
}
|
||||
|
||||
get url() {
|
||||
@@ -141,7 +139,7 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect): any
|
||||
}
|
||||
|
||||
close(reason: string) {
|
||||
this._ws.close(reason);
|
||||
this.connection.close(reason);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,10 +151,25 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect): any
|
||||
return WebSocket;
|
||||
}
|
||||
|
||||
export class WebSocketConnection {
|
||||
export class WebSocketConnection implements WebSocketMethods {
|
||||
[RpcPeer.PROPERTY_PROXY_PROPERTIES]: any;
|
||||
|
||||
constructor(public url: string) {
|
||||
[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS] = [
|
||||
"send",
|
||||
"close",
|
||||
];
|
||||
|
||||
constructor(public url: string, public websocketMethods: WebSocketMethods) {
|
||||
this[RpcPeer.PROPERTY_PROXY_PROPERTIES] = {
|
||||
url,
|
||||
}
|
||||
}
|
||||
|
||||
send(message: string | ArrayBufferLike): void {
|
||||
return this.websocketMethods.send(message);
|
||||
}
|
||||
close(message: string): void {
|
||||
return this.websocketMethods.close(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,16 +177,12 @@ export class WebSocketSerializer implements RpcSerializer {
|
||||
WebSocket: ReturnType<typeof createWebSocketClass>;
|
||||
|
||||
serialize(value: any, serializationContext?: any) {
|
||||
const connection = value as WebSocketConnection;
|
||||
connection[RpcPeer.PROPERTY_PROXY_PROPERTIES] = {
|
||||
url: connection.url,
|
||||
}
|
||||
return connection;
|
||||
throw new Error("WebSocketSerializer should only be used for deserialization.");
|
||||
}
|
||||
|
||||
deserialize(serialized: any, serializationContext?: any) {
|
||||
deserialize(serialized: WebSocketConnection, serializationContext?: any) {
|
||||
if (!this.WebSocket)
|
||||
return undefined;
|
||||
return new this.WebSocket(serialized.url);
|
||||
return new this.WebSocket(serialized);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,6 +336,9 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
|
||||
pluginReader = undefined;
|
||||
const script = main.toString();
|
||||
|
||||
scrypted.connect = (socket, options) => {
|
||||
process.send(options, socket);
|
||||
}
|
||||
|
||||
const forks = new Set<PluginRemote>();
|
||||
|
||||
|
||||
@@ -429,15 +429,16 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
|
||||
const retPromise = new Promise<ScryptedStatic>(resolve => done = resolve);
|
||||
|
||||
peer.params.getRemote = async (api: PluginAPI, pluginId: string) => {
|
||||
websocketSerializer.WebSocket = createWebSocketClass((url: string, callbacks: WebSocketConnectCallbacks) => {
|
||||
websocketSerializer.WebSocket = createWebSocketClass((connection, callbacks) => {
|
||||
const {url} = connection;
|
||||
if (url.startsWith('io://') || url.startsWith('ws://')) {
|
||||
const id = url.substring('xx://'.length);
|
||||
|
||||
ioSockets[id] = callbacks;
|
||||
|
||||
callbacks.connect(undefined, {
|
||||
close: () => api.ioClose(id),
|
||||
send: (message: string) => api.ioSend(id, message),
|
||||
close: (message) => connection.close(message),
|
||||
send: (message) => connection.send(message),
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -393,6 +393,12 @@ export class RpcPeer {
|
||||
if (!proxy)
|
||||
proxy = this.newProxy(__remote_proxy_id, __remote_constructor_name, __remote_proxy_props, __remote_proxy_oneway_methods);
|
||||
proxy[RpcPeer.finalizerIdSymbol] = __remote_proxy_finalizer_id;
|
||||
|
||||
const deserializer = this.nameDeserializerMap.get(__remote_constructor_name);
|
||||
if (deserializer) {
|
||||
return deserializer.deserialize(proxy, deserializationContext);
|
||||
}
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
|
||||
@@ -345,7 +345,14 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
||||
});
|
||||
|
||||
// @ts-expect-error
|
||||
await handler.onConnection(httpRequest, new WebSocketConnection(`ws://${id}`));
|
||||
await handler.onConnection(httpRequest, new WebSocketConnection(`ws://${id}`, {
|
||||
send(message) {
|
||||
ws.send(message);
|
||||
},
|
||||
close(message) {
|
||||
ws.close();
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
async getComponent(componentId: string): Promise<any> {
|
||||
|
||||
Reference in New Issue
Block a user