diff --git a/sdk/rollup.nodejs.config.mjs b/sdk/rollup.nodejs.config.mjs index 476402292..eedf5be78 100644 --- a/sdk/rollup.nodejs.config.mjs +++ b/sdk/rollup.nodejs.config.mjs @@ -10,8 +10,12 @@ const config = defineConfig({ output: { sourcemap: true, file: `${process.env.NODE_ENV === 'production' ? 'dist' : 'out'}/main.nodejs.js`, - format: 'cjs', + format: 'module', + banner: `//# sourceURL=/plugin/main.nodejs.js`, }, + external: [ + 'unifi-protect', + ], plugins: [ virtual({ diff --git a/sdk/src/index.ts b/sdk/src/index.ts index 74ff211be..33f68923d 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -17,28 +17,28 @@ export class ScryptedDeviceBase extends DeviceBase { get storage() { if (!this._storage) { - this._storage = deviceManager.getDeviceStorage(this.nativeId); + this._storage = sdk.deviceManager.getDeviceStorage(this.nativeId); } return this._storage; } get log() { if (!this._log) { - this._log = deviceManager.getDeviceLogger(this.nativeId); + this._log = sdk.deviceManager.getDeviceLogger(this.nativeId); } return this._log; } get console() { if (!this._console) { - this._console = deviceManager.getDeviceConsole(this.nativeId); + this._console = sdk.deviceManager.getDeviceConsole(this.nativeId); } return this._console; } async createMediaObject(data: any, mimeType: string) { - return mediaManager.createMediaObject(data, mimeType, { + return sdk.mediaManager.createMediaObject(data, mimeType, { sourceId: this.id, }); } @@ -46,16 +46,16 @@ export class ScryptedDeviceBase extends DeviceBase { getMediaObjectConsole(mediaObject: MediaObject): Console | undefined { if (typeof mediaObject.sourceId !== 'string') return this.console; - return deviceManager.getMixinConsole(mediaObject.sourceId, this.nativeId); + return sdk.deviceManager.getMixinConsole(mediaObject.sourceId, this.nativeId); } _lazyLoadDeviceState() { if (!this._deviceState) { if (this.nativeId) { - this._deviceState = deviceManager.getDeviceState(this.nativeId); + this._deviceState = sdk.deviceManager.getDeviceState(this.nativeId); } else { - this._deviceState = deviceManager.getDeviceState(); + this._deviceState = sdk.deviceManager.getDeviceState(); } } } @@ -64,7 +64,7 @@ export class ScryptedDeviceBase extends DeviceBase { * Fire an event for this device. */ onDeviceEvent(eventInterface: string, eventData: any): Promise { - return deviceManager.onDeviceEvent(this.nativeId, eventInterface, eventData); + return sdk.deviceManager.onDeviceEvent(this.nativeId, eventInterface, eventData); } } @@ -100,14 +100,14 @@ export class MixinDeviceBase extends DeviceBase implements DeviceState { this.mixinDeviceInterfaces = options.mixinDeviceInterfaces; this.mixinStorageSuffix = options.mixinStorageSuffix; this._deviceState = options.mixinDeviceState; - this.nativeId = systemManager.getDeviceById(this.id).nativeId; + this.nativeId = sdk.systemManager.getDeviceById(this.id).nativeId; this.mixinProviderNativeId = options.mixinProviderNativeId; // RpcProxy will trap all properties, and the following check/hack will determine // if the device state came from another node worker thread. // This should ultimately be discouraged and warned at some point in the future. if ((this._deviceState as any).__rpcproxy_traps_all_properties && typeof this._deviceState.id === 'string') { - this._deviceState = deviceManager.createDeviceState(this._deviceState.id, this._deviceState.setState); + this._deviceState = sdk.deviceManager.createDeviceState(this._deviceState.id, this._deviceState.setState); } } @@ -115,24 +115,24 @@ export class MixinDeviceBase extends DeviceBase implements DeviceState { if (!this._storage) { const mixinStorageSuffix = this.mixinStorageSuffix; const mixinStorageKey = this.id + (mixinStorageSuffix ? ':' + mixinStorageSuffix : ''); - this._storage = deviceManager.getMixinStorage(mixinStorageKey, this.mixinProviderNativeId); + this._storage = sdk.deviceManager.getMixinStorage(mixinStorageKey, this.mixinProviderNativeId); } return this._storage; } get console() { if (!this._console) { - if (deviceManager.getMixinConsole) - this._console = deviceManager.getMixinConsole(this.id, this.mixinProviderNativeId); + if (sdk.deviceManager.getMixinConsole) + this._console = sdk.deviceManager.getMixinConsole(this.id, this.mixinProviderNativeId); else - this._console = deviceManager.getDeviceConsole(this.mixinProviderNativeId); + this._console = sdk.deviceManager.getDeviceConsole(this.mixinProviderNativeId); } return this._console; } async createMediaObject(data: any, mimeType: string) { - return mediaManager.createMediaObject(data, mimeType, { + return sdk.mediaManager.createMediaObject(data, mimeType, { sourceId: this.id, }); } @@ -140,14 +140,14 @@ export class MixinDeviceBase extends DeviceBase implements DeviceState { getMediaObjectConsole(mediaObject: MediaObject): Console { if (typeof mediaObject.sourceId !== 'string') return this.console; - return deviceManager.getMixinConsole(mediaObject.sourceId, this.mixinProviderNativeId); + return sdk.deviceManager.getMixinConsole(mediaObject.sourceId, this.mixinProviderNativeId); } /** * Fire an event for this device. */ onDeviceEvent(eventInterface: string, eventData: any): Promise { - return deviceManager.onMixinEvent(this.id, this, eventInterface, eventData); + return sdk.deviceManager.onMixinEvent(this.id, this, eventInterface, eventData); } _lazyLoadDeviceState() { @@ -201,15 +201,16 @@ export class MixinDeviceBase extends DeviceBase implements DeviceState { } })(); -export const sdk: ScryptedStatic = {} as any; declare const deviceManager: DeviceManager; declare const endpointManager: EndpointManager; declare const mediaManager: MediaManager; declare const systemManager: SystemManager; declare const pluginHostAPI: any; declare const pluginRuntimeAPI: any; +export const sdk: ScryptedStatic = {} as any; try { + let runtimeAPI: any; try { runtimeAPI = pluginRuntimeAPI; @@ -217,7 +218,7 @@ try { catch (e) { } - Object.assign(sdk, { + sdkInit({ log: deviceManager.getDeviceLogger(undefined), deviceManager, endpointManager, @@ -234,7 +235,29 @@ try { } } catch (e) { - console.error('sdk initialization error, import @scrypted/types or use @scrypted/client instead', e); + // console.error('sdk initialization error, import @scrypted/types or use @scrypted/client instead', e); } export default sdk; + +export function sdkInit(sdkInit: { + deviceManager: DeviceManager, + endpointManager: EndpointManager, + mediaManager: MediaManager, + systemManager: SystemManager, + pluginHostAPI: any, + pluginRuntimeAPI: any, +}) { + + const { deviceManager, endpointManager, mediaManager, systemManager, pluginHostAPI, pluginRuntimeAPI } = sdkInit; + + Object.assign(sdk, { + log: deviceManager.getDeviceLogger(undefined), + deviceManager, + endpointManager, + mediaManager, + systemManager, + pluginHostAPI, + ...pluginRuntimeAPI, + }); +} diff --git a/server/.vscode/launch.json b/server/.vscode/launch.json index 0ac9cd418..a9ef7cde7 100644 --- a/server/.vscode/launch.json +++ b/server/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "autoAttachChildProcesses": false, + "autoAttachChildProcesses": true, "type": "node", "request": "launch", "name": "Launch Program", diff --git a/server/es-eval.js b/server/es-eval.js new file mode 100644 index 000000000..6caad6137 --- /dev/null +++ b/server/es-eval.js @@ -0,0 +1,5 @@ +export async function eseval(script) { + // const dataUrl = `data:text/javascript,${encodeURIComponent(script)}`; + const module = await import(script); + return module; +} diff --git a/server/src/plugin/plugin-remote-worker.ts b/server/src/plugin/plugin-remote-worker.ts index 767a260ec..cb8331f6d 100644 --- a/server/src/plugin/plugin-remote-worker.ts +++ b/server/src/plugin/plugin-remote-worker.ts @@ -24,6 +24,8 @@ import { NodeThreadWorker } from './runtime/node-thread-worker'; import { prepareZip } from './runtime/node-worker-common'; import { getBuiltinRuntimeHosts } from './runtime/runtime-host'; import { RuntimeWorker, RuntimeWorkerOptions } from './runtime/runtime-worker'; +// @ts-expect-error +import { eseval } from '../../es-eval.js'; const serverVersion = require('../../package.json').version; @@ -360,9 +362,17 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe try { const filename = zipOptions?.debug ? pluginMainNodeJs : pluginIdMainNodeJs; - evalLocal(peer, script, startPluginRemoteOptions?.sourceURL?.(filename) || filename, params); + if (packageJson.type === 'module') { + const p = path.join(unzippedPath, mainNodejs); + const module = await eseval(p); + params.module.exports = module; + } + else { + evalLocal(peer, script, startPluginRemoteOptions?.sourceURL?.(filename) || filename, params); + } const exports = params.module.exports; + exports.sdkInit?.(params); if (zipOptions?.fork) { // pluginConsole?.log('plugin forked'); const fork = exports.fork; diff --git a/server/tsconfig.json b/server/tsconfig.json index 0db484578..36a9dd02b 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -9,9 +9,9 @@ // skip error: Interface 'WebGL2RenderingContext' incorrectly extends interface 'WebGL2RenderingContextBase'. // https://github.com/tensorflow/tfjs/issues/4201 "skipLibCheck": true, - "sourceMap": true + "sourceMap": true, }, "include": [ - "src/**/*" + "src/**/*", "es-eval.js", ], } \ No newline at end of file