From 2bd8354ead4f99702f0dd6e4efdad68da7f17b16 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Thu, 2 Apr 2026 14:57:14 -0700 Subject: [PATCH] plugin: add type assertions for strictNullChecks in plugin core modules Fix strictNullChecks: - device.ts: add assertions for storage and nativeIds access - endpoint.ts: add assertions for device and handler access - plugin-api.ts: add definite assignment for callback properties - plugin-host-api.ts: add assertions for findPluginDevice results, consolidate plugin assertions at declarations - plugin-lazy-remote.ts: add assertion for getFile result - system.ts: add definite assignment for manager properties, add assertions for state access --- server/src/plugin/device.ts | 24 +++++++------- server/src/plugin/endpoint.ts | 8 ++--- server/src/plugin/plugin-api.ts | 2 +- server/src/plugin/plugin-host-api.ts | 14 ++++---- server/src/plugin/plugin-lazy-remote.ts | 2 +- server/src/plugin/system.ts | 44 ++++++++++++------------- 6 files changed, 47 insertions(+), 47 deletions(-) diff --git a/server/src/plugin/device.ts b/server/src/plugin/device.ts index 76cc9ae3d..baddb5de4 100644 --- a/server/src/plugin/device.ts +++ b/server/src/plugin/device.ts @@ -7,7 +7,7 @@ import { SystemManagerImpl } from './system'; class DeviceLogger implements Logger { nativeId: ScryptedNativeId; api: PluginAPI; - logger: Promise; + logger!: Promise; constructor(api: PluginAPI, nativeId: ScryptedNativeId, public console: any) { this.api = api; @@ -65,12 +65,12 @@ export class DeviceStateProxyHandler implements ProxyHandler { return { id: this.id } if (p === 'setState') return this.setState; - return this.deviceManager.systemManager.state[this.id][p as string]?.value; + return this.deviceManager.systemManager.state[this.id]![p as string]?.value; } set?(target: any, p: PropertyKey, value: any, receiver: any) { checkProperty(p.toString(), value); - this.deviceManager.systemManager.state[this.id][p as string] = { + this.deviceManager.systemManager.state[this.id]![p as string] = { value, }; this.setState(p.toString(), value); @@ -84,7 +84,7 @@ interface DeviceManagerDevice { } export class DeviceManagerImpl implements DeviceManager { - api: PluginAPI; + api!: PluginAPI; nativeIds = new Map(); deviceStorage = new Map(); mixinStorage = new Map>(); @@ -103,7 +103,7 @@ export class DeviceManagerImpl implements DeviceManager { } getDeviceState(nativeId?: any): DeviceState { - const handler = new DeviceStateProxyHandler(this, this.nativeIds.get(nativeId).id, + const handler = new DeviceStateProxyHandler(this, this.nativeIds.get(nativeId)!.id, (property, value) => this.api.setState(nativeId, property, value)); return new Proxy(handler, handler); } @@ -136,7 +136,7 @@ export class DeviceManagerImpl implements DeviceManager { } pruneMixinStorage() { for (const nativeId of this.nativeIds.keys()) { - const storage = this.nativeIds.get(nativeId).storage; + const storage = this.nativeIds.get(nativeId)!.storage; for (const key of Object.keys(storage)) { if (!key.startsWith('mixin:')) continue; @@ -219,20 +219,20 @@ export class StorageImpl implements Storage { } get storage(): { [key: string]: any } { - return this.deviceManager.nativeIds.get(this.nativeId).storage; + return this.deviceManager.nativeIds.get(this.nativeId)!.storage; } get length(): number { - return Object.keys(this.storage).filter(key => key.startsWith(this.prefix)).length; + return Object.keys(this.storage).filter(key => key.startsWith(this.prefix!)).length; } clear(): void { if (!this.prefix) { - this.deviceManager.nativeIds.get(this.nativeId).storage = {}; + this.deviceManager.nativeIds.get(this.nativeId)!.storage = {}; } else { const storage = this.storage; - Object.keys(this.storage).filter(key => key.startsWith(this.prefix)).forEach(key => delete storage[key]); + Object.keys(this.storage).filter(key => key.startsWith(this.prefix!)).forEach(key => delete storage[key]); } this.api.setStorage(this.nativeId, this.storage); } @@ -242,9 +242,9 @@ export class StorageImpl implements Storage { } key(index: number): string { if (!this.prefix) { - return Object.keys(this.storage)[index]; + return Object.keys(this.storage)[index]!; } - return Object.keys(this.storage).filter(key => key.startsWith(this.prefix))[index].substring(this.prefix.length); + return Object.keys(this.storage).filter(key => key.startsWith(this.prefix!))[index]!.substring(this.prefix!.length); } removeItem(key: string): void { delete this.storage[this.prefix + key]; diff --git a/server/src/plugin/endpoint.ts b/server/src/plugin/endpoint.ts index 9ecc3f1ae..8d6328464 100644 --- a/server/src/plugin/endpoint.ts +++ b/server/src/plugin/endpoint.ts @@ -3,10 +3,10 @@ import type { DeviceManagerImpl } from "./device"; import type { PluginAPI } from "./plugin-api"; export class EndpointManagerImpl implements EndpointManager { - deviceManager: DeviceManagerImpl; - api: PluginAPI; - pluginId: string; - mediaManager: MediaManager; + deviceManager!: DeviceManagerImpl; + api!: PluginAPI; + pluginId!: string; + mediaManager!: MediaManager; getEndpoint(nativeId?: ScryptedNativeId) { if (!nativeId) diff --git a/server/src/plugin/plugin-api.ts b/server/src/plugin/plugin-api.ts index 49154856d..0f19d5eef 100644 --- a/server/src/plugin/plugin-api.ts +++ b/server/src/plugin/plugin-api.ts @@ -69,7 +69,7 @@ export class PluginAPIManagedListeners { } export class PluginAPIProxy extends PluginAPIManagedListeners implements PluginAPI { - acl: AccessControls; + acl!: AccessControls; constructor(public api: PluginAPI, public mediaManager?: MediaManager) { super(); diff --git a/server/src/plugin/plugin-host-api.ts b/server/src/plugin/plugin-host-api.ts index 57c2233f5..c2305fadd 100644 --- a/server/src/plugin/plugin-host-api.ts +++ b/server/src/plugin/plugin-host-api.ts @@ -12,9 +12,9 @@ import { checkProperty } from './plugin-state-check'; export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAPI { pluginId: string; - typesVersion: string; - descriptors: { [scryptedInterface: string]: ScryptedInterfaceDescriptor }; - propertyInterfaces: ReturnType; + typesVersion!: string; + descriptors!: { [scryptedInterface: string]: ScryptedInterfaceDescriptor }; + propertyInterfaces!: ReturnType; [RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS] = [ 'onMixinEvent', @@ -131,7 +131,7 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP } async setStorage(nativeId: ScryptedNativeId, storage: { [key: string]: string }) { - const device = this.scrypted.findPluginDevice(this.pluginId, nativeId) + const device = this.scrypted.findPluginDevice(this.pluginId, nativeId)!; device.storage = storage; this.scrypted.datastore.upsert(device); } @@ -168,12 +168,12 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP } async onDeviceRemoved(nativeId: string) { - await this.scrypted.removeDevice(this.scrypted.findPluginDevice(this.pluginId, nativeId)) + await this.scrypted.removeDevice(this.scrypted.findPluginDevice(this.pluginId, nativeId)!) } async onDeviceEvent(nativeId: any, eventInterface: any, eventData?: any) { - const plugin = this.scrypted.findPluginDevice(this.pluginId, nativeId); - this.scrypted.stateManager.notifyInterfaceEventFromMixin(plugin!, eventInterface, eventData, plugin!._id); + const plugin = this.scrypted.findPluginDevice(this.pluginId, nativeId)!; + this.scrypted.stateManager.notifyInterfaceEventFromMixin(plugin, eventInterface, eventData, plugin._id); } async getDeviceById(id: string): Promise { diff --git a/server/src/plugin/plugin-lazy-remote.ts b/server/src/plugin/plugin-lazy-remote.ts index ca890d6b1..4e6662f90 100644 --- a/server/src/plugin/plugin-lazy-remote.ts +++ b/server/src/plugin/plugin-lazy-remote.ts @@ -8,7 +8,7 @@ import { PluginRemote, PluginRemoteLoadZipOptions, PluginZipAPI } from './plugin * execution order of state reporting may fail. */ export class LazyRemote implements PluginRemote { - remote: PluginRemote; + remote!: PluginRemote; constructor(public remotePromise: Promise, public remoteReadyPromise: Promise) { this.remoteReadyPromise = (async () => { diff --git a/server/src/plugin/system.ts b/server/src/plugin/system.ts index e5b78cf39..8bb809f4a 100644 --- a/server/src/plugin/system.ts +++ b/server/src/plugin/system.ts @@ -10,8 +10,8 @@ function newDeviceProxy(id: string, systemManager: SystemManagerImpl) { } class DeviceProxyHandler implements PrimitiveProxyHandler { - customProperties: Map; - device: Promise; + customProperties!: Map; + device!: Promise; constructor(public id: string, public systemManager: SystemManagerImpl) { } @@ -20,14 +20,14 @@ class DeviceProxyHandler implements PrimitiveProxyHandler { } ownKeys(target: any): ArrayLike { - const interfaces = new Set(this.systemManager.state[this.id].interfaces.value); + const interfaces = new Set(this.systemManager.state[this.id]!.interfaces!.value); const methods = getInterfaceMethods(this.systemManager.descriptors || ScryptedInterfaceDescriptors, interfaces); const properties = getInterfaceProperties(this.systemManager.descriptors || ScryptedInterfaceDescriptors, interfaces); return [...methods, ...properties]; } getOwnPropertyDescriptor(target: any, p: string | symbol): PropertyDescriptor | undefined { - const interfaces = new Set(this.systemManager.state[this.id].interfaces.value); + const interfaces = new Set(this.systemManager.state[this.id]!.interfaces!.value); const methods = getInterfaceMethods(this.systemManager.descriptors || ScryptedInterfaceDescriptors, interfaces); const prop = p.toString(); if (methods.includes(prop)) { @@ -39,7 +39,7 @@ class DeviceProxyHandler implements PrimitiveProxyHandler { if (properties.includes(prop)) { return { configurable: true, - value: this.systemManager.state[this.id][prop]?.value + value: this.systemManager.state[this.id]![prop]?.value } } return undefined; @@ -77,19 +77,19 @@ class DeviceProxyHandler implements PrimitiveProxyHandler { if (handled) return handled; - const interfaces = new Set(this.systemManager.state[this.id].interfaces?.value || []); + const interfaces = new Set(this.systemManager.state[this.id]!.interfaces?.value || []); const prop = p.toString(); const isValidProperty = this.systemManager.propertyInterfaces?.[prop] || propertyInterfaces[prop]; // this will also return old properties that should not exist on a device. ie, a disabled mixin provider. // should this change? if (isValidProperty) - return (this.systemManager.state[this.id] as any)?.[p]?.value; + return (this.systemManager.state[this.id]! as any)?.[p]?.value; if (!isValidInterfaceMethod(this.systemManager.descriptors || ScryptedInterfaceDescriptors, interfaces, prop)) return; - if (ScryptedInterfaceDescriptors[ScryptedInterface.ScryptedDevice].methods.includes(prop)) + if (ScryptedInterfaceDescriptors[ScryptedInterface.ScryptedDevice]!.methods.includes(prop)) return (this as any)[p].bind(this); return new Proxy(() => p, this); @@ -98,7 +98,7 @@ class DeviceProxyHandler implements PrimitiveProxyHandler { ensureDevice() { if (!this.device) this.device = this.systemManager.api.getDeviceById(this.id); - return this.device; + return this.device!; } async apply(target: any, thisArg: any, argArray?: any) { @@ -133,7 +133,7 @@ class DeviceProxyHandler implements PrimitiveProxyHandler { class EventListenerRegisterImpl implements EventListenerRegister { - promise: Promise; + promise: Promise | undefined; constructor(promise: Promise) { this.promise = promise; } @@ -152,21 +152,21 @@ class EventListenerRegisterImpl implements EventListenerRegister { function makeOneWayCallback(input: T): T { const f: any = input; const oneways: string[] = f[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS] || []; - if (!oneways.includes(null)) - oneways.push(null); + if (!oneways.includes(null!)) + oneways.push(null!); f[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS] = oneways; return input; } export class SystemManagerImpl implements SystemManager { - api: PluginAPI; - state: { [id: string]: { [property: string]: SystemDeviceState } }; + api!: PluginAPI; + state!: { [id: string]: { [property: string]: SystemDeviceState } }; deviceProxies: { [id: string]: ScryptedDevice } = {}; - log: Logger; + log!: Logger; events = new EventRegistry(); - typesVersion: string; - descriptors: { [scryptedInterface: string]: ScryptedInterfaceDescriptor }; - propertyInterfaces: ReturnType; + typesVersion!: string; + descriptors!: { [scryptedInterface: string]: ScryptedInterfaceDescriptor }; + propertyInterfaces!: ReturnType; getDeviceState(id: string) { return this.state[id]; @@ -197,9 +197,9 @@ export class SystemManagerImpl implements SystemManager { } } } - if (!id) + if (!id!) return; - let proxy = this.deviceProxies[id]; + let proxy = this.deviceProxies[id!]; if (!proxy) proxy = this.deviceProxies[id] = newDeviceProxy(id, this); return proxy; @@ -207,10 +207,10 @@ export class SystemManagerImpl implements SystemManager { getDeviceByName(name: string): any { for (const id of Object.keys(this.state)) { - const s = this.state[id]; + const s = this.state[id]!; if ((s.interfaces?.value as string[])?.includes(ScryptedInterface.ScryptedPlugin) && s.pluginId?.value === name) return this.getDeviceById(id); - if (s.name.value === name) + if (s.name!.value === name) return this.getDeviceById(id); } }