@scrypted/server 0.7.93 → 0.7.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @scrypted/server might be problematic. Click here for more details.
- package/dist/asynciterable-utils.d.ts +2 -0
- package/dist/asynciterable-utils.js +24 -0
- package/dist/asynciterable-utils.js.map +1 -0
- package/dist/cert.d.ts +6 -0
- package/dist/cert.js +75 -0
- package/dist/cert.js.map +1 -0
- package/dist/collection.d.ts +1 -0
- package/dist/collection.js +16 -0
- package/dist/collection.js.map +1 -0
- package/dist/db-types.d.ts +38 -0
- package/dist/db-types.js +45 -0
- package/dist/db-types.js.map +1 -0
- package/dist/event-registry.d.ts +19 -0
- package/dist/event-registry.js +92 -0
- package/dist/event-registry.js.map +1 -0
- package/dist/http-interfaces.d.ts +3 -0
- package/dist/http-interfaces.js +73 -0
- package/dist/http-interfaces.js.map +1 -0
- package/dist/infer-defaults.d.ts +11 -0
- package/dist/infer-defaults.js +119 -0
- package/dist/infer-defaults.js.map +1 -0
- package/dist/io.d.ts +22 -0
- package/dist/io.js +3 -0
- package/dist/io.js.map +1 -0
- package/dist/level.d.ts +110 -0
- package/dist/level.js +135 -0
- package/dist/level.js.map +1 -0
- package/dist/listen-zero.d.ts +13 -0
- package/dist/listen-zero.js +48 -0
- package/dist/listen-zero.js.map +1 -0
- package/dist/logger.d.ts +29 -0
- package/dist/logger.js +78 -0
- package/dist/logger.js.map +1 -0
- package/dist/media-helpers.d.ts +5 -0
- package/dist/media-helpers.js +89 -0
- package/dist/media-helpers.js.map +1 -0
- package/dist/mixin/mixin-cycle.d.ts +3 -0
- package/dist/mixin/mixin-cycle.js +32 -0
- package/dist/mixin/mixin-cycle.js.map +1 -0
- package/dist/plugin/acl.d.ts +16 -0
- package/dist/plugin/acl.js +83 -0
- package/dist/plugin/acl.js.map +1 -0
- package/dist/plugin/descriptor.d.ts +22 -0
- package/dist/plugin/descriptor.js +35 -0
- package/dist/plugin/descriptor.js.map +1 -0
- package/dist/plugin/media.d.ts +71 -0
- package/dist/plugin/media.js +420 -0
- package/dist/plugin/media.js.map +1 -0
- package/dist/plugin/mediaobject.d.ts +10 -0
- package/dist/plugin/mediaobject.js +26 -0
- package/dist/plugin/mediaobject.js.map +1 -0
- package/dist/plugin/plugin-api.d.ts +106 -0
- package/dist/plugin/plugin-api.js +120 -0
- package/dist/plugin/plugin-api.js.map +1 -0
- package/dist/plugin/plugin-console.d.ts +28 -0
- package/dist/plugin/plugin-console.js +291 -0
- package/dist/plugin/plugin-console.js.map +1 -0
- package/dist/plugin/plugin-debug.d.ts +4 -0
- package/dist/plugin/plugin-debug.js +3 -0
- package/dist/plugin/plugin-debug.js.map +1 -0
- package/dist/plugin/plugin-device.d.ts +54 -0
- package/dist/plugin/plugin-device.js +413 -0
- package/dist/plugin/plugin-device.js.map +1 -0
- package/dist/plugin/plugin-error.d.ts +2 -0
- package/dist/plugin/plugin-error.js +7 -0
- package/dist/plugin/plugin-error.js.map +1 -0
- package/dist/plugin/plugin-host-api.d.ts +43 -0
- package/dist/plugin/plugin-host-api.js +179 -0
- package/dist/plugin/plugin-host-api.js.map +1 -0
- package/dist/plugin/plugin-host.d.ts +45 -0
- package/dist/plugin/plugin-host.js +398 -0
- package/dist/plugin/plugin-host.js.map +1 -0
- package/dist/plugin/plugin-http.d.ts +18 -0
- package/dist/plugin/plugin-http.js +120 -0
- package/dist/plugin/plugin-http.js.map +1 -0
- package/dist/plugin/plugin-lazy-remote.d.ts +31 -0
- package/dist/plugin/plugin-lazy-remote.js +75 -0
- package/dist/plugin/plugin-lazy-remote.js.map +1 -0
- package/dist/plugin/plugin-npm-dependencies.d.ts +8 -0
- package/dist/plugin/plugin-npm-dependencies.js +103 -0
- package/dist/plugin/plugin-npm-dependencies.js.map +1 -0
- package/dist/plugin/plugin-remote-stats.d.ts +8 -0
- package/dist/plugin/plugin-remote-stats.js +30 -0
- package/dist/plugin/plugin-remote-stats.js.map +1 -0
- package/dist/plugin/plugin-remote-websocket.d.ts +29 -0
- package/dist/plugin/plugin-remote-websocket.js +152 -0
- package/dist/plugin/plugin-remote-websocket.js.map +1 -0
- package/dist/plugin/plugin-remote-worker.d.ts +5 -0
- package/dist/plugin/plugin-remote-worker.js +348 -0
- package/dist/plugin/plugin-remote-worker.js.map +1 -0
- package/dist/plugin/plugin-remote.d.ts +75 -0
- package/dist/plugin/plugin-remote.js +598 -0
- package/dist/plugin/plugin-remote.js.map +1 -0
- package/dist/plugin/plugin-repl.d.ts +2 -0
- package/dist/plugin/plugin-repl.js +74 -0
- package/dist/plugin/plugin-repl.js.map +1 -0
- package/dist/plugin/plugin-state-check.d.ts +1 -0
- package/dist/plugin/plugin-state-check.js +27 -0
- package/dist/plugin/plugin-state-check.js.map +1 -0
- package/dist/plugin/plugin-volume.d.ts +3 -0
- package/dist/plugin/plugin-volume.js +31 -0
- package/dist/plugin/plugin-volume.js.map +1 -0
- package/dist/plugin/runtime/child-process-worker.d.ts +20 -0
- package/dist/plugin/runtime/child-process-worker.js +42 -0
- package/dist/plugin/runtime/child-process-worker.js.map +1 -0
- package/dist/plugin/runtime/node-fork-worker.d.ts +9 -0
- package/dist/plugin/runtime/node-fork-worker.js +67 -0
- package/dist/plugin/runtime/node-fork-worker.js.map +1 -0
- package/dist/plugin/runtime/node-thread-worker.d.ts +20 -0
- package/dist/plugin/runtime/node-thread-worker.js +73 -0
- package/dist/plugin/runtime/node-thread-worker.js.map +1 -0
- package/dist/plugin/runtime/python-worker.d.ts +10 -0
- package/dist/plugin/runtime/python-worker.js +91 -0
- package/dist/plugin/runtime/python-worker.js.map +1 -0
- package/dist/plugin/runtime/runtime-worker.d.ts +26 -0
- package/dist/plugin/runtime/runtime-worker.js +3 -0
- package/dist/plugin/runtime/runtime-worker.js.map +1 -0
- package/dist/plugin/socket-serializer.d.ts +5 -0
- package/dist/plugin/socket-serializer.js +17 -0
- package/dist/plugin/socket-serializer.js.map +1 -0
- package/dist/plugin/system.d.ts +39 -0
- package/dist/plugin/system.js +216 -0
- package/dist/plugin/system.js.map +1 -0
- package/dist/rpc-buffer-serializer.d.ts +11 -0
- package/dist/rpc-buffer-serializer.js +30 -0
- package/dist/rpc-buffer-serializer.js.map +1 -0
- package/dist/rpc-serializer.d.ts +24 -0
- package/dist/rpc-serializer.js +144 -0
- package/dist/rpc-serializer.js.map +1 -0
- package/dist/rpc.d.ts +147 -0
- package/dist/rpc.js +689 -0
- package/dist/rpc.js.map +1 -0
- package/dist/runtime.d.ts +103 -0
- package/dist/runtime.js +815 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scrypted-main-exports.d.ts +6 -0
- package/dist/scrypted-main-exports.js +57 -0
- package/dist/scrypted-main-exports.js.map +1 -0
- package/dist/scrypted-main.d.ts +1 -0
- package/dist/scrypted-main.js +8 -0
- package/dist/scrypted-main.js.map +1 -0
- package/dist/scrypted-plugin-main.d.ts +2 -0
- package/dist/scrypted-plugin-main.js +43 -0
- package/dist/scrypted-plugin-main.js.map +1 -0
- package/dist/scrypted-server-main.d.ts +6 -0
- package/dist/scrypted-server-main.js +559 -0
- package/dist/scrypted-server-main.js.map +1 -0
- package/dist/server-settings.d.ts +5 -0
- package/dist/server-settings.js +91 -0
- package/dist/server-settings.js.map +1 -0
- package/dist/services/addresses.d.ts +7 -0
- package/dist/services/addresses.js +43 -0
- package/dist/services/addresses.js.map +1 -0
- package/dist/services/alerts.d.ts +9 -0
- package/dist/services/alerts.js +27 -0
- package/dist/services/alerts.js.map +1 -0
- package/dist/services/cors.d.ts +18 -0
- package/dist/services/cors.js +18 -0
- package/dist/services/cors.js.map +1 -0
- package/dist/services/info.d.ts +5 -0
- package/dist/services/info.js +18 -0
- package/dist/services/info.js.map +1 -0
- package/dist/services/plugin.d.ts +46 -0
- package/dist/services/plugin.js +172 -0
- package/dist/services/plugin.js.map +1 -0
- package/dist/services/service-control.d.ts +8 -0
- package/dist/services/service-control.js +39 -0
- package/dist/services/service-control.js.map +1 -0
- package/dist/services/users.d.ts +19 -0
- package/dist/services/users.js +75 -0
- package/dist/services/users.js.map +1 -0
- package/dist/sleep.d.ts +1 -0
- package/dist/sleep.js +8 -0
- package/dist/sleep.js.map +1 -0
- package/dist/state.d.ts +39 -0
- package/dist/state.js +247 -0
- package/dist/state.js.map +1 -0
- package/dist/threading.d.ts +3 -0
- package/dist/threading.js +93 -0
- package/dist/threading.js.map +1 -0
- package/dist/usertoken.d.ts +11 -0
- package/dist/usertoken.js +52 -0
- package/dist/usertoken.js.map +1 -0
- package/package.json +2 -2
- package/src/plugin/plugin-host-api.ts +3 -1
package/dist/runtime.js
ADDED
@@ -0,0 +1,815 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15
|
+
}) : function(o, v) {
|
16
|
+
o["default"] = v;
|
17
|
+
});
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
19
|
+
if (mod && mod.__esModule) return mod;
|
20
|
+
var result = {};
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
22
|
+
__setModuleDefault(result, mod);
|
23
|
+
return result;
|
24
|
+
};
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
27
|
+
};
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
29
|
+
exports.ScryptedRuntime = void 0;
|
30
|
+
const types_1 = require("@scrypted/types");
|
31
|
+
const adm_zip_1 = __importDefault(require("adm-zip"));
|
32
|
+
const axios_1 = __importDefault(require("axios"));
|
33
|
+
const io = __importStar(require("engine.io"));
|
34
|
+
const events_1 = require("events");
|
35
|
+
const path_1 = __importDefault(require("path"));
|
36
|
+
const rimraf_1 = __importDefault(require("rimraf"));
|
37
|
+
const semver_1 = __importDefault(require("semver"));
|
38
|
+
const stream_1 = require("stream");
|
39
|
+
const tar_1 = __importDefault(require("tar"));
|
40
|
+
const url_1 = require("url");
|
41
|
+
const ws_1 = require("ws");
|
42
|
+
const db_types_1 = require("./db-types");
|
43
|
+
const http_interfaces_1 = require("./http-interfaces");
|
44
|
+
const infer_defaults_1 = require("./infer-defaults");
|
45
|
+
const logger_1 = require("./logger");
|
46
|
+
const mixin_cycle_1 = require("./mixin/mixin-cycle");
|
47
|
+
const acl_1 = require("./plugin/acl");
|
48
|
+
const plugin_device_1 = require("./plugin/plugin-device");
|
49
|
+
const plugin_host_1 = require("./plugin/plugin-host");
|
50
|
+
const plugin_http_1 = require("./plugin/plugin-http");
|
51
|
+
const plugin_remote_websocket_1 = require("./plugin/plugin-remote-websocket");
|
52
|
+
const plugin_volume_1 = require("./plugin/plugin-volume");
|
53
|
+
const server_settings_1 = require("./server-settings");
|
54
|
+
const addresses_1 = require("./services/addresses");
|
55
|
+
const alerts_1 = require("./services/alerts");
|
56
|
+
const cors_1 = require("./services/cors");
|
57
|
+
const info_1 = require("./services/info");
|
58
|
+
const plugin_1 = require("./services/plugin");
|
59
|
+
const service_control_1 = require("./services/service-control");
|
60
|
+
const users_1 = require("./services/users");
|
61
|
+
const state_1 = require("./state");
|
62
|
+
const crypto_1 = __importDefault(require("crypto"));
|
63
|
+
const python_worker_1 = require("./plugin/runtime/python-worker");
|
64
|
+
const node_fork_worker_1 = require("./plugin/runtime/node-fork-worker");
|
65
|
+
const MIN_SCRYPTED_CORE_VERSION = 'v0.1.16';
|
66
|
+
const PLUGIN_DEVICE_STATE_VERSION = 2;
|
67
|
+
class ScryptedRuntime extends plugin_http_1.PluginHttp {
|
68
|
+
mainFilename;
|
69
|
+
clusterId = crypto_1.default.randomBytes(3).toString('hex');
|
70
|
+
clusterSecret = crypto_1.default.randomBytes(16).toString('hex');
|
71
|
+
datastore;
|
72
|
+
plugins = {};
|
73
|
+
pluginDevices = {};
|
74
|
+
devices = {};
|
75
|
+
stateManager = new state_1.ScryptedStateManager(this);
|
76
|
+
logger = new logger_1.Logger(this, '', 'Scrypted');
|
77
|
+
devicesLogger = this.logger.getLogger('device', 'Devices');
|
78
|
+
wss = new ws_1.Server({ noServer: true });
|
79
|
+
wsAtomic = 0;
|
80
|
+
shellio = new io.Server({
|
81
|
+
pingTimeout: 120000,
|
82
|
+
perMessageDeflate: true,
|
83
|
+
cors: (req, callback) => {
|
84
|
+
const header = this.getAccessControlAllowOrigin(req.headers);
|
85
|
+
callback(undefined, {
|
86
|
+
origin: header,
|
87
|
+
credentials: true,
|
88
|
+
});
|
89
|
+
},
|
90
|
+
});
|
91
|
+
pluginComponent = new plugin_1.PluginComponent(this);
|
92
|
+
servieControl = new service_control_1.ServiceControl(this);
|
93
|
+
alerts = new alerts_1.Alerts(this);
|
94
|
+
corsControl = new cors_1.CORSControl(this);
|
95
|
+
addressSettings = new addresses_1.AddressSettings(this);
|
96
|
+
usersService = new users_1.UsersService(this);
|
97
|
+
pluginHosts = new Map();
|
98
|
+
constructor(mainFilename, datastore, insecure, secure, app) {
|
99
|
+
super(app);
|
100
|
+
this.mainFilename = mainFilename;
|
101
|
+
this.datastore = datastore;
|
102
|
+
this.app = app;
|
103
|
+
// ensure that all the users are loaded from the db.
|
104
|
+
this.usersService.getAllUsers();
|
105
|
+
this.pluginHosts.set('python', (_, pluginId, options) => new python_worker_1.PythonRuntimeWorker(pluginId, options));
|
106
|
+
this.pluginHosts.set('node', (mainFilename, pluginId, options) => new node_fork_worker_1.NodeForkWorker(mainFilename, pluginId, options));
|
107
|
+
app.disable('x-powered-by');
|
108
|
+
this.addMiddleware();
|
109
|
+
app.all('/engine.io/shell', (req, res) => {
|
110
|
+
if (res.locals.aclId) {
|
111
|
+
res.writeHead(401);
|
112
|
+
res.end();
|
113
|
+
return;
|
114
|
+
}
|
115
|
+
this.shellHandler(req, res);
|
116
|
+
});
|
117
|
+
this.shellio.on('connection', connection => {
|
118
|
+
try {
|
119
|
+
const spawn = require('node-pty-prebuilt-multiarch').spawn;
|
120
|
+
const cp = spawn(process.env.SHELL, [], {});
|
121
|
+
cp.onData(data => connection.send(data));
|
122
|
+
connection.on('message', message => cp.write(message.toString()));
|
123
|
+
connection.on('close', () => cp.kill());
|
124
|
+
}
|
125
|
+
catch (e) {
|
126
|
+
connection.close();
|
127
|
+
}
|
128
|
+
});
|
129
|
+
insecure.on('upgrade', (req, socket, upgradeHead) => {
|
130
|
+
req.upgradeHead = upgradeHead;
|
131
|
+
app.handle(req, {
|
132
|
+
socket,
|
133
|
+
upgradeHead
|
134
|
+
});
|
135
|
+
});
|
136
|
+
secure.on('upgrade', (req, socket, upgradeHead) => {
|
137
|
+
req.upgradeHead = upgradeHead;
|
138
|
+
app.handle(req, {
|
139
|
+
socket,
|
140
|
+
upgradeHead
|
141
|
+
});
|
142
|
+
});
|
143
|
+
this.logger.on('log', (logEntry) => {
|
144
|
+
if (logEntry.level !== 'a')
|
145
|
+
return;
|
146
|
+
console.log('alert', logEntry);
|
147
|
+
const alert = new db_types_1.ScryptedAlert();
|
148
|
+
alert._id = (0, logger_1.makeAlertId)(logEntry.path, logEntry.message);
|
149
|
+
alert.message = logEntry.message;
|
150
|
+
alert.timestamp = logEntry.timestamp;
|
151
|
+
alert.path = logEntry.path;
|
152
|
+
alert.title = logEntry.title;
|
153
|
+
datastore.upsert(alert);
|
154
|
+
this.stateManager.notifyInterfaceEvent(null, 'Logger', logEntry);
|
155
|
+
});
|
156
|
+
// purge logs older than 2 hours every hour
|
157
|
+
setInterval(() => {
|
158
|
+
this.logger.purge(Date.now() - 48 * 60 * 60 * 1000);
|
159
|
+
}, 60 * 60 * 1000);
|
160
|
+
}
|
161
|
+
checkUpgrade(req, res, pluginData) {
|
162
|
+
// pluginData.pluginHost.io.
|
163
|
+
const { sid } = req.query;
|
164
|
+
const client = pluginData.pluginHost.io.clients[sid];
|
165
|
+
if (client) {
|
166
|
+
res.locals.username = 'existing-io-session';
|
167
|
+
}
|
168
|
+
}
|
169
|
+
addAccessControlHeaders(req, res) {
|
170
|
+
res.setHeader('Vary', 'Origin,Referer');
|
171
|
+
const header = this.getAccessControlAllowOrigin(req.headers);
|
172
|
+
if (header) {
|
173
|
+
res.setHeader('Access-Control-Allow-Origin', header);
|
174
|
+
}
|
175
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
176
|
+
res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
177
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
178
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With, Access-Control-Request-Method');
|
179
|
+
}
|
180
|
+
getAccessControlAllowOrigin(headers) {
|
181
|
+
let { origin, referer } = headers;
|
182
|
+
if (!origin && referer) {
|
183
|
+
try {
|
184
|
+
const u = new url_1.URL(headers.referer);
|
185
|
+
origin = u.origin;
|
186
|
+
}
|
187
|
+
catch (e) {
|
188
|
+
return;
|
189
|
+
}
|
190
|
+
}
|
191
|
+
if (!origin)
|
192
|
+
return;
|
193
|
+
const servers = process.env.SCRYPTED_ACCESS_CONTROL_ALLOW_ORIGINS?.split(',') || [];
|
194
|
+
servers.push(...Object.values(this.corsControl.origins).flat());
|
195
|
+
if (!servers.includes(origin))
|
196
|
+
return;
|
197
|
+
return origin;
|
198
|
+
}
|
199
|
+
getDeviceLogger(device) {
|
200
|
+
return this.devicesLogger.getLogger(device._id, (0, state_1.getState)(device, types_1.ScryptedInterfaceProperty.name));
|
201
|
+
}
|
202
|
+
async getPluginForEndpoint(endpoint) {
|
203
|
+
let pluginHost = this.plugins[endpoint] ?? this.getPluginHostForDeviceId(endpoint);
|
204
|
+
if (endpoint === '@scrypted/core') {
|
205
|
+
// enforce a minimum version on @scrypted/core
|
206
|
+
if (!pluginHost || semver_1.default.lt(pluginHost.packageJson.version, MIN_SCRYPTED_CORE_VERSION)) {
|
207
|
+
try {
|
208
|
+
pluginHost = await this.installNpm('@scrypted/core');
|
209
|
+
}
|
210
|
+
catch (e) {
|
211
|
+
console.error('@scrypted/core auto install failed', e);
|
212
|
+
}
|
213
|
+
}
|
214
|
+
}
|
215
|
+
const pluginDevice = this.findPluginDevice(endpoint) ?? this.findPluginDeviceById(endpoint);
|
216
|
+
return {
|
217
|
+
pluginHost,
|
218
|
+
pluginDevice,
|
219
|
+
};
|
220
|
+
}
|
221
|
+
async shellHandler(req, res) {
|
222
|
+
const isUpgrade = (0, plugin_http_1.isConnectionUpgrade)(req.headers);
|
223
|
+
const end = (code, message) => {
|
224
|
+
if (isUpgrade) {
|
225
|
+
const socket = res.socket;
|
226
|
+
socket.write(`HTTP/1.1 ${code} ${message}\r\n` +
|
227
|
+
'\r\n');
|
228
|
+
socket.destroy();
|
229
|
+
}
|
230
|
+
else {
|
231
|
+
res.status(code);
|
232
|
+
res.send(message);
|
233
|
+
}
|
234
|
+
};
|
235
|
+
if (!res.locals.username) {
|
236
|
+
end(401, 'Not Authorized');
|
237
|
+
return;
|
238
|
+
}
|
239
|
+
if (req.upgradeHead)
|
240
|
+
this.shellio.handleUpgrade(req, res.socket, req.upgradeHead);
|
241
|
+
else
|
242
|
+
this.shellio.handleRequest(req, res);
|
243
|
+
}
|
244
|
+
async getEndpointPluginData(req, endpoint, isUpgrade, isEngineIOEndpoint) {
|
245
|
+
const ret = await this.getPluginForEndpoint(endpoint);
|
246
|
+
if (req.url.indexOf('/engine.io/api') !== -1)
|
247
|
+
return ret;
|
248
|
+
const { pluginDevice } = ret;
|
249
|
+
// check if upgrade requests can be handled. must be websocket.
|
250
|
+
if (isUpgrade) {
|
251
|
+
if (!pluginDevice?.state.interfaces.value.includes(types_1.ScryptedInterface.EngineIOHandler)) {
|
252
|
+
return;
|
253
|
+
}
|
254
|
+
}
|
255
|
+
else {
|
256
|
+
if (!isEngineIOEndpoint && !pluginDevice?.state.interfaces.value.includes(types_1.ScryptedInterface.HttpRequestHandler)) {
|
257
|
+
return;
|
258
|
+
}
|
259
|
+
}
|
260
|
+
return ret;
|
261
|
+
}
|
262
|
+
async handleWebSocket(endpoint, httpRequest, ws, pluginData) {
|
263
|
+
const { pluginDevice } = pluginData;
|
264
|
+
const handler = this.getDevice(pluginDevice._id);
|
265
|
+
const id = 'ws-' + this.wsAtomic++;
|
266
|
+
const pluginHost = this.plugins[endpoint] ?? this.getPluginHostForDeviceId(endpoint);
|
267
|
+
if (!pluginHost) {
|
268
|
+
ws.close();
|
269
|
+
return;
|
270
|
+
}
|
271
|
+
pluginHost.ws[id] = ws;
|
272
|
+
ws.on('message', async (message) => {
|
273
|
+
try {
|
274
|
+
pluginHost.remote.ioEvent(id, 'message', message);
|
275
|
+
}
|
276
|
+
catch (e) {
|
277
|
+
ws.close();
|
278
|
+
}
|
279
|
+
});
|
280
|
+
ws.on('close', async (reason) => {
|
281
|
+
try {
|
282
|
+
pluginHost.remote.ioEvent(id, 'close');
|
283
|
+
}
|
284
|
+
catch (e) {
|
285
|
+
}
|
286
|
+
delete pluginHost.ws[id];
|
287
|
+
});
|
288
|
+
// @ts-expect-error
|
289
|
+
await handler.onConnection(httpRequest, new plugin_remote_websocket_1.WebSocketConnection(`ws://${id}`, {
|
290
|
+
send(message) {
|
291
|
+
ws.send(message);
|
292
|
+
},
|
293
|
+
close(message) {
|
294
|
+
ws.close();
|
295
|
+
},
|
296
|
+
}));
|
297
|
+
}
|
298
|
+
async getComponent(componentId) {
|
299
|
+
switch (componentId) {
|
300
|
+
case 'SCRYPTED_IP_ADDRESS':
|
301
|
+
return (0, server_settings_1.getIpAddress)();
|
302
|
+
case 'SCRYPTED_INSECURE_PORT':
|
303
|
+
return server_settings_1.SCRYPTED_INSECURE_PORT;
|
304
|
+
case 'SCRYPTED_SECURE_PORT':
|
305
|
+
return server_settings_1.SCRYPTED_SECURE_PORT;
|
306
|
+
case 'info':
|
307
|
+
return new info_1.Info();
|
308
|
+
case 'plugins':
|
309
|
+
return this.pluginComponent;
|
310
|
+
case 'service-control':
|
311
|
+
return this.servieControl;
|
312
|
+
case 'logger':
|
313
|
+
return this.logger;
|
314
|
+
case 'alerts':
|
315
|
+
return this.alerts;
|
316
|
+
case 'cors':
|
317
|
+
return this.corsControl;
|
318
|
+
case 'addresses':
|
319
|
+
return this.addressSettings;
|
320
|
+
case "users":
|
321
|
+
return this.usersService;
|
322
|
+
}
|
323
|
+
}
|
324
|
+
async getPackageJson(pluginId) {
|
325
|
+
let packageJson;
|
326
|
+
if (this.plugins[pluginId]) {
|
327
|
+
packageJson = this.plugins[pluginId].packageJson;
|
328
|
+
}
|
329
|
+
else {
|
330
|
+
const plugin = await this.datastore.tryGet(db_types_1.Plugin, pluginId);
|
331
|
+
packageJson = plugin.packageJson;
|
332
|
+
}
|
333
|
+
return packageJson;
|
334
|
+
}
|
335
|
+
async getAccessControls(username) {
|
336
|
+
if (!username)
|
337
|
+
return;
|
338
|
+
const user = await this.datastore.tryGet(db_types_1.ScryptedUser, username);
|
339
|
+
if (user?.aclId) {
|
340
|
+
const accessControl = this.getDevice(user.aclId);
|
341
|
+
const acls = await accessControl.getScryptedUserAccessControl();
|
342
|
+
if (!acls)
|
343
|
+
return;
|
344
|
+
return new acl_1.AccessControls(acls);
|
345
|
+
}
|
346
|
+
}
|
347
|
+
async handleEngineIOEndpoint(req, res, endpointRequest, pluginData) {
|
348
|
+
const { pluginHost, pluginDevice } = pluginData;
|
349
|
+
const { username } = res.locals;
|
350
|
+
let accessControls;
|
351
|
+
try {
|
352
|
+
accessControls = await this.getAccessControls(username);
|
353
|
+
if (accessControls?.shouldRejectMethod(pluginDevice._id, types_1.ScryptedInterfaceMethod.onConnection))
|
354
|
+
accessControls.deny();
|
355
|
+
}
|
356
|
+
catch (e) {
|
357
|
+
res.writeHead(401);
|
358
|
+
res.end();
|
359
|
+
return;
|
360
|
+
}
|
361
|
+
if (!pluginHost || !pluginDevice) {
|
362
|
+
console.error('plugin does not exist or is still starting up.');
|
363
|
+
res.writeHead(500);
|
364
|
+
res.end();
|
365
|
+
return;
|
366
|
+
}
|
367
|
+
req.scrypted = {
|
368
|
+
endpointRequest,
|
369
|
+
pluginDevice,
|
370
|
+
accessControls,
|
371
|
+
};
|
372
|
+
if (req.upgradeHead)
|
373
|
+
pluginHost.io.handleUpgrade(req, res.socket, req.upgradeHead);
|
374
|
+
else
|
375
|
+
pluginHost.io.handleRequest(req, res);
|
376
|
+
}
|
377
|
+
handleRequestEndpoint(req, res, endpointRequest, pluginData) {
|
378
|
+
const { pluginHost, pluginDevice } = pluginData;
|
379
|
+
const handler = this.getDevice(pluginDevice._id);
|
380
|
+
if (handler.interfaces.includes(types_1.ScryptedInterface.EngineIOHandler) && (0, plugin_http_1.isConnectionUpgrade)(req.headers) && req.headers.upgrade?.toLowerCase() === 'websocket') {
|
381
|
+
this.wss.handleUpgrade(req, req.socket, null, ws => {
|
382
|
+
console.log(ws);
|
383
|
+
});
|
384
|
+
}
|
385
|
+
const filesPath = path_1.default.join((0, plugin_volume_1.getPluginVolume)(pluginHost.pluginId), 'files');
|
386
|
+
handler.onRequest(endpointRequest, (0, http_interfaces_1.createResponseInterface)(res, pluginHost.unzippedPath, filesPath));
|
387
|
+
}
|
388
|
+
killPlugin(pluginId) {
|
389
|
+
const existing = this.plugins[pluginId];
|
390
|
+
if (existing) {
|
391
|
+
delete this.plugins[pluginId];
|
392
|
+
existing.kill();
|
393
|
+
}
|
394
|
+
}
|
395
|
+
// should this be async?
|
396
|
+
invalidatePluginDevice(id) {
|
397
|
+
const proxyPair = this.devices[id];
|
398
|
+
if (!proxyPair)
|
399
|
+
return;
|
400
|
+
proxyPair.handler.invalidate();
|
401
|
+
return proxyPair;
|
402
|
+
}
|
403
|
+
// should this be async?
|
404
|
+
rebuildPluginDeviceMixinTable(id) {
|
405
|
+
const proxyPair = this.devices[id];
|
406
|
+
if (!proxyPair)
|
407
|
+
return;
|
408
|
+
proxyPair.handler.rebuildMixinTable();
|
409
|
+
return proxyPair;
|
410
|
+
}
|
411
|
+
invalidateMixins(ids) {
|
412
|
+
const ret = new Set();
|
413
|
+
const remaining = [...ids];
|
414
|
+
// first pass:
|
415
|
+
// for every id, find anything it is acting on as a mixin, and clear out the entry.
|
416
|
+
while (remaining.length) {
|
417
|
+
const id = remaining.pop();
|
418
|
+
for (const device of Object.values(this.devices)) {
|
419
|
+
const foundIndex = device.handler?.mixinTable?.findIndex(mt => mt.mixinProviderId === id);
|
420
|
+
if (foundIndex === -1 || foundIndex === undefined)
|
421
|
+
continue;
|
422
|
+
const did = device.handler.id;
|
423
|
+
if (!ret.has(did)) {
|
424
|
+
// add this to the list of mixin providers that need to be rebuilt
|
425
|
+
ret.add(did);
|
426
|
+
remaining.push(did);
|
427
|
+
}
|
428
|
+
// if it is the last entry, that means it is the device itself.
|
429
|
+
// can this happen? i don't think it is possible. mixin provider id would be undefined.
|
430
|
+
if (foundIndex === device.handler.mixinTable.length - 1) {
|
431
|
+
console.warn('attempt to invalidate mixin on actual device?');
|
432
|
+
continue;
|
433
|
+
}
|
434
|
+
const removed = device.handler.mixinTable.splice(0, foundIndex + 1);
|
435
|
+
for (const entry of removed) {
|
436
|
+
console.log('invalidating mixin', device.handler.id, entry.mixinProviderId);
|
437
|
+
device.handler.invalidateEntry(entry);
|
438
|
+
}
|
439
|
+
}
|
440
|
+
}
|
441
|
+
// second pass:
|
442
|
+
// rebuild the mixin tables.
|
443
|
+
for (const id of ret) {
|
444
|
+
const device = this.devices[id];
|
445
|
+
device.handler.rebuildMixinTable();
|
446
|
+
}
|
447
|
+
return ret;
|
448
|
+
}
|
449
|
+
async installNpm(pkg, version, installedSet) {
|
450
|
+
if (!installedSet)
|
451
|
+
installedSet = new Set();
|
452
|
+
if (installedSet.has(pkg))
|
453
|
+
return;
|
454
|
+
installedSet.add(pkg);
|
455
|
+
const registry = (await (0, axios_1.default)(`https://registry.npmjs.org/${pkg}`)).data;
|
456
|
+
if (!version) {
|
457
|
+
version = registry['dist-tags'].latest;
|
458
|
+
}
|
459
|
+
console.log('installing package', pkg, version);
|
460
|
+
const tarball = (await (0, axios_1.default)(`${registry.versions[version].dist.tarball}`, {
|
461
|
+
responseType: 'arraybuffer'
|
462
|
+
})).data;
|
463
|
+
console.log('downloaded tarball', tarball?.length);
|
464
|
+
const parse = new tar_1.default.Parse();
|
465
|
+
const files = {};
|
466
|
+
parse.on('entry', async (entry) => {
|
467
|
+
console.log('parsing entry', entry.path);
|
468
|
+
const chunks = [];
|
469
|
+
entry.on('data', (data) => chunks.push(data));
|
470
|
+
entry.on('end', () => {
|
471
|
+
const buffer = Buffer.concat(chunks);
|
472
|
+
files[entry.path] = buffer;
|
473
|
+
});
|
474
|
+
});
|
475
|
+
const ret = (async () => {
|
476
|
+
await (0, events_1.once)(parse, 'end');
|
477
|
+
console.log('npm package files:', Object.keys(files).join(', '));
|
478
|
+
const packageJsonEntry = files['package/package.json'];
|
479
|
+
if (!packageJsonEntry)
|
480
|
+
throw new Error('package.json not found. are you behind a firewall?');
|
481
|
+
const packageJson = JSON.parse(packageJsonEntry.toString());
|
482
|
+
const pluginDependencies = packageJson.scrypted.pluginDependencies || [];
|
483
|
+
pluginDependencies.forEach(async (dep) => {
|
484
|
+
try {
|
485
|
+
const depId = this.findPluginDevice(dep);
|
486
|
+
if (depId)
|
487
|
+
throw new Error('Plugin already installed.');
|
488
|
+
await this.installNpm(dep);
|
489
|
+
}
|
490
|
+
catch (e) {
|
491
|
+
console.log('Skipping', dep, ':', e.message);
|
492
|
+
}
|
493
|
+
});
|
494
|
+
const npmPackage = packageJson.name;
|
495
|
+
const plugin = await this.datastore.tryGet(db_types_1.Plugin, npmPackage) || new db_types_1.Plugin();
|
496
|
+
plugin._id = npmPackage;
|
497
|
+
plugin.packageJson = packageJson;
|
498
|
+
plugin.zip = files['package/dist/plugin.zip'].toString('base64');
|
499
|
+
await this.datastore.upsert(plugin);
|
500
|
+
return this.installPlugin(plugin);
|
501
|
+
})();
|
502
|
+
const pt = new stream_1.PassThrough();
|
503
|
+
pt.write(Buffer.from(tarball));
|
504
|
+
pt.push(null);
|
505
|
+
pt.pipe(parse);
|
506
|
+
return ret;
|
507
|
+
}
|
508
|
+
async installPlugin(plugin, pluginDebug) {
|
509
|
+
const device = Object.assign({}, plugin.packageJson.scrypted, {
|
510
|
+
info: {
|
511
|
+
manufacturer: plugin.packageJson.name,
|
512
|
+
version: plugin.packageJson.version,
|
513
|
+
}
|
514
|
+
});
|
515
|
+
try {
|
516
|
+
if (!device.interfaces.includes(types_1.ScryptedInterface.Readme)) {
|
517
|
+
const zipData = Buffer.from(plugin.zip, 'base64');
|
518
|
+
const adm = new adm_zip_1.default(zipData);
|
519
|
+
const entry = adm.getEntry('README.md');
|
520
|
+
if (entry) {
|
521
|
+
device.interfaces = device.interfaces.slice();
|
522
|
+
device.interfaces.push(types_1.ScryptedInterface.Readme);
|
523
|
+
}
|
524
|
+
}
|
525
|
+
}
|
526
|
+
catch (e) {
|
527
|
+
}
|
528
|
+
this.upsertDevice(plugin._id, device);
|
529
|
+
return this.runPlugin(plugin, pluginDebug);
|
530
|
+
}
|
531
|
+
setupPluginHostAutoRestart(pluginHost) {
|
532
|
+
pluginHost.worker.once('exit', () => {
|
533
|
+
if (pluginHost.killed)
|
534
|
+
return;
|
535
|
+
pluginHost.kill();
|
536
|
+
const timeout = 60000;
|
537
|
+
console.error(`plugin unexpectedly exited, restarting in ${timeout}ms`, pluginHost.pluginId);
|
538
|
+
setTimeout(async () => {
|
539
|
+
const existing = this.plugins[pluginHost.pluginId];
|
540
|
+
if (existing !== pluginHost) {
|
541
|
+
console.log('scheduled plugin restart cancelled, plugin was restarted by user', pluginHost.pluginId);
|
542
|
+
return;
|
543
|
+
}
|
544
|
+
const plugin = await this.datastore.tryGet(db_types_1.Plugin, pluginHost.pluginId);
|
545
|
+
if (!plugin) {
|
546
|
+
console.log('scheduled plugin restart cancelled, plugin no longer exists', pluginHost.pluginId);
|
547
|
+
return;
|
548
|
+
}
|
549
|
+
try {
|
550
|
+
this.runPlugin(plugin);
|
551
|
+
}
|
552
|
+
catch (e) {
|
553
|
+
console.error('error restarting plugin', plugin._id, e);
|
554
|
+
}
|
555
|
+
}, timeout);
|
556
|
+
});
|
557
|
+
}
|
558
|
+
loadPlugin(plugin, pluginDebug) {
|
559
|
+
const pluginId = plugin._id;
|
560
|
+
this.killPlugin(pluginId);
|
561
|
+
const pluginDevices = this.findPluginDevices(pluginId);
|
562
|
+
for (const pluginDevice of pluginDevices) {
|
563
|
+
this.invalidatePluginDevice(pluginDevice._id);
|
564
|
+
}
|
565
|
+
const pluginHost = new plugin_host_1.PluginHost(this, plugin, pluginDebug);
|
566
|
+
this.setupPluginHostAutoRestart(pluginHost);
|
567
|
+
this.plugins[pluginId] = pluginHost;
|
568
|
+
return pluginHost;
|
569
|
+
}
|
570
|
+
probePluginDevices(plugin) {
|
571
|
+
const pluginId = plugin._id;
|
572
|
+
const pluginDevices = this.findPluginDevices(pluginId);
|
573
|
+
const pluginDeviceSet = new Set();
|
574
|
+
for (const pluginDevice of pluginDevices) {
|
575
|
+
if (pluginDeviceSet.has(pluginDevice._id))
|
576
|
+
continue;
|
577
|
+
pluginDeviceSet.add(pluginDevice._id);
|
578
|
+
this.getDevice(pluginDevice._id)?.probe().catch(() => { });
|
579
|
+
}
|
580
|
+
for (const pluginDevice of Object.values(this.pluginDevices)) {
|
581
|
+
const { _id } = pluginDevice;
|
582
|
+
if (pluginDeviceSet.has(_id))
|
583
|
+
continue;
|
584
|
+
for (const mixinId of (0, mixin_cycle_1.getMixins)(this, _id)) {
|
585
|
+
if (pluginDeviceSet.has(mixinId)) {
|
586
|
+
this.getDevice(_id)?.probe().catch(() => { });
|
587
|
+
}
|
588
|
+
}
|
589
|
+
}
|
590
|
+
}
|
591
|
+
runPlugin(plugin, pluginDebug) {
|
592
|
+
const pluginHost = this.loadPlugin(plugin, pluginDebug);
|
593
|
+
this.probePluginDevices(plugin);
|
594
|
+
return pluginHost;
|
595
|
+
}
|
596
|
+
findPluginDevice(pluginId, nativeId) {
|
597
|
+
// JSON stringify over rpc turns undefined into null.
|
598
|
+
if (nativeId === null)
|
599
|
+
nativeId = undefined;
|
600
|
+
return Object.values(this.pluginDevices).find(device => device.pluginId === pluginId && device.nativeId == nativeId);
|
601
|
+
}
|
602
|
+
findPluginDeviceById(id) {
|
603
|
+
return this.pluginDevices[id];
|
604
|
+
}
|
605
|
+
findPluginDevices(pluginId) {
|
606
|
+
return Object.values(this.pluginDevices).filter(e => e.state && e.pluginId === pluginId);
|
607
|
+
}
|
608
|
+
getPluginHostForDeviceId(id) {
|
609
|
+
const device = this.pluginDevices[id];
|
610
|
+
if (!device)
|
611
|
+
return;
|
612
|
+
return this.plugins[device.pluginId];
|
613
|
+
}
|
614
|
+
getDevice(id) {
|
615
|
+
const device = this.devices[id];
|
616
|
+
if (device)
|
617
|
+
return device.proxy;
|
618
|
+
if (!this.pluginDevices[id]) {
|
619
|
+
console.warn('device not found', id);
|
620
|
+
return;
|
621
|
+
}
|
622
|
+
const handler = new plugin_device_1.PluginDeviceProxyHandler(this, id);
|
623
|
+
const proxy = new Proxy(handler, handler);
|
624
|
+
this.devices[id] = {
|
625
|
+
proxy,
|
626
|
+
handler,
|
627
|
+
};
|
628
|
+
return proxy;
|
629
|
+
}
|
630
|
+
async removeDevice(device) {
|
631
|
+
// delete any devices provided by this device
|
632
|
+
const providedDevices = Object.values(this.pluginDevices).filter(pluginDevice => (0, state_1.getState)(pluginDevice, types_1.ScryptedInterfaceProperty.providerId) === device._id);
|
633
|
+
for (const provided of providedDevices) {
|
634
|
+
if (provided === device)
|
635
|
+
continue;
|
636
|
+
await this.removeDevice(provided);
|
637
|
+
}
|
638
|
+
const providerId = device.state?.providerId?.value;
|
639
|
+
device.state = undefined;
|
640
|
+
this.invalidatePluginDevice(device._id);
|
641
|
+
delete this.pluginDevices[device._id];
|
642
|
+
await this.datastore.remove(device);
|
643
|
+
this.stateManager.removeDevice(device._id);
|
644
|
+
// if this device is acting as a mixin on anything, can now remove invalidate it.
|
645
|
+
// when the mixin table is rebuilt, it will be automatically ignore and remove the dangling mixin.
|
646
|
+
this.invalidateMixins(new Set([device._id]));
|
647
|
+
// if the device is a plugin, kill and remove the plugin as well.
|
648
|
+
if (!device.nativeId) {
|
649
|
+
this.killPlugin(device.pluginId);
|
650
|
+
await this.datastore.removeId(db_types_1.Plugin, device.pluginId);
|
651
|
+
rimraf_1.default.sync((0, plugin_volume_1.getPluginVolume)(device.pluginId));
|
652
|
+
}
|
653
|
+
else {
|
654
|
+
try {
|
655
|
+
// notify the plugin that a device was removed.
|
656
|
+
const plugin = this.plugins[device.pluginId];
|
657
|
+
await plugin.remote.setNativeId(device.nativeId, undefined, undefined);
|
658
|
+
const provider = this.getDevice(providerId);
|
659
|
+
await provider?.releaseDevice(device._id, device.nativeId);
|
660
|
+
}
|
661
|
+
catch (e) {
|
662
|
+
// may throw if the plugin is killed, etc.
|
663
|
+
console.warn('error while reporting device removal to plugin remote', e);
|
664
|
+
}
|
665
|
+
}
|
666
|
+
}
|
667
|
+
upsertDevice(pluginId, device) {
|
668
|
+
// JSON stringify over rpc turns undefined into null.
|
669
|
+
if (device.nativeId === null)
|
670
|
+
device.nativeId = undefined;
|
671
|
+
let pluginDevice = this.findPluginDevice(pluginId, device.nativeId);
|
672
|
+
if (!pluginDevice) {
|
673
|
+
pluginDevice = new db_types_1.PluginDevice(this.datastore.nextId().toString());
|
674
|
+
pluginDevice.stateVersion = PLUGIN_DEVICE_STATE_VERSION;
|
675
|
+
}
|
676
|
+
this.pluginDevices[pluginDevice._id] = pluginDevice;
|
677
|
+
pluginDevice.pluginId = pluginId;
|
678
|
+
pluginDevice.nativeId = device.nativeId;
|
679
|
+
pluginDevice.state = pluginDevice.state || {};
|
680
|
+
if (pluginDevice.state[types_1.ScryptedInterfaceProperty.nativeId]?.value !== pluginDevice.nativeId) {
|
681
|
+
(0, state_1.setState)(pluginDevice, types_1.ScryptedInterfaceProperty.nativeId, pluginDevice.nativeId);
|
682
|
+
}
|
683
|
+
const providedType = device.type;
|
684
|
+
const isUsingDefaultType = (0, infer_defaults_1.getDisplayType)(pluginDevice) === (0, infer_defaults_1.getProvidedTypeOrDefault)(pluginDevice);
|
685
|
+
const providedName = device.name;
|
686
|
+
const isUsingDefaultName = (0, infer_defaults_1.getDisplayName)(pluginDevice) === (0, infer_defaults_1.getProvidedNameOrDefault)(pluginDevice);
|
687
|
+
const providedRoom = device.room;
|
688
|
+
const isUsingDefaultRoom = (0, infer_defaults_1.getDisplayRoom)(pluginDevice) === (0, infer_defaults_1.getProvidedRoomOrDefault)(pluginDevice);
|
689
|
+
let providedInterfaces = device.interfaces.slice();
|
690
|
+
if (!device.nativeId)
|
691
|
+
providedInterfaces.push(types_1.ScryptedInterface.ScryptedPlugin);
|
692
|
+
else
|
693
|
+
providedInterfaces = providedInterfaces.filter(iface => iface !== types_1.ScryptedInterface.ScryptedPlugin);
|
694
|
+
providedInterfaces = plugin_device_1.PluginDeviceProxyHandler.sortInterfaces(providedInterfaces);
|
695
|
+
// assure final mixin resolved interface list has at least all the
|
696
|
+
// interfaces from the provided. the actual list will resolve lazily.
|
697
|
+
let mixinInterfaces = [];
|
698
|
+
const mixins = (0, state_1.getState)(pluginDevice, types_1.ScryptedInterfaceProperty.mixins) || [];
|
699
|
+
if (mixins.length)
|
700
|
+
mixinInterfaces.push(...(0, state_1.getState)(pluginDevice, types_1.ScryptedInterfaceProperty.interfaces) || []);
|
701
|
+
mixinInterfaces.push(...providedInterfaces.slice());
|
702
|
+
mixinInterfaces = plugin_device_1.PluginDeviceProxyHandler.sortInterfaces(mixinInterfaces);
|
703
|
+
this.stateManager.setPluginDeviceState(pluginDevice, types_1.ScryptedInterfaceProperty.pluginId, pluginId);
|
704
|
+
let interfacesChanged = this.stateManager.setPluginDeviceState(pluginDevice, types_1.ScryptedInterfaceProperty.providedInterfaces, providedInterfaces);
|
705
|
+
interfacesChanged = this.stateManager.setPluginDeviceState(pluginDevice, types_1.ScryptedInterfaceProperty.interfaces, mixinInterfaces)
|
706
|
+
|| interfacesChanged;
|
707
|
+
if (device.info !== undefined)
|
708
|
+
this.stateManager.setPluginDeviceState(pluginDevice, types_1.ScryptedInterfaceProperty.info, device.info);
|
709
|
+
const provider = this.findPluginDevice(pluginId, device.providerNativeId);
|
710
|
+
this.stateManager.setPluginDeviceState(pluginDevice, types_1.ScryptedInterfaceProperty.providerId, provider?._id);
|
711
|
+
this.stateManager.setPluginDeviceState(pluginDevice, types_1.ScryptedInterfaceProperty.providedName, providedName);
|
712
|
+
this.stateManager.setPluginDeviceState(pluginDevice, types_1.ScryptedInterfaceProperty.providedType, providedType);
|
713
|
+
if (isUsingDefaultType)
|
714
|
+
this.stateManager.setPluginDeviceState(pluginDevice, types_1.ScryptedInterfaceProperty.type, (0, infer_defaults_1.getProvidedTypeOrDefault)(pluginDevice));
|
715
|
+
if (isUsingDefaultName)
|
716
|
+
this.stateManager.setPluginDeviceState(pluginDevice, types_1.ScryptedInterfaceProperty.name, (0, infer_defaults_1.getProvidedNameOrDefault)(pluginDevice));
|
717
|
+
this.stateManager.setPluginDeviceState(pluginDevice, types_1.ScryptedInterfaceProperty.providedRoom, providedRoom);
|
718
|
+
if (isUsingDefaultRoom)
|
719
|
+
this.stateManager.setPluginDeviceState(pluginDevice, types_1.ScryptedInterfaceProperty.room, (0, infer_defaults_1.getProvidedRoomOrDefault)(pluginDevice));
|
720
|
+
const ret = this.notifyPluginDeviceDescriptorChanged(pluginDevice);
|
721
|
+
return {
|
722
|
+
pluginDevicePromise: ret,
|
723
|
+
interfacesChanged,
|
724
|
+
};
|
725
|
+
}
|
726
|
+
notifyPluginDeviceDescriptorChanged(pluginDevice) {
|
727
|
+
const ret = this.datastore.upsert(pluginDevice);
|
728
|
+
// the descriptor events should happen after everything is set, as it's an atomic operation.
|
729
|
+
this.stateManager.updateDescriptor(pluginDevice);
|
730
|
+
this.stateManager.notifyInterfaceEvent(pluginDevice, types_1.ScryptedInterface.ScryptedDevice, undefined);
|
731
|
+
return ret;
|
732
|
+
}
|
733
|
+
killall() {
|
734
|
+
for (const host of Object.values(this.plugins)) {
|
735
|
+
host?.kill();
|
736
|
+
}
|
737
|
+
process.exit();
|
738
|
+
}
|
739
|
+
async start() {
|
740
|
+
// catch ctrl-c
|
741
|
+
process.on('SIGINT', () => this.killall());
|
742
|
+
// catch kill
|
743
|
+
process.on('SIGTERM', () => this.killall());
|
744
|
+
for await (const pluginDevice of this.datastore.getAll(db_types_1.PluginDevice)) {
|
745
|
+
// this may happen due to race condition around deletion/update. investigate.
|
746
|
+
if (!pluginDevice.state) {
|
747
|
+
this.datastore.remove(pluginDevice);
|
748
|
+
continue;
|
749
|
+
}
|
750
|
+
this.pluginDevices[pluginDevice._id] = pluginDevice;
|
751
|
+
let mixins = (0, state_1.getState)(pluginDevice, types_1.ScryptedInterfaceProperty.mixins) || [];
|
752
|
+
let dirty = false;
|
753
|
+
if (mixins.includes(null) || mixins.includes(undefined)) {
|
754
|
+
dirty = true;
|
755
|
+
(0, state_1.setState)(pluginDevice, types_1.ScryptedInterfaceProperty.mixins, mixins.filter(e => !!e));
|
756
|
+
}
|
757
|
+
const interfaces = (0, state_1.getState)(pluginDevice, types_1.ScryptedInterfaceProperty.providedInterfaces);
|
758
|
+
if (!pluginDevice.nativeId && !interfaces.includes(types_1.ScryptedInterface.ScryptedPlugin)) {
|
759
|
+
dirty = true;
|
760
|
+
interfaces.push(types_1.ScryptedInterface.ScryptedPlugin);
|
761
|
+
(0, state_1.setState)(pluginDevice, types_1.ScryptedInterfaceProperty.providedInterfaces, plugin_device_1.PluginDeviceProxyHandler.sortInterfaces(interfaces));
|
762
|
+
}
|
763
|
+
const pluginId = (0, state_1.getState)(pluginDevice, types_1.ScryptedInterfaceProperty.pluginId);
|
764
|
+
if (!pluginId) {
|
765
|
+
dirty = true;
|
766
|
+
(0, state_1.setState)(pluginDevice, types_1.ScryptedInterfaceProperty.pluginId, pluginDevice.pluginId);
|
767
|
+
}
|
768
|
+
if (pluginDevice.state[types_1.ScryptedInterfaceProperty.nativeId]?.value !== pluginDevice.nativeId) {
|
769
|
+
dirty = true;
|
770
|
+
(0, state_1.setState)(pluginDevice, types_1.ScryptedInterfaceProperty.nativeId, pluginDevice.nativeId);
|
771
|
+
}
|
772
|
+
if (dirty) {
|
773
|
+
this.datastore.upsert(pluginDevice)
|
774
|
+
.catch(e => {
|
775
|
+
console.error('There was an error saving the device? Ignoring...', e);
|
776
|
+
// return this.datastore.remove(pluginDevice);
|
777
|
+
});
|
778
|
+
}
|
779
|
+
}
|
780
|
+
for (const id of Object.keys(this.stateManager.getSystemState())) {
|
781
|
+
if ((0, mixin_cycle_1.hasMixinCycle)(this, id)) {
|
782
|
+
console.warn(`initialize: ${id} has a mixin cycle. Clearing mixins.`);
|
783
|
+
const pluginDevice = this.findPluginDeviceById(id);
|
784
|
+
(0, state_1.setState)(pluginDevice, types_1.ScryptedInterfaceProperty.mixins, []);
|
785
|
+
}
|
786
|
+
}
|
787
|
+
const plugins = [];
|
788
|
+
for await (const plugin of this.datastore.getAll(db_types_1.Plugin)) {
|
789
|
+
plugins.push(plugin);
|
790
|
+
}
|
791
|
+
for (const plugin of plugins) {
|
792
|
+
try {
|
793
|
+
const pluginDevice = this.findPluginDevice(plugin._id);
|
794
|
+
(0, state_1.setState)(pluginDevice, types_1.ScryptedInterfaceProperty.info, {
|
795
|
+
manufacturer: plugin.packageJson.name,
|
796
|
+
version: plugin.packageJson.version,
|
797
|
+
});
|
798
|
+
this.loadPlugin(plugin);
|
799
|
+
}
|
800
|
+
catch (e) {
|
801
|
+
console.error('error loading plugin', plugin._id, e);
|
802
|
+
}
|
803
|
+
}
|
804
|
+
for (const plugin of plugins) {
|
805
|
+
try {
|
806
|
+
this.probePluginDevices(plugin);
|
807
|
+
}
|
808
|
+
catch (e) {
|
809
|
+
console.error('error probing plugin devices', plugin._id, e);
|
810
|
+
}
|
811
|
+
}
|
812
|
+
}
|
813
|
+
}
|
814
|
+
exports.ScryptedRuntime = ScryptedRuntime;
|
815
|
+
//# sourceMappingURL=runtime.js.map
|