266 lines
9.6 KiB
TypeScript
266 lines
9.6 KiB
TypeScript
/**
|
|
* Desktop-only surfaces that have no web equivalent in V1 (ticket #13, lot F1):
|
|
* the native folder picker, OS view-windows and the main↔detached focused-project
|
|
* channel, plus SSH/WSL remote. On the web client these must fail with a clear,
|
|
* explicit error rather than reaching for Tauri (which is absent).
|
|
*
|
|
* The B0 inventory classified these as `desktop-only`; F1 does NOT implement them
|
|
* for web. If/when the product wants an equivalent (e.g. a server-side folder
|
|
* browser to replace `pickFolder`), it gets its own lot.
|
|
*/
|
|
|
|
import type {
|
|
EmbeddedServerStatus,
|
|
GatewayError,
|
|
PluginAdmin,
|
|
PluginCommandTask,
|
|
PluginConfigDocument,
|
|
PluginConfigDocumentWriteResult,
|
|
PluginEventBatch,
|
|
PluginEventSubscription,
|
|
PluginInstallResult,
|
|
PluginToolchainDiagnostic,
|
|
PluginProjectStructure,
|
|
PluginReview,
|
|
PluginRuntimeContributionCatalog,
|
|
PluginUninstallResult,
|
|
PluginWorkspaceBinaryFile,
|
|
PluginWorkspaceDirectoryListing,
|
|
PluginWorkspaceStat,
|
|
PluginWorkspaceTextFile,
|
|
ServerExposurePreview,
|
|
ServerExposureSettings,
|
|
Unsubscribe,
|
|
} from "@/domain";
|
|
import type {
|
|
DesktopServerGateway,
|
|
FocusedProject,
|
|
FocusedProjectGateway,
|
|
PluginConfigDocumentReadInput,
|
|
PluginConfigDocumentUpdateInput,
|
|
PluginConfigGateway,
|
|
PluginEventGateway,
|
|
PluginEventPollInput,
|
|
PluginEventSubscribeInput,
|
|
PluginEventUnsubscribeInput,
|
|
PluginGateway,
|
|
PluginProjectStructureQuery,
|
|
PluginRunCommandInput,
|
|
PluginTaskGateway,
|
|
PluginTaskStatusInput,
|
|
PluginToolchainDiagnosticRequest,
|
|
PluginToolchainGateway,
|
|
PluginWorkspaceGateway,
|
|
PluginWorkspacePathInput,
|
|
PluginWorkspaceWriteBinaryInput,
|
|
PluginWorkspaceWriteTextInput,
|
|
RemoteGateway,
|
|
ReviewPluginPackageInput,
|
|
ViewWindowClosed,
|
|
ViewWindowSnapshot,
|
|
WindowGateway,
|
|
} from "@/ports";
|
|
|
|
/** Throws a stable, explicit "unsupported on web" {@link GatewayError}. */
|
|
export function unsupportedOnWeb(what: string): never {
|
|
const err: GatewayError = {
|
|
code: "UNSUPPORTED_ON_WEB",
|
|
message: `${what} is not available in the web client (desktop-only).`,
|
|
};
|
|
throw err;
|
|
}
|
|
|
|
/** Web stub: OS view-windows are desktop-only (no `WebviewWindow` on the web). */
|
|
export class WebWindowGateway implements WindowGateway {
|
|
openViewWindow(): Promise<void> {
|
|
return unsupportedOnWeb("Detaching a panel into an OS window");
|
|
}
|
|
closeViewWindow(): Promise<void> {
|
|
return unsupportedOnWeb("Closing a detached OS window");
|
|
}
|
|
listOpenViewWindows(): Promise<ViewWindowSnapshot[]> {
|
|
// Best-effort by contract (callers treat rejection as "no reconciliation"),
|
|
// but there are simply never detached OS windows on web → empty list.
|
|
return Promise.resolve([]);
|
|
}
|
|
onViewWindowClosed(
|
|
_handler: (event: ViewWindowClosed) => void,
|
|
): Promise<Unsubscribe> {
|
|
// No OS windows ⇒ nothing ever fires; return a no-op unsubscribe.
|
|
return Promise.resolve(() => {});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Web stub for the focused-project channel. With no detached windows on web the
|
|
* channel has no cross-window consumer; F1 keeps it inert (no publish, no focus).
|
|
* A future multi-tab web build could back this with `BroadcastChannel`.
|
|
*/
|
|
export class WebFocusedProjectGateway implements FocusedProjectGateway {
|
|
setFocusedProject(_project: FocusedProject | null): Promise<void> {
|
|
// No-op: nothing reads the channel on web V1.
|
|
return Promise.resolve();
|
|
}
|
|
getFocusedProject(): Promise<FocusedProject | null> {
|
|
return Promise.resolve(null);
|
|
}
|
|
onFocusedProjectChanged(
|
|
_handler: (project: FocusedProject | null) => void,
|
|
): Promise<Unsubscribe> {
|
|
return Promise.resolve(() => {});
|
|
}
|
|
}
|
|
|
|
/** Web stub: SSH/WSL remote connection is desktop-only in V1. */
|
|
export class WebRemoteGateway implements RemoteGateway {
|
|
connect(): Promise<void> {
|
|
return unsupportedOnWeb("Remote (SSH/WSL) connection");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Web stub: the embedded server is desktop-only (ticket #68). A web client is
|
|
* *served by* this very server, so letting it reconfigure or stop the server
|
|
* would let a remote device saw off the branch it sits on. The desktop app owns
|
|
* this surface; every call fails explicitly rather than reaching for Tauri.
|
|
*/
|
|
export class WebDesktopServerGateway implements DesktopServerGateway {
|
|
// `async` on purpose: these must *reject* rather than throw synchronously, so
|
|
// callers using `.then(ok, err)` (not just `await`) still see the failure.
|
|
async getExposureSettings(): Promise<ServerExposureSettings> {
|
|
return unsupportedOnWeb("Embedded server settings");
|
|
}
|
|
async saveExposureSettings(_settings: ServerExposureSettings): Promise<void> {
|
|
return unsupportedOnWeb("Embedded server settings");
|
|
}
|
|
async previewExposure(
|
|
_settings: ServerExposureSettings,
|
|
): Promise<ServerExposurePreview> {
|
|
return unsupportedOnWeb("Embedded server settings");
|
|
}
|
|
async status(): Promise<EmbeddedServerStatus> {
|
|
return unsupportedOnWeb("Embedded server status");
|
|
}
|
|
async start(): Promise<EmbeddedServerStatus> {
|
|
return unsupportedOnWeb("Starting the embedded server");
|
|
}
|
|
async stop(): Promise<EmbeddedServerStatus> {
|
|
return unsupportedOnWeb("Stopping the embedded server");
|
|
}
|
|
onStatusChanged(
|
|
_handler: (status: EmbeddedServerStatus) => void,
|
|
): Promise<Unsubscribe> {
|
|
// Never fires on web; a no-op unsubscribe keeps callers' teardown honest.
|
|
return Promise.resolve(() => {});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Web stub: the plugin system (#43) installs/loads full-trust local ESM
|
|
* bundles from the desktop filesystem — no server-side equivalent in V1. The
|
|
* web client must not manage or load plugins on behalf of the desktop app.
|
|
*/
|
|
export class WebPluginGateway implements PluginGateway {
|
|
async listPlugins(): Promise<PluginAdmin[]> {
|
|
return unsupportedOnWeb("Plugin management");
|
|
}
|
|
async reviewPackage(_input: ReviewPluginPackageInput): Promise<PluginReview> {
|
|
return unsupportedOnWeb("Plugin management");
|
|
}
|
|
async installFromArchive(_path: string): Promise<PluginInstallResult> {
|
|
return unsupportedOnWeb("Plugin management");
|
|
}
|
|
async installFromDirectory(_path: string): Promise<PluginInstallResult> {
|
|
return unsupportedOnWeb("Plugin management");
|
|
}
|
|
async setEnabled(_pluginId: string, _enabled: boolean): Promise<PluginAdmin> {
|
|
return unsupportedOnWeb("Plugin management");
|
|
}
|
|
async uninstall(_pluginId: string): Promise<PluginUninstallResult> {
|
|
return unsupportedOnWeb("Plugin management");
|
|
}
|
|
async listRuntimeContributions(): Promise<PluginRuntimeContributionCatalog> {
|
|
// No plugin bundles are ever loaded on the web client — an empty catalog
|
|
// lets the bootstrap loader run unconditionally without a transport check.
|
|
return { plugins: [] };
|
|
}
|
|
async openPluginsFolder(_pluginId?: string): Promise<void> {
|
|
return unsupportedOnWeb("Plugin management");
|
|
}
|
|
}
|
|
|
|
/** Web stub: public plugin workspace services are only meaningful where plugins run. */
|
|
export class WebPluginWorkspaceGateway implements PluginWorkspaceGateway {
|
|
async readText(_input: PluginWorkspacePathInput): Promise<PluginWorkspaceTextFile> {
|
|
return unsupportedOnWeb("Plugin workspace access");
|
|
}
|
|
async readBinary(_input: PluginWorkspacePathInput): Promise<PluginWorkspaceBinaryFile> {
|
|
return unsupportedOnWeb("Plugin workspace access");
|
|
}
|
|
async writeText(_input: PluginWorkspaceWriteTextInput): Promise<void> {
|
|
return unsupportedOnWeb("Plugin workspace access");
|
|
}
|
|
async writeBinary(_input: PluginWorkspaceWriteBinaryInput): Promise<void> {
|
|
return unsupportedOnWeb("Plugin workspace access");
|
|
}
|
|
async listDir(_input: PluginWorkspacePathInput): Promise<PluginWorkspaceDirectoryListing> {
|
|
return unsupportedOnWeb("Plugin workspace access");
|
|
}
|
|
async stat(_input: PluginWorkspacePathInput): Promise<PluginWorkspaceStat> {
|
|
return unsupportedOnWeb("Plugin workspace access");
|
|
}
|
|
async queryProjectStructure(
|
|
_input: PluginProjectStructureQuery,
|
|
): Promise<PluginProjectStructure> {
|
|
return unsupportedOnWeb("Plugin workspace access");
|
|
}
|
|
}
|
|
|
|
/** Web stub: plugin command tasks are desktop-hosted in this runtime. */
|
|
export class WebPluginTaskGateway implements PluginTaskGateway {
|
|
async runCommand(_input: PluginRunCommandInput): Promise<PluginCommandTask> {
|
|
return unsupportedOnWeb("Plugin command tasks");
|
|
}
|
|
|
|
async getStatus(_input: PluginTaskStatusInput): Promise<PluginCommandTask | null> {
|
|
return unsupportedOnWeb("Plugin command tasks");
|
|
}
|
|
}
|
|
|
|
/** Web stub: plugin toolchain diagnostics run on the desktop host. */
|
|
export class WebPluginToolchainGateway implements PluginToolchainGateway {
|
|
async diagnose(
|
|
_input: PluginToolchainDiagnosticRequest,
|
|
): Promise<PluginToolchainDiagnostic> {
|
|
return unsupportedOnWeb("Plugin toolchain diagnostics");
|
|
}
|
|
}
|
|
|
|
/** Web stub: plugin public events are sourced from the desktop host. */
|
|
export class WebPluginEventGateway implements PluginEventGateway {
|
|
async subscribe(_input: PluginEventSubscribeInput): Promise<PluginEventSubscription> {
|
|
return unsupportedOnWeb("Plugin public events");
|
|
}
|
|
|
|
async poll(_input: PluginEventPollInput): Promise<PluginEventBatch> {
|
|
return unsupportedOnWeb("Plugin public events");
|
|
}
|
|
|
|
async unsubscribe(_input: PluginEventUnsubscribeInput): Promise<PluginEventSubscription> {
|
|
return unsupportedOnWeb("Plugin public events");
|
|
}
|
|
}
|
|
|
|
/** Web stub: plugin config documents are read/written by the desktop host. */
|
|
export class WebPluginConfigGateway implements PluginConfigGateway {
|
|
async readDocument(_input: PluginConfigDocumentReadInput): Promise<PluginConfigDocument> {
|
|
return unsupportedOnWeb("Plugin structured config documents");
|
|
}
|
|
|
|
async updateDocument(
|
|
_input: PluginConfigDocumentUpdateInput,
|
|
): Promise<PluginConfigDocumentWriteResult> {
|
|
return unsupportedOnWeb("Plugin structured config documents");
|
|
}
|
|
}
|