Files
openswarm/electron/preload.js
T
TheAchiever6823 fe0c3af0f2 [shawn] chore: merge origin/dev into mcp-integrations, resolve conflicts
Brings the branch current with dev (was 18 behind) so the PR
squash-merges into dev with zero conflicts. Resolved 6 conflicts:

- backend/main.py: keep both workflows and telegram_bot SubApps
- backend/auth.py: keep the Spotify OAuth callback path exemptions
- backend/apps/agents/agent_manager.py: take the deletions of
  _build_connected_tools_context, _approx_tokens, and
  _summarize_message_block (confirmed dead, zero callers on dev)
- backend/apps/tools_lib/tools_lib.py: keep both dev's
  google_oauth_token_proxy and the MCP credential endpoints
- electron/main.js: keep both the Instagram MCP CLI helpers and
  dev's one-line waitForBackend comment
- frontend/src/app/pages/Tools/Tools.tsx: keep the credential dialogs

Also widens the Tools.tsx snackbar severity union to include
'warning', a value its own code already passes (a pre-existing type
error surfaced once dev was merged and tsc was run).

Verified: 832 backend tests pass, webpack build succeeds, tsc clean
except one pre-existing allowpopups error in dev's BrowserCard.tsx.
2026-05-20 03:15:04 -07:00

136 lines
7.0 KiB
JavaScript

const { contextBridge, ipcRenderer } = require('electron');
(async () => {
const port = await ipcRenderer.invoke('get-backend-port');
const webviewPreloadPath = await ipcRenderer.invoke('get-webview-preload-path');
contextBridge.exposeInMainWorld('__OPENSWARM_PORT__', port);
contextBridge.exposeInMainWorld('openswarm', {
getBackendPort: () => port,
getWebviewPreloadPath: () => webviewPreloadPath,
// Per-install auth token required for WS + HTTP calls to the
// localhost backend. Returns a Promise<string>. The renderer should
// await this on startup and include the token on every WS URL
// (`?token=...`) and HTTP request (`Authorization: Bearer ...`).
// We deliberately do NOT expose the token as a plain window global
// or a sync getter — contextBridge + IPC keeps it off the
// renderer's global object so third-party scripts (including any
// code that leaks through <webview>) can't scrape it.
getAuthToken: () => ipcRenderer.invoke('get-auth-token'),
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
openExternal: (url) => ipcRenderer.invoke('open-external', url),
// Returns the persisted install state (app_install_id, ref, ...).
// Renderer attaches the ref to Stripe checkout + sign-in flows so
// the cloud can credit the affiliate. Resolves to {} if no state yet.
getInstallState: () => ipcRenderer.invoke('get-install-state'),
connectSlack: () => ipcRenderer.invoke('connect-slack'),
/** Desktop Instagram OAuth via instagram-mcp-buddy CLI (keychain). Optional env merged from tool mcp_config.env */
instagramConnect: (mcpEnv) => ipcRenderer.invoke('instagram-connect', mcpEnv),
// instagramUpgrade: (payload) => ipcRenderer.invoke('instagram-upgrade-session', payload), // disabled until trusted-notification polling is implemented
instagramLogout: (mcpEnv) => ipcRenderer.invoke('instagram-logout', mcpEnv),
/** Spawns `uvx linkedin-scraper-mcp@latest --login`; the server opens its own Chromium for sign-in. */
linkedinConnect: (payload) => ipcRenderer.invoke('linkedin-connect', payload),
/** Spawns `uvx linkedin-scraper-mcp@latest --logout` to wipe the Patchright profile. */
linkedinLogout: () => ipcRenderer.invoke('linkedin-logout'),
sendCdpCommand: (wcId, method, params) => ipcRenderer.invoke('send-cdp-command', wcId, method, params),
cdpCacheSet: (wcId, indexMap) => ipcRenderer.invoke('cdp-cache-set', wcId, indexMap),
cdpCacheGet: (wcId) => ipcRenderer.invoke('cdp-cache-get', wcId),
cdpCacheClear: (wcId) => ipcRenderer.invoke('cdp-cache-clear', wcId),
capturePage: (rect) => ipcRenderer.invoke('capture-page', rect),
getUpdateStatus: () => ipcRenderer.invoke('get-update-status'),
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
downloadUpdate: () => ipcRenderer.invoke('download-update'),
installUpdate: () => ipcRenderer.invoke('install-update'),
setAllowPrerelease: (value) => ipcRenderer.invoke('set-allow-prerelease', value),
onUpdateAvailable: (cb) => {
const listener = (_event, info) => cb(info);
ipcRenderer.on('update-available', listener);
return () => ipcRenderer.removeListener('update-available', listener);
},
onUpdateNotAvailable: (cb) => {
const listener = (_event, info) => cb(info);
ipcRenderer.on('update-not-available', listener);
return () => ipcRenderer.removeListener('update-not-available', listener);
},
onDownloadProgress: (cb) => {
const listener = (_event, progress) => cb(progress);
ipcRenderer.on('download-progress', listener);
return () => ipcRenderer.removeListener('download-progress', listener);
},
onUpdateDownloaded: (cb) => {
const listener = (_event, info) => cb(info);
ipcRenderer.on('update-downloaded', listener);
return () => ipcRenderer.removeListener('update-downloaded', listener);
},
onUpdateError: (cb) => {
const listener = (_event, message) => cb(message);
ipcRenderer.on('update-error', listener);
return () => ipcRenderer.removeListener('update-error', listener);
},
onWebviewNewWindow: (cb) => {
const listener = (_event, url, webContentsId) => cb(url, webContentsId);
ipcRenderer.on('webview-new-window', listener);
return () => ipcRenderer.removeListener('webview-new-window', listener);
},
// Deep-link callback: fires when the OS opens the app with an
// openswarm://auth?token=... URL (after Stripe-hosted checkout).
onAuthUrl: (cb) => {
const listener = (_event, url) => cb(url);
ipcRenderer.on('openswarm:auth-url', listener);
return () => ipcRenderer.removeListener('openswarm:auth-url', listener);
},
// OAuth claim deep-link channel. Receives openswarm://oauth/{provider}/complete
// after the user finishes an OAuth flow in their browser.
onOauthClaim: (cb) => {
const listener = (_event, url) => cb(url);
ipcRenderer.on('openswarm:oauth-claim', listener);
return () => ipcRenderer.removeListener('openswarm:oauth-claim', listener);
},
// Window blur/focus events — analytics signal for "user switched
// to another app" (temp-churn measurement). Throttled in main.js to
// at most once per 2s per direction so OS-level focus storms don't
// pollute the event stream.
onWindowFocus: (cb) => {
const listener = (_event, payload) => cb(payload);
ipcRenderer.on('openswarm:window-focus', listener);
return () => ipcRenderer.removeListener('openswarm:window-focus', listener);
},
// Workflow lifecycle IPCs. ScheduleFacet uses these to render the
// app-open status badge and the one-click "Fix" to enable launch-at-
// login (and the tray, which is enabled by default once setup runs).
getAppOpenInfo: () => ipcRenderer.invoke('workflows:get-app-open-info'),
setLoginItem: (value) => ipcRenderer.invoke('workflows:set-login-item', value),
enableTray: (_value) => Promise.resolve(true),
getActiveRuns: () => ipcRenderer.invoke('workflows:get-active'),
notify: (payload) => ipcRenderer.invoke('workflows:notify', payload),
// Subscribed by WebSocketManager so notification button clicks
// (Looks good / Re-run / Adjust / Open) route back into the app.
onNotificationAction: (cb) => {
const listener = (_event, payload) => cb(payload);
ipcRenderer.on('workflow:notification-action', listener);
return () => ipcRenderer.removeListener('workflow:notification-action', listener);
},
// OAuth popup callback. Fires when any child webContents navigates to
// localhost:20128/callback?code=... — main.js watches for this and
// forwards the parsed params here. Used as a belt-and-suspenders
// alongside window.opener.postMessage (which silently fails on some
// Anthropic flows that reset the opener chain during redirect).
onOauthCallback: (cb) => {
const listener = (_event, data) => cb(data);
ipcRenderer.on('openswarm:oauth-callback', listener);
return () => ipcRenderer.removeListener('openswarm:oauth-callback', listener);
},
});
})();