import { contextBridge, ipcRenderer } from "electron";import type { DesktopBridge } from "@t3tools/contracts"; const PICK_FOLDER_CHANNEL = "desktop:pick-folder";const CONFIRM_CHANNEL = "desktop:confirm";const SET_THEME_CHANNEL = "desktop:set-theme";const CONTEXT_MENU_CHANNEL = "desktop:context-menu";const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";const MENU_ACTION_CHANNEL = "desktop:menu-action";const UPDATE_STATE_CHANNEL = "desktop:update-state";const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";const UPDATE_SET_CHANNEL_CHANNEL = "desktop:update-set-channel";const UPDATE_CHECK_CHANNEL = "desktop:update-check";const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download";const UPDATE_INSTALL_CHANNEL = "desktop:update-install";const GET_APP_BRANDING_CHANNEL = "desktop:get-app-branding";const GET_LOCAL_ENVIRONMENT_BOOTSTRAP_CHANNEL = "desktop:get-local-environment-bootstrap";const GET_CLIENT_SETTINGS_CHANNEL = "desktop:get-client-settings";const SET_CLIENT_SETTINGS_CHANNEL = "desktop:set-client-settings";const GET_SAVED_ENVIRONMENT_REGISTRY_CHANNEL = "desktop:get-saved-environment-registry";const SET_SAVED_ENVIRONMENT_REGISTRY_CHANNEL = "desktop:set-saved-environment-registry";const GET_SAVED_ENVIRONMENT_SECRET_CHANNEL = "desktop:get-saved-environment-secret";const SET_SAVED_ENVIRONMENT_SECRET_CHANNEL = "desktop:set-saved-environment-secret";const REMOVE_SAVED_ENVIRONMENT_SECRET_CHANNEL = "desktop:remove-saved-environment-secret";const DISCOVER_SSH_HOSTS_CHANNEL = "desktop:discover-ssh-hosts";const ENSURE_SSH_ENVIRONMENT_CHANNEL = "desktop:ensure-ssh-environment";const DISCONNECT_SSH_ENVIRONMENT_CHANNEL = "desktop:disconnect-ssh-environment";const FETCH_SSH_ENVIRONMENT_DESCRIPTOR_CHANNEL = "desktop:fetch-ssh-environment-descriptor";const BOOTSTRAP_SSH_BEARER_SESSION_CHANNEL = "desktop:bootstrap-ssh-bearer-session";const FETCH_SSH_SESSION_STATE_CHANNEL = "desktop:fetch-ssh-session-state";const ISSUE_SSH_WEBSOCKET_TOKEN_CHANNEL = "desktop:issue-ssh-websocket-token";const SSH_PASSWORD_PROMPT_CHANNEL = "desktop:ssh-password-prompt";const RESOLVE_SSH_PASSWORD_PROMPT_CHANNEL = "desktop:resolve-ssh-password-prompt";const GET_SERVER_EXPOSURE_STATE_CHANNEL = "desktop:get-server-exposure-state";const SET_SERVER_EXPOSURE_MODE_CHANNEL = "desktop:set-server-exposure-mode";const SET_TAILSCALE_SERVE_ENABLED_CHANNEL = "desktop:set-tailscale-serve-enabled";const GET_ADVERTISED_ENDPOINTS_CHANNEL = "desktop:get-advertised-endpoints";const SSH_PASSWORD_PROMPT_CANCELLED_RESULT = "ssh-password-prompt-cancelled"; function unwrapEnsureSshEnvironmentResult(result: unknown) { if ( typeof result === "object" && result !== null && "type" in result && result.type === SSH_PASSWORD_PROMPT_CANCELLED_RESULT ) { const message = "message" in result && typeof result.message === "string" ? result.message : "SSH authentication cancelled."; throw new Error(message); } return result as Awaited<ReturnType<DesktopBridge["ensureSshEnvironment"]>>;} contextBridge.exposeInMainWorld("desktopBridge", { getAppBranding: () => { const result = ipcRenderer.sendSync(GET_APP_BRANDING_CHANNEL); if (typeof result !== "object" || result === null) { return null; } return result as ReturnType<DesktopBridge["getAppBranding"]>; }, getLocalEnvironmentBootstrap: () => { const result = ipcRenderer.sendSync(GET_LOCAL_ENVIRONMENT_BOOTSTRAP_CHANNEL); if (typeof result !== "object" || result === null) { return null; } return result as ReturnType<DesktopBridge["getLocalEnvironmentBootstrap"]>; }, getClientSettings: () => ipcRenderer.invoke(GET_CLIENT_SETTINGS_CHANNEL), setClientSettings: (settings) => ipcRenderer.invoke(SET_CLIENT_SETTINGS_CHANNEL, settings), getSavedEnvironmentRegistry: () => ipcRenderer.invoke(GET_SAVED_ENVIRONMENT_REGISTRY_CHANNEL), setSavedEnvironmentRegistry: (records) => ipcRenderer.invoke(SET_SAVED_ENVIRONMENT_REGISTRY_CHANNEL, records), getSavedEnvironmentSecret: (environmentId) => ipcRenderer.invoke(GET_SAVED_ENVIRONMENT_SECRET_CHANNEL, environmentId), setSavedEnvironmentSecret: (environmentId, secret) => ipcRenderer.invoke(SET_SAVED_ENVIRONMENT_SECRET_CHANNEL, environmentId, secret), removeSavedEnvironmentSecret: (environmentId) => ipcRenderer.invoke(REMOVE_SAVED_ENVIRONMENT_SECRET_CHANNEL, environmentId), discoverSshHosts: () => ipcRenderer.invoke(DISCOVER_SSH_HOSTS_CHANNEL), ensureSshEnvironment: async (target, options) => unwrapEnsureSshEnvironmentResult( await ipcRenderer.invoke(ENSURE_SSH_ENVIRONMENT_CHANNEL, target, options), ), disconnectSshEnvironment: (target) => ipcRenderer.invoke(DISCONNECT_SSH_ENVIRONMENT_CHANNEL, target), fetchSshEnvironmentDescriptor: (httpBaseUrl) => ipcRenderer.invoke(FETCH_SSH_ENVIRONMENT_DESCRIPTOR_CHANNEL, httpBaseUrl), bootstrapSshBearerSession: (httpBaseUrl, credential) => ipcRenderer.invoke(BOOTSTRAP_SSH_BEARER_SESSION_CHANNEL, httpBaseUrl, credential), fetchSshSessionState: (httpBaseUrl, bearerToken) => ipcRenderer.invoke(FETCH_SSH_SESSION_STATE_CHANNEL, httpBaseUrl, bearerToken), issueSshWebSocketToken: (httpBaseUrl, bearerToken) => ipcRenderer.invoke(ISSUE_SSH_WEBSOCKET_TOKEN_CHANNEL, httpBaseUrl, bearerToken), onSshPasswordPrompt: (listener) => { const wrappedListener = (_event: Electron.IpcRendererEvent, request: unknown) => { if (typeof request !== "object" || request === null) return; listener(request as Parameters<typeof listener>[0]); }; ipcRenderer.on(SSH_PASSWORD_PROMPT_CHANNEL, wrappedListener); return () => { ipcRenderer.removeListener(SSH_PASSWORD_PROMPT_CHANNEL, wrappedListener); }; }, resolveSshPasswordPrompt: (requestId, password) => ipcRenderer.invoke(RESOLVE_SSH_PASSWORD_PROMPT_CHANNEL, requestId, password), getServerExposureState: () => ipcRenderer.invoke(GET_SERVER_EXPOSURE_STATE_CHANNEL), setServerExposureMode: (mode) => ipcRenderer.invoke(SET_SERVER_EXPOSURE_MODE_CHANNEL, mode), setTailscaleServeEnabled: (input) => ipcRenderer.invoke(SET_TAILSCALE_SERVE_ENABLED_CHANNEL, input), getAdvertisedEndpoints: () => ipcRenderer.invoke(GET_ADVERTISED_ENDPOINTS_CHANNEL), pickFolder: (options) => ipcRenderer.invoke(PICK_FOLDER_CHANNEL, options), confirm: (message) => ipcRenderer.invoke(CONFIRM_CHANNEL, message), setTheme: (theme) => ipcRenderer.invoke(SET_THEME_CHANNEL, theme), showContextMenu: (items, position) => ipcRenderer.invoke(CONTEXT_MENU_CHANNEL, items, position), openExternal: (url: string) => ipcRenderer.invoke(OPEN_EXTERNAL_CHANNEL, url), onMenuAction: (listener) => { const wrappedListener = (_event: Electron.IpcRendererEvent, action: unknown) => { if (typeof action !== "string") return; listener(action); }; ipcRenderer.on(MENU_ACTION_CHANNEL, wrappedListener); return () => { ipcRenderer.removeListener(MENU_ACTION_CHANNEL, wrappedListener); }; }, getUpdateState: () => ipcRenderer.invoke(UPDATE_GET_STATE_CHANNEL), setUpdateChannel: (channel) => ipcRenderer.invoke(UPDATE_SET_CHANNEL_CHANNEL, channel), checkForUpdate: () => ipcRenderer.invoke(UPDATE_CHECK_CHANNEL), downloadUpdate: () => ipcRenderer.invoke(UPDATE_DOWNLOAD_CHANNEL), installUpdate: () => ipcRenderer.invoke(UPDATE_INSTALL_CHANNEL), onUpdateState: (listener) => { const wrappedListener = (_event: Electron.IpcRendererEvent, state: unknown) => { if (typeof state !== "object" || state === null) return; listener(state as Parameters<typeof listener>[0]); }; ipcRenderer.on(UPDATE_STATE_CHANNEL, wrappedListener); return () => { ipcRenderer.removeListener(UPDATE_STATE_CHANNEL, wrappedListener); }; },} satisfies DesktopBridge);
import { contextBridge, ipcRenderer } from "electron";
import type { DesktopBridge } from "@t3tools/contracts";
const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
const CONFIRM_CHANNEL = "desktop:confirm";
const SET_THEME_CHANNEL = "desktop:set-theme";
const CONTEXT_MENU_CHANNEL = "desktop:context-menu";
const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";
const MENU_ACTION_CHANNEL = "desktop:menu-action";
const UPDATE_STATE_CHANNEL = "desktop:update-state";
const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
const UPDATE_SET_CHANNEL_CHANNEL = "desktop:update-set-channel";
const UPDATE_CHECK_CHANNEL = "desktop:update-check";
const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download";
const UPDATE_INSTALL_CHANNEL = "desktop:update-install";
const GET_APP_BRANDING_CHANNEL = "desktop:get-app-branding";
const GET_LOCAL_ENVIRONMENT_BOOTSTRAP_CHANNEL = "desktop:get-local-environment-bootstrap";
const GET_CLIENT_SETTINGS_CHANNEL = "desktop:get-client-settings";
const SET_CLIENT_SETTINGS_CHANNEL = "desktop:set-client-settings";
const GET_SAVED_ENVIRONMENT_REGISTRY_CHANNEL = "desktop:get-saved-environment-registry";
const SET_SAVED_ENVIRONMENT_REGISTRY_CHANNEL = "desktop:set-saved-environment-registry";
const GET_SAVED_ENVIRONMENT_SECRET_CHANNEL = "desktop:get-saved-environment-secret";
const SET_SAVED_ENVIRONMENT_SECRET_CHANNEL = "desktop:set-saved-environment-secret";
const REMOVE_SAVED_ENVIRONMENT_SECRET_CHANNEL = "desktop:remove-saved-environment-secret";
const DISCOVER_SSH_HOSTS_CHANNEL = "desktop:discover-ssh-hosts";
const ENSURE_SSH_ENVIRONMENT_CHANNEL = "desktop:ensure-ssh-environment";
const DISCONNECT_SSH_ENVIRONMENT_CHANNEL = "desktop:disconnect-ssh-environment";
const FETCH_SSH_ENVIRONMENT_DESCRIPTOR_CHANNEL = "desktop:fetch-ssh-environment-descriptor";
const BOOTSTRAP_SSH_BEARER_SESSION_CHANNEL = "desktop:bootstrap-ssh-bearer-session";
const FETCH_SSH_SESSION_STATE_CHANNEL = "desktop:fetch-ssh-session-state";
const ISSUE_SSH_WEBSOCKET_TOKEN_CHANNEL = "desktop:issue-ssh-websocket-token";
const SSH_PASSWORD_PROMPT_CHANNEL = "desktop:ssh-password-prompt";
const RESOLVE_SSH_PASSWORD_PROMPT_CHANNEL = "desktop:resolve-ssh-password-prompt";
const GET_SERVER_EXPOSURE_STATE_CHANNEL = "desktop:get-server-exposure-state";
const SET_SERVER_EXPOSURE_MODE_CHANNEL = "desktop:set-server-exposure-mode";
const SET_TAILSCALE_SERVE_ENABLED_CHANNEL = "desktop:set-tailscale-serve-enabled";
const GET_ADVERTISED_ENDPOINTS_CHANNEL = "desktop:get-advertised-endpoints";
const SSH_PASSWORD_PROMPT_CANCELLED_RESULT = "ssh-password-prompt-cancelled";
function unwrapEnsureSshEnvironmentResult(result: unknown) {
if (
typeof result === "object" &&
result !== null &&
"type" in result &&
result.type === SSH_PASSWORD_PROMPT_CANCELLED_RESULT
) {
const message =
"message" in result && typeof result.message === "string"
? result.message
: "SSH authentication cancelled.";
throw new Error(message);
}
return result as Awaited<ReturnType<DesktopBridge["ensureSshEnvironment"]>>;
}
contextBridge.exposeInMainWorld("desktopBridge", {
getAppBranding: () => {
const result = ipcRenderer.sendSync(GET_APP_BRANDING_CHANNEL);
if (typeof result !== "object" || result === null) {
return null;
}
return result as ReturnType<DesktopBridge["getAppBranding"]>;
},
getLocalEnvironmentBootstrap: () => {
const result = ipcRenderer.sendSync(GET_LOCAL_ENVIRONMENT_BOOTSTRAP_CHANNEL);
if (typeof result !== "object" || result === null) {
return null;
}
return result as ReturnType<DesktopBridge["getLocalEnvironmentBootstrap"]>;
},
getClientSettings: () => ipcRenderer.invoke(GET_CLIENT_SETTINGS_CHANNEL),
setClientSettings: (settings) => ipcRenderer.invoke(SET_CLIENT_SETTINGS_CHANNEL, settings),
getSavedEnvironmentRegistry: () => ipcRenderer.invoke(GET_SAVED_ENVIRONMENT_REGISTRY_CHANNEL),
setSavedEnvironmentRegistry: (records) =>
ipcRenderer.invoke(SET_SAVED_ENVIRONMENT_REGISTRY_CHANNEL, records),
getSavedEnvironmentSecret: (environmentId) =>
ipcRenderer.invoke(GET_SAVED_ENVIRONMENT_SECRET_CHANNEL, environmentId),
setSavedEnvironmentSecret: (environmentId, secret) =>
ipcRenderer.invoke(SET_SAVED_ENVIRONMENT_SECRET_CHANNEL, environmentId, secret),
removeSavedEnvironmentSecret: (environmentId) =>
ipcRenderer.invoke(REMOVE_SAVED_ENVIRONMENT_SECRET_CHANNEL, environmentId),
discoverSshHosts: () => ipcRenderer.invoke(DISCOVER_SSH_HOSTS_CHANNEL),
ensureSshEnvironment: async (target, options) =>
unwrapEnsureSshEnvironmentResult(
await ipcRenderer.invoke(ENSURE_SSH_ENVIRONMENT_CHANNEL, target, options),
),
disconnectSshEnvironment: (target) =>
ipcRenderer.invoke(DISCONNECT_SSH_ENVIRONMENT_CHANNEL, target),
fetchSshEnvironmentDescriptor: (httpBaseUrl) =>
ipcRenderer.invoke(FETCH_SSH_ENVIRONMENT_DESCRIPTOR_CHANNEL, httpBaseUrl),
bootstrapSshBearerSession: (httpBaseUrl, credential) =>
ipcRenderer.invoke(BOOTSTRAP_SSH_BEARER_SESSION_CHANNEL, httpBaseUrl, credential),
fetchSshSessionState: (httpBaseUrl, bearerToken) =>
ipcRenderer.invoke(FETCH_SSH_SESSION_STATE_CHANNEL, httpBaseUrl, bearerToken),
issueSshWebSocketToken: (httpBaseUrl, bearerToken) =>
ipcRenderer.invoke(ISSUE_SSH_WEBSOCKET_TOKEN_CHANNEL, httpBaseUrl, bearerToken),
onSshPasswordPrompt: (listener) => {
const wrappedListener = (_event: Electron.IpcRendererEvent, request: unknown) => {
if (typeof request !== "object" || request === null) return;
listener(request as Parameters<typeof listener>[0]);
};
ipcRenderer.on(SSH_PASSWORD_PROMPT_CHANNEL, wrappedListener);
return () => {
ipcRenderer.removeListener(SSH_PASSWORD_PROMPT_CHANNEL, wrappedListener);
};
},
resolveSshPasswordPrompt: (requestId, password) =>
ipcRenderer.invoke(RESOLVE_SSH_PASSWORD_PROMPT_CHANNEL, requestId, password),
getServerExposureState: () => ipcRenderer.invoke(GET_SERVER_EXPOSURE_STATE_CHANNEL),
setServerExposureMode: (mode) => ipcRenderer.invoke(SET_SERVER_EXPOSURE_MODE_CHANNEL, mode),
setTailscaleServeEnabled: (input) =>
ipcRenderer.invoke(SET_TAILSCALE_SERVE_ENABLED_CHANNEL, input),
getAdvertisedEndpoints: () => ipcRenderer.invoke(GET_ADVERTISED_ENDPOINTS_CHANNEL),
pickFolder: (options) => ipcRenderer.invoke(PICK_FOLDER_CHANNEL, options),
confirm: (message) => ipcRenderer.invoke(CONFIRM_CHANNEL, message),
setTheme: (theme) => ipcRenderer.invoke(SET_THEME_CHANNEL, theme),
showContextMenu: (items, position) => ipcRenderer.invoke(CONTEXT_MENU_CHANNEL, items, position),
openExternal: (url: string) => ipcRenderer.invoke(OPEN_EXTERNAL_CHANNEL, url),
onMenuAction: (listener) => {
const wrappedListener = (_event: Electron.IpcRendererEvent, action: unknown) => {
if (typeof action !== "string") return;
listener(action);
};
ipcRenderer.on(MENU_ACTION_CHANNEL, wrappedListener);
return () => {
ipcRenderer.removeListener(MENU_ACTION_CHANNEL, wrappedListener);
};
},
getUpdateState: () => ipcRenderer.invoke(UPDATE_GET_STATE_CHANNEL),
setUpdateChannel: (channel) => ipcRenderer.invoke(UPDATE_SET_CHANNEL_CHANNEL, channel),
checkForUpdate: () => ipcRenderer.invoke(UPDATE_CHECK_CHANNEL),
downloadUpdate: () => ipcRenderer.invoke(UPDATE_DOWNLOAD_CHANNEL),
installUpdate: () => ipcRenderer.invoke(UPDATE_INSTALL_CHANNEL),
onUpdateState: (listener) => {
const wrappedListener = (_event: Electron.IpcRendererEvent, state: unknown) => {
if (typeof state !== "object" || state === null) return;
listener(state as Parameters<typeof listener>[0]);
};
ipcRenderer.on(UPDATE_STATE_CHANNEL, wrappedListener);
return () => {
ipcRenderer.removeListener(UPDATE_STATE_CHANNEL, wrappedListener);
};
},
} satisfies DesktopBridge);