cartethyia 2.4.6 → 2.5.1
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 +45 -13
- package/dist/dashboard/assets/BannedPage-ChqnTRpZ.js +1 -0
- package/dist/dashboard/assets/BannedPage-ChqnTRpZ.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-Bcy150Jo.js → CliToolDetailPage-Bb9CNuoh.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bb9CNuoh.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DyNXefph.js → CliToolsPage-BXUaRCQQ.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BXUaRCQQ.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CisNZOUt.js +1 -0
- package/dist/dashboard/assets/CombosPage-CisNZOUt.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CATpAEOz.js → ConsoleLogPage-BOpI_LOH.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-BOpI_LOH.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-D6jEU7nL.js → CustomizationPage-DAkdvJ4k.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-DAkdvJ4k.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-CZkpn0a7.js → HealthEventsModal-DeT-VXHd.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-DeT-VXHd.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-BJlMVN4U.js → LoginPage-CytxSeGP.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CytxSeGP.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-Br3CXEau.js → ModelPicker-oQ-SDXp8.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CU7S1wJs.js +1 -0
- package/dist/dashboard/assets/OverviewPage-CU7S1wJs.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DLm2baot.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-DLm2baot.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DOXfuzfL.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-DOXfuzfL.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-wvk0ciQN.js +7 -0
- package/dist/dashboard/assets/ProxyPage-wvk0ciQN.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-Cn_fcUHE.js +1 -0
- package/dist/dashboard/assets/QuotaPage-Cn_fcUHE.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-BHW4Zt06.js → SettingsPage-8-eVadhc.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-8-eVadhc.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-VOYC_lIa.js → SetupPage-uEIXPyGO.js} +1 -1
- package/dist/dashboard/assets/SetupPage-uEIXPyGO.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DCY0TPTZ.js +1 -0
- package/dist/dashboard/assets/SortableList-DCY0TPTZ.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-BNIDLiU6.js → StudioPage-DqK5E26W.js} +4 -4
- package/dist/dashboard/assets/StudioPage-DqK5E26W.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-38JfiZ0H.js +56 -0
- package/dist/dashboard/assets/UsagePage-38JfiZ0H.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BETfaMYZ.js → api-keys-BmDbsior.js} +1 -1
- package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
- package/dist/dashboard/assets/app-BbpSarVV.css +2 -0
- package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
- package/dist/dashboard/assets/assert-BHfaei-X.js +1 -0
- package/dist/dashboard/assets/assert-BHfaei-X.js.gz +0 -0
- package/dist/dashboard/assets/{badge-BTyGk_DL.js → badge-DpXT4Ixt.js} +1 -1
- package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
- package/dist/dashboard/assets/{banner-bujTWYM4.js → banner-CSAAWLJv.js} +1 -1
- package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
- package/dist/dashboard/assets/{card-B0Bnx5CA.js → card-DEhRXXw4.js} +1 -1
- package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-DE2ug812.js → cli-tools-B0wBq-RL.js} +1 -1
- package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-R_rArH8S.js → clipboard-button-DOo6EU8O.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BWIP_EvW.js → dialog-BvQeBMfP.js} +1 -1
- package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BKcgVugF.js → drawer-CMwPXwQB.js} +1 -1
- package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
- package/dist/dashboard/assets/entry--yj84zk3.js +4 -0
- package/dist/dashboard/assets/entry--yj84zk3.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CcWkGR4a.js → entry-D4O2229X.js} +1 -1
- package/dist/dashboard/assets/{entry-CcWkGR4a.js.gz → entry-D4O2229X.js.gz} +0 -0
- package/dist/dashboard/assets/{entry-kTiTFrvh.js → entry-DKvr5Q9K.js} +1 -1
- package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-Booc5mNe.js → github-badge-zliLvBwr.js} +1 -1
- package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
- package/dist/dashboard/assets/{index-D70pChfn.js → index-CcD1eSJ6.js} +2 -2
- package/dist/dashboard/assets/index-CcD1eSJ6.js.gz +0 -0
- package/dist/dashboard/assets/{inline-D1TCqM2D.js → inline-D1IlZZ8H.js} +1 -1
- package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
- package/dist/dashboard/assets/{input-B9oyU93y.js → input-BpVZdrbc.js} +1 -1
- package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
- package/dist/dashboard/assets/{layout-BArzXirQ.js → layout-BioLW7gN.js} +1 -1
- package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-BVZzaoHF.js → list-row-CwVcGAIc.js} +1 -1
- package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
- package/dist/dashboard/assets/{live-EmuIayqp.js → live-yfEJFF9T.js} +1 -1
- package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CSPQ9xB0.js → model-bans-CT-TxSNX.js} +1 -1
- package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
- package/dist/dashboard/assets/providers/cursor.webp +0 -0
- package/dist/dashboard/assets/providers/trae.webp +0 -0
- package/dist/dashboard/assets/{routing-BExgxxBU.js → routing-Cs6jl49L.js} +1 -1
- package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
- package/dist/dashboard/assets/select-XPJ-dIw3.js +41 -0
- package/dist/dashboard/assets/select-XPJ-dIw3.js.gz +0 -0
- package/dist/dashboard/assets/settings-CfcTcOyU.js +1 -0
- package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
- package/dist/dashboard/assets/{stack-Ca5HxI5i.js → stack-C9_uUjaA.js} +1 -1
- package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
- package/dist/dashboard/assets/{state-CzUPfLXo.js → state-DA0B95UQ.js} +1 -1
- package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DOUIky_i.js → switch-Cittxy-x.js} +1 -1
- package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
- package/dist/dashboard/assets/{toast-C76WrMhc.js → toast-CNt_jzO4.js} +1 -1
- package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-D87FZwH0.js → useMutation-BQKMcJ1H.js} +1 -1
- package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +51 -52
- package/migrations/full-postgres/0015_add_payload_stages.sql +18 -0
- package/migrations/full-postgres/0016_add_share_link_slug.sql +20 -0
- package/migrations/full-postgres/0017_add_models_pro_only.sql +18 -0
- package/migrations/full-postgres/0018_add_accounts_quota_plan.sql +18 -0
- package/migrations/full-postgres/0019_add_models_free_tier.sql +15 -0
- package/migrations/lite-sqlite3/0015_add_payload_stages.sql +18 -0
- package/migrations/lite-sqlite3/0016_add_share_link_slug.sql +20 -0
- package/migrations/lite-sqlite3/0017_add_models_pro_only.sql +18 -0
- package/migrations/lite-sqlite3/0018_add_accounts_quota_plan.sql +18 -0
- package/migrations/lite-sqlite3/0019_add_models_free_tier.sql +15 -0
- package/package.json +3 -2
- package/scripts/cli/command/console-menu.ts +30 -11
- package/scripts/cli/command/start.ts +77 -13
- package/scripts/cli/command/tray.ts +47 -8
- package/scripts/cli/command/update.ts +1 -1
- package/scripts/cli/gateway/instance-lock.ts +105 -0
- package/scripts/cli/gateway/spawn.ts +173 -101
- package/scripts/cli/ui/background.ts +110 -49
- package/src/boot/app.ts +20 -6
- package/src/boot/dependencies.ts +19 -0
- package/src/boot/elysia-typebox-bridge.ts +37 -0
- package/src/boot/lifecycle.ts +2 -0
- package/src/config/cooldowns.ts +138 -138
- package/src/config/providers.ts +5 -5
- package/src/config/runtime.ts +19 -3
- package/src/config/spec.ts +9 -0
- package/src/console/api-keys/contracts.ts +414 -391
- package/src/console/api-keys/routes.ts +880 -870
- package/src/console/auth/account-reset-store.ts +14 -0
- package/src/console/auth/account-reset.ts +3 -2
- package/src/console/auth/setup-service.ts +6 -3
- package/src/console/domain-registration.ts +9 -2
- package/src/console/observability/contracts.ts +475 -455
- package/src/console/observability/store.ts +1015 -952
- package/src/console/providers/catalog/contracts.ts +31 -0
- package/src/console/providers/catalog/provider-operations.ts +8 -0
- package/src/console/providers/catalog/store.ts +1532 -1525
- package/src/console/providers/oauth/callback-listener.ts +18 -3
- package/src/console/providers/oauth/routes.ts +38 -15
- package/src/console/quota/refresh.ts +19 -0
- package/src/console/updates/routes.ts +162 -4
- package/src/main.ts +3 -0
- package/src/network/http2-bidi.ts +85 -0
- package/src/network/outbound-fetch.ts +1 -26
- package/src/network/pool/agent.ts +478 -460
- package/src/network/pool/resolver.ts +1104 -544
- package/src/network/pool-health-machine.ts +12 -5
- package/src/observability/payload-capture.ts +344 -324
- package/src/persistence/db-pg.ts +9 -3
- package/src/persistence/db-sqlite.ts +497 -495
- package/src/persistence/health-event-flush.ts +137 -0
- package/src/persistence/migrate.ts +181 -129
- package/src/persistence/schema.ts +1360 -1295
- package/src/persistence/settings-revision.ts +25 -0
- package/src/persistence/shared-cache.ts +217 -0
- package/src/persistence/stores/share-store.ts +556 -528
- package/src/persistence/usage-flush.ts +61 -1
- package/src/protocol/canonical-model.ts +23 -1
- package/src/protocol/response/chat.ts +143 -10
- package/src/protocol/response/omp-tool-call-markup.ts +221 -0
- package/src/providers/default-registry.ts +173 -1
- package/src/providers/discovery/openai-model-discovery.ts +39 -3
- package/src/providers/discovery/openrouter-free-roster.ts +98 -0
- package/src/providers/discovery/probing-service.ts +1022 -1001
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +39 -0
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +913 -914
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +18 -5
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +4 -0
- package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -175
- package/src/providers/integrations/buddy/codebuddy/shared.ts +15 -6
- package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -204
- package/src/providers/integrations/buddy/workbuddy/shared.ts +3 -3
- package/src/providers/integrations/claude/claude.ts +606 -582
- package/src/providers/integrations/cline/cline-quota.ts +18 -16
- package/src/providers/integrations/cline/cline.ts +539 -468
- package/src/providers/integrations/commandcode/commandcode.ts +675 -541
- package/src/providers/integrations/commandcode/discovery.ts +116 -0
- package/src/providers/integrations/commandcode/quota.ts +160 -0
- package/src/providers/integrations/cursor/cursor-auth.ts +418 -0
- package/src/providers/integrations/cursor/cursor-catalog.ts +368 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +300 -0
- package/src/providers/integrations/cursor/cursor-identity.ts +92 -0
- package/src/providers/integrations/cursor/cursor-request.ts +471 -0
- package/src/providers/integrations/cursor/cursor-session-manager.ts +281 -0
- package/src/providers/integrations/cursor/cursor-shared.ts +108 -0
- package/src/providers/integrations/cursor/cursor.ts +960 -0
- package/src/providers/integrations/cursor/protocol/framing.ts +129 -0
- package/src/providers/integrations/cursor/protocol/messages.ts +2355 -0
- package/src/providers/integrations/cursor/protocol/struct.ts +305 -0
- package/src/providers/integrations/github/github.ts +4 -4
- package/src/providers/integrations/hermes.ts +79 -8
- package/src/providers/integrations/kilo/kilo-discovery.ts +10 -1
- package/src/providers/integrations/kilo/kilo.ts +153 -146
- package/src/providers/integrations/opencode/provider.ts +532 -538
- package/src/providers/integrations/openrouter/provider.ts +139 -281
- package/src/providers/integrations/qoder/qoder-catalog.ts +450 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +310 -257
- package/src/providers/integrations/qoder/qoder-realm.ts +106 -0
- package/src/providers/integrations/qoder/qoder-virtual.ts +77 -0
- package/src/providers/integrations/qoder/qoder.ts +865 -730
- package/src/providers/integrations/trae/trae-catalog.ts +190 -0
- package/src/providers/integrations/trae/trae-headers.ts +149 -0
- package/src/providers/integrations/trae/trae-oauth.ts +773 -0
- package/src/providers/integrations/trae/trae-protocol.ts +400 -0
- package/src/providers/integrations/trae/trae-realm.ts +220 -0
- package/src/providers/integrations/trae/trae.ts +458 -0
- package/src/providers/operations/account-health-service.ts +107 -10
- package/src/providers/operations/client-versions.ts +133 -6
- package/src/providers/operations/provider-catalog-seeder.ts +95 -86
- package/src/providers/operations/provider-credential-service.ts +46 -7
- package/src/providers/provider-contracts.ts +76 -0
- package/src/providers/provider-metadata.ts +78 -6
- package/src/providers/provider-model-definition.ts +373 -349
- package/src/security/api-key-auth.ts +126 -8
- package/src/transport/contracts.ts +3 -0
- package/src/transport/dispatch/attempt-finalize.ts +32 -1
- package/src/transport/dispatch/attempt-loop.ts +530 -515
- package/src/transport/dispatch/generation.ts +88 -21
- package/src/transport/dispatch/leases.ts +99 -2
- package/src/transport/dispatch/proxy-request.ts +60 -3
- package/src/transport/dispatch/streaming/finalize.ts +16 -1
- package/src/transport/dispatch/streaming/stream.ts +22 -2
- package/src/transport/dispatch/upstream.ts +19 -1
- package/src/transport/guards/auth-guard.ts +26 -1
- package/src/transport/guards/body-policy.ts +5 -4
- package/src/transport/request/preparer.ts +103 -0
- package/src/transport/request/state.ts +14 -0
- package/src/transport/routing/plan-gate.ts +45 -0
- package/src/transport/routing/route-catalog.ts +799 -777
- package/src/transport/routing/route-model.ts +52 -1
- package/src/transport/routing/router.ts +51 -0
- package/dist/dashboard/assets/BannedPage-QhvbJB9o.js +0 -1
- package/dist/dashboard/assets/BannedPage-QhvbJB9o.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-Bcy150Jo.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DyNXefph.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-Cak0ljnO.js +0 -1
- package/dist/dashboard/assets/CombosPage-Cak0ljnO.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-D6jEU7nL.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BJlMVN4U.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-Br3CXEau.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CXiumA53.js +0 -7
- package/dist/dashboard/assets/ProxyPage-CXiumA53.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js +0 -1
- package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BHW4Zt06.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-VOYC_lIa.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DpOTnGuH.js +0 -1
- package/dist/dashboard/assets/SortableList-DpOTnGuH.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-BNIDLiU6.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BAFaSDQV.js +0 -56
- package/dist/dashboard/assets/UsagePage-BAFaSDQV.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BETfaMYZ.js.gz +0 -0
- package/dist/dashboard/assets/app-BLCvGdzb.css +0 -2
- package/dist/dashboard/assets/app-BLCvGdzb.css.gz +0 -0
- package/dist/dashboard/assets/assert-7J6_LVD3.js +0 -1
- package/dist/dashboard/assets/assert-7J6_LVD3.js.gz +0 -0
- package/dist/dashboard/assets/badge-BTyGk_DL.js.gz +0 -0
- package/dist/dashboard/assets/banner-bujTWYM4.js.gz +0 -0
- package/dist/dashboard/assets/card-B0Bnx5CA.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-DE2ug812.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-R_rArH8S.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BWIP_EvW.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BKcgVugF.js.gz +0 -0
- package/dist/dashboard/assets/entry-7dpgvpJ3.js +0 -4
- package/dist/dashboard/assets/entry-7dpgvpJ3.js.gz +0 -0
- package/dist/dashboard/assets/entry-kTiTFrvh.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-Booc5mNe.js.gz +0 -0
- package/dist/dashboard/assets/index-D70pChfn.js.gz +0 -0
- package/dist/dashboard/assets/inline-D1TCqM2D.js.gz +0 -0
- package/dist/dashboard/assets/input-B9oyU93y.js.gz +0 -0
- package/dist/dashboard/assets/layout-BArzXirQ.js.gz +0 -0
- package/dist/dashboard/assets/list-row-BVZzaoHF.js.gz +0 -0
- package/dist/dashboard/assets/live-EmuIayqp.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CSPQ9xB0.js.gz +0 -0
- package/dist/dashboard/assets/routing-BExgxxBU.js.gz +0 -0
- package/dist/dashboard/assets/select-C9mMoEpJ.js +0 -41
- package/dist/dashboard/assets/select-C9mMoEpJ.js.gz +0 -0
- package/dist/dashboard/assets/settings-C4_Gn_wq.js +0 -1
- package/dist/dashboard/assets/settings-C4_Gn_wq.js.gz +0 -0
- package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js +0 -1
- package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js.gz +0 -0
- package/dist/dashboard/assets/stack-Ca5HxI5i.js.gz +0 -0
- package/dist/dashboard/assets/state-CzUPfLXo.js.gz +0 -0
- package/dist/dashboard/assets/switch-DOUIky_i.js.gz +0 -0
- package/dist/dashboard/assets/toast-C76WrMhc.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-D87FZwH0.js.gz +0 -0
- package/src/network/tls/bun-client-hello.ts +0 -112
- /package/dist/dashboard/assets/{app-Cjq7nrwu.js → app-C-Y4oZT0.js} +0 -0
- /package/dist/dashboard/assets/{app-Cjq7nrwu.js.gz → app-C-Y4oZT0.js.gz} +0 -0
|
@@ -1,257 +1,310 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Qoder device-code OAuth client.
|
|
3
|
-
*
|
|
4
|
-
* Qoder's login is a custom device flow, not RFC 8628: the PKCE pair, nonce,
|
|
5
|
-
* and machine id are generated locally, the user opens
|
|
6
|
-
* `qoder.com/device/selectAccounts` in a browser, and the gateway polls the
|
|
7
|
-
* region's deviceToken endpoint until a `dt-...` token appears. The flow uses
|
|
8
|
-
* the same endpoints as the upstream client.
|
|
9
|
-
*
|
|
10
|
-
* The issued token is used directly at dispatch: `dt-...` tokens skip the
|
|
11
|
-
* PAT→jobToken exchange. There is no working refresh grant —
|
|
12
|
-
* the upstream refresh endpoint answers 403 for this flow — so no refresher
|
|
13
|
-
* is registered and the token is used exactly as issued until the operator
|
|
14
|
-
* re-runs login. The account's Qoder user id and machine id travel in
|
|
15
|
-
* `auth_state`, because the token itself carries neither and every dispatch
|
|
16
|
-
* needs both for COSY signing.
|
|
17
|
-
*/
|
|
18
|
-
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
readonly
|
|
49
|
-
readonly
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
parsed =
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Qoder device-code OAuth client.
|
|
3
|
+
*
|
|
4
|
+
* Qoder's login is a custom device flow, not RFC 8628: the PKCE pair, nonce,
|
|
5
|
+
* and machine id are generated locally, the user opens
|
|
6
|
+
* `qoder.com/device/selectAccounts` in a browser, and the gateway polls the
|
|
7
|
+
* region's deviceToken endpoint until a `dt-...` token appears. The flow uses
|
|
8
|
+
* the same endpoints as the upstream client.
|
|
9
|
+
*
|
|
10
|
+
* The issued token is used directly at dispatch: `dt-...` tokens skip the
|
|
11
|
+
* PAT→jobToken exchange. There is no working refresh grant —
|
|
12
|
+
* the upstream refresh endpoint answers 403 for this flow — so no refresher
|
|
13
|
+
* is registered and the token is used exactly as issued until the operator
|
|
14
|
+
* re-runs login. The account's Qoder user id and machine id travel in
|
|
15
|
+
* `auth_state`, because the token itself carries neither and every dispatch
|
|
16
|
+
* needs both for COSY signing.
|
|
17
|
+
*/
|
|
18
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
19
|
+
import { QODER_REALMS, type QoderRealm } from "./qoder-realm";
|
|
20
|
+
import {
|
|
21
|
+
base64UrlEncode,
|
|
22
|
+
nonEmptyTrimmedString,
|
|
23
|
+
record,
|
|
24
|
+
} from "../../authentication/oauth-flow-store";
|
|
25
|
+
import type {
|
|
26
|
+
OAuthDeviceFlowContext,
|
|
27
|
+
OAuthDevicePollResult,
|
|
28
|
+
OAuthDeviceStartResult,
|
|
29
|
+
OAuthExchangeResult,
|
|
30
|
+
} from "../../authentication/oauth-flow-store";
|
|
31
|
+
import type { OAuthTokenRefreshResult } from "../../authentication/oauth-refresh-service";
|
|
32
|
+
import { OAuthDeviceFlow } from "../../authentication/oauth-device-flow";
|
|
33
|
+
|
|
34
|
+
/** Global/intl endpoints. Kept as the default realm's values, unchanged. */
|
|
35
|
+
export const QODER_LOGIN_URL = "https://qoder.com/device/selectAccounts" as const;
|
|
36
|
+
export const QODER_DEVICE_TOKEN_URL = "https://openapi.qoder.sh/api/v1/deviceToken/poll" as const;
|
|
37
|
+
export const QODER_USERINFO_URL = "https://openapi.qoder.sh/api/v1/userinfo" as const;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Per-realm device endpoints, derived from `QODER_REALMS`.
|
|
41
|
+
*
|
|
42
|
+
* Every realm's openapi host differs, so the three URLs are built from the
|
|
43
|
+
* table rather than hardcoded — a CN login pointed at `openapi.qoder.sh` is
|
|
44
|
+
* refused before it ever reaches an approval screen.
|
|
45
|
+
*/
|
|
46
|
+
export function qoderOAuthUrls(realm: QoderRealm): {
|
|
47
|
+
readonly loginUrl: string;
|
|
48
|
+
readonly deviceTokenUrl: string;
|
|
49
|
+
readonly userinfoUrl: string;
|
|
50
|
+
} {
|
|
51
|
+
const config = QODER_REALMS[realm];
|
|
52
|
+
return {
|
|
53
|
+
loginUrl: `${config.website}/device/selectAccounts`,
|
|
54
|
+
deviceTokenUrl: `${config.openapi}/api/v1/deviceToken/poll`,
|
|
55
|
+
userinfoUrl: `${config.openapi}/api/v1/userinfo`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Device authorizations live 5 minutes in the dashboard before expiring. */
|
|
60
|
+
const DEVICE_EXPIRY_SECONDS = 300;
|
|
61
|
+
/** Dashboard poll cadence for the device authorization. */
|
|
62
|
+
const DEVICE_INTERVAL_SECONDS = 5;
|
|
63
|
+
/** Individual poll request timeout; a stalled socket is a failed attempt, retried next tick. */
|
|
64
|
+
const POLL_TIMEOUT_MS = 15_000;
|
|
65
|
+
/** Token lifetime assumed when the upstream omits every expiry hint. */
|
|
66
|
+
const EXPIRY_FALLBACK_MS = 30 * 24 * 60 * 60 * 1000;
|
|
67
|
+
|
|
68
|
+
/** Non-secret per-account Qoder identity persisted in `auth_state`. */
|
|
69
|
+
export interface QoderOAuthState {
|
|
70
|
+
readonly userId: string;
|
|
71
|
+
readonly machineId: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Reads the persisted Qoder identity; `undefined` when the account predates it. */
|
|
75
|
+
export function parseQoderOAuthState(value: unknown): QoderOAuthState | undefined {
|
|
76
|
+
const root = record(value);
|
|
77
|
+
if (root === undefined) return undefined;
|
|
78
|
+
const userId = nonEmptyTrimmedString(root.userId);
|
|
79
|
+
const machineId = nonEmptyTrimmedString(root.machineId);
|
|
80
|
+
if (userId === undefined || machineId === undefined) return undefined;
|
|
81
|
+
return { userId, machineId };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Converts the upstream's expiry hint into a Unix-millisecond timestamp.
|
|
87
|
+
*
|
|
88
|
+
* Accepts a numeric ms-epoch (number or numeric string), an RFC3339 string,
|
|
89
|
+
* or seconds-from-now. Falls back to now + 30 days when both are missing.
|
|
90
|
+
* Numeric strings are checked before `Date.parse` because it accepts short
|
|
91
|
+
* numerics like "2026" as years and would otherwise return a misleading date.
|
|
92
|
+
*/
|
|
93
|
+
export function parseQoderExpiry(expiresAt: unknown, expiresInSeconds: unknown): number {
|
|
94
|
+
if (typeof expiresAt === "number" && Number.isFinite(expiresAt) && expiresAt > 0) {
|
|
95
|
+
return expiresAt;
|
|
96
|
+
}
|
|
97
|
+
const trimmed = typeof expiresAt === "string" ? expiresAt.trim() : "";
|
|
98
|
+
if (trimmed) {
|
|
99
|
+
if (/^\d+$/.test(trimmed)) {
|
|
100
|
+
const ms = Number.parseInt(trimmed, 10);
|
|
101
|
+
if (Number.isFinite(ms) && ms > 0) return ms;
|
|
102
|
+
}
|
|
103
|
+
const parsed = Date.parse(trimmed);
|
|
104
|
+
if (!Number.isNaN(parsed)) return parsed;
|
|
105
|
+
}
|
|
106
|
+
if (
|
|
107
|
+
typeof expiresInSeconds === "number" &&
|
|
108
|
+
Number.isFinite(expiresInSeconds) &&
|
|
109
|
+
expiresInSeconds >= 0
|
|
110
|
+
) {
|
|
111
|
+
return Date.now() + expiresInSeconds * 1000;
|
|
112
|
+
}
|
|
113
|
+
return Date.now() + EXPIRY_FALLBACK_MS;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface QoderDeviceState {
|
|
117
|
+
readonly verifier: string;
|
|
118
|
+
readonly machineId: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parseDeviceState(value: string | undefined): QoderDeviceState | undefined {
|
|
122
|
+
if (!value) return undefined;
|
|
123
|
+
let parsed: unknown;
|
|
124
|
+
try {
|
|
125
|
+
parsed = JSON.parse(value) as unknown;
|
|
126
|
+
} catch {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const root = record(parsed);
|
|
130
|
+
const verifier = root === undefined ? undefined : nonEmptyTrimmedString(root.verifier);
|
|
131
|
+
const machineId = root === undefined ? undefined : nonEmptyTrimmedString(root.machineId);
|
|
132
|
+
if (verifier === undefined || machineId === undefined) return undefined;
|
|
133
|
+
return { verifier, machineId };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export class QoderOAuthClient extends OAuthDeviceFlow {
|
|
137
|
+
override readonly supportsDeviceCode = true;
|
|
138
|
+
override readonly supportsBrowserCode = false;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Which deployment this login talks to.
|
|
142
|
+
*
|
|
143
|
+
* Defaults to intl, whose parameter set is left exactly as it was working:
|
|
144
|
+
* the CN shape below is the only branch that changes behavior, so adding a
|
|
145
|
+
* realm cannot regress an existing global login.
|
|
146
|
+
*/
|
|
147
|
+
private readonly realm: QoderRealm;
|
|
148
|
+
|
|
149
|
+
constructor(fetchFn: typeof fetch = globalThis.fetch, realm: QoderRealm = "intl") {
|
|
150
|
+
super(fetchFn);
|
|
151
|
+
this.realm = realm;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The three device endpoints for this client's realm. */
|
|
155
|
+
get urls(): { loginUrl: string; deviceTokenUrl: string; userinfoUrl: string } {
|
|
156
|
+
return qoderOAuthUrls(this.realm);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
protected override readonly providerLabel = "Qoder";
|
|
160
|
+
// The device endpoints take no client id; the abstract field stays empty
|
|
161
|
+
// rather than inventing one, and every request below omits it.
|
|
162
|
+
protected override readonly clientId = "";
|
|
163
|
+
protected override readonly tokenUrl = QODER_DEVICE_TOKEN_URL;
|
|
164
|
+
protected override readonly scopes = "";
|
|
165
|
+
|
|
166
|
+
override async startDeviceAuth(
|
|
167
|
+
_context?: OAuthDeviceFlowContext,
|
|
168
|
+
): Promise<OAuthDeviceStartResult> {
|
|
169
|
+
const verifier = base64UrlEncode(randomBytes(32));
|
|
170
|
+
const challenge = base64UrlEncode(createHash("sha256").update(verifier).digest());
|
|
171
|
+
// CN carries a dashed nonce and additionally states the client id and
|
|
172
|
+
// native redirect; intl stays on the 32-hex nonce and the two params it
|
|
173
|
+
// has always sent. Both shapes are upstream fact from the donor, and only
|
|
174
|
+
// the CN branch is new, so the working intl URL is untouched.
|
|
175
|
+
const nonce = this.realm === "cn" ? randomUUID() : randomUUID().replaceAll("-", "");
|
|
176
|
+
const machineId = randomUUID();
|
|
177
|
+
const params = new URLSearchParams({
|
|
178
|
+
challenge,
|
|
179
|
+
challenge_method: "S256",
|
|
180
|
+
machine_id: machineId,
|
|
181
|
+
nonce,
|
|
182
|
+
});
|
|
183
|
+
if (this.realm === "cn") {
|
|
184
|
+
const config = QODER_REALMS.cn;
|
|
185
|
+
params.set("client_id", config.clientId);
|
|
186
|
+
params.set("redirect_uri", config.redirectUri);
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
verificationUri: `${qoderOAuthUrls(this.realm).loginUrl}?${params.toString()}`,
|
|
190
|
+
// This flow shows no user code: approval happens against the
|
|
191
|
+
// nonce-bound browser session, so there is nothing to type back.
|
|
192
|
+
userCode: "",
|
|
193
|
+
deviceAuthId: nonce,
|
|
194
|
+
intervalSeconds: DEVICE_INTERVAL_SECONDS,
|
|
195
|
+
expiresInSeconds: DEVICE_EXPIRY_SECONDS,
|
|
196
|
+
providerState: JSON.stringify({ verifier, machineId }),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
override async pollDeviceAuth(
|
|
201
|
+
deviceAuthId: string,
|
|
202
|
+
context?: OAuthDeviceFlowContext,
|
|
203
|
+
): Promise<OAuthDevicePollResult> {
|
|
204
|
+
const state = parseDeviceState(context?.providerState);
|
|
205
|
+
if (state === undefined) return { status: "failed", reason: "unknown device authorization" };
|
|
206
|
+
const url =
|
|
207
|
+
`${qoderOAuthUrls(this.realm).deviceTokenUrl}?nonce=${encodeURIComponent(deviceAuthId)}` +
|
|
208
|
+
`&verifier=${encodeURIComponent(state.verifier)}&challenge_method=S256`;
|
|
209
|
+
let response: Response;
|
|
210
|
+
try {
|
|
211
|
+
response = await this.fetchFn(url, {
|
|
212
|
+
method: "GET",
|
|
213
|
+
headers: { accept: "application/json", "user-agent": "Go-http-client/2.0" },
|
|
214
|
+
signal: AbortSignal.timeout(POLL_TIMEOUT_MS),
|
|
215
|
+
});
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return {
|
|
218
|
+
status: "failed",
|
|
219
|
+
reason: error instanceof Error ? error.message : "Qoder device polling failed",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// 202/404 are the endpoint's "not approved yet" answers, not failures.
|
|
223
|
+
if (response.status === 202 || response.status === 404) return { status: "pending" };
|
|
224
|
+
let payload: unknown;
|
|
225
|
+
try {
|
|
226
|
+
payload = await response.json();
|
|
227
|
+
} catch {
|
|
228
|
+
return { status: "failed", reason: "Qoder device polling returned invalid JSON" };
|
|
229
|
+
}
|
|
230
|
+
if (!response.ok) {
|
|
231
|
+
const message = record(payload);
|
|
232
|
+
return {
|
|
233
|
+
status: "failed",
|
|
234
|
+
reason:
|
|
235
|
+
nonEmptyTrimmedString(message?.message) ??
|
|
236
|
+
`Qoder device polling failed (${response.status})`,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const body = record(payload);
|
|
240
|
+
const accessToken = body === undefined ? undefined : nonEmptyTrimmedString(body.token);
|
|
241
|
+
// A 200 without a token means the upstream changed shape — fail loudly
|
|
242
|
+
// rather than storing an empty credential that 401s every dispatch.
|
|
243
|
+
if (accessToken === undefined) {
|
|
244
|
+
return { status: "failed", reason: "Qoder device polling returned no token" };
|
|
245
|
+
}
|
|
246
|
+
const refreshToken = nonEmptyTrimmedString(body?.refresh_token);
|
|
247
|
+
const expireTime = parseQoderExpiry(body?.expires_at, body?.expires_in);
|
|
248
|
+
const info = await this.fetchUserInfo(accessToken);
|
|
249
|
+
const label = info.name || info.email || undefined;
|
|
250
|
+
// The token response does not always name the user id; the profile
|
|
251
|
+
// endpoint does, so it is the fallback before giving up on the identity
|
|
252
|
+
// dispatch needs for COSY signing.
|
|
253
|
+
const resolvedUserId = nonEmptyTrimmedString(body?.user_id) ?? info.id;
|
|
254
|
+
const result: OAuthExchangeResult = {
|
|
255
|
+
access: accessToken,
|
|
256
|
+
// No refresh grant exists, so the upstream refresh token (when present)
|
|
257
|
+
// is mirrored here for the identity fingerprint that keys a repeated
|
|
258
|
+
// login to the same account. It is never sent to a token endpoint —
|
|
259
|
+
// no refresher is registered for this provider.
|
|
260
|
+
refresh: refreshToken ?? accessToken,
|
|
261
|
+
expiresAt: new Date(expireTime),
|
|
262
|
+
...(label === undefined ? {} : { accountLabel: label }),
|
|
263
|
+
// The token carries neither the user id nor the machine id, and every
|
|
264
|
+
// dispatch needs both for COSY signing — persist them beside the tokens.
|
|
265
|
+
auth_state: { userId: resolvedUserId, machineId: state.machineId },
|
|
266
|
+
};
|
|
267
|
+
return { status: "complete", result };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Best-effort profile lookup for the account label; never blocks login. */
|
|
271
|
+
async fetchUserInfo(accessToken: string): Promise<{ name: string; email: string; id: string }> {
|
|
272
|
+
try {
|
|
273
|
+
const response = await this.fetchFn(qoderOAuthUrls(this.realm).userinfoUrl, {
|
|
274
|
+
method: "GET",
|
|
275
|
+
headers: {
|
|
276
|
+
authorization: `Bearer ${accessToken}`,
|
|
277
|
+
accept: "application/json",
|
|
278
|
+
"user-agent": "Go-http-client/2.0",
|
|
279
|
+
},
|
|
280
|
+
signal: AbortSignal.timeout(POLL_TIMEOUT_MS),
|
|
281
|
+
});
|
|
282
|
+
if (!response.ok) return { name: "", email: "", id: "" };
|
|
283
|
+
const body = record(await response.json());
|
|
284
|
+
if (body === undefined) return { name: "", email: "", id: "" };
|
|
285
|
+
return {
|
|
286
|
+
name: (nonEmptyTrimmedString(body.name) ?? nonEmptyTrimmedString(body.username) ?? "").trim(),
|
|
287
|
+
email: (nonEmptyTrimmedString(body.email) ?? "").trim(),
|
|
288
|
+
id:
|
|
289
|
+
nonEmptyTrimmedString(body.id) ??
|
|
290
|
+
nonEmptyTrimmedString(body.userId) ??
|
|
291
|
+
nonEmptyTrimmedString(body.user_id) ??
|
|
292
|
+
"",
|
|
293
|
+
};
|
|
294
|
+
} catch {
|
|
295
|
+
return { name: "", email: "", id: "" };
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
override async refresh(
|
|
300
|
+
_refreshToken: string,
|
|
301
|
+
_signal?: AbortSignal,
|
|
302
|
+
): Promise<OAuthTokenRefreshResult> {
|
|
303
|
+
throw new Error("Qoder does not support token refresh; sign in again");
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export const qoderOAuthClient = new QoderOAuthClient();
|
|
308
|
+
|
|
309
|
+
/** Device-login client for the China deployment. */
|
|
310
|
+
export const qoderCnOAuthClient = new QoderOAuthClient(globalThis.fetch, "cn");
|