wizards 0.1.9
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/LICENSE +110 -0
- package/README.md +170 -0
- package/apps/runtime/dist/agents/architect.js +453 -0
- package/apps/runtime/dist/agents/project-assistant.js +354 -0
- package/apps/runtime/dist/agents/subscription.js +206 -0
- package/apps/runtime/dist/agents/thought.js +21 -0
- package/apps/runtime/dist/agents/website.js +197 -0
- package/apps/runtime/dist/app.js +153 -0
- package/apps/runtime/dist/auth/account.js +121 -0
- package/apps/runtime/dist/auth/index.js +329 -0
- package/apps/runtime/dist/auth/keys.js +55 -0
- package/apps/runtime/dist/auth/local-ticket.js +23 -0
- package/apps/runtime/dist/authoring/example.js +73 -0
- package/apps/runtime/dist/authoring/guide.js +259 -0
- package/apps/runtime/dist/authoring/ops.js +117 -0
- package/apps/runtime/dist/cli/autostart.js +134 -0
- package/apps/runtime/dist/cli/clients.js +44 -0
- package/apps/runtime/dist/cli/environment.js +81 -0
- package/apps/runtime/dist/cli/exec.js +45 -0
- package/apps/runtime/dist/cli/home.js +74 -0
- package/apps/runtime/dist/cli/machine.js +145 -0
- package/apps/runtime/dist/cli/main.js +125 -0
- package/apps/runtime/dist/cli/setup.js +273 -0
- package/apps/runtime/dist/cli/start.js +190 -0
- package/apps/runtime/dist/cli/status.js +120 -0
- package/apps/runtime/dist/cli/ui.js +12 -0
- package/apps/runtime/dist/cli/update.js +58 -0
- package/apps/runtime/dist/connectors/builtin.js +70 -0
- package/apps/runtime/dist/connectors/external.js +400 -0
- package/apps/runtime/dist/connectors/gmail.js +107 -0
- package/apps/runtime/dist/connectors/imap.js +217 -0
- package/apps/runtime/dist/connectors/index.js +266 -0
- package/apps/runtime/dist/connectors/mail.js +72 -0
- package/apps/runtime/dist/connectors/outlook.js +90 -0
- package/apps/runtime/dist/credits/credits.js +103 -0
- package/apps/runtime/dist/credits/estimate.js +132 -0
- package/apps/runtime/dist/db/adopt.js +149 -0
- package/apps/runtime/dist/db/client.js +146 -0
- package/apps/runtime/dist/db/control-schema.js +70 -0
- package/apps/runtime/dist/db/migrations/control/0000_last_absorbing_man.sql +47 -0
- package/apps/runtime/dist/db/migrations/control/0001_gorgeous_nicolaos.sql +41 -0
- package/apps/runtime/dist/db/migrations/control/0002_secret_penance.sql +1 -0
- package/apps/runtime/dist/db/migrations/control/0003_nasty_mordo.sql +3 -0
- package/apps/runtime/dist/db/migrations/control/0004_breezy_eddie_brock.sql +6 -0
- package/apps/runtime/dist/db/migrations/control/meta/0000_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0001_snapshot.json +599 -0
- package/apps/runtime/dist/db/migrations/control/meta/0002_snapshot.json +607 -0
- package/apps/runtime/dist/db/migrations/control/meta/0003_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0004_snapshot.json +350 -0
- package/apps/runtime/dist/db/migrations/control/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/migrations/tenant/0000_eminent_freak.sql +188 -0
- package/apps/runtime/dist/db/migrations/tenant/0001_salty_master_chief.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0002_cloudy_firebird.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0003_brown_warbound.sql +52 -0
- package/apps/runtime/dist/db/migrations/tenant/0004_romantic_nocturne.sql +6 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0000_snapshot.json +1323 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0001_snapshot.json +1330 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0002_snapshot.json +1337 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0003_snapshot.json +1605 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0004_snapshot.json +1645 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/schema.js +278 -0
- package/apps/runtime/dist/documents/parse.js +230 -0
- package/apps/runtime/dist/engenty/connections-external/build-connector.js +193 -0
- package/apps/runtime/dist/engenty/connections-external/errors.js +11 -0
- package/apps/runtime/dist/engenty/connections-external/import-service.js +270 -0
- package/apps/runtime/dist/engenty/connections-external/importer/classify.js +34 -0
- package/apps/runtime/dist/engenty/connections-external/importer/map-auth.js +185 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-mcp.js +63 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-openapi.js +130 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/http-invoker.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/mcp-client.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/net/guarded-fetch.js +119 -0
- package/apps/runtime/dist/engenty/connections-external/oauth-dcr.js +88 -0
- package/apps/runtime/dist/engenty/connections-external/registry-client.js +336 -0
- package/apps/runtime/dist/engenty/connections-external/registry-source.js +82 -0
- package/apps/runtime/dist/engenty/connections-external/types.js +7 -0
- package/apps/runtime/dist/engenty/connections-github/connector.js +209 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/calendar.js +359 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/contacts.js +94 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/drive.js +290 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/gmail.js +567 -0
- package/apps/runtime/dist/engenty/connections-google/definitions.js +11 -0
- package/apps/runtime/dist/engenty/connections-google/shared.js +73 -0
- package/apps/runtime/dist/engenty/connections-hubspot/connector.js +140 -0
- package/apps/runtime/dist/engenty/connections-microsoft/action.js +17 -0
- package/apps/runtime/dist/engenty/connections-microsoft/graph.js +114 -0
- package/apps/runtime/dist/engenty/connections-microsoft/onedrive.js +286 -0
- package/apps/runtime/dist/engenty/connections-microsoft/outlook.js +464 -0
- package/apps/runtime/dist/engenty/connections-s3/s3.js +328 -0
- package/apps/runtime/dist/engenty/connections-sdk/files-capability.js +86 -0
- package/apps/runtime/dist/engenty/connections-sdk/oauth2.js +150 -0
- package/apps/runtime/dist/engenty/connections-sdk/registry.js +89 -0
- package/apps/runtime/dist/engenty/connections-sdk/storage-capability.js +84 -0
- package/apps/runtime/dist/engenty/connections-sdk/types.js +30 -0
- package/apps/runtime/dist/engenty/connections-slack/connector.js +360 -0
- package/apps/runtime/dist/engenty/csv-import/csv-matrix.js +178 -0
- package/apps/runtime/dist/engenty/csv-import/import-sources.js +87 -0
- package/apps/runtime/dist/engenty/csv-import/parse-csv.js +129 -0
- package/apps/runtime/dist/engenty/csv-import/serialize-csv.js +20 -0
- package/apps/runtime/dist/engenty/csv-import/types.js +1 -0
- package/apps/runtime/dist/engenty/csv-import/xlsx-workbook.js +159 -0
- package/apps/runtime/dist/engenty/doc-converter/interface.js +9 -0
- package/apps/runtime/dist/engenty/doc-converter/page-break.js +92 -0
- package/apps/runtime/dist/engenty/doc-converter/providers/local/index.js +72 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/classifier.js +30 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/index.js +4 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/invoice.js +73 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/receipt.js +63 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/shared.js +40 -0
- package/apps/runtime/dist/engenty/search-index/chunks.js +164 -0
- package/apps/runtime/dist/engenty/search-index/contracts.js +9 -0
- package/apps/runtime/dist/engenty/shims/connections-sdk.js +39 -0
- package/apps/runtime/dist/engenty/shims/plugin-sdk.js +1 -0
- package/apps/runtime/dist/engenty/shims/web-ingest.js +3 -0
- package/apps/runtime/dist/engenty/web-ingest/ssrf.js +277 -0
- package/apps/runtime/dist/engine/asks.js +60 -0
- package/apps/runtime/dist/engine/events.js +46 -0
- package/apps/runtime/dist/engine/input.js +170 -0
- package/apps/runtime/dist/engine/prepare.js +51 -0
- package/apps/runtime/dist/engine/requirements.js +124 -0
- package/apps/runtime/dist/engine/resources.js +89 -0
- package/apps/runtime/dist/engine/runner.js +560 -0
- package/apps/runtime/dist/engine/steps.js +598 -0
- package/apps/runtime/dist/engine/template.js +183 -0
- package/apps/runtime/dist/engine/types.js +3 -0
- package/apps/runtime/dist/env.js +224 -0
- package/apps/runtime/dist/files/blobs.js +22 -0
- package/apps/runtime/dist/files/objects.js +86 -0
- package/apps/runtime/dist/files/storage.js +95 -0
- package/apps/runtime/dist/geocode.js +58 -0
- package/apps/runtime/dist/harness/claude.js +138 -0
- package/apps/runtime/dist/harness/codex.js +228 -0
- package/apps/runtime/dist/harness/cursor.js +93 -0
- package/apps/runtime/dist/harness/env.js +124 -0
- package/apps/runtime/dist/harness/gemini.js +126 -0
- package/apps/runtime/dist/harness/index.js +99 -0
- package/apps/runtime/dist/harness/model.js +267 -0
- package/apps/runtime/dist/harness/prompt.js +121 -0
- package/apps/runtime/dist/harness/terminal.js +193 -0
- package/apps/runtime/dist/harness/types.js +7 -0
- package/apps/runtime/dist/index.js +76 -0
- package/apps/runtime/dist/limits.js +57 -0
- package/apps/runtime/dist/link-preview.js +59 -0
- package/apps/runtime/dist/manage.js +151 -0
- package/apps/runtime/dist/mcp/auth.js +102 -0
- package/apps/runtime/dist/mcp/bridge.js +59 -0
- package/apps/runtime/dist/mcp/handler.js +42 -0
- package/apps/runtime/dist/mcp/scopes.js +5 -0
- package/apps/runtime/dist/mcp/tools.js +401 -0
- package/apps/runtime/dist/media/ffmpeg.js +121 -0
- package/apps/runtime/dist/media/generate.js +120 -0
- package/apps/runtime/dist/media/marking.js +121 -0
- package/apps/runtime/dist/media/transcribe.js +30 -0
- package/apps/runtime/dist/model-errors.js +3 -0
- package/apps/runtime/dist/models.js +489 -0
- package/apps/runtime/dist/plugin.js +61 -0
- package/apps/runtime/dist/render/chromium.js +85 -0
- package/apps/runtime/dist/render/convert.js +161 -0
- package/apps/runtime/dist/render/downloads.js +277 -0
- package/apps/runtime/dist/render/guard.js +25 -0
- package/apps/runtime/dist/routes/api.js +74 -0
- package/apps/runtime/dist/routes/delivery.js +52 -0
- package/apps/runtime/dist/routes/internal.js +49 -0
- package/apps/runtime/dist/routes/marketplace.js +36 -0
- package/apps/runtime/dist/routes/runs.js +805 -0
- package/apps/runtime/dist/routes/studio.js +702 -0
- package/apps/runtime/dist/routes/wizard-stream.js +30 -0
- package/apps/runtime/dist/running.js +39 -0
- package/apps/runtime/dist/sandbox/agentos.js +191 -0
- package/apps/runtime/dist/sandbox/docker.js +77 -0
- package/apps/runtime/dist/sandbox/index.js +30 -0
- package/apps/runtime/dist/sandbox/types.js +1 -0
- package/apps/runtime/dist/secrets/crypto.js +28 -0
- package/apps/runtime/dist/secrets/signing.js +34 -0
- package/apps/runtime/dist/secrets/vault.js +59 -0
- package/apps/runtime/dist/services/architect.js +92 -0
- package/apps/runtime/dist/services/brand.js +20 -0
- package/apps/runtime/dist/services/cloud.js +52 -0
- package/apps/runtime/dist/services/draft-events.js +27 -0
- package/apps/runtime/dist/services/errors.js +24 -0
- package/apps/runtime/dist/services/files.js +129 -0
- package/apps/runtime/dist/services/links.js +16 -0
- package/apps/runtime/dist/services/marketplace.js +292 -0
- package/apps/runtime/dist/services/package.js +202 -0
- package/apps/runtime/dist/services/profile.js +23 -0
- package/apps/runtime/dist/services/project-files.js +266 -0
- package/apps/runtime/dist/services/project-index.js +109 -0
- package/apps/runtime/dist/services/projects.js +181 -0
- package/apps/runtime/dist/services/runs.js +224 -0
- package/apps/runtime/dist/services/shares.js +129 -0
- package/apps/runtime/dist/services/widgets.js +34 -0
- package/apps/runtime/dist/services/wizards.js +342 -0
- package/apps/runtime/dist/settings.js +16 -0
- package/apps/runtime/dist/store/index.js +279 -0
- package/apps/runtime/dist/tenants/control.js +106 -0
- package/apps/runtime/dist/tenants/tenant.js +26 -0
- package/apps/runtime/dist/tools/browser.js +380 -0
- package/apps/runtime/dist/tools/connector.js +204 -0
- package/apps/runtime/dist/tools/index.js +234 -0
- package/apps/runtime/dist/tools/mail.js +104 -0
- package/apps/runtime/dist/tools/net-guard.js +59 -0
- package/apps/runtime/dist/tools/project.js +53 -0
- package/apps/runtime/dist/tools/shared.js +42 -0
- package/apps/runtime/dist/tools/store.js +238 -0
- package/apps/runtime/dist/update.js +118 -0
- package/apps/runtime/dist/widgets/bundle.js +99 -0
- package/apps/runtime/dist/widgets/render.js +312 -0
- package/apps/runtime/dist/widgets/runtime.js +84 -0
- package/apps/runtime/dist/widgets/script.js +30 -0
- package/apps/runtime/dist/widgets/step.js +192 -0
- package/apps/web/dist/ai-labels/ai-black.svg +20 -0
- package/apps/web/dist/ai-labels/ai-generated-black.svg +31 -0
- package/apps/web/dist/ai-labels/ai-generated-white.svg +32 -0
- package/apps/web/dist/ai-labels/ai-modified-black.svg +30 -0
- package/apps/web/dist/ai-labels/ai-modified-white.svg +31 -0
- package/apps/web/dist/ai-labels/ai-white.svg +21 -0
- package/apps/web/dist/assets/ChangelogDialog-O00vEnbm.js +1 -0
- package/apps/web/dist/assets/ChatPanel-CH9ry0Hp.js +1 -0
- package/apps/web/dist/assets/CreditsDialog-DRzecwUw.js +1 -0
- package/apps/web/dist/assets/EditorPage-BRnBf02Q.js +2 -0
- package/apps/web/dist/assets/FlowPreview-DDOj8IAH.js +1 -0
- package/apps/web/dist/assets/HomePage-CAGTTG3N.js +1 -0
- package/apps/web/dist/assets/ImportWizard-0RRrAnX3.js +1 -0
- package/apps/web/dist/assets/LocalRuntime-BrP-ENHg.css +1 -0
- package/apps/web/dist/assets/LocalRuntime-C-TBYrzs.js +16 -0
- package/apps/web/dist/assets/NewWizardPage-kI90h3_9.js +2 -0
- package/apps/web/dist/assets/PublicRunner-CKRDeIVd.js +1 -0
- package/apps/web/dist/assets/RunnerView-Rc4H8wYH.js +2 -0
- package/apps/web/dist/assets/SettingsPage-CESMuZxq.js +2 -0
- package/apps/web/dist/assets/SetupPage-D1rrYU14.js +1 -0
- package/apps/web/dist/assets/SharePage-COTAmOgh.js +1 -0
- package/apps/web/dist/assets/api-BY28cjGY.js +10 -0
- package/apps/web/dist/assets/arrow-left-UpjOBfQp.js +1 -0
- package/apps/web/dist/assets/arrow-right-CucQycEy.js +1 -0
- package/apps/web/dist/assets/changelog-p3wsaDDS.js +1 -0
- package/apps/web/dist/assets/check-Bjx41xbB.js +1 -0
- package/apps/web/dist/assets/createLucideIcon-DBu2Ovxj.js +1 -0
- package/apps/web/dist/assets/definition-cy7uvY5L.js +55 -0
- package/apps/web/dist/assets/engenty-CWIFpgKq.js +1 -0
- package/apps/web/dist/assets/esm-D1OEhjTK.js +7 -0
- package/apps/web/dist/assets/external-link-CRg6DVdO.js +1 -0
- package/apps/web/dist/assets/fluffy-engenty-CJ3wCQgT.js +456 -0
- package/apps/web/dist/assets/index-CQ_q9iMO.css +1 -0
- package/apps/web/dist/assets/index-D0gxh90t.js +10 -0
- package/apps/web/dist/assets/jsx-runtime-Dk72oS4N.js +1 -0
- package/apps/web/dist/assets/meta-BbSdtTUa.js +1 -0
- package/apps/web/dist/assets/mic-CeqQ89Dj.js +1 -0
- package/apps/web/dist/assets/oss-credits-cPuZUFHD.js +1 -0
- package/apps/web/dist/assets/outputs-D6pxH6ve.js +69 -0
- package/apps/web/dist/assets/plus-HA210wXA.js +1 -0
- package/apps/web/dist/assets/preload-helper-CNEI7N0N.js +1 -0
- package/apps/web/dist/assets/qr-scanner-worker.min-CJaXo3yu.js +98 -0
- package/apps/web/dist/assets/qr-scanner.min-C_UI0gH-.js +1 -0
- package/apps/web/dist/assets/type-BzYgVWKL.js +1 -0
- package/apps/web/dist/assets/ui-Ddwl35A6.js +1 -0
- package/apps/web/dist/assets/upload-DsgLfQ3U.js +1 -0
- package/apps/web/dist/assets/video-Dk1fuTDl.js +1 -0
- package/apps/web/dist/assets/wand-sparkles-Bf97fs8T.js +1 -0
- package/apps/web/dist/embed.js +102 -0
- package/apps/web/dist/favicon.svg +1 -0
- package/apps/web/dist/icons/apple-touch-icon.png +0 -0
- package/apps/web/dist/icons/icon-192.png +0 -0
- package/apps/web/dist/icons/icon-512.png +0 -0
- package/apps/web/dist/icons/icon-maskable-512.png +0 -0
- package/apps/web/dist/index.html +46 -0
- package/apps/web/dist/install.sh +532 -0
- package/apps/web/dist/manifest.webmanifest +20 -0
- package/apps/web/dist/sw.js +15 -0
- package/bin/wizards.mjs +13 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.d.ts +77 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.js +2 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +1415 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.js +762 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.d.ts +5 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.js +132 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.d.ts +11 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.js +209 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.d.ts +532 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.js +216 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.d.ts +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.js +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.d.ts +14 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.js +166 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.d.ts +33 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.js +99 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +35 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +86 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.d.ts +20 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +133 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +298 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.js +211 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.d.ts +7 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.js +40 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +74 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.js +96 -0
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +219 -0
- package/node_modules/@engenty-wizards/shared/dist/run.js +1 -0
- package/node_modules/@engenty-wizards/shared/dist/store.d.ts +100 -0
- package/node_modules/@engenty-wizards/shared/dist/store.js +44 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.d.ts +16 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.js +51 -0
- package/node_modules/@engenty-wizards/shared/package.json +10 -0
- package/package.json +62 -0
- package/plugin/.claude-plugin/plugin.json +9 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/commands/wizard.md +8 -0
- package/plugin/skills/build-wizard/SKILL.md +27 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { webContext } from "../render/chromium.js";
|
|
2
|
+
import { htmlToMarkdown } from "../render/convert.js";
|
|
3
|
+
import { safeFetch } from "../tools/net-guard.js";
|
|
4
|
+
const UA = { "user-agent": "Mozilla/5.0 (compatible; engenty-wizards/0.1)" };
|
|
5
|
+
const attr = (tag, name) => tag
|
|
6
|
+
.match(new RegExp(`\\b${name}\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`, "i"))
|
|
7
|
+
?.slice(2)
|
|
8
|
+
.find(Boolean) ?? "";
|
|
9
|
+
function absolute(href, base) {
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(href.trim(), base);
|
|
12
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : null;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function hex(value) {
|
|
19
|
+
const v = value.trim().toLowerCase();
|
|
20
|
+
const short = v.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);
|
|
21
|
+
if (short) {
|
|
22
|
+
return `#${short[1]}${short[1]}${short[2]}${short[2]}${short[3]}${short[3]}`;
|
|
23
|
+
}
|
|
24
|
+
if (/^#[0-9a-f]{6}$/.test(v)) {
|
|
25
|
+
return v;
|
|
26
|
+
}
|
|
27
|
+
if (/^#[0-9a-f]{8}$/.test(v)) {
|
|
28
|
+
return v.slice(0, 7);
|
|
29
|
+
}
|
|
30
|
+
const rgb = v.match(/^rgba?\(\s*(\d{1,3})[\s,]+(\d{1,3})[\s,]+(\d{1,3})/);
|
|
31
|
+
if (rgb) {
|
|
32
|
+
return `#${rgb
|
|
33
|
+
.slice(1, 4)
|
|
34
|
+
.map((n) => Math.min(255, Number(n)).toString(16).padStart(2, "0"))
|
|
35
|
+
.join("")}`;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
/** How far a colour is from grey, 0–255. */
|
|
40
|
+
function chroma(color) {
|
|
41
|
+
const [r, g, b] = [1, 3, 5].map((i) => Number.parseInt(color.slice(i, i + 2), 16));
|
|
42
|
+
return Math.max(r, g, b) - Math.min(r, g, b);
|
|
43
|
+
}
|
|
44
|
+
function coloursOf(css) {
|
|
45
|
+
const variables = [];
|
|
46
|
+
for (const m of css.matchAll(/(--[\w-]+)\s*:\s*(#[0-9a-fA-F]{3,8}|rgba?\([^)]*\))/g)) {
|
|
47
|
+
const value = hex(m[2]);
|
|
48
|
+
if (value && !variables.some((v) => v.name === m[1])) {
|
|
49
|
+
variables.push({ name: m[1], value });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const counts = new Map();
|
|
53
|
+
for (const m of css.matchAll(/#[0-9a-fA-F]{3,8}\b|rgba?\([^)]*\)/g)) {
|
|
54
|
+
const value = hex(m[0]);
|
|
55
|
+
if (value) {
|
|
56
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const telling = /brand|primary|accent|main|theme|secondary|highlight|cta|link/i;
|
|
60
|
+
return {
|
|
61
|
+
variables: variables
|
|
62
|
+
.sort((a, b) => Number(telling.test(b.name)) - Number(telling.test(a.name)))
|
|
63
|
+
.slice(0, 24),
|
|
64
|
+
frequent: [...counts.entries()]
|
|
65
|
+
.sort((a, b) => Number(chroma(b[0]) > 24) - Number(chroma(a[0]) > 24) || b[1] - a[1])
|
|
66
|
+
.slice(0, 12)
|
|
67
|
+
.map(([value]) => value),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function stylesheet(url, signal) {
|
|
71
|
+
try {
|
|
72
|
+
const res = await safeFetch(url, {
|
|
73
|
+
headers: UA,
|
|
74
|
+
signal: AbortSignal.any([AbortSignal.timeout(10_000), ...(signal ? [signal] : [])]),
|
|
75
|
+
});
|
|
76
|
+
return res.ok ? (await res.text()).slice(0, 400_000) : "";
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return "";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** The page as a browser shows it, for sites that write their text with scripts. */
|
|
83
|
+
async function rendered(url) {
|
|
84
|
+
try {
|
|
85
|
+
const context = await webContext();
|
|
86
|
+
try {
|
|
87
|
+
const page = await context.newPage();
|
|
88
|
+
await page.goto(url, { waitUntil: "networkidle", timeout: 25_000 }).catch(() => undefined);
|
|
89
|
+
return await page.content();
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
await context.close();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export async function readWebsite(raw, signal) {
|
|
100
|
+
const res = await safeFetch(/^https?:\/\//i.test(raw) ? raw : `https://${raw}`, {
|
|
101
|
+
headers: UA,
|
|
102
|
+
signal: AbortSignal.any([AbortSignal.timeout(20_000), ...(signal ? [signal] : [])]),
|
|
103
|
+
});
|
|
104
|
+
const base = res.url;
|
|
105
|
+
let html = (await res.text()).slice(0, 1_500_000);
|
|
106
|
+
// Hardly any text in the page as sent: it is written in the browser.
|
|
107
|
+
if (res.ok && htmlToMarkdown(html).replace(/\s+/g, " ").length < 500) {
|
|
108
|
+
html = ((await rendered(base)) ?? html).slice(0, 1_500_000);
|
|
109
|
+
}
|
|
110
|
+
const tags = (name) => [...html.matchAll(new RegExp(`<${name}\\b[^>]*>`, "gi"))].map((m) => m[0]);
|
|
111
|
+
const meta = (key) => {
|
|
112
|
+
const tag = tags("meta").find((t) => [attr(t, "name"), attr(t, "property")].some((v) => v.toLowerCase() === key));
|
|
113
|
+
return tag ? attr(tag, "content") : "";
|
|
114
|
+
};
|
|
115
|
+
const logos = [];
|
|
116
|
+
const addLogo = (href, hint) => {
|
|
117
|
+
const url = absolute(href, base);
|
|
118
|
+
if (url && !logos.some((l) => l.url === url)) {
|
|
119
|
+
logos.push({ url, hint });
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const images = [];
|
|
123
|
+
for (const tag of tags("img")) {
|
|
124
|
+
const src = attr(tag, "src") || attr(tag, "data-src");
|
|
125
|
+
const alt = attr(tag, "alt");
|
|
126
|
+
if (!src || src.startsWith("data:")) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (/logo/i.test(`${src} ${alt} ${attr(tag, "class")} ${attr(tag, "id")}`)) {
|
|
130
|
+
addLogo(src, `image on the page${alt ? `, alt "${alt}"` : ""}`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const url = absolute(src, base);
|
|
134
|
+
if (url && images.length < 12 && !images.some((i) => i.url === url)) {
|
|
135
|
+
images.push({ url, alt });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const tag of tags("link")) {
|
|
140
|
+
const rel = attr(tag, "rel").toLowerCase();
|
|
141
|
+
if (/apple-touch-icon|mask-icon|(^|\s)icon/.test(rel)) {
|
|
142
|
+
addLogo(attr(tag, "href"), `${rel} ${attr(tag, "sizes")}`.trim());
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const og = meta("og:image");
|
|
146
|
+
if (og) {
|
|
147
|
+
const url = absolute(og, base);
|
|
148
|
+
if (url) {
|
|
149
|
+
images.unshift({ url, alt: "share image (og:image)" });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const sheets = tags("link")
|
|
153
|
+
.filter((t) => /stylesheet/i.test(attr(t, "rel")))
|
|
154
|
+
.map((t) => absolute(attr(t, "href"), base))
|
|
155
|
+
.filter((u) => Boolean(u))
|
|
156
|
+
.slice(0, 4);
|
|
157
|
+
const inline = [...html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style>/gi)].map((m) => m[1]);
|
|
158
|
+
const styled = [...html.matchAll(/\bstyle\s*=\s*"([^"]*)"/gi)].map((m) => m[1]);
|
|
159
|
+
const css = [
|
|
160
|
+
...inline,
|
|
161
|
+
...styled,
|
|
162
|
+
...(await Promise.all(sheets.map((s) => stylesheet(s, signal)))),
|
|
163
|
+
].join("\n");
|
|
164
|
+
const host = new URL(base).hostname;
|
|
165
|
+
const links = [];
|
|
166
|
+
for (const m of html.matchAll(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi)) {
|
|
167
|
+
const url = absolute(attr(`<a ${m[1]}>`, "href"), base);
|
|
168
|
+
const text = m[2]
|
|
169
|
+
.replace(/<[^>]+>/g, " ")
|
|
170
|
+
.replace(/\s+/g, " ")
|
|
171
|
+
.trim()
|
|
172
|
+
.slice(0, 60);
|
|
173
|
+
if (url &&
|
|
174
|
+
new URL(url).hostname === host &&
|
|
175
|
+
/impressum|imprint|kontakt|contact|about|ueber|über|team|legal|unternehmen|company/i.test(`${url} ${text}`) &&
|
|
176
|
+
!links.some((l) => l.url === url)) {
|
|
177
|
+
links.push({ url, text });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const text = htmlToMarkdown(html);
|
|
181
|
+
return {
|
|
182
|
+
url: base,
|
|
183
|
+
status: res.status,
|
|
184
|
+
title: html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1].trim() ?? "",
|
|
185
|
+
description: meta("description") || meta("og:description"),
|
|
186
|
+
text: text.length > 9000
|
|
187
|
+
? `${text.slice(0, 9000)}\n…[${text.length - 9000} more characters]`
|
|
188
|
+
: text,
|
|
189
|
+
brand: {
|
|
190
|
+
themeColor: hex(meta("theme-color")),
|
|
191
|
+
...coloursOf(css),
|
|
192
|
+
logos: logos.slice(0, 10),
|
|
193
|
+
images,
|
|
194
|
+
},
|
|
195
|
+
links: links.slice(0, 10),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
6
|
+
import { Hono } from "hono";
|
|
7
|
+
import { HTTPException } from "hono/http-exception";
|
|
8
|
+
import { ZodError } from "zod";
|
|
9
|
+
import { authRoutes, principalOf } from "./auth/index.js";
|
|
10
|
+
import { withTenant } from "./db/client.js";
|
|
11
|
+
import { basePath, env } from "./env.js";
|
|
12
|
+
import { linkPreview } from "./link-preview.js";
|
|
13
|
+
import { discovery, managed } from "./manage.js";
|
|
14
|
+
import { bridgeRequest } from "./mcp/bridge.js";
|
|
15
|
+
import { mcpHandler } from "./mcp/handler.js";
|
|
16
|
+
import { MCP_RESOURCE, SCOPES } from "./mcp/scopes.js";
|
|
17
|
+
import { PLUGIN_NAME, pluginArchive, pluginMarketplace } from "./plugin.js";
|
|
18
|
+
import { apiRoutes } from "./routes/api.js";
|
|
19
|
+
import { internalRoutes } from "./routes/internal.js";
|
|
20
|
+
import { marketplaceStudio } from "./routes/marketplace.js";
|
|
21
|
+
import { connectCallback, publicRoutes, runRoutes, shareRoutes } from "./routes/runs.js";
|
|
22
|
+
import { studio } from "./routes/studio.js";
|
|
23
|
+
import { wizardStream } from "./routes/wizard-stream.js";
|
|
24
|
+
import { ServiceError } from "./services/errors.js";
|
|
25
|
+
import { tenantOfLink, tenantStatus } from "./tenants/control.js";
|
|
26
|
+
const app = new Hono();
|
|
27
|
+
app.onError((err, c) => {
|
|
28
|
+
if (err instanceof ServiceError) {
|
|
29
|
+
return c.json(err.toJSON(), err.status);
|
|
30
|
+
}
|
|
31
|
+
if (err instanceof ZodError) {
|
|
32
|
+
return c.json({ error: "Ungültige Eingabe", issues: err.issues }, 400);
|
|
33
|
+
}
|
|
34
|
+
if (err instanceof HTTPException) {
|
|
35
|
+
return err.getResponse();
|
|
36
|
+
}
|
|
37
|
+
console.error(err);
|
|
38
|
+
return c.json({ error: "Interner Fehler" }, 500);
|
|
39
|
+
});
|
|
40
|
+
// --- who may talk to this server at all ---------------------------------------------
|
|
41
|
+
// A runtime that runs alone listens on the loopback interface. A web page in the person's
|
|
42
|
+
// browser could still reach it there, so the Host header must be one of ours (DNS rebinding)
|
|
43
|
+
// and a request that changes something must come from our own origin.
|
|
44
|
+
const ownHosts = new Set([new URL(env.appUrl).host, ...env.allowedHosts]);
|
|
45
|
+
const loopback = /^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/;
|
|
46
|
+
const hostAllowed = (host) => ownHosts.has(host) || loopback.test(host);
|
|
47
|
+
app.use("*", async (c, next) => {
|
|
48
|
+
if (managed) {
|
|
49
|
+
return next();
|
|
50
|
+
}
|
|
51
|
+
if (!hostAllowed(c.req.header("host") ?? new URL(c.req.url).host)) {
|
|
52
|
+
return c.text("Forbidden host", 403);
|
|
53
|
+
}
|
|
54
|
+
const origin = c.req.header("origin");
|
|
55
|
+
const safe = c.req.method === "GET" || c.req.method === "HEAD" || c.req.method === "OPTIONS";
|
|
56
|
+
// MCP clients and scripts send no Origin; a browser always does on a cross-origin write.
|
|
57
|
+
if (!safe && origin && !hostAllowed(new URL(origin).host)) {
|
|
58
|
+
return c.text("Forbidden origin", 403);
|
|
59
|
+
}
|
|
60
|
+
return next();
|
|
61
|
+
});
|
|
62
|
+
app.get("/api/health", (c) => c.json({ ok: true }));
|
|
63
|
+
app.route("/api", authRoutes);
|
|
64
|
+
app.get("/api/config", (c) => c.json({
|
|
65
|
+
mode: managed ? "managed" : "local",
|
|
66
|
+
devLogin: !managed && env.devLogin,
|
|
67
|
+
/** The product's own site, for a runtime that runs alone. */
|
|
68
|
+
site: managed ? null : env.local.cloudUrl,
|
|
69
|
+
signedOutUrl: managed && env.signedOutUrl ? env.signedOutUrl : null,
|
|
70
|
+
}));
|
|
71
|
+
// OAuth discovery for MCP clients (RFC 9728): the Manage-App is the authorization server.
|
|
72
|
+
app.get("/.well-known/oauth-protected-resource/*", async (c) => {
|
|
73
|
+
if (!managed) {
|
|
74
|
+
return c.notFound();
|
|
75
|
+
}
|
|
76
|
+
const d = await discovery();
|
|
77
|
+
return c.json({
|
|
78
|
+
resource: MCP_RESOURCE,
|
|
79
|
+
authorization_servers: [d.issuer],
|
|
80
|
+
scopes_supported: SCOPES,
|
|
81
|
+
bearer_methods_supported: ["header"],
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
/** Studio requests act as the signed-in person, inside that person's tenant. */
|
|
85
|
+
app.use("/api/studio/*", async (c, next) => {
|
|
86
|
+
const user = await principalOf(c);
|
|
87
|
+
if (!user) {
|
|
88
|
+
return c.json({ error: "unauthorized" }, 401);
|
|
89
|
+
}
|
|
90
|
+
if ((await tenantStatus(user.tenantId)) === "suspended") {
|
|
91
|
+
return c.json({ error: "Dieses Konto ist gesperrt.", code: "suspended" }, 403);
|
|
92
|
+
}
|
|
93
|
+
c.set("user", user);
|
|
94
|
+
return withTenant(user.tenantId, next);
|
|
95
|
+
});
|
|
96
|
+
const mcp = mcpHandler();
|
|
97
|
+
app.on(["GET", "POST", "DELETE"], "/api/mcp", (c) => mcp(c.req.raw));
|
|
98
|
+
// The tools of one model call, for the installed Claude Code while it answers that call.
|
|
99
|
+
app.on(["GET", "POST", "DELETE"], "/api/mcp/bridge/:token", (c) => bridgeRequest(c.req.param("token"), c.req.raw));
|
|
100
|
+
app.route("/api/studio/wizards", wizardStream);
|
|
101
|
+
app.route("/api/studio/marketplace", marketplaceStudio);
|
|
102
|
+
app.route("/api/studio", studio);
|
|
103
|
+
app.route("/api/v1", apiRoutes);
|
|
104
|
+
app.route("/api/internal", internalRoutes);
|
|
105
|
+
app.route("/api/public", publicRoutes);
|
|
106
|
+
app.route("/api/runs", runRoutes);
|
|
107
|
+
app.route("/api/shares", shareRoutes);
|
|
108
|
+
app.route("/api/connect", connectCallback);
|
|
109
|
+
// The Claude Code plugin of this deployment: `claude plugin marketplace add <APP_URL>/api/claude-plugin/marketplace.json`.
|
|
110
|
+
app.get("/api/claude-plugin/marketplace.json", async (c) => c.json(await pluginMarketplace()));
|
|
111
|
+
app.get(`/api/claude-plugin/${PLUGIN_NAME}.zip`, async (c) => {
|
|
112
|
+
const { zip } = await pluginArchive();
|
|
113
|
+
return c.body(new Uint8Array(zip), 200, { "content-type": "application/zip" });
|
|
114
|
+
});
|
|
115
|
+
app.all("/api/*", (c) => c.json({ error: "not found" }, 404));
|
|
116
|
+
/** Link cards for `/w/<token>` and `/s/<token>`; the token names the tenant. */
|
|
117
|
+
async function previewFor(path) {
|
|
118
|
+
const m = path.match(/^\/(w|s)\/([A-Za-z0-9_-]+)\/?$/);
|
|
119
|
+
const tenant = m ? await tenantOfLink(m[2], m[1] === "w" ? "wizard" : "result") : null;
|
|
120
|
+
return tenant ? withTenant(tenant, () => linkPreview(path)) : null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Production: the built app. Its files and the studio's pages sit below `/studio`; the public
|
|
124
|
+
* pages are `/w/<token>` and `/s/<token>`. Of the root only what others fetch there is served:
|
|
125
|
+
* the installer, the embed script for websites, and the service worker of public wizards. The
|
|
126
|
+
* rest of the root belongs to others (the landing page); a runtime alone sends `/` to the studio.
|
|
127
|
+
*/
|
|
128
|
+
const webDir = resolve(dirname(fileURLToPath(import.meta.url)), "../../web/dist");
|
|
129
|
+
const ROOT_FILES = ["/install.sh", "/embed.js", "/sw.js"];
|
|
130
|
+
const PAGES = /^\/(studio(\/|$)|w\/|s\/)/;
|
|
131
|
+
if (existsSync(webDir)) {
|
|
132
|
+
for (const file of ROOT_FILES) {
|
|
133
|
+
app.get(file, serveStatic({ root: webDir }));
|
|
134
|
+
}
|
|
135
|
+
app.use("/studio/*", serveStatic({ root: webDir, rewriteRequestPath: (path) => path.slice("/studio".length) }));
|
|
136
|
+
app.get("/", (c) => c.redirect(`${basePath}/studio/`));
|
|
137
|
+
app.get("*", async (c) => {
|
|
138
|
+
if (!PAGES.test(c.req.path)) {
|
|
139
|
+
return c.text("Not found", 404);
|
|
140
|
+
}
|
|
141
|
+
const html = await readFile(join(webDir, "index.html"), "utf8");
|
|
142
|
+
const preview = await previewFor(c.req.path).catch(() => null);
|
|
143
|
+
// A wizard's link installs as that wizard: its own name and start address on the home screen.
|
|
144
|
+
const token = c.req.path.match(/^\/w\/([A-Za-z0-9_-]{6,64})(?:\/|$)/)?.[1];
|
|
145
|
+
const page = token
|
|
146
|
+
? html.replace(/href="[^"]*manifest\.webmanifest"/, `href="${basePath}/api/public/wizards/${token}/manifest.webmanifest"`)
|
|
147
|
+
: html;
|
|
148
|
+
return c.html(preview
|
|
149
|
+
? page.replace(/<title>[^<]*<\/title>/, "").replace("</head>", `${preview}\n</head>`)
|
|
150
|
+
: page);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
export default app;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { env } from "../env.js";
|
|
3
|
+
import { discovery, tokenRequest, unverifiedClaims } from "../manage.js";
|
|
4
|
+
import { seal, unseal } from "../secrets/crypto.js";
|
|
5
|
+
import { vaultDelete, vaultGet, vaultSet } from "../secrets/vault.js";
|
|
6
|
+
import { deleteSetting, readSetting, writeSetting } from "../settings.js";
|
|
7
|
+
/**
|
|
8
|
+
* The account a runtime that runs alone is linked to: sign-in (or sign-up) happens in the system
|
|
9
|
+
* browser at the Manage-App, the code comes back to this server on the loopback interface. The
|
|
10
|
+
* account is optional; it unlocks credits through the gateway and publishing to the cloud.
|
|
11
|
+
*/
|
|
12
|
+
const CLIENT = { id: "wizards-desktop" };
|
|
13
|
+
const REFRESH = "account.refresh";
|
|
14
|
+
const SCOPE = "openid profile email offline_access wizards:read wizards:write wizards:publish";
|
|
15
|
+
/** Loopback redirect on the port this server listens on (RFC 8252). */
|
|
16
|
+
function redirectUri() {
|
|
17
|
+
return `http://127.0.0.1:${env.port}/api/auth/callback`;
|
|
18
|
+
}
|
|
19
|
+
export async function startLink() {
|
|
20
|
+
const d = await discovery(env.local.accountUrl);
|
|
21
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
22
|
+
const state = { verifier, redirect: redirectUri(), exp: Date.now() + 15 * 60_000 };
|
|
23
|
+
const url = new URL(d.authorization_endpoint);
|
|
24
|
+
url.search = new URLSearchParams({
|
|
25
|
+
response_type: "code",
|
|
26
|
+
client_id: CLIENT.id,
|
|
27
|
+
redirect_uri: state.redirect,
|
|
28
|
+
scope: SCOPE,
|
|
29
|
+
resource: env.local.cloudUrl,
|
|
30
|
+
code_challenge: createHash("sha256").update(verifier).digest("base64url"),
|
|
31
|
+
code_challenge_method: "S256",
|
|
32
|
+
state: seal(state),
|
|
33
|
+
}).toString();
|
|
34
|
+
return url.toString();
|
|
35
|
+
}
|
|
36
|
+
let cached = null;
|
|
37
|
+
async function keep(tokens) {
|
|
38
|
+
const claims = unverifiedClaims(tokens.access_token);
|
|
39
|
+
if (!claims) {
|
|
40
|
+
throw new Error("The account's token names no tenant.");
|
|
41
|
+
}
|
|
42
|
+
if (tokens.refresh_token) {
|
|
43
|
+
await vaultSet(REFRESH, tokens.refresh_token);
|
|
44
|
+
}
|
|
45
|
+
cached = {
|
|
46
|
+
token: tokens.access_token,
|
|
47
|
+
expiresAt: Date.now() + (tokens.expires_in ?? 600) * 1000,
|
|
48
|
+
};
|
|
49
|
+
const account = {
|
|
50
|
+
userId: claims.userId,
|
|
51
|
+
tenantId: claims.tenantId,
|
|
52
|
+
name: claims.name,
|
|
53
|
+
email: claims.email,
|
|
54
|
+
};
|
|
55
|
+
await writeSetting("account", account);
|
|
56
|
+
return account;
|
|
57
|
+
}
|
|
58
|
+
export async function finishLink(code, rawState) {
|
|
59
|
+
const state = unseal(rawState);
|
|
60
|
+
if (!state || state.exp < Date.now()) {
|
|
61
|
+
throw new Error("This sign-in has expired.");
|
|
62
|
+
}
|
|
63
|
+
const tokens = await tokenRequest({
|
|
64
|
+
grant_type: "authorization_code",
|
|
65
|
+
code,
|
|
66
|
+
redirect_uri: state.redirect,
|
|
67
|
+
code_verifier: state.verifier,
|
|
68
|
+
resource: env.local.cloudUrl,
|
|
69
|
+
}, CLIENT, env.local.accountUrl);
|
|
70
|
+
return keep(tokens);
|
|
71
|
+
}
|
|
72
|
+
export function linkedAccount() {
|
|
73
|
+
return readSetting("account");
|
|
74
|
+
}
|
|
75
|
+
/** A fresh access token of the linked account, or null when none is linked (or it was revoked). */
|
|
76
|
+
export async function accountToken() {
|
|
77
|
+
if (cached && cached.expiresAt - Date.now() > 60_000) {
|
|
78
|
+
return cached.token;
|
|
79
|
+
}
|
|
80
|
+
const refresh = await vaultGet(REFRESH);
|
|
81
|
+
if (!refresh) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const tokens = await tokenRequest({ grant_type: "refresh_token", refresh_token: refresh, resource: env.local.cloudUrl }, CLIENT, env.local.accountUrl);
|
|
86
|
+
await keep(tokens);
|
|
87
|
+
return tokens.access_token;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export async function unlink() {
|
|
94
|
+
const refresh = await vaultGet(REFRESH);
|
|
95
|
+
if (refresh) {
|
|
96
|
+
const d = await discovery(env.local.accountUrl).catch(() => null);
|
|
97
|
+
if (d?.revocation_endpoint) {
|
|
98
|
+
await fetch(d.revocation_endpoint, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
101
|
+
body: new URLSearchParams({ token: refresh, client_id: CLIENT.id }),
|
|
102
|
+
signal: AbortSignal.timeout(10_000),
|
|
103
|
+
}).catch(() => undefined);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
cached = null;
|
|
107
|
+
await vaultDelete(REFRESH);
|
|
108
|
+
await deleteSetting("account");
|
|
109
|
+
}
|
|
110
|
+
/** Balance and tenants of the linked account, straight from the Manage-App. */
|
|
111
|
+
export async function accountOverview() {
|
|
112
|
+
const token = await accountToken();
|
|
113
|
+
if (!token) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const res = await fetch(`${env.local.accountUrl}/v1/me`, {
|
|
117
|
+
headers: { authorization: `Bearer ${token}` },
|
|
118
|
+
signal: AbortSignal.timeout(10_000),
|
|
119
|
+
}).catch(() => null);
|
|
120
|
+
return res?.ok ? (await res.json()) : null;
|
|
121
|
+
}
|