plugin: add type assertions for strictNullChecks in plugin-host and media

Fix strictNullChecks:
- plugin-host.ts: consolidate logger assertion at declaration,
  extract handler variable to avoid repeated assertions,
  use undefined! for clusterWorkerId, add definite assignment
- media.ts: add assertions for converter and mediaObject access
This commit is contained in:
Koushik Dutta
2026-04-02 14:51:35 -07:00
parent 61d9345bf6
commit 01aab01e46
2 changed files with 41 additions and 40 deletions

View File

@@ -348,7 +348,7 @@ export abstract class MediaManagerBase implements MediaManager {
fromMimeType: mediaObject.mimeType,
toMimeType,
convert: async (data, fromMimeType, toMimeType) => {
return mediaObject.convert(toMimeType);
return mediaObject.convert!(toMimeType);
}
});
@@ -366,10 +366,10 @@ export abstract class MediaManagerBase implements MediaManager {
// connect the intrinsic converter to other converters
for (const candidate of converters) {
try {
const candidateMime = new MimeType(candidate.fromMimeType);
const candidateMime = new MimeType(candidate.fromMimeType!);
if (!mimeMatches(convertedMime, candidateMime))
continue;
const outputWeight = parseFloat(candidateMime.parameters.get('converter-weight')) || (candidateMime.essence === '*/*' ? 1000 : 1);
const outputWeight = parseFloat(candidateMime.parameters.get('converter-weight')!) || (candidateMime.essence === '*/*' ? 1000 : 1);
const candidateId = candidate.id;
node[candidateId] = outputWeight;
}
@@ -381,10 +381,10 @@ export abstract class MediaManagerBase implements MediaManager {
for (const converter of converters) {
try {
const inputMime = new MimeType(converter.fromMimeType);
const convertedMime = new MimeType(converter.toMimeType);
const inputMime = new MimeType(converter.fromMimeType!);
const convertedMime = new MimeType(converter.toMimeType!);
// catch all converters should be heavily weighted so as not to use them.
const inputWeight = parseFloat(inputMime.parameters.get('converter-weight')) || (inputMime.essence === '*/*' ? 1000 : 1);
const inputWeight = parseFloat(inputMime.parameters.get('converter-weight')!) || (inputMime.essence === '*/*' ? 1000 : 1);
// const convertedWeight = parseFloat(convertedMime.parameters.get('converter-weight')) || (convertedMime.essence === ScryptedMimeTypes.MediaObject ? 1000 : 1);
// const conversionWeight = inputWeight + convertedWeight;
const targetId = converter.id;
@@ -393,10 +393,10 @@ export abstract class MediaManagerBase implements MediaManager {
// edge matches
for (const candidate of converters) {
try {
const candidateMime = new MimeType(candidate.fromMimeType);
const candidateMime = new MimeType(candidate.fromMimeType!);
if (!mimeMatches(convertedMime, candidateMime))
continue;
const outputWeight = parseFloat(candidateMime.parameters.get('converter-weight')) || (candidateMime.essence === '*/*' ? 1000 : 1);
const outputWeight = parseFloat(candidateMime.parameters.get('converter-weight')!) || (candidateMime.essence === '*/*' ? 1000 : 1);
const candidateId = candidate.id;
node[candidateId] = inputWeight + outputWeight;
}
@@ -436,23 +436,23 @@ export abstract class MediaManagerBase implements MediaManager {
let valueMime = new MimeType(mediaObject.mimeType);
while (route.length) {
const node = route.shift();
const converter = converterMap.get(node);
const converterToMimeType = new MimeType(converter.toMimeType);
const converterFromMimeType = new MimeType(converter.fromMimeType);
const node = route.shift()!;
const converter = converterMap.get(node)!;
const converterToMimeType = new MimeType(converter.toMimeType!);
const converterFromMimeType = new MimeType(converter.fromMimeType!);
const type = converterToMimeType.type === '*' ? valueMime.type : converterToMimeType.type;
const subtype = converterToMimeType.subtype === '*' ? valueMime.subtype : converterToMimeType.subtype;
let targetMimeType = `${type}/${subtype}`;
if (!route.length && outputMime.parameters.size) {
const withParameters = new MimeType(targetMimeType);
for (const k of outputMime.parameters.keys()) {
withParameters.parameters.set(k, outputMime.parameters.get(k));
withParameters.parameters.set(k, outputMime.parameters.get(k)!);
}
targetMimeType = outputMime.toString();
}
if (converter.toMimeType === ScryptedMimeTypes.MediaObject) {
const mo = await converter.convert(value, valueMime.essence, toMimeType, { sourceId }) as MediaObjectInterface;
const mo = await converter.convert!(value, valueMime.essence, toMimeType, { sourceId }) as MediaObjectInterface;
const found = await this.convertMediaObject(mo, toMimeType);
return {
data: found,
@@ -460,7 +460,7 @@ export abstract class MediaManagerBase implements MediaManager {
};
}
value = await converter.convert(value, valueMime.essence, targetMimeType, { sourceId }) as string | Buffer;
value = await converter.convert!(value, valueMime.essence, targetMimeType, { sourceId }) as string | Buffer;
valueMime = new MimeType(targetMimeType);
}

View File

@@ -36,8 +36,8 @@ export class UnsupportedRuntimeError extends Error {
}
export class PluginHost {
worker: RuntimeWorker;
peer: RpcPeer;
worker!: RuntimeWorker;
peer!: RpcPeer;
pluginId: string;
module: Promise<any>;
scrypted: ScryptedRuntime;
@@ -50,7 +50,7 @@ export class PluginHost {
perMessageDeflate: true,
cors: (req, callback) => {
const header = this.scrypted.getAccessControlAllowOrigin(req.headers);
callback(undefined, {
callback(undefined!, {
origin: header,
credentials: true,
})
@@ -61,11 +61,11 @@ export class PluginHost {
pluginName: string;
packageJson: any;
killed = false;
consoleServer: Promise<ConsoleServer>;
consoleServer!: Promise<ConsoleServer>;
zipHash: string;
zipFile: string;
unzippedPath: string;
clusterWorkerId: Promise<string>;
clusterWorkerId!: Promise<string>;
kill() {
this.killed = true;
@@ -103,10 +103,11 @@ export class PluginHost {
if (!needInvalidate) {
// may also need to invalidate if the the plugin did not previously return a device
// because it had not yet completed the discovery process.
const device = this.scrypted.devices[pi._id];
const device = this.scrypted.devices[pi._id]!;
try {
if (device.handler?.mixinTable)
needInvalidate = !(await device.handler.mixinTable?.[device.handler.mixinTable.length - 1].entry).proxy;
const handler = device.handler;
if (handler?.mixinTable)
needInvalidate = !(await handler.mixinTable?.[handler.mixinTable.length - 1]!.entry).proxy;
}
catch (e) {
// device retrieval had previously failed, fetch again.
@@ -125,16 +126,16 @@ export class PluginHost {
this.pluginName = plugin.packageJson?.name;
this.packageJson = plugin.packageJson;
const pluginDeviceId = scrypted.findPluginDevice(this.pluginId)._id;
const logger = scrypted.getDeviceLogger(scrypted.findPluginDevice(this.pluginId));
const pluginDeviceId = scrypted.findPluginDevice(this.pluginId)!._id;
const logger = scrypted.getDeviceLogger(scrypted.findPluginDevice(this.pluginId)!)!;
const volume = getScryptedVolume();
const pluginVolume = ensurePluginVolume(this.pluginId);
{
const zipBuffer = Buffer.from(plugin.zip, 'base64');
const zipBuffer = Buffer.from(plugin.zip!, 'base64');
// allow garbage collection of the base 64 contents
plugin = undefined;
(plugin as any) = undefined;
const hash = crypto.createHash('md5').update(zipBuffer).digest().toString('hex');
this.zipHash = hash;
@@ -146,7 +147,7 @@ export class PluginHost {
const peerPromise = this.startPluginHost(logger, {
SCRYPTED_VOLUME: volume,
SCRYPTED_PLUGIN_VOLUME: pluginVolume,
}, pluginDebug);
}, pluginDebug!);
this.io.on('connection', async (socket) => {
try {
@@ -157,8 +158,8 @@ export class PluginHost {
} = (socket.request as any).scrypted;
try {
if (socket.request.url.indexOf('/engine.io/api') !== -1) {
if (socket.request.url.indexOf('/public') !== -1) {
if (socket.request.url!.indexOf('/engine.io/api') !== -1) {
if (socket.request.url!.indexOf('/public') !== -1) {
socket.close();
return;
}
@@ -205,13 +206,13 @@ export class PluginHost {
? new MediaManagerHostImpl(pluginDeviceId, () => scrypted.stateManager.getSystemState(), console, id => scrypted.getDevice(id))
: undefined;
this.api = new PluginHostAPI(scrypted, this.pluginId, this, mediaManager);
this.api = new PluginHostAPI(scrypted, this.pluginId, this, mediaManager!);
logger.log('i', `loading ${this.pluginName}`);
logger.log('i', 'pid ' + this.worker?.pid);
const remotePromise = this.prepareRemote(peerPromise, logger, pluginDebug);
const init = this.initializeRemote(remotePromise, logger, pluginDebug);
const remotePromise = this.prepareRemote(peerPromise, logger, pluginDebug!);
const init = this.initializeRemote(remotePromise, logger, pluginDebug!);
init.catch(e => {
console.error('plugin failed to load', e);
@@ -280,7 +281,7 @@ export class PluginHost {
}
catch (e) {
logger.log('e', 'plugin failed to start ' + e);
throw new RPCResultError(this.peer, 'cluster plugin start failed', e);
throw new RPCResultError(this.peer, 'cluster plugin start failed', e as Error | undefined);
}
const startupTime = Date.now();
@@ -333,8 +334,8 @@ export class PluginHost {
let { runtime } = this.packageJson.scrypted;
runtime ||= 'node';
const pluginDevice = this.scrypted.findPluginDevice(this.pluginId);
const customRuntime = pluginDevice.state.interfaces.value.includes(ScryptedInterface.ScryptedPluginRuntime);
const pluginDevice = this.scrypted.findPluginDevice(this.pluginId)!;
const customRuntime = pluginDevice.state.interfaces!.value.includes(ScryptedInterface.ScryptedPluginRuntime);
if (customRuntime) {
runtime = 'custom';
}
@@ -375,7 +376,7 @@ export class PluginHost {
this.worker.stdout.on('data', data => console.log(data.toString()));
this.worker.stderr.on('data', data => console.error(data.toString()));
this.clusterWorkerId = Promise.resolve(undefined);
this.clusterWorkerId = Promise.resolve(undefined!);
}
else {
const scrypted: ClusterForkOptions = JSON.parse(JSON.stringify(this.packageJson.scrypted));
@@ -414,7 +415,7 @@ export class PluginHost {
peer.killedSafe.finally(() => originalPeer.kill());
}).catch(() => { });
this.clusterWorkerId = clusterWorkerId;
this.clusterWorkerId = clusterWorkerId as Promise<string>;
clusterWorkerId.then(clusterWorkerId => {
console.log('cluster worker id', clusterWorkerId);
}).catch(() => {
@@ -483,7 +484,7 @@ export class PluginHost {
serializer.sendMessage(message, reject, serializationContext);
}
catch (e) {
reject?.(e);
reject?.(e as Error);
}
});
rpcPeer.tags.acl = accessControls;
@@ -500,6 +501,6 @@ export class PluginHost {
socket.on('close', kill);
socket.on('error', kill);
return setupPluginRemote(rpcPeer, api, null, { serverVersion }, () => this.scrypted.stateManager.getSystemState());
return setupPluginRemote(rpcPeer, api, null!, { serverVersion }, () => this.scrypted.stateManager.getSystemState());
}
}