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
This commit is contained in:
Koushik Dutta
2026-04-02 14:57:14 -07:00
parent 34a9e698ae
commit 2bd8354ead
6 changed files with 47 additions and 47 deletions

View File

@@ -7,7 +7,7 @@ import { SystemManagerImpl } from './system';
class DeviceLogger implements Logger {
nativeId: ScryptedNativeId;
api: PluginAPI;
logger: Promise<PluginLogger>;
logger!: Promise<PluginLogger>;
constructor(api: PluginAPI, nativeId: ScryptedNativeId, public console: any) {
this.api = api;
@@ -65,12 +65,12 @@ export class DeviceStateProxyHandler implements ProxyHandler<any> {
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<ScryptedNativeId, DeviceManagerDevice>();
deviceStorage = new Map<ScryptedNativeId, StorageImpl>();
mixinStorage = new Map<ScryptedNativeId, Map<string, StorageImpl>>();
@@ -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];

View File

@@ -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)

View File

@@ -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();

View File

@@ -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<typeof getPropertyInterfaces>;
typesVersion!: string;
descriptors!: { [scryptedInterface: string]: ScryptedInterfaceDescriptor };
propertyInterfaces!: ReturnType<typeof getPropertyInterfaces>;
[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<T>(id: string): Promise<T & ScryptedDevice> {

View File

@@ -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<PluginRemote>, public remoteReadyPromise: Promise<PluginRemote>) {
this.remoteReadyPromise = (async () => {

View File

@@ -10,8 +10,8 @@ function newDeviceProxy(id: string, systemManager: SystemManagerImpl) {
}
class DeviceProxyHandler implements PrimitiveProxyHandler<any> {
customProperties: Map<string | number | symbol, any>;
device: Promise<ScryptedDevice>;
customProperties!: Map<string | number | symbol, any>;
device!: Promise<ScryptedDevice | undefined>;
constructor(public id: string, public systemManager: SystemManagerImpl) {
}
@@ -20,14 +20,14 @@ class DeviceProxyHandler implements PrimitiveProxyHandler<any> {
}
ownKeys(target: any): ArrayLike<string | symbol> {
const interfaces = new Set<string>(this.systemManager.state[this.id].interfaces.value);
const interfaces = new Set<string>(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<string>(this.systemManager.state[this.id].interfaces.value);
const interfaces = new Set<string>(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<any> {
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<any> {
if (handled)
return handled;
const interfaces = new Set<string>(this.systemManager.state[this.id].interfaces?.value || []);
const interfaces = new Set<string>(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<any> {
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<any> {
class EventListenerRegisterImpl implements EventListenerRegister {
promise: Promise<EventListenerRegister>;
promise: Promise<EventListenerRegister> | undefined;
constructor(promise: Promise<EventListenerRegister>) {
this.promise = promise;
}
@@ -152,21 +152,21 @@ class EventListenerRegisterImpl implements EventListenerRegister {
function makeOneWayCallback<T>(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<typeof getPropertyInterfaces>;
typesVersion!: string;
descriptors!: { [scryptedInterface: string]: ScryptedInterfaceDescriptor };
propertyInterfaces!: ReturnType<typeof getPropertyInterfaces>;
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);
}
}