Files
fusion/packages/mobile/src/plugins/native-shell.ts
Fusion 2b7b92229c feat(FN-3400): add native shell connection handoff, plugin management CLI/l
This merge adds a complete plugin management system (FN-3565) with CLI commands, a loader, runner, and dashboard routes, along with project-scoped auth storage (FN-3544), native shell connection support for mobile (FN-3400) spanning onboarding, connection manager, and remote desktop handoff, and ref

Fusion-Task-Id: FN-3400
2026-05-06 05:29:56 -07:00

85 lines
2.3 KiB
TypeScript

import type {
FusionShellApi,
ShellConnectionProfile,
ShellConnectionProfileInput,
ShellConnectionState,
} from "../types.js";
import {
deleteShellProfile,
listShellProfiles,
loadShellProfiles,
saveShellProfile,
setActiveShellProfile,
} from "./connection-profiles.js";
import { QrScanner, type QrScanResult } from "./qr-scanner.js";
type Listener = (state: ShellConnectionState) => void;
export class MobileNativeShellBridge implements FusionShellApi {
private listeners = new Set<Listener>();
constructor(private readonly qrScanner: QrScanner = new QrScanner()) {}
private async buildState(): Promise<ShellConnectionState> {
const persisted = await loadShellProfiles();
return {
host: "mobile-shell",
activeProfileId: persisted.activeProfileId,
profiles: persisted.profiles,
};
}
private async emitState(): Promise<ShellConnectionState> {
const state = await this.buildState();
for (const listener of this.listeners) {
listener(state);
}
return state;
}
getState(): Promise<ShellConnectionState> {
return this.buildState();
}
listProfiles(): Promise<ShellConnectionProfile[]> {
return listShellProfiles();
}
async saveProfile(profile: ShellConnectionProfileInput): Promise<ShellConnectionProfile> {
const saved = await saveShellProfile(profile);
await this.emitState();
return saved;
}
async deleteProfile(profileId: string): Promise<void> {
await deleteShellProfile(profileId);
await this.emitState();
}
setActiveProfile(profileId: string | null): Promise<ShellConnectionState> {
return setActiveShellProfile(profileId).then(() => this.emitState());
}
setDesktopMode(): Promise<ShellConnectionState> {
return Promise.reject(new Error("Desktop mode is not supported in mobile shell"));
}
startQrScan(): Promise<QrScanResult> {
return this.qrScanner.scanConnection();
}
async openConnectionManager(): Promise<void> {
if (typeof window !== "undefined") {
window.dispatchEvent(new CustomEvent("shell:open-connection-manager"));
}
}
subscribe(listener: Listener): () => void {
this.listeners.add(listener);
void this.getState().then(listener);
return () => {
this.listeners.delete(listener);
};
}
}