plugin: add type assertions for strictNullChecks in plugin-device and remote modules

Fix strictNullChecks:
- plugin-device.ts: consolidate entry/host assertions at declarations,
  use undefined! for proxy values, add definite assignment for mixinTable
- plugin-remote.ts: add assertions for callbacks and nativeIds access
- plugin-remote-worker.ts: fix clusterWorkerId as Promise<string | undefined>,
  add assertions for worker and options properties
This commit is contained in:
Koushik Dutta
2026-04-02 14:54:40 -07:00
parent 01aab01e46
commit 34a9e698ae
3 changed files with 50 additions and 48 deletions

View File

@@ -31,7 +31,7 @@ export const QueryInterfaceSymbol = Symbol("ScryptedPluginDeviceQueryInterface")
export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
scrypted: ScryptedRuntime;
id: string;
mixinTable: MixinTable[];
mixinTable!: MixinTable[];
releasing = new Set<any>();
static sortInterfaces(interfaces: string[]): string[] {
@@ -65,8 +65,8 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
async getMixinProviderId(id: string, mixinDevice: any) {
if (this.releasing.has(mixinDevice))
return true;
await this.scrypted.devices[id].handler.ensureProxy();
for (const mixin of this.scrypted.devices[id].handler.mixinTable) {
await this.scrypted.devices[id]!.handler.ensureProxy();
for (const mixin of this.scrypted.devices[id]!.handler.mixinTable) {
const { proxy } = await mixin.entry;
if (proxy === mixinDevice) {
return mixin.mixinProviderId || id;
@@ -78,7 +78,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
// should this be async?
invalidate() {
const mixinTable = this.mixinTable;
this.mixinTable = undefined;
this.mixinTable = undefined!;
for (const mixinEntry of (mixinTable || [])) {
this.invalidateEntry(mixinEntry);
}
@@ -102,7 +102,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
const mixins = getState(pluginDevice, ScryptedInterfaceProperty.mixins) || [];
// iterate the new mixin table to find the last good mixin,
// and resume creation from there.
let lastValidMixinId: string;
let lastValidMixinId: string | undefined;
for (const mixinId of mixins) {
if (!previousMixinIds.length) {
// reached of the previous mixin table, meaning
@@ -121,7 +121,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
// invalidate and remove everything up to lastValidMixinId
while (true) {
const entry = this.mixinTable[0];
const entry = this.mixinTable[0]!;
if (entry.mixinProviderId === lastValidMixinId)
break;
this.mixinTable.shift();
@@ -149,7 +149,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
try {
if (!pluginDevice.nativeId) {
const plugin = this.scrypted.plugins[pluginDevice.pluginId];
proxy = await plugin.module;
proxy = await plugin!.module;
}
else {
const providerId = getState(pluginDevice, ScryptedInterfaceProperty.providerId);
@@ -174,7 +174,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
})();
this.mixinTable.unshift({
mixinProviderId: undefined,
mixinProviderId: undefined!,
entry,
});
}
@@ -202,7 +202,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
});
}
return this.mixinTable[0].entry.then(entry => {
return this.mixinTable[0]!.entry.then(entry => {
if (entry.error) {
console.error('Mixin device creation completed with error. Merging with previous interface set to retain device descriptor.');
const previousInterfaces = getState(pluginDevice, ScryptedInterfaceProperty.interfaces) as string[] || [];
@@ -219,7 +219,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
async rebuildEntry(pluginDevice: PluginDevice, mixinId: string, wrappedMixinTablePromise: Promise<MixinTable[]>): Promise<MixinTableEntry> {
const wrappedMixinTable = await wrappedMixinTablePromise;
const previousEntry = wrappedMixinTable[0].entry;
const previousEntry = wrappedMixinTable[0]!.entry;
const type = getDisplayType(pluginDevice);
@@ -255,7 +255,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
passthrough: true,
allInterfaces,
interfaces: new Set<string>(),
proxy: undefined as any,
proxy: undefined!,
};
}
@@ -269,7 +269,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
const wrappedProxy = new Proxy(wrappedHandler, wrappedHandler);
const implementer = await (mixinProvider as any)[QueryInterfaceSymbol](ScryptedInterface.MixinProvider);
const host = this.scrypted.getPluginHostForDeviceId(implementer);
const host = this.scrypted.getPluginHostForDeviceId(implementer)!;
const propertyInterfaces = getPropertyInterfaces(host.api.descriptors || ScryptedInterfaceDescriptors);
// todo: remove this and pass the setter directly.
const deviceState = await host.remote.createDeviceState(this.id,
@@ -304,8 +304,8 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
passthrough: false,
allInterfaces,
interfaces: new Set<string>(),
error: e,
proxy: undefined as any,
error: e as Error,
proxy: undefined!,
};
}
}
@@ -328,7 +328,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
if (p === RefreshSymbol || p === QueryInterfaceSymbol)
return new Proxy(() => p, this);
if (ScryptedInterfaceDescriptors[ScryptedInterface.ScryptedDevice].methods.includes(prop))
if (ScryptedInterfaceDescriptors[ScryptedInterface.ScryptedDevice]!.methods.includes(prop))
return (this as any)[p].bind(this);
return new Proxy(() => prop, this);
@@ -445,7 +445,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
if (method === 'getReadmeMarkdown') {
const pluginDevice = this.scrypted.findPluginDeviceById(this.id);
if (pluginDevice && !pluginDevice.nativeId) {
const plugin = this.scrypted.plugins[pluginDevice.pluginId];
const plugin = this.scrypted.plugins[pluginDevice.pluginId]!;
if (!plugin.packageJson.scrypted.interfaces.includes(ScryptedInterface.Readme)) {
const readmePath = path.join(plugin.unzippedPath, 'README.md');
if (fs.existsSync(readmePath)) {

View File

@@ -85,7 +85,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS] = (_api as any)[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS];
override setStorage(nativeId: string, storage: { [key: string]: any; }): Promise<void> {
const id = deviceManager.nativeIds.get(nativeId).id;
const id = deviceManager.nativeIds.get(nativeId)!.id;
for (const r of forks) {
r.setNativeId(nativeId, id, storage);
}
@@ -104,7 +104,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
if (name === 'repl') {
if (!replPort)
throw new Error('REPL unavailable: Plugin not loaded.')
return [await replPort, process.env.SCRYPTED_CLUSTER_ADDRESS];
return [await replPort, process.env.SCRYPTED_CLUSTER_ADDRESS!] as [number, string];
}
throw new Error(`unknown service ${name}`);
},
@@ -183,11 +183,11 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
process.on('uncaughtException', e => {
getPluginConsole().error('uncaughtException', e);
scrypted.log.e('uncaughtException ' + (e.stack || e?.toString()));
scrypted.log!.e('uncaughtException ' + (e.stack || e?.toString()));
});
process.on('unhandledRejection', e => {
getPluginConsole().error('unhandledRejection', e);
scrypted.log.e('unhandledRejection ' + ((e as Error).stack || e?.toString()));
scrypted.log!.e('unhandledRejection ' + ((e as Error).stack || e?.toString()));
});
installSourceMapSupport({
@@ -209,10 +209,10 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
await installOptionalDependencies(getPluginConsole(), packageJson);
const main = await pluginReader(mainNodejs);
const script = main.toString();
const script = main!.toString();
scrypted.connect = (socket, options) => {
process.send(options, socket);
process.send!(options, socket);
}
const pluginRemoteAPI: PluginRemote = scrypted.pluginRemoteAPI;
@@ -221,12 +221,12 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
let forkPeer: Promise<RpcPeer>;
let runtimeWorker: RuntimeWorker;
let nativeWorker: child_process.ChildProcess | worker_threads.Worker;
let clusterWorkerId: Promise<string>;
let clusterWorkerId: Promise<string> | undefined;
const runtimeWorkerOptions: RuntimeWorkerOptions = {
packageJson,
env: undefined,
pluginDebug: undefined,
pluginDebug: undefined!,
zipFile,
unzippedPath,
zipHash,
@@ -234,13 +234,15 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
// if running in a cluster, fork to a matching cluster worker only if necessary.
if (utilizesClusterForkWorker(options)) {
({ runtimeWorker, forkPeer, clusterWorkerId } = createClusterForkWorker(
const result = createClusterForkWorker(
runtimeWorkerOptions,
options,
options!,
api.getComponent('cluster-fork'),
() => zipAPI.getZip(),
scrypted.connectRPCObject)
);
scrypted.connectRPCObject);
runtimeWorker = result.runtimeWorker;
forkPeer = result.forkPeer;
clusterWorkerId = result.clusterWorkerId as Promise<string>;
}
else {
if (options?.runtime) {
@@ -248,10 +250,10 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
const runtime = builtins.get(options.runtime);
if (!runtime)
throw new Error('unknown runtime ' + options.runtime);
runtimeWorker = runtime(mainFilename, runtimeWorkerOptions, undefined);
runtimeWorker = runtime(mainFilename, runtimeWorkerOptions, undefined!);
if (runtimeWorker instanceof ChildProcessWorker) {
nativeWorker = runtimeWorker.childProcess;
nativeWorker = runtimeWorker.childProcess!;
}
}
else {
@@ -261,7 +263,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
const ntw = new NodeThreadWorker(mainFilename, pluginId, {
packageJson,
env: undefined,
pluginDebug: undefined,
pluginDebug: undefined!,
zipFile,
unzippedPath,
zipHash,
@@ -317,7 +319,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS] = (api as any)[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS];
override setStorage(nativeId: string, storage: { [key: string]: any; }): Promise<void> {
const id = deviceManager.nativeIds.get(nativeId).id;
const id = deviceManager.nativeIds.get(nativeId)!.id;
pluginRemoteAPI.setNativeId(nativeId, id, storage);
for (const r of forks) {
if (r === remote)
@@ -361,7 +363,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
removeListener(event, listener) {
return runtimeWorker.removeListener(event as any, listener);
},
nativeWorker,
nativeWorker: nativeWorker!,
};
return {
[Symbol.dispose]() {
@@ -376,7 +378,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
try {
const isModule = packageJson.type === 'module';
const filename = zipOptions?.debug ? pluginMainNodeJs : pluginIdMainNodeJs;
const sdkVersion = await pluginReader('sdk.json').then(b => JSON.parse(b.toString()).version).catch(() => { });
const sdkVersion = await pluginReader('sdk.json').then(b => JSON.parse(b!.toString()).version).catch(() => { });
const mainNodeJsOnFilesystem = path.join(unzippedPath, mainNodejs);
if (sdkVersion) {
// todo: remove this, only existed in prerelease versions

View File

@@ -51,7 +51,7 @@ export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId:
delete state[id];
continue;
}
state[id] = getAccessControlDeviceState(id, state[id]);
state[id] = getAccessControlDeviceState(id, state[id]!)!;
}
}
@@ -67,11 +67,11 @@ export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId:
if (eventDetails.eventInterface === ScryptedInterface.ScryptedDevice) {
if (eventDetails.property === ScryptedInterfaceProperty.id) {
// a change on the id property means device was deleted
remote.updateDeviceState(eventData, undefined);
remote.updateDeviceState(eventData, undefined!);
}
else {
// a change on anything else is a descriptor update
remote.updateDeviceState(id, getAccessControlDeviceState(id));
remote.updateDeviceState(id, getAccessControlDeviceState(id)!);
}
return;
}
@@ -127,7 +127,7 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
ioSockets[id] = callbacks;
callbacks.connect(undefined, {
callbacks.connect(undefined!, {
close: (message) => connection.close(message),
send: (message) => connection.send(message),
});
@@ -140,14 +140,14 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
api = await options?.onGetRemote?.(api, pluginId) || api;
const systemManager = new SystemManagerImpl();
const deviceManager = new DeviceManagerImpl(systemManager, getDeviceConsole, getMixinConsole);
const deviceManager = new DeviceManagerImpl(systemManager, getDeviceConsole!, getMixinConsole!);
const endpointManager = new EndpointManagerImpl();
const clusterManager = new ClusterManagerImpl(undefined, api, undefined);
const clusterManager = new ClusterManagerImpl(undefined, api, undefined!);
const hostMediaManager = await api.getMediaManager();
if (!hostMediaManager) {
peer.params['createMediaManager'] = async () => createMediaManager(systemManager, deviceManager);
peer.params['createMediaManager'] = async () => createMediaManager!(systemManager, deviceManager);
}
const mediaManager = hostMediaManager || await createMediaManager(systemManager, deviceManager);
const mediaManager = hostMediaManager || await createMediaManager!(systemManager, deviceManager);
peer.params['mediaManager'] = mediaManager;
systemManager.api = api;
@@ -163,11 +163,11 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
clusterManager,
log,
pluginHostAPI: api,
pluginRemoteAPI: undefined,
pluginRemoteAPI: undefined!,
serverVersion: hostInfo?.serverVersion,
connect: undefined,
fork: undefined,
connectRPCObject: undefined,
connect: undefined!,
fork: undefined!,
connectRPCObject: undefined!,
};
delete peer.params.getRemote;
@@ -188,7 +188,7 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
'ioEvent',
'setNativeId',
],
getServicePort,
getServicePort: getServicePort!,
async createDeviceState(id: string, setState: (property: string, value: any) => Promise<void>) {
return deviceManager.createDeviceState(id, setState);
},
@@ -300,7 +300,7 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
params.pluginRuntimeAPI = ret;
try {
return await options.onLoadZip(ret, params, packageJson, zipAPI, zipOptions);
return await options!.onLoadZip!(ret, params, packageJson, zipAPI, zipOptions!);
}
catch (e) {
console.error('plugin start/fork failed', e)