@vantran-se/openchamber-web 1.23.0 → 1.24.3
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/bin/cli.test.js +29 -6
- package/bin/lib/cli-http.js +5 -1
- package/bin/lib/commands-status.js +5 -0
- package/dist/assets/{CodeMirrorCommentWidgets-DFg2wnpo.js → CodeMirrorCommentWidgets-BjJHrzmU.js} +1 -1
- package/dist/assets/DiffView-COVQhWGd.js +4 -0
- package/dist/assets/FilesView-B1V5JxFX.js +13 -0
- package/dist/assets/GitView-CDOJy5Ay.js +6 -0
- package/dist/assets/GuestAttachDialog-D3S0Rjic.js +1 -0
- package/dist/assets/{JsonTreeView-BKdsjg5B.js → JsonTreeView-D0C9nU7K.js} +1 -1
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/LinearIssuesView-D7FKg1XJ.js +1 -0
- package/dist/assets/{MarkdownImageGallery-_5gEVr0X.js → MarkdownImageGallery-BrFjAVEf.js} +1 -1
- package/dist/assets/MarkdownRendererImpl-DWepTafh.js +12 -0
- package/dist/assets/{OnboardingScreen-DdP4_nuc.js → OnboardingScreen-Dyxxiqdt.js} +1 -1
- package/dist/assets/{PatchDiff-am3SAss2.js → PatchDiff-DnEhjXcC.js} +1 -1
- package/dist/assets/PierreDiffViewer-DyxZVVVR.js +210 -0
- package/dist/assets/{PlanView-BoJxdRZe.js → PlanView-LW2z_EAP.js} +4 -4
- package/dist/assets/PluginPane-B4tKno2h.js +3 -0
- package/dist/assets/SettingsView-DEaMulN9.js +29 -0
- package/dist/assets/{SettingsWindow-DVTdDSol.js → SettingsWindow-FLDJXOgQ.js} +1 -1
- package/dist/assets/SubmoduleDiffSummary-CvVwD-Vi.js +1 -0
- package/dist/assets/ToolOutputDialog-BiP0x2Rw.js +15 -0
- package/dist/assets/{ToolPartDiffPreview-C7WLk5i9.js → ToolPartDiffPreview-CkR4msKP.js} +2 -2
- package/dist/assets/{VirtualizedFileDiff-DcdgkH-x.js → VirtualizedFileDiff-0pyr_mHY.js} +4 -4
- package/dist/assets/WalkthroughView-yNj4nOP5.js +4 -0
- package/dist/assets/appThemeRegistry-BuUKhbYQ.js +1 -0
- package/dist/assets/commitAndPush-DvJKa3Ks.js +1 -0
- package/dist/assets/{computeVirtualFileMetrics-C8byMnRh.js → computeVirtualFileMetrics-DxTBMuu-.js} +2 -2
- package/dist/assets/de-DgxgFyGG.js +10 -0
- package/dist/assets/es-BFsZj5F_.js +16 -0
- package/dist/assets/fr-jdZ5KKQi.js +14 -0
- package/dist/assets/{index-BB7ryz8y.js → index-CKHjPKbq.js} +1 -1
- package/dist/assets/index-Cbci4i1W.js +40 -0
- package/dist/assets/index-DqaMWNrC.css +1 -0
- package/dist/assets/{isDiffPlainText-45ujMPGb.js → isDiffPlainText-D7FkBMP6.js} +1 -1
- package/dist/assets/{isFilePlainText-Cc4KFSYi.js → isFilePlainText-CL2vsrIZ.js} +1 -1
- package/dist/assets/ja-C5kKrhO4.js +16 -0
- package/dist/assets/ko-DXDTT_0O.js +16 -0
- package/dist/assets/main-CZALtVek.js +896 -0
- package/dist/assets/main-CgXMBNvG.js +2 -0
- package/dist/assets/{markdown-shiki.worker-D5fBU-lw.js → markdown-shiki.worker-CahLvHIq.js} +5 -5
- package/dist/assets/markdownCore-Dh7WEsdM.js +5 -0
- package/dist/assets/miniChat-CwTmDixi.js +2 -0
- package/dist/assets/mobile-CLf2vCtO.js +2 -0
- package/dist/assets/patchFileDiff-Bsba8KFp.js +13 -0
- package/dist/assets/pl-D-7x4Y8Q.js +16 -0
- package/dist/assets/pt-BR-CSBZrHa8.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-C5gAFBk0.js +2 -0
- package/dist/assets/renderMobileApp-V4hYd32H.js +25 -0
- package/dist/assets/runtimeEndpointReset-DhlHCn-2.js +25 -0
- package/dist/assets/scheduledTasksApi-DzjbLd8P.js +1 -0
- package/dist/assets/shikiHighlight-8_3csmr6.js +1 -0
- package/dist/assets/surface-B6n7wJtt.js +1 -0
- package/dist/assets/textMateThemeFromAppTheme-CHo2fCgW.js +1 -0
- package/dist/assets/tr-Dr-oJ22L.js +7 -0
- package/dist/assets/uk-9Nx83UcI.js +16 -0
- package/dist/assets/useAppFontEffects-khZXpFL_.js +7333 -0
- package/dist/assets/useInlineCommentController-DkQ_gqpn.js +2 -0
- package/dist/assets/useRootScrollLock-DdvHf9Pt.js +2 -0
- package/dist/assets/{vendor-base-ui-DdV8vhHn.js → vendor-base-ui-CsXYnR5c.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-DzlSIu_W.js → vendor-codemirror-language-data-CdzUmUyZ.js} +2 -2
- package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +1 -0
- package/dist/assets/vendor-katex-Cp579UoJ.css +1 -0
- package/dist/assets/{vendor-motion-dom-D_o3BTI3.js → vendor-motion-dom-DcCgeG69.js} +1 -1
- package/dist/assets/{vendor-remixicon-react-Dil0zKKT.js → vendor-remixicon-react-BDmGmFCA.js} +1 -1
- package/dist/assets/vendor-strip-json-comments-5dJlOkP1.js +4 -0
- package/dist/assets/{vendor-tanstack-react-virtual-g05Cx3z9.js → vendor-tanstack-react-virtual-ClYN8CzB.js} +1 -1
- package/dist/assets/{vendor-tanstack-virtual-core-Dubg10g5.js → vendor-tanstack-virtual-core-BewVQryB.js} +1 -1
- package/dist/assets/vendor-zod-CgAdXWZO.js +39 -0
- package/dist/assets/{web-components-CdGfR2XJ.js → web-components-B8IcVQAA.js} +2 -2
- package/dist/assets/zh-CN-Bxy_Hj3r.js +16 -0
- package/dist/assets/zh-TW-0BB4gpHM.js +16 -0
- package/dist/index.html +6 -4
- package/dist/mini-chat.html +6 -4
- package/dist/mobile.html +6 -4
- package/package.json +12 -9
- package/server/built-in-extensions/registry.json +4 -0
- package/server/index.d.ts +2 -0
- package/server/index.js +47 -23
- package/server/lib/agent-memory/runtime.js +7 -2
- package/server/lib/agent-memory/runtime.test.js +22 -0
- package/server/lib/agent-tool/DOCUMENTATION.md +17 -4
- package/server/lib/agent-tool/runtime.js +62 -15
- package/server/lib/agent-tool/runtime.test.js +88 -2
- package/server/lib/changelog/update-notes.js +2 -1
- package/server/lib/changelog/update-notes.test.js +19 -0
- package/server/lib/event-stream/DOCUMENTATION.md +26 -0
- package/server/lib/event-stream/delta-coalescer.js +0 -0
- package/server/lib/event-stream/delta-coalescer.test.js +292 -0
- package/server/lib/event-stream/global-hub.js +68 -13
- package/server/lib/event-stream/global-hub.test.js +182 -0
- package/server/lib/event-stream/global-ws-bridge.js +16 -18
- package/server/lib/event-stream/index.js +4 -0
- package/server/lib/event-stream/protocol.js +21 -7
- package/server/lib/event-stream/runtime.js +9 -6
- package/server/lib/event-stream/runtime.test.js +60 -0
- package/server/lib/fs/DOCUMENTATION.md +10 -1
- package/server/lib/fs/git-process.test.js +77 -0
- package/server/lib/fs/routes.js +58 -5
- package/server/lib/fs/routes.test.js +116 -0
- package/server/lib/fs/search.js +2 -1
- package/server/lib/git/DOCUMENTATION.md +30 -6
- package/server/lib/git/routes.js +37 -11
- package/server/lib/git/routes.test.js +78 -0
- package/server/lib/git/serial-refresh.js +93 -0
- package/server/lib/git/serial-refresh.test.js +112 -0
- package/server/lib/git/service.js +588 -74
- package/server/lib/git/service.test.js +492 -0
- package/server/lib/github/DOCUMENTATION.md +5 -0
- package/server/lib/github/pr-status.js +5 -5
- package/server/lib/github/pr-status.test.js +1 -1
- package/server/lib/github/routes.js +6 -6
- package/server/lib/guests/DOCUMENTATION.md +68 -0
- package/server/lib/guests/auth-store.js +184 -0
- package/server/lib/guests/auth-store.test.js +112 -0
- package/server/lib/guests/background.test.js +83 -0
- package/server/lib/guests/builtins.js +34 -0
- package/server/lib/guests/builtins.test.js +174 -0
- package/server/lib/guests/catalog.js +477 -0
- package/server/lib/guests/catalog.test.js +390 -0
- package/server/lib/guests/clone.js +297 -0
- package/server/lib/guests/extract-zip.js +136 -0
- package/server/lib/guests/extract-zip.test.js +109 -0
- package/server/lib/guests/files.js +308 -0
- package/server/lib/guests/files.test.js +262 -0
- package/server/lib/guests/grant-scope.js +81 -0
- package/server/lib/guests/grant-scope.test.js +75 -0
- package/server/lib/guests/host-session.js +73 -0
- package/server/lib/guests/host-session.test.js +30 -0
- package/server/lib/guests/html-styles.js +6 -0
- package/server/lib/guests/html-styles.test.js +45 -0
- package/server/lib/guests/html-tokens.js +73 -0
- package/server/lib/guests/html-tokens.test.js +53 -0
- package/server/lib/guests/install.js +406 -0
- package/server/lib/guests/install.test.js +338 -0
- package/server/lib/guests/oauth.js +501 -0
- package/server/lib/guests/oauth.test.js +481 -0
- package/server/lib/guests/persist.js +336 -0
- package/server/lib/guests/persist.test.js +288 -0
- package/server/lib/guests/request.js +112 -0
- package/server/lib/guests/request.test.js +206 -0
- package/server/lib/guests/routes.js +838 -0
- package/server/lib/guests/service.js +538 -0
- package/server/lib/guests/service.test.js +277 -0
- package/server/lib/guests/sockets.js +129 -0
- package/server/lib/guests/sockets.test.js +110 -0
- package/server/lib/guests/ssh-install.test.js +188 -0
- package/server/lib/guests/storage.js +68 -0
- package/server/lib/guests/storage.test.js +59 -0
- package/server/lib/guests/updates.js +294 -0
- package/server/lib/guests/updates.test.js +334 -0
- package/server/lib/guests/upload.js +50 -0
- package/server/lib/guests/upload.test.js +131 -0
- package/server/lib/linear/DOCUMENTATION.md +1 -1
- package/server/lib/message-queue/DOCUMENTATION.md +17 -1
- package/server/lib/message-queue/runtime.js +34 -1
- package/server/lib/message-queue/runtime.test.js +83 -6
- package/server/lib/notifications/DOCUMENTATION.md +1 -1
- package/server/lib/notifications/emitter-runtime.js +4 -3
- package/server/lib/notifications/routes.js +5 -0
- package/server/lib/openchamber-control/DOCUMENTATION.md +6 -0
- package/server/lib/openchamber-sessions/routes.js +62 -29
- package/server/lib/openchamber-sessions/routes.test.js +96 -0
- package/server/lib/opencode/DOCUMENTATION.md +73 -5
- package/server/lib/opencode/bootstrap-runtime.js +1 -0
- package/server/lib/opencode/core-routes.js +39 -1
- package/server/lib/opencode/env-runtime.js +5 -1
- package/server/lib/opencode/env-runtime.test.js +72 -0
- package/server/lib/opencode/feature-routes-runtime.js +30 -1
- package/server/lib/opencode/lifecycle-process.test.js +84 -0
- package/server/lib/opencode/lifecycle.js +58 -70
- package/server/lib/opencode/lifecycle.test.js +29 -0
- package/server/lib/opencode/managed-plugin-config.js +1 -1
- package/server/lib/opencode/openchamber-routes.js +72 -14
- package/server/lib/opencode/openchamber-routes.test.js +71 -3
- package/server/lib/opencode/proxy.js +33 -0
- package/server/lib/opencode/session-runtime.js +68 -0
- package/server/lib/opencode/session-runtime.test.js +43 -0
- package/server/lib/opencode/settings-helpers.js +11 -0
- package/server/lib/opencode/settings-helpers.test.js +24 -2
- package/server/lib/opencode/settings-normalization-runtime.js +2 -0
- package/server/lib/opencode/settings-normalization-runtime.test.js +13 -0
- package/server/lib/opencode/settings-registry.json +18 -0
- package/server/lib/opencode/settings-runtime.js +36 -9
- package/server/lib/opencode/settings-runtime.test.js +158 -2
- package/server/lib/opencode/shutdown-runtime.js +3 -0
- package/server/lib/opencode/shutdown-runtime.test.js +24 -0
- package/server/lib/opencode/theme-archive.js +53 -0
- package/server/lib/opencode/theme-catalog.js +171 -0
- package/server/lib/opencode/theme-catalog.test.js +125 -0
- package/server/lib/opencode/theme-import.test.js +144 -0
- package/server/lib/opencode/theme-runtime.js +100 -28
- package/server/lib/opencode/theme-runtime.test.js +15 -0
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +8 -0
- package/server/lib/permission-auto-accept/runtime.js +13 -0
- package/server/lib/permission-auto-accept/runtime.test.js +37 -1
- package/server/lib/project-context/DOCUMENTATION.md +13 -3
- package/server/lib/project-context/runtime.js +8 -2
- package/server/lib/project-context/runtime.test.js +54 -0
- package/server/lib/projects/DOCUMENTATION.md +2 -1
- package/server/lib/projects/project-config.js +40 -11
- package/server/lib/projects/project-config.test.js +97 -0
- package/server/lib/projects/project-id.js +26 -0
- package/server/lib/quota/DOCUMENTATION.md +0 -1
- package/server/lib/quota/providers/index.js +0 -7
- package/server/lib/realtime-proxy-terminal.test.js +150 -0
- package/server/lib/realtime-proxy.js +2 -0
- package/server/lib/relay/DOCUMENTATION.md +20 -0
- package/server/lib/relay/host-client.js +7 -0
- package/server/lib/relay/host-client.test.js +14 -0
- package/server/lib/routing/DOCUMENTATION.md +83 -0
- package/server/lib/routing/defaults.js +98 -0
- package/server/lib/routing/feature-flag.js +11 -0
- package/server/lib/routing/history.js +34 -0
- package/server/lib/routing/jev.js +98 -0
- package/server/lib/routing/routes.http.test.js +100 -0
- package/server/lib/routing/routes.js +81 -0
- package/server/lib/routing/runtime.js +217 -0
- package/server/lib/routing/runtime.test.js +188 -0
- package/server/lib/routing/store.js +249 -0
- package/server/lib/routing/store.test.js +96 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +9 -1
- package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +28 -0
- package/server/lib/scheduled-tasks/runtime.js +14 -4
- package/server/lib/scheduled-tasks/runtime.test.js +67 -0
- package/server/lib/security/request-security.js +9 -5
- package/server/lib/security/request-security.test.js +37 -0
- package/server/lib/session-assist/DOCUMENTATION.md +131 -66
- package/server/lib/session-assist/context.js +127 -0
- package/server/lib/session-assist/context.test.js +132 -0
- package/server/lib/session-assist/prompt.js +54 -0
- package/server/lib/session-assist/runtime.js +111 -257
- package/server/lib/session-assist/runtime.test.js +203 -0
- package/server/lib/small-model/DOCUMENTATION.md +14 -2
- package/server/lib/small-model/call.js +37 -6
- package/server/lib/small-model/call.test.js +182 -0
- package/server/lib/terminal/DOCUMENTATION.md +23 -2
- package/server/lib/terminal/runtime.js +27 -5
- package/server/lib/terminal/runtime.test.js +67 -0
- package/server/lib/terminal/shutdown.js +106 -0
- package/server/lib/terminal/shutdown.test.js +84 -0
- package/server/lib/tts/service.test.js +78 -0
- package/server/lib/ui-auth/DOCUMENTATION.md +8 -0
- package/server/lib/ui-auth/session-cookie.js +51 -0
- package/server/lib/ui-auth/session-cookie.test.js +46 -0
- package/server/lib/ui-auth/ui-auth.js +56 -17
- package/server/lib/ui-auth/ui-auth.test.js +96 -0
- package/server/lib/walkthrough/DOCUMENTATION.md +15 -5
- package/server/lib/walkthrough/pr-source.test.js +21 -0
- package/server/lib/walkthrough/pull-request.js +7 -4
- package/server/lib/walkthrough/pull-request.test.js +16 -0
- package/server/lib/walkthrough/routes.js +19 -0
- package/server/lib/walkthrough/routes.test.js +26 -0
- package/server/lib/walkthrough/sources.js +13 -2
- package/server/opencode-worktree-readiness.test.js +130 -0
- package/dist/assets/DiffView-3Dn4QHbu.js +0 -4
- package/dist/assets/DirtyBranchSwitchDialog-CwOerqVi.js +0 -1
- package/dist/assets/FilesView-BdTOpew9.js +0 -17
- package/dist/assets/GitView-Dqf9vHJh.js +0 -6
- package/dist/assets/LinearIssuesView-CmbeL7I6.js +0 -1
- package/dist/assets/MarkdownRendererImpl-wVZlx4dd.js +0 -12
- package/dist/assets/PierreDiffViewer-DCsydlKs.js +0 -161
- package/dist/assets/SettingsView-CcM8pF5O.js +0 -28
- package/dist/assets/ToolOutputDialog-BgfTbDZ4.js +0 -15
- package/dist/assets/WalkthroughView-BEVUDxuw.js +0 -4
- package/dist/assets/appThemeRegistry-BK0dHKa6.js +0 -1
- package/dist/assets/de-C8jV7J9y.js +0 -10
- package/dist/assets/es-BI6J1M5R.js +0 -16
- package/dist/assets/fr-D6oDFt7E.js +0 -14
- package/dist/assets/index-BqhiCmYS.js +0 -39
- package/dist/assets/index-FqSOBz0J.css +0 -1
- package/dist/assets/ja-C_4i5EQu.js +0 -16
- package/dist/assets/ko-BFbOTpbq.js +0 -16
- package/dist/assets/main-C9ETLKFc.js +0 -2
- package/dist/assets/main-CJmb2dF8.js +0 -930
- package/dist/assets/markdownCore-DVuZpnli.js +0 -5
- package/dist/assets/miniChat-B4Ytoq2n.js +0 -2
- package/dist/assets/mobile-BUT6-lo1.js +0 -2
- package/dist/assets/patchFileDiff-8j1apICU.js +0 -13
- package/dist/assets/pl-COFvT56l.js +0 -16
- package/dist/assets/pt-BR-DtVOO2Lj.js +0 -16
- package/dist/assets/registerCustomTheme-BBXUCRnG.js +0 -1
- package/dist/assets/renderElectronMiniChatApp-B6vUG-vF.js +0 -2
- package/dist/assets/renderMobileApp-BNgHraq6.js +0 -25
- package/dist/assets/runtimeEndpointReset-B8LYpWvr.js +0 -17
- package/dist/assets/scheduledTasksApi-GC4epIYA.js +0 -1
- package/dist/assets/shikiHighlight-BJISeZsQ.js +0 -1
- package/dist/assets/surface-BvRzS58U.js +0 -1
- package/dist/assets/tr-B8Q0Go2l.js +0 -7
- package/dist/assets/uk-Dk4a-Fn-.js +0 -16
- package/dist/assets/useAppFontEffects-DpFNefUA.js +0 -7284
- package/dist/assets/useGitComparison-Dfxaqd0s.js +0 -1
- package/dist/assets/useInlineCommentController-CscsE4t-.js +0 -2
- package/dist/assets/useRootScrollLock-BaCvuRlT.js +0 -1
- package/dist/assets/vendor-framer-motion-livSKyyV.js +0 -1
- package/dist/assets/vendor-zod-BPAANqvH.js +0 -39
- package/dist/assets/zh-CN-B_BXxG1Q.js +0 -16
- package/dist/assets/zh-TW-DLrl1SPZ.js +0 -16
- package/server/lib/mcp-reconnect/DOCUMENTATION.md +0 -55
- package/server/lib/mcp-reconnect/runtime.js +0 -122
- package/server/lib/mcp-reconnect/runtime.test.js +0 -140
- package/server/lib/quota/providers/crof.js +0 -96
- package/server/lib/quota/providers/crof.test.js +0 -98
- /package/dist/assets/{vendor-katex-k3Ppq1Ha.js → vendor-katex-CHD9BdDO.js} +0 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from 'bun:test';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { getGuestAuth, guestAuthPersistPath, patchGuestAuth } from './auth-store.js';
|
|
7
|
+
import {
|
|
8
|
+
clearGuestPendingForTests,
|
|
9
|
+
consumeGuestAuthorization,
|
|
10
|
+
credentialTarget,
|
|
11
|
+
createPkcePair,
|
|
12
|
+
encodeBasicCredential,
|
|
13
|
+
guestAuthorizationHeader,
|
|
14
|
+
guestRedirectUri,
|
|
15
|
+
saveGuestAccessToken,
|
|
16
|
+
saveGuestOAuthClient,
|
|
17
|
+
refreshGuestAccessToken,
|
|
18
|
+
startGuestAuthorization,
|
|
19
|
+
storedTokensUsable,
|
|
20
|
+
toPublicGuestAuth,
|
|
21
|
+
} from './oauth.js';
|
|
22
|
+
|
|
23
|
+
const clickupGuest = {
|
|
24
|
+
id: 'clickup',
|
|
25
|
+
integration: {
|
|
26
|
+
name: 'ClickUp',
|
|
27
|
+
description: 'Tasks',
|
|
28
|
+
oauth: {
|
|
29
|
+
authorizeUrl: 'https://app.clickup.com/api',
|
|
30
|
+
tokenUrl: 'https://api.clickup.com/api/v2/oauth/token',
|
|
31
|
+
apiOrigin: 'https://api.clickup.com',
|
|
32
|
+
account: { path: '/api/v2/user', name: 'user.username' },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
clearGuestPendingForTests();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('guestRedirectUri', () => {
|
|
42
|
+
test('stays on the server origin', () => {
|
|
43
|
+
expect(guestRedirectUri('http://127.0.0.1:4096', 'clickup'))
|
|
44
|
+
.toBe('http://127.0.0.1:4096/api/guests/clickup/oauth/callback');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('toPublicGuestAuth', () => {
|
|
49
|
+
test('never forwards tokens', () => {
|
|
50
|
+
expect(toPublicGuestAuth({
|
|
51
|
+
clientId: 'id',
|
|
52
|
+
clientSecret: 'secret',
|
|
53
|
+
accessToken: 'tok',
|
|
54
|
+
account: 'ada',
|
|
55
|
+
settings: { 'list-id': '1' },
|
|
56
|
+
})).toEqual({
|
|
57
|
+
connected: true,
|
|
58
|
+
account: 'ada',
|
|
59
|
+
hasClient: true,
|
|
60
|
+
settings: { 'list-id': '1' },
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('startGuestAuthorization', () => {
|
|
66
|
+
test('builds a PKCE authorize URL after a client id is saved', async () => {
|
|
67
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-oauth-'));
|
|
68
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
69
|
+
await patchGuestAuth('clickup', { clientId: 'app-id', clientSecret: 'app-secret', clientTarget: credentialTarget(clickupGuest.integration) }, persistPath);
|
|
70
|
+
const started = await startGuestAuthorization({
|
|
71
|
+
guest: clickupGuest,
|
|
72
|
+
persistPath,
|
|
73
|
+
origin: 'http://127.0.0.1:4096',
|
|
74
|
+
});
|
|
75
|
+
const url = new URL(started.authorizationUrl);
|
|
76
|
+
expect(url.origin + url.pathname).toBe('https://app.clickup.com/api');
|
|
77
|
+
expect(url.searchParams.get('client_id')).toBe('app-id');
|
|
78
|
+
expect(url.searchParams.get('redirect_uri')).toBe('http://127.0.0.1:4096/api/guests/clickup/oauth/callback');
|
|
79
|
+
expect(url.searchParams.get('code_challenge_method')).toBe('S256');
|
|
80
|
+
expect(url.searchParams.get('state')).toBeTruthy();
|
|
81
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('refuses start without a client id', async () => {
|
|
85
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-oauth-'));
|
|
86
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
87
|
+
try {
|
|
88
|
+
await startGuestAuthorization({
|
|
89
|
+
guest: clickupGuest,
|
|
90
|
+
persistPath,
|
|
91
|
+
origin: 'http://127.0.0.1:4096',
|
|
92
|
+
});
|
|
93
|
+
throw new Error('should have thrown');
|
|
94
|
+
} catch (error) {
|
|
95
|
+
expect(error.code).toBe('CLIENT_MISSING');
|
|
96
|
+
}
|
|
97
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('consumeGuestAuthorization', () => {
|
|
102
|
+
test('stores tokens from the token endpoint and never returns them', async () => {
|
|
103
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-oauth-'));
|
|
104
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
105
|
+
await patchGuestAuth('clickup', { clientId: 'app-id', clientSecret: 'app-secret', clientTarget: credentialTarget(clickupGuest.integration) }, persistPath);
|
|
106
|
+
const started = await startGuestAuthorization({
|
|
107
|
+
guest: clickupGuest,
|
|
108
|
+
persistPath,
|
|
109
|
+
origin: 'http://127.0.0.1:4096',
|
|
110
|
+
});
|
|
111
|
+
const state = new URL(started.authorizationUrl).searchParams.get('state');
|
|
112
|
+
const originalFetch = globalThis.fetch;
|
|
113
|
+
globalThis.fetch = async (url) => {
|
|
114
|
+
const target = String(url);
|
|
115
|
+
if (target === 'https://api.clickup.com/api/v2/oauth/token') {
|
|
116
|
+
return new Response(JSON.stringify({
|
|
117
|
+
access_token: 'access-1',
|
|
118
|
+
refresh_token: 'refresh-1',
|
|
119
|
+
token_type: 'Bearer',
|
|
120
|
+
expires_in: 3600,
|
|
121
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
122
|
+
}
|
|
123
|
+
if (target === 'https://api.clickup.com/api/v2/user') {
|
|
124
|
+
return new Response(JSON.stringify({ user: { username: 'ada' } }), {
|
|
125
|
+
status: 200,
|
|
126
|
+
headers: { 'Content-Type': 'application/json' },
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
throw new Error(`unexpected fetch ${target}`);
|
|
130
|
+
};
|
|
131
|
+
try {
|
|
132
|
+
const result = await consumeGuestAuthorization({
|
|
133
|
+
guest: clickupGuest,
|
|
134
|
+
persistPath,
|
|
135
|
+
code: 'auth-code',
|
|
136
|
+
state,
|
|
137
|
+
});
|
|
138
|
+
expect(result).toEqual({ connected: true, account: 'ada' });
|
|
139
|
+
const stored = await getGuestAuth('clickup', persistPath);
|
|
140
|
+
expect(stored.accessToken).toBe('access-1');
|
|
141
|
+
expect(toPublicGuestAuth(stored).accessToken).toBeUndefined();
|
|
142
|
+
} finally {
|
|
143
|
+
globalThis.fetch = originalFetch;
|
|
144
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('rejects a callback without state even when this guest has one pending exchange', async () => {
|
|
149
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-oauth-'));
|
|
150
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
151
|
+
await patchGuestAuth('clickup', { clientId: 'app-id', clientSecret: 'app-secret', clientTarget: credentialTarget(clickupGuest.integration) }, persistPath);
|
|
152
|
+
await startGuestAuthorization({
|
|
153
|
+
guest: clickupGuest,
|
|
154
|
+
persistPath,
|
|
155
|
+
origin: 'http://127.0.0.1:4096',
|
|
156
|
+
});
|
|
157
|
+
const originalFetch = globalThis.fetch;
|
|
158
|
+
globalThis.fetch = async (url) => {
|
|
159
|
+
const target = String(url);
|
|
160
|
+
if (target === 'https://api.clickup.com/api/v2/oauth/token') {
|
|
161
|
+
return new Response(JSON.stringify({ access_token: 'access-2' }), {
|
|
162
|
+
status: 200,
|
|
163
|
+
headers: { 'Content-Type': 'application/json' },
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (target === 'https://api.clickup.com/api/v2/user') {
|
|
167
|
+
return new Response(JSON.stringify({ user: { username: 'ada' } }), {
|
|
168
|
+
status: 200,
|
|
169
|
+
headers: { 'Content-Type': 'application/json' },
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
throw new Error(`unexpected fetch ${target}`);
|
|
173
|
+
};
|
|
174
|
+
try {
|
|
175
|
+
await expect(consumeGuestAuthorization({
|
|
176
|
+
guest: clickupGuest,
|
|
177
|
+
persistPath,
|
|
178
|
+
code: 'auth-code',
|
|
179
|
+
state: '',
|
|
180
|
+
})).rejects.toThrow();
|
|
181
|
+
} finally {
|
|
182
|
+
globalThis.fetch = originalFetch;
|
|
183
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('falls back to a JSON token body when form exchange fails', async () => {
|
|
188
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-oauth-'));
|
|
189
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
190
|
+
await patchGuestAuth('clickup', { clientId: 'app-id', clientSecret: 'app-secret', clientTarget: credentialTarget(clickupGuest.integration) }, persistPath);
|
|
191
|
+
const started = await startGuestAuthorization({
|
|
192
|
+
guest: clickupGuest,
|
|
193
|
+
persistPath,
|
|
194
|
+
origin: 'http://127.0.0.1:4096',
|
|
195
|
+
});
|
|
196
|
+
const state = new URL(started.authorizationUrl).searchParams.get('state');
|
|
197
|
+
const originalFetch = globalThis.fetch;
|
|
198
|
+
const seen = [];
|
|
199
|
+
globalThis.fetch = async (url, init) => {
|
|
200
|
+
const target = String(url);
|
|
201
|
+
if (target === 'https://api.clickup.com/api/v2/oauth/token') {
|
|
202
|
+
seen.push(init.headers['Content-Type']);
|
|
203
|
+
if (init.headers['Content-Type'] === 'application/x-www-form-urlencoded') {
|
|
204
|
+
return new Response(JSON.stringify({ error: 'invalid_request' }), { status: 400 });
|
|
205
|
+
}
|
|
206
|
+
const body = JSON.parse(init.body);
|
|
207
|
+
expect(body).toEqual({
|
|
208
|
+
client_id: 'app-id',
|
|
209
|
+
client_secret: 'app-secret',
|
|
210
|
+
code: 'auth-code',
|
|
211
|
+
});
|
|
212
|
+
return new Response(JSON.stringify({ access_token: 'access-json' }), { status: 200 });
|
|
213
|
+
}
|
|
214
|
+
if (target === 'https://api.clickup.com/api/v2/user') {
|
|
215
|
+
return new Response(JSON.stringify({ user: { username: 'ada' } }), { status: 200 });
|
|
216
|
+
}
|
|
217
|
+
throw new Error(`unexpected fetch ${target}`);
|
|
218
|
+
};
|
|
219
|
+
try {
|
|
220
|
+
const result = await consumeGuestAuthorization({
|
|
221
|
+
guest: clickupGuest,
|
|
222
|
+
persistPath,
|
|
223
|
+
code: 'auth-code',
|
|
224
|
+
state,
|
|
225
|
+
});
|
|
226
|
+
expect(result).toEqual({ connected: true, account: 'ada' });
|
|
227
|
+
expect(seen).toEqual([
|
|
228
|
+
'application/x-www-form-urlencoded',
|
|
229
|
+
'application/json',
|
|
230
|
+
]);
|
|
231
|
+
} finally {
|
|
232
|
+
globalThis.fetch = originalFetch;
|
|
233
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('rejects an unknown state', async () => {
|
|
238
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-oauth-'));
|
|
239
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
240
|
+
try {
|
|
241
|
+
await consumeGuestAuthorization({
|
|
242
|
+
guest: clickupGuest,
|
|
243
|
+
persistPath,
|
|
244
|
+
code: 'auth-code',
|
|
245
|
+
state: 'nope',
|
|
246
|
+
});
|
|
247
|
+
throw new Error('should have thrown');
|
|
248
|
+
} catch (error) {
|
|
249
|
+
expect(error.code).toBe('STATE_MISMATCH');
|
|
250
|
+
}
|
|
251
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('createPkcePair', () => {
|
|
256
|
+
test('returns a verifier and challenge', () => {
|
|
257
|
+
const pair = createPkcePair();
|
|
258
|
+
expect(pair.verifier.length).toBeGreaterThan(20);
|
|
259
|
+
expect(pair.challenge.length).toBeGreaterThan(20);
|
|
260
|
+
expect(pair.verifier).not.toBe(pair.challenge);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('saveGuestAccessToken with a basic scheme', () => {
|
|
265
|
+
const jiraGuest = {
|
|
266
|
+
id: 'jira',
|
|
267
|
+
integration: {
|
|
268
|
+
name: 'Jira',
|
|
269
|
+
description: 'Issues',
|
|
270
|
+
token: {
|
|
271
|
+
apiOrigin: 'https://acme.atlassian.net',
|
|
272
|
+
scheme: 'basic',
|
|
273
|
+
account: { path: '/rest/api/3/myself', name: 'displayName' },
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
test('encodes username:token, probes with a Basic header, and stores only the pair', async () => {
|
|
279
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-basic-'));
|
|
280
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
281
|
+
const originalFetch = globalThis.fetch;
|
|
282
|
+
let seenAuthorization = '';
|
|
283
|
+
globalThis.fetch = async (url, init) => {
|
|
284
|
+
expect(String(url)).toBe('https://acme.atlassian.net/rest/api/3/myself');
|
|
285
|
+
seenAuthorization = init.headers.Authorization;
|
|
286
|
+
return new Response(JSON.stringify({ displayName: 'Ada Lovelace' }), {
|
|
287
|
+
status: 200,
|
|
288
|
+
headers: { 'Content-Type': 'application/json' },
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
try {
|
|
292
|
+
const result = await saveGuestAccessToken({ guest: jiraGuest, persistPath, token: 'api-token', username: 'ada@acme.example' });
|
|
293
|
+
expect(result).toEqual({ connected: true, account: 'Ada Lovelace' });
|
|
294
|
+
const expected = encodeBasicCredential('ada@acme.example', 'api-token');
|
|
295
|
+
expect(seenAuthorization).toBe(`Basic ${expected}`);
|
|
296
|
+
const stored = await getGuestAuth('jira', persistPath);
|
|
297
|
+
expect(stored.accessToken).toBe(expected);
|
|
298
|
+
expect(stored.tokenType).toBe('basic');
|
|
299
|
+
expect(guestAuthorizationHeader(stored.accessToken, 'basic')).toBe(`Basic ${expected}`);
|
|
300
|
+
} finally {
|
|
301
|
+
globalThis.fetch = originalFetch;
|
|
302
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('refuses a basic integration without a username', async () => {
|
|
307
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-basic-'));
|
|
308
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
309
|
+
try {
|
|
310
|
+
await expect(saveGuestAccessToken({ guest: jiraGuest, persistPath, token: 'api-token' }))
|
|
311
|
+
.rejects.toMatchObject({ code: 'USERNAME_MISSING' });
|
|
312
|
+
} finally {
|
|
313
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test('uses the username as the account label when the manifest declares no account probe', async () => {
|
|
318
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-basic-'));
|
|
319
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
320
|
+
const guest = { ...jiraGuest, integration: { ...jiraGuest.integration, token: { apiOrigin: 'https://acme.atlassian.net', scheme: 'basic' } } };
|
|
321
|
+
try {
|
|
322
|
+
const result = await saveGuestAccessToken({ guest, persistPath, token: 'api-token', username: 'ada@acme.example' });
|
|
323
|
+
expect(result).toEqual({ connected: true, account: 'ada@acme.example' });
|
|
324
|
+
} finally {
|
|
325
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe('a package that changes its endpoints mid sign-in', () => {
|
|
331
|
+
test('the callback refuses the exchange and nothing reaches the new endpoint', async () => {
|
|
332
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-oauth-'));
|
|
333
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
334
|
+
await patchGuestAuth('clickup', { clientId: 'app-id', clientSecret: 'app-secret', clientTarget: credentialTarget(clickupGuest.integration) }, persistPath);
|
|
335
|
+
const started = await startGuestAuthorization({ guest: clickupGuest, persistPath, origin: 'http://127.0.0.1:4096' });
|
|
336
|
+
const state = new URL(started.authorizationUrl).searchParams.get('state');
|
|
337
|
+
const moved = {
|
|
338
|
+
...clickupGuest,
|
|
339
|
+
integration: { ...clickupGuest.integration, oauth: { ...clickupGuest.integration.oauth, tokenUrl: 'https://evil.example/token' } },
|
|
340
|
+
};
|
|
341
|
+
const originalFetch = globalThis.fetch;
|
|
342
|
+
const seen = [];
|
|
343
|
+
globalThis.fetch = async (url) => {
|
|
344
|
+
seen.push(String(url));
|
|
345
|
+
return new Response('{}', { status: 200 });
|
|
346
|
+
};
|
|
347
|
+
try {
|
|
348
|
+
await expect(consumeGuestAuthorization({ guest: moved, persistPath, code: 'auth-code', state }))
|
|
349
|
+
.rejects.toMatchObject({ code: 'TARGET_CHANGED' });
|
|
350
|
+
expect(seen).toEqual([]);
|
|
351
|
+
expect((await getGuestAuth('clickup', persistPath))?.accessToken).toBeUndefined();
|
|
352
|
+
} finally {
|
|
353
|
+
globalThis.fetch = originalFetch;
|
|
354
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test('stored tokens record the target and stop counting once it moves', async () => {
|
|
359
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-oauth-'));
|
|
360
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
361
|
+
await patchGuestAuth('clickup', { clientId: 'app-id', clientSecret: 'app-secret', clientTarget: credentialTarget(clickupGuest.integration) }, persistPath);
|
|
362
|
+
const started = await startGuestAuthorization({ guest: clickupGuest, persistPath, origin: 'http://127.0.0.1:4096' });
|
|
363
|
+
const state = new URL(started.authorizationUrl).searchParams.get('state');
|
|
364
|
+
const originalFetch = globalThis.fetch;
|
|
365
|
+
globalThis.fetch = async (url) => {
|
|
366
|
+
if (String(url) === 'https://api.clickup.com/api/v2/oauth/token') {
|
|
367
|
+
return new Response(JSON.stringify({ access_token: 'access-1', refresh_token: 'refresh-1', token_type: 'Bearer', expires_in: 3600 }), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
368
|
+
}
|
|
369
|
+
return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
370
|
+
};
|
|
371
|
+
try {
|
|
372
|
+
await consumeGuestAuthorization({ guest: clickupGuest, persistPath, code: 'auth-code', state });
|
|
373
|
+
const stored = await getGuestAuth('clickup', persistPath);
|
|
374
|
+
expect(stored.target).toEqual({
|
|
375
|
+
apiOrigin: 'https://api.clickup.com',
|
|
376
|
+
authorizeUrl: 'https://app.clickup.com/api',
|
|
377
|
+
tokenUrl: 'https://api.clickup.com/api/v2/oauth/token',
|
|
378
|
+
});
|
|
379
|
+
expect(storedTokensUsable(stored, clickupGuest.integration)).toBe(true);
|
|
380
|
+
const moved = { ...clickupGuest.integration, oauth: { ...clickupGuest.integration.oauth, apiOrigin: 'https://api.other.example' } };
|
|
381
|
+
expect(storedTokensUsable(stored, moved)).toBe(false);
|
|
382
|
+
} finally {
|
|
383
|
+
globalThis.fetch = originalFetch;
|
|
384
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe('client credentials after the package moved its endpoints', () => {
|
|
390
|
+
const moved = {
|
|
391
|
+
...clickupGuest,
|
|
392
|
+
integration: { ...clickupGuest.integration, oauth: { ...clickupGuest.integration.oauth, tokenUrl: 'https://evil.example/token' } },
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
test('a new Connect refuses to reuse the old client id and secret', async () => {
|
|
396
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-oauth-'));
|
|
397
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
398
|
+
await patchGuestAuth('clickup', { clientId: 'app-id', clientSecret: 'app-secret', clientTarget: credentialTarget(clickupGuest.integration) }, persistPath);
|
|
399
|
+
try {
|
|
400
|
+
await expect(startGuestAuthorization({ guest: moved, persistPath, origin: 'http://127.0.0.1:4096' }))
|
|
401
|
+
.rejects.toMatchObject({ code: 'CLIENT_MISSING' });
|
|
402
|
+
expect(toPublicGuestAuth(await getGuestAuth('clickup', persistPath), moved.integration).hasClient).toBe(false);
|
|
403
|
+
expect(toPublicGuestAuth(await getGuestAuth('clickup', persistPath), clickupGuest.integration).hasClient).toBe(true);
|
|
404
|
+
} finally {
|
|
405
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test('a refresh that finished after a new Connect for other endpoints is dropped', async () => {
|
|
410
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-oauth-'));
|
|
411
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
412
|
+
await patchGuestAuth('clickup', {
|
|
413
|
+
clientId: 'app-id',
|
|
414
|
+
clientSecret: 'app-secret',
|
|
415
|
+
clientTarget: credentialTarget(clickupGuest.integration),
|
|
416
|
+
accessToken: 'access-a',
|
|
417
|
+
refreshToken: 'refresh-a',
|
|
418
|
+
target: credentialTarget(clickupGuest.integration),
|
|
419
|
+
}, persistPath);
|
|
420
|
+
const originalFetch = globalThis.fetch;
|
|
421
|
+
let release;
|
|
422
|
+
const gate = new Promise((resolve) => { release = resolve; });
|
|
423
|
+
globalThis.fetch = async () => {
|
|
424
|
+
await gate;
|
|
425
|
+
return new Response(JSON.stringify({ access_token: 'access-a2', refresh_token: 'refresh-a2', token_type: 'Bearer', expires_in: 3600 }), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
426
|
+
};
|
|
427
|
+
try {
|
|
428
|
+
const refreshing = refreshGuestAccessToken({ guest: clickupGuest, persistPath });
|
|
429
|
+
// Meanwhile the user connected the moved package: new client, new tokens, new target.
|
|
430
|
+
await patchGuestAuth('clickup', {
|
|
431
|
+
clientId: 'app-id-b',
|
|
432
|
+
clientSecret: 'app-secret-b',
|
|
433
|
+
clientTarget: credentialTarget(moved.integration),
|
|
434
|
+
accessToken: 'access-b',
|
|
435
|
+
refreshToken: 'refresh-b',
|
|
436
|
+
target: credentialTarget(moved.integration),
|
|
437
|
+
}, persistPath);
|
|
438
|
+
release();
|
|
439
|
+
expect(await refreshing).toBeNull();
|
|
440
|
+
const stored = await getGuestAuth('clickup', persistPath);
|
|
441
|
+
expect(stored.accessToken).toBe('access-b');
|
|
442
|
+
expect(stored.refreshToken).toBe('refresh-b');
|
|
443
|
+
expect(stored.target).toEqual(credentialTarget(moved.integration));
|
|
444
|
+
} finally {
|
|
445
|
+
globalThis.fetch = originalFetch;
|
|
446
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
describe('saveGuestOAuthClient', () => {
|
|
452
|
+
const moved = {
|
|
453
|
+
...clickupGuest,
|
|
454
|
+
integration: { ...clickupGuest.integration, oauth: { ...clickupGuest.integration.oauth, tokenUrl: 'https://evil.example/token' } },
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
test('an empty secret keeps the stored one only for the same client and endpoints', async () => {
|
|
458
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-guest-oauth-'));
|
|
459
|
+
const persistPath = guestAuthPersistPath(dir);
|
|
460
|
+
try {
|
|
461
|
+
await saveGuestOAuthClient({ guest: clickupGuest, persistPath, clientId: 'app-id', clientSecret: 'app-secret' });
|
|
462
|
+
await saveGuestOAuthClient({ guest: clickupGuest, persistPath, clientId: 'app-id' });
|
|
463
|
+
expect((await getGuestAuth('clickup', persistPath)).clientSecret).toBe('app-secret');
|
|
464
|
+
|
|
465
|
+
// Same endpoints, new client id: the old secret does not belong to it.
|
|
466
|
+
await saveGuestOAuthClient({ guest: clickupGuest, persistPath, clientId: 'app-id-2' });
|
|
467
|
+
expect((await getGuestAuth('clickup', persistPath)).clientSecret).toBeUndefined();
|
|
468
|
+
|
|
469
|
+
// Same client id, moved endpoints: the old secret must not follow.
|
|
470
|
+
await saveGuestOAuthClient({ guest: clickupGuest, persistPath, clientId: 'app-id-2', clientSecret: 'secret-2' });
|
|
471
|
+
await saveGuestOAuthClient({ guest: moved, persistPath, clientId: 'app-id-2' });
|
|
472
|
+
const stored = await getGuestAuth('clickup', persistPath);
|
|
473
|
+
expect(stored.clientSecret).toBeUndefined();
|
|
474
|
+
expect(stored.clientTarget).toEqual(credentialTarget(moved.integration));
|
|
475
|
+
await expect(startGuestAuthorization({ guest: moved, persistPath, origin: 'http://127.0.0.1:4096' }))
|
|
476
|
+
.resolves.toMatchObject({ authorizationUrl: expect.stringContaining('client_id=app-id-2') });
|
|
477
|
+
} finally {
|
|
478
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
});
|