sliccy 4.28.2 → 4.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -16
- package/dist/node-server/bridge-security.d.ts +26 -0
- package/dist/node-server/bridge-security.js +41 -2
- package/dist/node-server/chrome-launch.d.ts +62 -1
- package/dist/node-server/chrome-launch.js +168 -2
- package/dist/node-server/electron-controller.d.ts +115 -5
- package/dist/node-server/electron-controller.js +167 -28
- package/dist/node-server/electron-main.js +37 -2
- package/dist/node-server/electron-runtime.d.ts +0 -2
- package/dist/node-server/electron-runtime.js +63 -68
- package/dist/node-server/fetch-proxy-headers.d.ts +25 -0
- package/dist/node-server/fetch-proxy-headers.js +36 -0
- package/dist/node-server/index.js +46 -11
- package/dist/node-server/routes/fetch-proxy.js +12 -12
- package/dist/ui/assets/account-store-lwRLg8qZ.js +38 -0
- package/dist/ui/assets/account-store-o8Dx0iny.js +99 -0
- package/dist/ui/assets/adobe-oauth-state-DJ7nvXJJ.js +1 -0
- package/dist/ui/assets/adobe-oauth-state-fjk_YXgL.js +1 -0
- package/dist/ui/assets/adobe-t0y7yh0d.js +2 -0
- package/dist/ui/assets/adobe-w6Q7iSKn.js +1 -0
- package/dist/ui/assets/{agent-message-to-chat-DQErB0Zj.js → agent-message-to-chat-yezUzugT.js} +1 -1
- package/dist/ui/assets/{anthropic-BaI_sJYa.js → anthropic-DwYAPtk5.js} +1 -1
- package/dist/ui/assets/{apps-BAXGsVHu.js → apps-D-6_Des4.js} +1 -1
- package/dist/ui/assets/{azure-openai-CKd81QhZ.js → azure-openai-BUQWXp4U.js} +1 -1
- package/dist/ui/assets/{azure-openai-DTsNmcxo.js → azure-openai-Cza179on.js} +1 -1
- package/dist/ui/assets/{azure-openai-responses-CMjHELHX.js → azure-openai-responses-Bnxym5u-.js} +1 -1
- package/dist/ui/assets/backend-local-mrUgrni5.js +9 -0
- package/dist/ui/assets/{bedrock-camp-DS1dgU6g.js → bedrock-camp-C1lmRgcD.js} +2 -2
- package/dist/ui/assets/bsh-watchdog-aV3rA0w_.js +60 -0
- package/dist/ui/assets/cdp-bridge-CUqQUcaF.js +1 -0
- package/dist/ui/assets/{cdp-D4JkCTey.js → cdp-z9uzg44m.js} +5 -5
- package/dist/ui/assets/composer-speech-BJF-32Qm.js +1 -0
- package/dist/ui/assets/{connect-surface-CyQMJFj3.js → connect-surface-v46dKJYB.js} +1 -1
- package/dist/ui/assets/dip-Iosy_HJh.js +1 -0
- package/dist/ui/assets/{dist-CVvNIY9j.js → dist-BHeXHQU_.js} +1 -1
- package/dist/ui/assets/{dist-Ciobg3J4.js → dist-Fdgn9mju.js} +2 -2
- package/dist/ui/assets/ensure-speech-assets-CILvdvdL.js +1 -0
- package/dist/ui/assets/{es-DjQZltrp.js → es-Z-wNZ0C3.js} +4 -4
- package/dist/ui/assets/esp8266-CL1TORn3.js +1 -0
- package/dist/ui/assets/{esptool-operations-SV6FPHJC.js → esptool-operations-CYdcX9Mk.js} +2 -2
- package/dist/ui/assets/extension-bridge-transport-DYOF2OYC.js +1 -0
- package/dist/ui/assets/fetch-body-D2PwksUW.js +1 -0
- package/dist/ui/assets/fetch-body-DDu_AU5I.js +1 -0
- package/dist/ui/assets/{fs-Bv2avR3Y.js → fs-CruOlxoS.js} +2 -2
- package/dist/ui/assets/{fs-B-yB8nmS.js → fs-crZm8xnS.js} +1 -1
- package/dist/ui/assets/github-CQtjJB1r.js +2 -0
- package/dist/ui/assets/github-FUziE_96.js +1 -0
- package/dist/ui/assets/{github-copilot--B7IUER8.js → github-copilot-DOj1kxaL.js} +1 -1
- package/dist/ui/assets/{github-copilot-CFeOxiAY.js → github-copilot-yTNPvKEy.js} +1 -1
- package/dist/ui/assets/{google-yB8wbiw4.js → google-CSaY-DQE.js} +1 -1
- package/dist/ui/assets/{google-shared-CeGHiQ17.js → google-shared-RiqqHuiz.js} +1 -1
- package/dist/ui/assets/{google-vertex-Dbruz2uo.js → google-vertex-CBHKHWKe.js} +1 -1
- package/dist/ui/assets/hear-COc8-jz9.js +1 -0
- package/dist/ui/assets/hear-Ca-dJ63I.js +1 -0
- package/dist/ui/assets/hid-picker-DUJBI46p.js +1 -0
- package/dist/ui/assets/{intercepted-oauth-tLpKagkf.js → intercepted-oauth-E9BfBX02.js} +1 -1
- package/dist/ui/assets/js-realm-worker-BIJifCVd.js +47 -0
- package/dist/ui/assets/{json-parse-BIGBjpLL.js → json-parse-CWht3lea.js} +1 -1
- package/dist/ui/assets/kernel-worker-CFGgP28u.js +10060 -0
- package/dist/ui/assets/{kokoro-IuvgZkZk.js → kokoro-B5jJ2WUJ.js} +2 -2
- package/dist/ui/assets/{kokoro-pZaPWf_-.js → kokoro-BdpwC7DT.js} +2 -2
- package/dist/ui/assets/kokoro-engine-io4cnyoV.js +1 -0
- package/dist/ui/assets/{lick-ws-bridge-CbVQgXpI.js → lick-ws-bridge-s5Y2i_Ua.js} +1 -1
- package/dist/ui/assets/{local-llm-FEU-X2iM.js → local-llm-CKo8wJkz.js} +1 -1
- package/dist/ui/assets/magick-wasm-oRIuPV7a.js +43 -0
- package/dist/ui/assets/main-CY_go8Ex.js +24 -0
- package/dist/ui/assets/{main-cherry-B1mI8ArD.js → main-cherry-BRV9Q0tu.js} +1 -1
- package/dist/ui/assets/{mistral-CJVvXrOL.js → mistral-BTNTllXI.js} +1 -1
- package/dist/ui/assets/mount-DlwSk_tU.js +1 -0
- package/dist/ui/assets/mount-bridge-client-DDnAgZEL.js +2 -0
- package/dist/ui/assets/mount-id-_7_PCY9F.js +1 -0
- package/dist/ui/assets/mount-pc-SiJqJ.js +2 -0
- package/dist/ui/assets/mount-picker-popup-Hrvzr69c.js +1 -0
- package/dist/ui/assets/{mount-table-store-CQJEV6Ij.js → mount-table-store-ggb440m4.js} +1 -1
- package/dist/ui/assets/new-session-CfjGoE_n.js +2 -0
- package/dist/ui/assets/oauth-bootstrap-Le_gIjYT.js +2 -0
- package/dist/ui/assets/oauth-service-D4iuqFv6.js +1 -0
- package/dist/ui/assets/oauth-service-YGl8VFS-.js +1 -0
- package/dist/ui/assets/{openai-codex-BpHxok9y.js → openai-codex-CU65yKca.js} +1 -1
- package/dist/ui/assets/{openai-codex-DIIqvcoZ.js → openai-codex-Dx5taSaF.js} +1 -1
- package/dist/ui/assets/{openai-codex-responses-CLrAtex4.js → openai-codex-responses-7JN1dUxK.js} +1 -1
- package/dist/ui/assets/{openai-completions-CyC3I8Ls.js → openai-completions-YNL_qr9N.js} +1 -1
- package/dist/ui/assets/{openai-responses-D3BggRn6.js → openai-responses-nczmKECx.js} +1 -1
- package/dist/ui/assets/{openai-responses-shared-GJLWYx-P.js → openai-responses-shared-9YVaeX5D.js} +1 -1
- package/dist/ui/assets/panel-responder-BKgfV-nl.js +1 -0
- package/dist/ui/assets/{panel-rpc-Ck8CVyMh.js → panel-rpc-D0J5pskz.js} +1 -1
- package/dist/ui/assets/panel-rpc-handlers-BFGsncpn.js +2 -0
- package/dist/ui/assets/{picker-approval-tzbkMw-e.js → picker-approval-Dc390nC7.js} +1 -1
- package/dist/ui/assets/picker-popup-CpulHQsi.js +1 -0
- package/dist/ui/assets/preview-vfs-responder-CSDC7Oy2.js +1 -0
- package/dist/ui/assets/provider-Lb9gUmQt.js +2 -0
- package/dist/ui/assets/provider-store-access-COfmBqcb.js +1 -0
- package/dist/ui/assets/provider-store-access-CjPYCH1J.js +1 -0
- package/dist/ui/assets/{provider-UGTRh0sb.js → provider-vMORCKxQ.js} +1 -1
- package/dist/ui/assets/{providers-C-thqKYx.js → providers-CkeJGhLV.js} +1 -1
- package/dist/ui/assets/proxied-fetch-CKDBmEMl.js +2 -0
- package/dist/ui/assets/proxied-fetch-CM0jk_Mr.js +1 -0
- package/dist/ui/assets/py-realm-worker-hOFi8vjM.js +242 -0
- package/dist/ui/assets/{pyodide-BvA4c-59.js → pyodide-CqQhZelL.js} +2 -2
- package/dist/ui/assets/{quick-llm-pCxnbK4A.js → quick-llm-CksvHcNV.js} +1 -1
- package/dist/ui/assets/{remote-cache-BffIqHgi.js → remote-cache-BL6NRenp.js} +1 -1
- package/dist/ui/assets/remote-terminal-view-Qv9yhXQm.js +5 -0
- package/dist/ui/assets/remote-vfs-client-Z2qM01yP.js +1 -0
- package/dist/ui/assets/secret-env-DncfFSea.js +1 -0
- package/dist/ui/assets/secret-topology-bwXIzzAK.js +1 -0
- package/dist/ui/assets/secrets-bridge-client-BSipj-5f.js +2 -0
- package/dist/ui/assets/serial-picker-B6s4NeaB.js +1 -0
- package/dist/ui/assets/session-freezer-1tkfzApv.js +1 -0
- package/dist/ui/assets/shared-Bn9ltvMZ.js +1 -0
- package/dist/ui/assets/speak-Bri-_DZL.js +1 -0
- package/dist/ui/assets/speak-D9WnbzxD.js +2 -0
- package/dist/ui/assets/speech-assets-bridge-B8NqCAE8.js +1 -0
- package/dist/ui/assets/speech-assets-bridge-BUDIvdid.js +1 -0
- package/dist/ui/assets/{sprinkle-bridge-DrAUCEC_.js → sprinkle-bridge-Du2dCM8G.js} +1 -1
- package/dist/ui/assets/{sprinkle-manager-C3_2AgeR.js → sprinkle-manager-BhhaIwAz.js} +1 -1
- package/dist/ui/assets/{sql-wasm-Cu5Uk_6k.js → sql-wasm-BWbkWL23.js} +1 -1
- package/dist/ui/assets/{store-DSwnBJEJ.js → store-iS53cbT5.js} +1 -1
- package/dist/ui/assets/sudo-DH2Ru5S3.js +5 -0
- package/dist/ui/assets/tool-ui-DfXu6quJ.js +1 -0
- package/dist/ui/assets/tool-ui-t6ocn6AG.js +1 -0
- package/dist/ui/assets/transformers-env-2Uj9Fgd2.js +1 -0
- package/dist/ui/assets/transformers-env-CgaV7qpU.js +1 -0
- package/dist/ui/assets/transformers.web-BxHOGVSB.js +45 -0
- package/dist/ui/assets/transformers.web-CuUo4MLm.js +39 -0
- package/dist/ui/assets/{tray-leave-runtime-Dk3OSbMO.js → tray-leave-runtime-ND08HLeb.js} +1 -1
- package/dist/ui/assets/{upgrade-detection-Dcc69AtK.js → upgrade-detection-Y3TdGYus.js} +1 -1
- package/dist/ui/assets/usb-picker-D_K0weAY.js +1 -0
- package/dist/ui/assets/{voice-reply-UcYXGV0I.js → voice-reply-DfKSbOma.js} +1 -1
- package/dist/ui/assets/wc-attach-BvytbCpS.js +13 -0
- package/dist/ui/assets/{wc-detached-CJFrvQ5w.js → wc-detached-Bw0mLsiP.js} +1 -1
- package/dist/ui/assets/wc-extension-DS-jtOJw.js +2 -0
- package/dist/ui/assets/wc-live-B3kXelNl.js +253 -0
- package/dist/ui/assets/wc-nav-tMI3vvSL.js +2 -0
- package/dist/ui/assets/wc-onboarding-Dm8BUVRJ.js +2 -0
- package/dist/ui/assets/wc-permissions-mVh0SOdZ.js +3 -0
- package/dist/ui/assets/wc-permissions-providers-BufEXbhX.js +2 -0
- package/dist/ui/assets/wc-permissions-registry-CR1rTd2H.js +1 -0
- package/dist/ui/assets/wc-permissions-registry-Dx-BRiKK.js +1 -0
- package/dist/ui/assets/wc-placeholder-DEo1-1V5.js +4 -0
- package/dist/ui/assets/wc-settings-D3QCZnur.js +24 -0
- package/dist/ui/assets/{wc-shell-BGaEWRGh.js → wc-shell-DgOdmKyR.js} +603 -101
- package/dist/ui/assets/wc-sprinkles-D5si03_4.js +2 -0
- package/dist/ui/assets/wc-tray-lTqtwMFb.js +5 -0
- package/dist/ui/assets/whisper-engine-Emfsg6Pq.js +1 -0
- package/dist/ui/assets/whisper-session-Dt-B3KmT.js +2 -0
- package/dist/ui/assets/worker-D1KsWRGg.js +1 -1
- package/dist/ui/assets/writable-vfs-client-BKZGifKZ.js +1 -0
- package/dist/ui/assets/{xai-grok-BX1SWx3y.js → xai-grok-D4a_2yZf.js} +1 -1
- package/dist/ui/assets/{xai-grok-CggP425A.js → xai-grok-Dpq_7ZV8.js} +1 -1
- package/dist/ui/electron-overlay-entry.css +37 -0
- package/dist/ui/electron-overlay-entry.js +5853 -222
- package/dist/ui/index.html +7 -3
- package/dist/ui/llm-proxy-sw.js +1 -1
- package/dist/ui/packages/webapp/index.html +7 -3
- package/dist/ui/preview-sw.js +1 -1
- package/package.json +6 -1
- package/dist/ui/assets/__vite-browser-external-CM3Eme-n.js +0 -1
- package/dist/ui/assets/account-store-Dha2nLml.js +0 -38
- package/dist/ui/assets/account-store-DmjUSua_.js +0 -99
- package/dist/ui/assets/adobe-CpNl5eLk.js +0 -2
- package/dist/ui/assets/adobe-CtI7bDCt.js +0 -1
- package/dist/ui/assets/backend-local-CMfz45hs.js +0 -9
- package/dist/ui/assets/biome_wasm-DEjrJMqV.js +0 -1
- package/dist/ui/assets/bsh-watchdog-B27e-Og-.js +0 -74
- package/dist/ui/assets/cdn-url-builder-Cq7LLgru.js +0 -1
- package/dist/ui/assets/chunk-CMxvf4Kt.js +0 -1
- package/dist/ui/assets/common-ewBM5he1.js +0 -1
- package/dist/ui/assets/composer-speech-DYshRh0w.js +0 -1
- package/dist/ui/assets/dip-De30SVSa.js +0 -1
- package/dist/ui/assets/dist-DElqKLgF.js +0 -43
- package/dist/ui/assets/esp32-BTVUAltJ.js +0 -1
- package/dist/ui/assets/esp32c2-Cvhq9547.js +0 -1
- package/dist/ui/assets/esp32c3-D9lOBWYZ.js +0 -1
- package/dist/ui/assets/esp32c5-D2poFXyG.js +0 -1
- package/dist/ui/assets/esp32c6-Ct3LxBMw.js +0 -1
- package/dist/ui/assets/esp32c61-BsudQf3e.js +0 -1
- package/dist/ui/assets/esp32h2-CPO25efF.js +0 -1
- package/dist/ui/assets/esp32p4-GHiT8voO.js +0 -1
- package/dist/ui/assets/esp32s2-DrH4e7iJ.js +0 -1
- package/dist/ui/assets/esp32s3-BHbeScNz.js +0 -1
- package/dist/ui/assets/esp8266-DNz2CP4r.js +0 -1
- package/dist/ui/assets/github-DRvbws57.js +0 -2
- package/dist/ui/assets/github-DcwUCUyU.js +0 -1
- package/dist/ui/assets/hear-20YdcW5b.js +0 -1
- package/dist/ui/assets/hear-BOsXTls8.js +0 -1
- package/dist/ui/assets/js-realm-worker-BWFis6jC.js +0 -5
- package/dist/ui/assets/kernel-worker-DuaByjq4.js +0 -9820
- package/dist/ui/assets/kokoro-engine-CHIq9iyX.js +0 -2
- package/dist/ui/assets/kokoro-engine-CjyxHaAM.js +0 -1
- package/dist/ui/assets/magick-wasm-BvNy9LFP.js +0 -1
- package/dist/ui/assets/main-D-3iO2Pb.js +0 -24
- package/dist/ui/assets/mount-B60ugIMY.js +0 -1
- package/dist/ui/assets/mount-TPowGzXN.js +0 -2
- package/dist/ui/assets/mount-id-DtHfwjbZ.js +0 -1
- package/dist/ui/assets/mount-picker-popup-BORxgnGu.js +0 -1
- package/dist/ui/assets/new-session-BjrjaQDu.js +0 -2
- package/dist/ui/assets/nodejs-BQ9Oov1A.js +0 -1
- package/dist/ui/assets/oauth-bootstrap-D15vXH8k.js +0 -2
- package/dist/ui/assets/oauth-service-CoJ-pYVD.js +0 -1
- package/dist/ui/assets/oauth-service-D49nt7eJ.js +0 -1
- package/dist/ui/assets/panel-rpc-handlers-Ca3yEyOL.js +0 -2
- package/dist/ui/assets/preview-vfs-responder-dyQBWIl5.js +0 -1
- package/dist/ui/assets/provider-CT4MGofi.js +0 -2
- package/dist/ui/assets/provider-store-access-BHHITTJF.js +0 -1
- package/dist/ui/assets/provider-store-access-BSS9maA7.js +0 -1
- package/dist/ui/assets/proxied-fetch-BBO8HwcV.js +0 -1
- package/dist/ui/assets/py-realm-worker-BnG_DxBd.js +0 -242
- package/dist/ui/assets/remote-terminal-view-CSqiig2G.js +0 -6
- package/dist/ui/assets/remote-vfs-client-BwJhhtIV.js +0 -1
- package/dist/ui/assets/secret-env-DcfTe28K.js +0 -1
- package/dist/ui/assets/session-freezer-NFd-CALu.js +0 -1
- package/dist/ui/assets/setup-sudo-KPDfIsQ7.js +0 -1
- package/dist/ui/assets/shared-B7TdM-Ik.js +0 -1
- package/dist/ui/assets/speak-D511iTBd.js +0 -1
- package/dist/ui/assets/speak-EKtIqp6j.js +0 -1
- package/dist/ui/assets/sudo-B4yfntyh.js +0 -4
- package/dist/ui/assets/tool-ui-N76-EdVc.js +0 -1
- package/dist/ui/assets/tool-ui-YOPqzMmL.js +0 -1
- package/dist/ui/assets/transformers-env-6vj5CcH9.js +0 -1
- package/dist/ui/assets/transformers-env-O5Vtz_xj.js +0 -1
- package/dist/ui/assets/transformers.web-C9_H_wuQ.js +0 -37
- package/dist/ui/assets/transformers.web-DJfCkvxA.js +0 -43
- package/dist/ui/assets/typescript-qBO0Z4wm.js +0 -448
- package/dist/ui/assets/wc-attach-B62W7N_T.js +0 -13
- package/dist/ui/assets/wc-extension-C1CIBnT2.js +0 -2
- package/dist/ui/assets/wc-live-B3Dxlxlf.js +0 -253
- package/dist/ui/assets/wc-nav-DWMYNnDA.js +0 -2
- package/dist/ui/assets/wc-onboarding-DpKk5u_V.js +0 -2
- package/dist/ui/assets/wc-placeholder-CGPP_IEm.js +0 -4
- package/dist/ui/assets/wc-settings-DjqUSzpO.js +0 -24
- package/dist/ui/assets/wc-sprinkles-B5_aCIM5.js +0 -2
- package/dist/ui/assets/wc-tray-kVPhrwG1.js +0 -5
- package/dist/ui/assets/web-BMWiUYJ-.js +0 -1
- package/dist/ui/assets/whisper-session-D89gZWHf.js +0 -2
- package/dist/ui/assets/writable-vfs-client-DMYsajEE.js +0 -1
|
@@ -6,9 +6,67 @@ import * as https from 'https';
|
|
|
6
6
|
import { promisify } from 'util';
|
|
7
7
|
import { WebSocket } from 'ws';
|
|
8
8
|
import { inflateSync } from 'zlib';
|
|
9
|
-
import {
|
|
9
|
+
import { BRIDGE_TOKEN_QUERY_PARAM, BRIDGE_WS_QUERY_PARAM } from './bridge-security.js';
|
|
10
|
+
import { buildElectronAppLaunchSpec, buildElectronOverlayBootstrapScript, buildElectronOverlayEntryUrl, ELECTRON_OVERLAY_APP_PATH, getElectronOverlayEntryDistPath, getElectronServeOrigin, selectBestOverlayTargets, } from './electron-runtime.js';
|
|
10
11
|
const execFile = promisify(nodeExecFile);
|
|
11
12
|
const ELECTRON_OVERLAY_SYNC_INTERVAL_MS = 1500;
|
|
13
|
+
/**
|
|
14
|
+
* Query param name used to mark the role of an overlay tab on the hosted
|
|
15
|
+
* launcher URL. The pinned leader carries `role=leader`; auto-follow
|
|
16
|
+
* followers carry `role=follower`. The hosted webapp interprets these to
|
|
17
|
+
* decide which tab anchors the CDP bridge vs. follows the leader.
|
|
18
|
+
*/
|
|
19
|
+
export const BRIDGE_ROLE_QUERY_PARAM = 'role';
|
|
20
|
+
export const BRIDGE_ROLE_LEADER = 'leader';
|
|
21
|
+
export const BRIDGE_ROLE_FOLLOWER = 'follower';
|
|
22
|
+
/**
|
|
23
|
+
* Build the thin-bridge config for the Electron overlay injector. The
|
|
24
|
+
* hosted-leader origin defaults to production `https://www.sliccy.ai`
|
|
25
|
+
* (overridable via `SLICC_HOSTED_LEADER_ORIGIN` / `WORKER_BASE_URL`), so
|
|
26
|
+
* the only genuinely unresolvable case is a missing per-process bridge
|
|
27
|
+
* token — in which case this returns `null` and the caller fails fast
|
|
28
|
+
* rather than falling back to a (now-retired) bundled overlay.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveOverlayThinBridge(env, bridgeToken, servePort) {
|
|
31
|
+
if (!bridgeToken)
|
|
32
|
+
return null;
|
|
33
|
+
return {
|
|
34
|
+
hostedLeaderOrigin: resolveHostedLeaderOrigin(env),
|
|
35
|
+
bridgeWsUrl: `ws://localhost:${servePort}/cdp`,
|
|
36
|
+
bridgeToken,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build the hosted launcher URL for an overlay injection. Mirrors the
|
|
41
|
+
* standalone Path A launch-URL shape (`bridge`, `bridgeToken` query
|
|
42
|
+
* params) with one Electron-specific addition: a `role` param that pins
|
|
43
|
+
* the first injected tab as the leader and marks every subsequent tab as
|
|
44
|
+
* an auto-follow follower.
|
|
45
|
+
*/
|
|
46
|
+
export function buildThinOverlayAppUrl(opts) {
|
|
47
|
+
const url = new URL(ELECTRON_OVERLAY_APP_PATH, opts.hostedLeaderOrigin);
|
|
48
|
+
url.searchParams.set(BRIDGE_WS_QUERY_PARAM, opts.bridgeWsUrl);
|
|
49
|
+
url.searchParams.set(BRIDGE_TOKEN_QUERY_PARAM, opts.bridgeToken);
|
|
50
|
+
url.searchParams.set(BRIDGE_ROLE_QUERY_PARAM, opts.role);
|
|
51
|
+
if (opts.activeTab && opts.activeTab !== 'chat') {
|
|
52
|
+
url.searchParams.set('tab', opts.activeTab);
|
|
53
|
+
}
|
|
54
|
+
return url.toString();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the hosted leader origin Chrome / Electron should open in thin
|
|
58
|
+
* mode. Prefers explicit overrides (`SLICC_HOSTED_LEADER_ORIGIN`, then
|
|
59
|
+
* `WORKER_BASE_URL`) so dev can point at staging; defaults to production
|
|
60
|
+
* `https://www.sliccy.ai`. Trailing slashes are stripped so callers can
|
|
61
|
+
* safely concatenate paths.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveHostedLeaderOrigin(env = process.env) {
|
|
64
|
+
const explicit = env['SLICC_HOSTED_LEADER_ORIGIN'] ?? env['WORKER_BASE_URL'];
|
|
65
|
+
if (explicit) {
|
|
66
|
+
return explicit.replace(/\/+$/, '');
|
|
67
|
+
}
|
|
68
|
+
return 'https://www.sliccy.ai';
|
|
69
|
+
}
|
|
12
70
|
function commandLineExecutableMatchesPattern(commandLine, pattern) {
|
|
13
71
|
// Extract the executable (first whitespace-separated token) from the command line.
|
|
14
72
|
// Only match when the target app path is the executable itself, not an argument —
|
|
@@ -427,28 +485,72 @@ function buildFulfillResponseHeaders(rawHeaders, contentLength) {
|
|
|
427
485
|
}
|
|
428
486
|
return { responseHeaders, strippedCSP };
|
|
429
487
|
}
|
|
488
|
+
/**
|
|
489
|
+
* JS probe that reports whether the overlay iframe actually loaded. Walks the
|
|
490
|
+
* `<slicc-launcher>` host's (open) shadow root to find the iframe
|
|
491
|
+
* depth-agnostically, then classifies by cross-origin reachability: the
|
|
492
|
+
* thin-bridge overlay is ALWAYS a different origin (hosted webapp) than the
|
|
493
|
+
* app document, so a committed cross-origin navigation makes
|
|
494
|
+
* `iframe.contentWindow.location.href` THROW — that throw is the ONLY success
|
|
495
|
+
* signal. Any READABLE href (`about:blank`, `''`, or a CSP-blocked swap to
|
|
496
|
+
* `chrome-error://chromewebdata/`) means the cross-origin nav did NOT commit,
|
|
497
|
+
* so the overlay did not load and the setBypassCSP escalation must fire.
|
|
498
|
+
* Returns `'ok'` only from the catch; otherwise `'no-host' / 'no-iframe' /
|
|
499
|
+
* 'no-src' / 'blank:<href>'`.
|
|
500
|
+
*/
|
|
501
|
+
export const OVERLAY_LOADED_PROBE_EXPRESSION = `(function() {
|
|
502
|
+
var host = document.getElementById('slicc-electron-overlay-root');
|
|
503
|
+
if (!host || !host.shadowRoot) return 'no-host';
|
|
504
|
+
var iframe = host.shadowRoot.querySelector('iframe');
|
|
505
|
+
if (!iframe) return 'no-iframe';
|
|
506
|
+
if (!iframe.src) return 'no-src';
|
|
507
|
+
try {
|
|
508
|
+
// Thin-bridge overlay is ALWAYS cross-origin (hosted webapp) vs the app
|
|
509
|
+
// document. A committed cross-origin navigation makes this access THROW.
|
|
510
|
+
// Any READABLE href means the cross-origin nav did NOT commit — still
|
|
511
|
+
// about:blank, or swapped to chrome-error://chromewebdata/ by a CSP block —
|
|
512
|
+
// so the overlay did NOT load and the setBypassCSP escalation must fire.
|
|
513
|
+
var href = iframe.contentWindow && iframe.contentWindow.location ? iframe.contentWindow.location.href : '';
|
|
514
|
+
return 'blank:' + href;
|
|
515
|
+
} catch (e) {
|
|
516
|
+
return 'ok';
|
|
517
|
+
}
|
|
518
|
+
})()`;
|
|
430
519
|
export class ElectronOverlayInjector {
|
|
431
520
|
cdpPort;
|
|
432
521
|
servePort;
|
|
433
|
-
|
|
522
|
+
/** Thin-mode bootstrap pair — the only overlay path. */
|
|
523
|
+
thinBootstraps;
|
|
434
524
|
probeDelayMs;
|
|
435
525
|
connections = new Map();
|
|
436
526
|
cspBypassedTargets = new Set();
|
|
527
|
+
/**
|
|
528
|
+
* URL of the target currently elected as the pinned leader. Cleared by
|
|
529
|
+
* `syncTargets` when that target disappears so the next injection
|
|
530
|
+
* re-elects a fresh leader.
|
|
531
|
+
*/
|
|
532
|
+
leaderTargetUrl = null;
|
|
437
533
|
syncTimer = null;
|
|
438
534
|
syncing = false;
|
|
439
|
-
constructor(cdpPort, servePort,
|
|
535
|
+
constructor(cdpPort, servePort, thinBootstraps, probeDelayMs = 1500) {
|
|
440
536
|
this.cdpPort = cdpPort;
|
|
441
537
|
this.servePort = servePort;
|
|
442
|
-
this.
|
|
538
|
+
this.thinBootstraps = thinBootstraps;
|
|
443
539
|
this.probeDelayMs = probeDelayMs;
|
|
444
540
|
}
|
|
445
541
|
static async create(options) {
|
|
446
542
|
const bundleSource = await loadElectronOverlayBundleSource(options);
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
543
|
+
const thinBootstraps = {
|
|
544
|
+
leader: buildElectronOverlayBootstrapScript({
|
|
545
|
+
bundleSource,
|
|
546
|
+
appUrl: buildThinOverlayAppUrl({ ...options.thinBridge, role: BRIDGE_ROLE_LEADER }),
|
|
547
|
+
}),
|
|
548
|
+
follower: buildElectronOverlayBootstrapScript({
|
|
549
|
+
bundleSource,
|
|
550
|
+
appUrl: buildThinOverlayAppUrl({ ...options.thinBridge, role: BRIDGE_ROLE_FOLLOWER }),
|
|
551
|
+
}),
|
|
552
|
+
};
|
|
553
|
+
return new ElectronOverlayInjector(options.cdpPort, options.servePort, thinBootstraps);
|
|
452
554
|
}
|
|
453
555
|
/**
|
|
454
556
|
* Test-only factory: skips bundle loading and lets tests drive the per-target
|
|
@@ -456,7 +558,15 @@ export class ElectronOverlayInjector {
|
|
|
456
558
|
* `_testing_*` hooks on `ElectronOverlayInjector`.
|
|
457
559
|
*/
|
|
458
560
|
static _createForTesting(options) {
|
|
459
|
-
return new ElectronOverlayInjector(options.cdpPort ?? 9223, options.servePort, options.
|
|
561
|
+
return new ElectronOverlayInjector(options.cdpPort ?? 9223, options.servePort, options.thinBootstraps ?? { leader: '/* test-leader */', follower: '/* test-follower */' }, options.probeDelayMs ?? 1500);
|
|
562
|
+
}
|
|
563
|
+
/** Test-only: snapshot the elected leader target URL (null when no leader). */
|
|
564
|
+
_testingLeaderTargetUrl() {
|
|
565
|
+
return this.leaderTargetUrl;
|
|
566
|
+
}
|
|
567
|
+
/** Test-only: seed the elected leader (drives the follower-election path). */
|
|
568
|
+
_testingSeedLeaderTargetUrl(url) {
|
|
569
|
+
this.leaderTargetUrl = url;
|
|
460
570
|
}
|
|
461
571
|
/** Test-only: drive the per-target connect flow without going through `start`. */
|
|
462
572
|
_testingConnectToTarget(target) {
|
|
@@ -470,6 +580,10 @@ export class ElectronOverlayInjector {
|
|
|
470
580
|
_testingBypassedTargets() {
|
|
471
581
|
return new Set(this.cspBypassedTargets);
|
|
472
582
|
}
|
|
583
|
+
/** Test-only: drive a single `syncTargets` pass without `start()`'s interval. */
|
|
584
|
+
async _testingSyncTargets() {
|
|
585
|
+
await this.syncTargets();
|
|
586
|
+
}
|
|
473
587
|
/** Test-only: close any sockets opened by `_testingConnectToTarget`. */
|
|
474
588
|
_testingCloseConnections() {
|
|
475
589
|
for (const connection of this.connections.values()) {
|
|
@@ -522,6 +636,16 @@ export class ElectronOverlayInjector {
|
|
|
522
636
|
}
|
|
523
637
|
}
|
|
524
638
|
const liveConnectionIds = new Set(injectableTargets.map((target) => target.webSocketDebuggerUrl));
|
|
639
|
+
// Thin mode: drop the elected leader if its target is no longer
|
|
640
|
+
// present so the next injection re-elects. Without this a stale
|
|
641
|
+
// leaderTargetUrl would block every future tab from becoming the
|
|
642
|
+
// pinned leader after the original leader closed.
|
|
643
|
+
if (this.leaderTargetUrl !== null) {
|
|
644
|
+
const liveTargetUrls = new Set(injectableTargets.map((target) => target.url));
|
|
645
|
+
if (!liveTargetUrls.has(this.leaderTargetUrl)) {
|
|
646
|
+
this.leaderTargetUrl = null;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
525
649
|
for (const [targetId, connection] of this.connections.entries()) {
|
|
526
650
|
if (liveConnectionIds.has(targetId))
|
|
527
651
|
continue;
|
|
@@ -549,22 +673,20 @@ export class ElectronOverlayInjector {
|
|
|
549
673
|
}
|
|
550
674
|
}
|
|
551
675
|
/**
|
|
552
|
-
* Check if the overlay iframe loaded successfully by evaluating a probe
|
|
553
|
-
*
|
|
676
|
+
* Check if the overlay iframe loaded successfully by evaluating a probe
|
|
677
|
+
* script. Walks the `<slicc-launcher>` host's (open) shadow root to find the
|
|
678
|
+
* iframe depth-agnostically and classifies by cross-origin reachability: the
|
|
679
|
+
* thin-bridge overlay is ALWAYS a different origin than the app document, so
|
|
680
|
+
* only a THROW on `iframe.contentWindow.location.href` (a committed
|
|
681
|
+
* cross-origin navigation) counts as loaded. Any readable href — including a
|
|
682
|
+
* CSP-blocked swap to `chrome-error://chromewebdata/` — means the nav did not
|
|
683
|
+
* commit, so the Fetch-proxy escalation must still fire.
|
|
684
|
+
* See {@link OVERLAY_LOADED_PROBE_EXPRESSION}.
|
|
554
685
|
*/
|
|
555
686
|
async probeOverlayIframeLoaded(ws, send) {
|
|
556
687
|
return new Promise((resolve) => {
|
|
557
688
|
const probeId = send('Runtime.evaluate', {
|
|
558
|
-
expression:
|
|
559
|
-
var host = document.getElementById('slicc-electron-overlay-root');
|
|
560
|
-
if (!host || !host.shadowRoot) return 'no-host';
|
|
561
|
-
var sidebar = host.shadowRoot.querySelector('slicc-electron-sidebar');
|
|
562
|
-
if (!sidebar || !sidebar.shadowRoot) return 'no-sidebar';
|
|
563
|
-
var iframe = sidebar.shadowRoot.querySelector('iframe');
|
|
564
|
-
if (!iframe) return 'no-iframe';
|
|
565
|
-
if (!iframe.src) return 'no-src';
|
|
566
|
-
return 'ok';
|
|
567
|
-
})()`,
|
|
689
|
+
expression: OVERLAY_LOADED_PROBE_EXPRESSION,
|
|
568
690
|
awaitPromise: false,
|
|
569
691
|
returnByValue: true,
|
|
570
692
|
});
|
|
@@ -592,13 +714,30 @@ export class ElectronOverlayInjector {
|
|
|
592
714
|
ws.on('message', onMessage);
|
|
593
715
|
});
|
|
594
716
|
}
|
|
717
|
+
/**
|
|
718
|
+
* Pick the bootstrap script for `target`, electing the leader on first
|
|
719
|
+
* use when thin-mode is active. Same target URL ↔ same role across
|
|
720
|
+
* reconnects so a page that bounces its CDP session stays the leader
|
|
721
|
+
* (no re-election on transient drops, only on `syncTargets` cleanup).
|
|
722
|
+
*/
|
|
723
|
+
resolveBootstrapForTarget(target) {
|
|
724
|
+
if (this.leaderTargetUrl === target.url) {
|
|
725
|
+
return this.thinBootstraps.leader;
|
|
726
|
+
}
|
|
727
|
+
if (this.leaderTargetUrl === null) {
|
|
728
|
+
this.leaderTargetUrl = target.url;
|
|
729
|
+
return this.thinBootstraps.leader;
|
|
730
|
+
}
|
|
731
|
+
return this.thinBootstraps.follower;
|
|
732
|
+
}
|
|
595
733
|
/**
|
|
596
734
|
* Build a script that sets the SLICC theme preference in localStorage to
|
|
597
|
-
* match the target app's detected theme, then runs the bootstrap.
|
|
735
|
+
* match the target app's detected theme, then runs the bootstrap. The
|
|
736
|
+
* bootstrap is target-specific (leader vs. follower).
|
|
598
737
|
*/
|
|
599
|
-
buildThemedBootstrap(theme) {
|
|
738
|
+
buildThemedBootstrap(theme, target) {
|
|
600
739
|
const themeScript = `try{localStorage.setItem('slicc-theme',${JSON.stringify(theme)})}catch(e){}`;
|
|
601
|
-
return `${themeScript}\n${this.
|
|
740
|
+
return `${themeScript}\n${this.resolveBootstrapForTarget(target)}`;
|
|
602
741
|
}
|
|
603
742
|
/**
|
|
604
743
|
* Handle the initial CDP `ws.on('open', ...)` event for a target: enable
|
|
@@ -625,7 +764,7 @@ export class ElectronOverlayInjector {
|
|
|
625
764
|
if (ws.readyState !== WebSocket.OPEN)
|
|
626
765
|
return;
|
|
627
766
|
send('Runtime.evaluate', {
|
|
628
|
-
expression: this.buildThemedBootstrap(theme),
|
|
767
|
+
expression: this.buildThemedBootstrap(theme, target),
|
|
629
768
|
awaitPromise: false,
|
|
630
769
|
});
|
|
631
770
|
});
|
|
@@ -642,7 +781,7 @@ export class ElectronOverlayInjector {
|
|
|
642
781
|
return;
|
|
643
782
|
console.log(`[electron-float] Injecting overlay (first attempt, theme=${theme})...`);
|
|
644
783
|
send('Runtime.evaluate', {
|
|
645
|
-
expression: this.buildThemedBootstrap(theme),
|
|
784
|
+
expression: this.buildThemedBootstrap(theme, target),
|
|
646
785
|
awaitPromise: false,
|
|
647
786
|
});
|
|
648
787
|
// After a short delay, probe whether the overlay iframe loaded.
|
|
@@ -690,7 +829,7 @@ export class ElectronOverlayInjector {
|
|
|
690
829
|
if (ws.readyState !== WebSocket.OPEN)
|
|
691
830
|
return;
|
|
692
831
|
send('Runtime.evaluate', {
|
|
693
|
-
expression: this.buildThemedBootstrap(theme),
|
|
832
|
+
expression: this.buildThemedBootstrap(theme, target),
|
|
694
833
|
awaitPromise: false,
|
|
695
834
|
});
|
|
696
835
|
});
|
|
@@ -3,13 +3,38 @@ import { app, BrowserWindow, nativeTheme, session } from 'electron';
|
|
|
3
3
|
import { readFile } from 'fs/promises';
|
|
4
4
|
import { resolve } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import {
|
|
6
|
+
import { mintBridgeToken } from './bridge-security.js';
|
|
7
|
+
import { BRIDGE_ROLE_LEADER, buildThinOverlayAppUrl, resolveHostedLeaderOrigin, } from './electron-controller.js';
|
|
8
|
+
import { buildElectronOverlayEntryUrl, buildElectronOverlayInjectionCall, buildElectronServerSpawnConfig, getElectronOverlayEntryDistPath, getElectronServeOrigin, parseElectronFloatFlags, } from './electron-runtime.js';
|
|
7
9
|
const Dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
8
10
|
const PROJECT_ROOT = resolve(Dirname, '..', '..');
|
|
9
11
|
const FLAGS = parseElectronFloatFlags(process.argv.slice(2));
|
|
10
12
|
const SERVE_ORIGIN = getElectronServeOrigin(FLAGS.servePort);
|
|
11
|
-
const OVERLAY_APP_URL = buildElectronOverlayAppUrl(SERVE_ORIGIN);
|
|
12
13
|
const ELECTRON_PARTITION = 'persist:slicc-electron-float';
|
|
14
|
+
/**
|
|
15
|
+
* Per-process thin-bridge token. The Electron float opens its overlay on
|
|
16
|
+
* the sliccy.ai-hosted launcher (Path B) and the launcher dials back to
|
|
17
|
+
* the spawned CLI server's `/cdp` WebSocket using this token via the
|
|
18
|
+
* `Sec-WebSocket-Protocol` header. The token is also forwarded to the
|
|
19
|
+
* child server as `SLICC_BRIDGE_TOKEN` so a future index.ts gate
|
|
20
|
+
* activation can enforce it without another mint.
|
|
21
|
+
*/
|
|
22
|
+
const BRIDGE_TOKEN = mintBridgeToken();
|
|
23
|
+
const HOSTED_LEADER_ORIGIN = resolveHostedLeaderOrigin(process.env);
|
|
24
|
+
const BRIDGE_WS_URL = `ws://localhost:${FLAGS.servePort}/cdp`;
|
|
25
|
+
/**
|
|
26
|
+
* Hosted launcher URL injected into every Electron float `BrowserWindow`.
|
|
27
|
+
* The float owns a single window so its overlay is always the pinned
|
|
28
|
+
* leader; external Electron apps (Slack/Teams etc.) attached via
|
|
29
|
+
* `--electron <app>` go through `ElectronOverlayInjector` which handles
|
|
30
|
+
* its own leader/follower election.
|
|
31
|
+
*/
|
|
32
|
+
const OVERLAY_APP_URL = buildThinOverlayAppUrl({
|
|
33
|
+
hostedLeaderOrigin: HOSTED_LEADER_ORIGIN,
|
|
34
|
+
bridgeWsUrl: BRIDGE_WS_URL,
|
|
35
|
+
bridgeToken: BRIDGE_TOKEN,
|
|
36
|
+
role: BRIDGE_ROLE_LEADER,
|
|
37
|
+
});
|
|
13
38
|
app.commandLine.appendSwitch('remote-debugging-port', String(FLAGS.cdpPort));
|
|
14
39
|
let cliServerProcess = null;
|
|
15
40
|
let quitting = false;
|
|
@@ -116,6 +141,12 @@ function startCliServer() {
|
|
|
116
141
|
env: {
|
|
117
142
|
...process.env,
|
|
118
143
|
PORT: String(FLAGS.servePort),
|
|
144
|
+
// Forward the per-process bridge token to the child server. The
|
|
145
|
+
// current --serve-only mode doesn't gate /cdp on it (THIN_BRIDGE_MODE
|
|
146
|
+
// is false), but the env var is in place so a follow-up index.ts
|
|
147
|
+
// change can flip the gate on without retouching this file.
|
|
148
|
+
SLICC_BRIDGE_TOKEN: BRIDGE_TOKEN,
|
|
149
|
+
SLICC_HOSTED_LEADER_ORIGIN: HOSTED_LEADER_ORIGIN,
|
|
119
150
|
},
|
|
120
151
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
121
152
|
});
|
|
@@ -161,6 +192,10 @@ async function main() {
|
|
|
161
192
|
configureElectronSession();
|
|
162
193
|
cliServerProcess = startCliServer();
|
|
163
194
|
await waitForServerReady(SERVE_ORIGIN);
|
|
195
|
+
// Print the overlay URL with the bridge token redacted — it's a
|
|
196
|
+
// session capability and must never appear in logs / process listings.
|
|
197
|
+
const sanitizedOverlayUrl = OVERLAY_APP_URL.replace(/([?&])bridgeToken=[^&]+/, '$1bridgeToken=<redacted>');
|
|
198
|
+
console.log(`[electron-float] Hosted overlay URL: ${sanitizedOverlayUrl}`);
|
|
164
199
|
await createFloatWindow(FLAGS.targetUrl);
|
|
165
200
|
app.on('activate', () => {
|
|
166
201
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
@@ -25,7 +25,6 @@ export declare const DEFAULT_ELECTRON_SERVE_PORT = 5710;
|
|
|
25
25
|
export declare const DEFAULT_ELECTRON_SERVE_HOST = "localhost";
|
|
26
26
|
export declare const DEFAULT_ELECTRON_CDP_PORT = 9223;
|
|
27
27
|
export declare const DEFAULT_ELECTRON_TARGET_URL = "about:blank";
|
|
28
|
-
export declare const DEFAULT_ELECTRON_OVERLAY_TAB = "chat";
|
|
29
28
|
export declare const ELECTRON_OVERLAY_APP_PATH = "/electron";
|
|
30
29
|
export declare const PORT_HASH_RANGE = 40;
|
|
31
30
|
/**
|
|
@@ -61,7 +60,6 @@ export declare function buildElectronServerSpawnConfig(projectRoot: string, opti
|
|
|
61
60
|
nodePath?: string;
|
|
62
61
|
}): ElectronServerSpawnConfig;
|
|
63
62
|
export declare function getElectronServeOrigin(servePort: number): string;
|
|
64
|
-
export declare function buildElectronOverlayAppUrl(serveOrigin: string, activeTab?: string): string;
|
|
65
63
|
export declare function buildElectronOverlayEntryUrl(serveOrigin: string): string;
|
|
66
64
|
export declare function getElectronOverlayEntryDistPath(projectRoot: string): string;
|
|
67
65
|
export declare function buildElectronOverlayInjectionCall(options: {
|
|
@@ -4,7 +4,6 @@ export const DEFAULT_ELECTRON_SERVE_PORT = 5710;
|
|
|
4
4
|
export const DEFAULT_ELECTRON_SERVE_HOST = 'localhost';
|
|
5
5
|
export const DEFAULT_ELECTRON_CDP_PORT = 9223;
|
|
6
6
|
export const DEFAULT_ELECTRON_TARGET_URL = 'about:blank';
|
|
7
|
-
export const DEFAULT_ELECTRON_OVERLAY_TAB = 'chat';
|
|
8
7
|
export const ELECTRON_OVERLAY_APP_PATH = '/electron';
|
|
9
8
|
// Port allocation constants
|
|
10
9
|
export const PORT_HASH_RANGE = 40;
|
|
@@ -100,74 +99,77 @@ export function getElectronAppDisplayName(appPath) {
|
|
|
100
99
|
}
|
|
101
100
|
return fileName || trimmedPath;
|
|
102
101
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
102
|
+
function isExecutableFile(path) {
|
|
103
|
+
try {
|
|
104
|
+
accessSync(path, constants.X_OK);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function findMacOSExecutable(macOSDir, expectedName) {
|
|
112
|
+
// First try the expected name (app name without .app)
|
|
113
|
+
const expectedPath = join(macOSDir, expectedName);
|
|
114
|
+
try {
|
|
115
|
+
const stat = statSync(expectedPath);
|
|
116
|
+
if (stat.isFile()) {
|
|
117
|
+
return expectedPath;
|
|
118
118
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (entries.includes('Electron')) {
|
|
137
|
-
const electronPath = join(macOSDir, 'Electron');
|
|
138
|
-
try {
|
|
139
|
-
const stat = statSync(electronPath);
|
|
140
|
-
if (stat.isFile() && isExecutable(electronPath)) {
|
|
141
|
-
return electronPath;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
catch {
|
|
145
|
-
// Continue to next fallback
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Expected path doesn't exist, scan the directory
|
|
122
|
+
}
|
|
123
|
+
// Scan MacOS directory for the main executable
|
|
124
|
+
// Many Electron apps use "Electron" as the executable name
|
|
125
|
+
// Prefer known main executable names, filter out helper processes
|
|
126
|
+
const helperPatterns = [/helper/i, /crash/i, /gpu/i, /renderer/i, /plugin/i, /utility/i];
|
|
127
|
+
try {
|
|
128
|
+
const entries = readdirSync(macOSDir);
|
|
129
|
+
// First pass: look for "Electron" executable (common in Electron apps)
|
|
130
|
+
if (entries.includes('Electron')) {
|
|
131
|
+
const electronPath = join(macOSDir, 'Electron');
|
|
132
|
+
try {
|
|
133
|
+
const stat = statSync(electronPath);
|
|
134
|
+
if (stat.isFile() && isExecutableFile(electronPath)) {
|
|
135
|
+
return electronPath;
|
|
146
136
|
}
|
|
147
137
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Skip hidden files, scripts, and helper executables
|
|
151
|
-
if (entry.startsWith('.') || entry.endsWith('.sh'))
|
|
152
|
-
continue;
|
|
153
|
-
if (helperPatterns.some((p) => p.test(entry)))
|
|
154
|
-
continue;
|
|
155
|
-
const entryPath = join(macOSDir, entry);
|
|
156
|
-
try {
|
|
157
|
-
const stat = statSync(entryPath);
|
|
158
|
-
if (stat.isFile() && isExecutable(entryPath)) {
|
|
159
|
-
return entryPath;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
catch { }
|
|
138
|
+
catch {
|
|
139
|
+
// Continue to next fallback
|
|
163
140
|
}
|
|
164
141
|
}
|
|
165
|
-
|
|
166
|
-
|
|
142
|
+
// Second pass: find first non-helper executable with execute permission
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
// Skip hidden files, scripts, and helper executables
|
|
145
|
+
if (entry.startsWith('.') || entry.endsWith('.sh'))
|
|
146
|
+
continue;
|
|
147
|
+
if (helperPatterns.some((p) => p.test(entry)))
|
|
148
|
+
continue;
|
|
149
|
+
const entryPath = join(macOSDir, entry);
|
|
150
|
+
try {
|
|
151
|
+
const stat = statSync(entryPath);
|
|
152
|
+
if (stat.isFile() && isExecutableFile(entryPath)) {
|
|
153
|
+
return entryPath;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch { }
|
|
167
157
|
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Can't read directory, fall back to expected path
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
export function resolveElectronAppExecutablePath(appPath, platform = process.platform) {
|
|
165
|
+
const resolvedAppPath = resolve(appPath);
|
|
166
|
+
if (platform === 'darwin' && resolvedAppPath.toLowerCase().endsWith('.app')) {
|
|
167
|
+
const macOSDir = join(resolvedAppPath, 'Contents', 'MacOS');
|
|
168
|
+
const expectedName = getElectronAppDisplayName(resolvedAppPath);
|
|
169
|
+
const expectedPath = join(macOSDir, expectedName);
|
|
168
170
|
// Fall back to expected path even if it doesn't exist
|
|
169
171
|
// (error will be caught later)
|
|
170
|
-
return expectedPath;
|
|
172
|
+
return findMacOSExecutable(macOSDir, expectedName) ?? expectedPath;
|
|
171
173
|
}
|
|
172
174
|
return resolvedAppPath;
|
|
173
175
|
}
|
|
@@ -247,13 +249,6 @@ export function buildElectronServerSpawnConfig(projectRoot, options) {
|
|
|
247
249
|
export function getElectronServeOrigin(servePort) {
|
|
248
250
|
return `http://${DEFAULT_ELECTRON_SERVE_HOST}:${servePort}`;
|
|
249
251
|
}
|
|
250
|
-
export function buildElectronOverlayAppUrl(serveOrigin, activeTab = DEFAULT_ELECTRON_OVERLAY_TAB) {
|
|
251
|
-
const url = new URL(ELECTRON_OVERLAY_APP_PATH, serveOrigin);
|
|
252
|
-
if (activeTab && activeTab !== DEFAULT_ELECTRON_OVERLAY_TAB) {
|
|
253
|
-
url.searchParams.set('tab', activeTab);
|
|
254
|
-
}
|
|
255
|
-
return url.toString();
|
|
256
|
-
}
|
|
257
252
|
export function buildElectronOverlayEntryUrl(serveOrigin) {
|
|
258
253
|
return new URL('/electron-overlay-entry.js', serveOrigin).toString();
|
|
259
254
|
}
|
|
@@ -18,3 +18,28 @@
|
|
|
18
18
|
* module load.
|
|
19
19
|
*/
|
|
20
20
|
export declare const FETCH_PROXY_SKIP_HEADERS: ReadonlySet<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Response-side header names the proxy must NOT copy from the upstream
|
|
23
|
+
* response onto the browser-facing response. The bridge's
|
|
24
|
+
* `createThinBridgeCorsMiddleware` / `buildCorsHeaders` set the
|
|
25
|
+
* authoritative CORS headers for the browser→bridge hop BEFORE this
|
|
26
|
+
* route runs; an upstream that emits its own `access-control-*`
|
|
27
|
+
* (e.g. `huggingface.co` → `*`, GitHub Pages → its own origin) would
|
|
28
|
+
* otherwise `res.setHeader`-clobber the bridge's value, leaving the
|
|
29
|
+
* browser with a CORS mismatch (`*` + `Allow-Credentials: true` is
|
|
30
|
+
* forbidden; a foreign origin obviously doesn't match localhost) and
|
|
31
|
+
* surfacing as an opaque `TypeError: Failed to fetch`. Stripping the
|
|
32
|
+
* full `access-control-*` family keeps the bridge as the sole CORS
|
|
33
|
+
* authority on the local hop. Names are lowercased to match
|
|
34
|
+
* `upstream.headers.forEach` key casing.
|
|
35
|
+
*/
|
|
36
|
+
export declare const FETCH_PROXY_SKIP_RESPONSE_HEADERS: ReadonlySet<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Lowercased prefixes whose upstream response headers are also skipped.
|
|
39
|
+
* `access-control-` covers the entire CORS-response family (allow-origin,
|
|
40
|
+
* allow-credentials, allow-methods, allow-headers, expose-headers,
|
|
41
|
+
* max-age, allow-private-network, plus any future entries) so the bridge
|
|
42
|
+
* middleware remains the sole CORS authority. `x-proxy-` is the proxy's
|
|
43
|
+
* own response-marker namespace — never echo an upstream value.
|
|
44
|
+
*/
|
|
45
|
+
export declare const FETCH_PROXY_SKIP_RESPONSE_PREFIXES: readonly string[];
|
|
@@ -27,5 +27,41 @@ export const FETCH_PROXY_SKIP_HEADERS = new Set([
|
|
|
27
27
|
'x-proxy-cookie',
|
|
28
28
|
'x-proxy-origin',
|
|
29
29
|
'x-proxy-referer',
|
|
30
|
+
// Kept in sync with `BRIDGE_TOKEN_HEADER` in `bridge-security.ts`
|
|
31
|
+
// (lowercased because Node lowercases incoming request header keys).
|
|
30
32
|
'x-bridge-token',
|
|
31
33
|
]);
|
|
34
|
+
/**
|
|
35
|
+
* Response-side header names the proxy must NOT copy from the upstream
|
|
36
|
+
* response onto the browser-facing response. The bridge's
|
|
37
|
+
* `createThinBridgeCorsMiddleware` / `buildCorsHeaders` set the
|
|
38
|
+
* authoritative CORS headers for the browser→bridge hop BEFORE this
|
|
39
|
+
* route runs; an upstream that emits its own `access-control-*`
|
|
40
|
+
* (e.g. `huggingface.co` → `*`, GitHub Pages → its own origin) would
|
|
41
|
+
* otherwise `res.setHeader`-clobber the bridge's value, leaving the
|
|
42
|
+
* browser with a CORS mismatch (`*` + `Allow-Credentials: true` is
|
|
43
|
+
* forbidden; a foreign origin obviously doesn't match localhost) and
|
|
44
|
+
* surfacing as an opaque `TypeError: Failed to fetch`. Stripping the
|
|
45
|
+
* full `access-control-*` family keeps the bridge as the sole CORS
|
|
46
|
+
* authority on the local hop. Names are lowercased to match
|
|
47
|
+
* `upstream.headers.forEach` key casing.
|
|
48
|
+
*/
|
|
49
|
+
export const FETCH_PROXY_SKIP_RESPONSE_HEADERS = new Set([
|
|
50
|
+
'transfer-encoding',
|
|
51
|
+
'content-encoding',
|
|
52
|
+
'content-length',
|
|
53
|
+
'www-authenticate',
|
|
54
|
+
'set-cookie',
|
|
55
|
+
]);
|
|
56
|
+
/**
|
|
57
|
+
* Lowercased prefixes whose upstream response headers are also skipped.
|
|
58
|
+
* `access-control-` covers the entire CORS-response family (allow-origin,
|
|
59
|
+
* allow-credentials, allow-methods, allow-headers, expose-headers,
|
|
60
|
+
* max-age, allow-private-network, plus any future entries) so the bridge
|
|
61
|
+
* middleware remains the sole CORS authority. `x-proxy-` is the proxy's
|
|
62
|
+
* own response-marker namespace — never echo an upstream value.
|
|
63
|
+
*/
|
|
64
|
+
export const FETCH_PROXY_SKIP_RESPONSE_PREFIXES = [
|
|
65
|
+
'access-control-',
|
|
66
|
+
'x-proxy-',
|
|
67
|
+
];
|