mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
feat(desktop): bundled renderer, modules and instance accounts (#3254)
This commit is contained in:
1 parent
c748c8af4e
commit
7c5fa2180a
1275 files changed
+215074
-81246
No files matched your search
@@ -0,0 +1,129 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {REQUEST_ID_MAX_LENGTH, readBoundedString, readExactPlainRecord} from '@electron/common/PlainRecord';
|
||||
import {resolveDesktopRuntimePlan} from '@electron/main/DesktopRuntimeDiscovery';
|
||||
import {projectDesktopRuntimePlan} from '@electron/main/DesktopRuntimePlanProjection';
|
||||
import {getDesktopLocalAppProtocol} from '@electron/main/LocalAppProtocol';
|
||||
import type {RendererDocumentOwnerFactory} from '@electron/main/RendererDocumentOwner';
|
||||
import type {
|
||||
RendererDocumentIpcEvent,
|
||||
RendererDocumentOwner,
|
||||
RendererDocumentOwnerWatcher,
|
||||
} from '@electron/main/RendererDocumentOwnership';
|
||||
import type {DesktopRuntimePlan} from '@fluxer/desktop_ipc/src/LocalAppRuntimeContract';
|
||||
|
||||
const MAX_PENDING_RESOLUTIONS = 8;
|
||||
const RESOLVE_INPUT_MAX_LENGTH = 8192;
|
||||
|
||||
class InvalidDesktopRuntimeResolutionRequestError extends TypeError {
|
||||
public constructor(context: string) {
|
||||
super(`Desktop runtime config ${context} is malformed`);
|
||||
this.name = 'InvalidDesktopRuntimeResolutionRequestError';
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopRuntimeResolutionCapacityError extends Error {
|
||||
public constructor() {
|
||||
super('Desktop runtime config resolution capacity is exhausted');
|
||||
this.name = 'DesktopRuntimeResolutionCapacityError';
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopRuntimeResolutionAlreadyExistsError extends Error {
|
||||
public constructor(requestId: string) {
|
||||
super(`Desktop runtime resolution ${requestId} already exists`);
|
||||
this.name = 'DesktopRuntimeResolutionAlreadyExistsError';
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopRuntimeResolutionOwnershipError extends Error {
|
||||
public constructor(requestId: string) {
|
||||
super(`Desktop runtime resolution ${requestId} belongs to another renderer document`);
|
||||
this.name = 'DesktopRuntimeResolutionOwnershipError';
|
||||
}
|
||||
}
|
||||
|
||||
interface PendingRuntimeResolution {
|
||||
readonly requestId: string;
|
||||
readonly owner: RendererDocumentOwner;
|
||||
readonly watcher: RendererDocumentOwnerWatcher;
|
||||
readonly controller: AbortController;
|
||||
}
|
||||
|
||||
export class DesktopRuntimeResolutionRegistry {
|
||||
private readonly pending = new Map<string, PendingRuntimeResolution>();
|
||||
|
||||
public constructor(private readonly rendererDocuments: RendererDocumentOwnerFactory) {}
|
||||
|
||||
public async resolve(event: RendererDocumentIpcEvent, request: unknown): Promise<DesktopRuntimePlan> {
|
||||
const record = readExactPlainRecord({value: request, expectedKeys: ['input', 'requestId']});
|
||||
if (record == null) {
|
||||
throw new InvalidDesktopRuntimeResolutionRequestError('resolve request');
|
||||
}
|
||||
const input = readBoundedString(record.input, RESOLVE_INPUT_MAX_LENGTH);
|
||||
const requestId = readBoundedString(record.requestId, REQUEST_ID_MAX_LENGTH);
|
||||
if (input == null || requestId == null) {
|
||||
throw new InvalidDesktopRuntimeResolutionRequestError('resolve request');
|
||||
}
|
||||
if (this.pending.has(requestId)) {
|
||||
throw new DesktopRuntimeResolutionAlreadyExistsError(requestId);
|
||||
}
|
||||
if (this.pending.size >= MAX_PENDING_RESOLUTIONS) {
|
||||
throw new DesktopRuntimeResolutionCapacityError();
|
||||
}
|
||||
const owner = this.rendererDocuments.capture(event, 'Desktop runtime config resolve');
|
||||
const controller = new AbortController();
|
||||
const watcher = owner.watchInvalidation(() => {
|
||||
this.dispose(requestId);
|
||||
});
|
||||
const pending = {requestId, owner, watcher, controller};
|
||||
this.pending.set(requestId, pending);
|
||||
try {
|
||||
const protocol = getDesktopLocalAppProtocol();
|
||||
const signal = AbortSignal.any([controller.signal, protocol.getShutdownSignal()]);
|
||||
const plan = await resolveDesktopRuntimePlan({input, signal});
|
||||
owner.requireCurrent('Desktop runtime config resolve');
|
||||
protocol.cacheRuntimePlan(plan);
|
||||
return projectDesktopRuntimePlan(plan);
|
||||
} finally {
|
||||
this.dispose(requestId, pending);
|
||||
}
|
||||
}
|
||||
|
||||
public cancel(event: RendererDocumentIpcEvent, request: unknown): void {
|
||||
const requestId = requireResolutionId(request);
|
||||
const pending = this.pending.get(requestId);
|
||||
if (pending === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!pending.owner.matchesEvent(event)) {
|
||||
throw new DesktopRuntimeResolutionOwnershipError(requestId);
|
||||
}
|
||||
this.dispose(requestId, pending);
|
||||
}
|
||||
|
||||
public cleanup(): void {
|
||||
for (const requestId of [...this.pending.keys()]) {
|
||||
this.dispose(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
private dispose(requestId: string, expected?: PendingRuntimeResolution): void {
|
||||
const pending = this.pending.get(requestId);
|
||||
if (pending === undefined || (expected !== undefined && pending !== expected)) {
|
||||
return;
|
||||
}
|
||||
this.pending.delete(requestId);
|
||||
pending.controller.abort();
|
||||
pending.watcher.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
function requireResolutionId(request: unknown): string {
|
||||
const record = readExactPlainRecord({value: request, expectedKeys: ['requestId']});
|
||||
const requestId = record == null ? null : readBoundedString(record.requestId, REQUEST_ID_MAX_LENGTH);
|
||||
if (requestId === null) {
|
||||
throw new InvalidDesktopRuntimeResolutionRequestError('resolution cancellation request');
|
||||
}
|
||||
return requestId;
|
||||
}
|
||||
Reference in new issue
Block a user