@agent-native/core 0.32.1 → 0.32.17
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 +3 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +48 -10
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +12 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +104 -6
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/cli/app-skill.js +2 -2
- package/dist/cli/app-skill.js.map +1 -1
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +6 -1
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/code-agent-output-smoother.d.ts +7 -0
- package/dist/cli/code-agent-output-smoother.d.ts.map +1 -0
- package/dist/cli/code-agent-output-smoother.js +111 -0
- package/dist/cli/code-agent-output-smoother.js.map +1 -0
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +5 -0
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/migrate.js +17 -42
- package/dist/cli/migrate.js.map +1 -1
- package/dist/cli/skills.d.ts +23 -2
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +405 -41
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/templates-meta.d.ts.map +1 -1
- package/dist/cli/templates-meta.js +7 -105
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +41 -7
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AgentTaskCard.d.ts.map +1 -1
- package/dist/client/AgentTaskCard.js +0 -28
- package/dist/client/AgentTaskCard.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +8 -23
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +359 -205
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +254 -14
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +14 -9
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-chat.d.ts +24 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +73 -0
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/assistant-ui-recovery.d.ts +34 -0
- package/dist/client/assistant-ui-recovery.d.ts.map +1 -0
- package/dist/client/assistant-ui-recovery.js +122 -0
- package/dist/client/assistant-ui-recovery.js.map +1 -0
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +7 -1
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +7 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +22 -2
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/frame-protocol.d.ts +6 -2
- package/dist/client/frame-protocol.d.ts.map +1 -1
- package/dist/client/frame-protocol.js.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +2 -1
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/progress/RunsTray.d.ts +13 -3
- package/dist/client/progress/RunsTray.d.ts.map +1 -1
- package/dist/client/progress/RunsTray.js +105 -36
- package/dist/client/progress/RunsTray.js.map +1 -1
- package/dist/client/route-warmup.d.ts +61 -0
- package/dist/client/route-warmup.d.ts.map +1 -0
- package/dist/client/route-warmup.js +456 -0
- package/dist/client/route-warmup.js.map +1 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +2 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +5 -0
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +10 -4
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/use-action.d.ts +1 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +22 -4
- package/dist/client/use-action.js.map +1 -1
- package/dist/code-agents/background-run.d.ts +2 -0
- package/dist/code-agents/background-run.d.ts.map +1 -1
- package/dist/code-agents/background-run.js.map +1 -1
- package/dist/db/client.d.ts +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +25 -1
- package/dist/db/client.js.map +1 -1
- package/dist/deploy/build.d.ts +4 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +171 -14
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/immutable-assets.d.ts +1 -0
- package/dist/deploy/immutable-assets.d.ts.map +1 -1
- package/dist/deploy/immutable-assets.js +1 -0
- package/dist/deploy/immutable-assets.js.map +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/connect-route.d.ts.map +1 -1
- package/dist/mcp/connect-route.js +118 -82
- package/dist/mcp/connect-route.js.map +1 -1
- package/dist/progress/routes.d.ts.map +1 -1
- package/dist/progress/routes.js +1 -0
- package/dist/progress/routes.js.map +1 -1
- package/dist/progress/store.d.ts +13 -0
- package/dist/progress/store.d.ts.map +1 -1
- package/dist/progress/store.js +18 -0
- package/dist/progress/store.js.map +1 -1
- package/dist/progress/types.d.ts +2 -0
- package/dist/progress/types.d.ts.map +1 -1
- package/dist/progress/types.js.map +1 -1
- package/dist/scripts/db/wipe-leaked-builder-keys.d.ts +2 -2
- package/dist/scripts/db/wipe-leaked-builder-keys.d.ts.map +1 -1
- package/dist/scripts/db/wipe-leaked-builder-keys.js +14 -3
- package/dist/scripts/db/wipe-leaked-builder-keys.js.map +1 -1
- package/dist/server/action-routes.d.ts +1 -0
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +36 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +123 -25
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-discovery.d.ts.map +1 -1
- package/dist/server/agent-discovery.js +14 -1
- package/dist/server/agent-discovery.js.map +1 -1
- package/dist/server/agent-teams-run-queue.d.ts +80 -0
- package/dist/server/agent-teams-run-queue.d.ts.map +1 -0
- package/dist/server/agent-teams-run-queue.js +208 -0
- package/dist/server/agent-teams-run-queue.js.map +1 -0
- package/dist/server/agent-teams.d.ts +67 -0
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +607 -180
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/auth-marketing.d.ts.map +1 -1
- package/dist/server/auth-marketing.js +0 -64
- package/dist/server/auth-marketing.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +67 -14
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-browser.d.ts +12 -2
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +24 -0
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +66 -5
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/credential-provider.d.ts +10 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +82 -3
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/csrf.js +3 -0
- package/dist/server/csrf.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts +1 -0
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +14 -1
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/self-dispatch.d.ts +44 -0
- package/dist/server/self-dispatch.d.ts.map +1 -0
- package/dist/server/self-dispatch.js +113 -0
- package/dist/server/self-dispatch.js.map +1 -0
- package/dist/server/social-og-image.d.ts +14 -0
- package/dist/server/social-og-image.d.ts.map +1 -0
- package/dist/server/social-og-image.js +251 -0
- package/dist/server/social-og-image.js.map +1 -0
- package/dist/server/ssr-handler.d.ts +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +27 -11
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/shared/cache-control.d.ts +7 -0
- package/dist/shared/cache-control.d.ts.map +1 -1
- package/dist/shared/cache-control.js +7 -0
- package/dist/shared/cache-control.js.map +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/route-warmup-config.d.ts +28 -0
- package/dist/shared/route-warmup-config.d.ts.map +1 -0
- package/dist/shared/route-warmup-config.js +58 -0
- package/dist/shared/route-warmup-config.js.map +1 -0
- package/dist/shared/social-meta.d.ts +5 -0
- package/dist/shared/social-meta.d.ts.map +1 -1
- package/dist/shared/social-meta.js +36 -2
- package/dist/shared/social-meta.js.map +1 -1
- package/dist/shared/streaming-text-smoothing.d.ts +12 -0
- package/dist/shared/streaming-text-smoothing.d.ts.map +1 -0
- package/dist/shared/streaming-text-smoothing.js +52 -0
- package/dist/shared/streaming-text-smoothing.js.map +1 -0
- package/dist/styles/agent-native.css +4 -4
- package/dist/templates/default/AGENTS.md +9 -4
- package/dist/templates/default/DEVELOPING.md +15 -1
- package/dist/templates/workspace-core/AGENTS.md +7 -3
- package/dist/templates/workspace-root/AGENTS.md +7 -3
- package/dist/vite/client.d.ts +13 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +36 -1
- package/dist/vite/client.js.map +1 -1
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js.map +1 -1
- package/docs/content/client.md +62 -1
- package/docs/content/code-agents-ui.md +6 -13
- package/docs/content/context-awareness.md +186 -21
- package/docs/content/deployment.md +8 -11
- package/docs/content/dispatch.md +1 -1
- package/docs/content/external-agents.md +32 -2
- package/docs/content/migration-workbench.md +4 -21
- package/docs/content/multi-app-workspace.md +1 -1
- package/docs/content/recurring-jobs.md +1 -1
- package/docs/content/security.md +0 -1
- package/docs/content/sharing.md +1 -3
- package/docs/content/skills-guide.md +12 -10
- package/docs/content/template-assets.md +21 -1
- package/docs/content/template-design.md +23 -5
- package/docs/content/template-dispatch.md +1 -1
- package/package.json +2 -1
- package/src/templates/default/AGENTS.md +9 -4
- package/src/templates/default/DEVELOPING.md +15 -1
- package/src/templates/workspace-core/AGENTS.md +7 -3
- package/src/templates/workspace-root/AGENTS.md +7 -3
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared self-dispatch helper for the framework's serverless background-work
|
|
3
|
+
* pattern: enqueue a unit of work to SQL, then fire a fresh HTTP POST back to
|
|
4
|
+
* this same deployment so the work runs in its own function invocation (with
|
|
5
|
+
* its own full timeout budget) instead of riding on the request that created
|
|
6
|
+
* it.
|
|
7
|
+
*
|
|
8
|
+
* This is the single mechanism that makes background work portable across every
|
|
9
|
+
* host Nitro deploys to:
|
|
10
|
+
* - Netlify Lambda / Vercel Functions / AWS Lambda — the dispatched request
|
|
11
|
+
* hits a fresh function with its own budget; no `waitUntil` needed.
|
|
12
|
+
* - Cloudflare Workers — same (and `waitUntil` still works as a belt-and-
|
|
13
|
+
* suspenders fallback where the in-process path is used).
|
|
14
|
+
* - Self-hosted / long-lived Node — the dispatch comes back as another
|
|
15
|
+
* request to the same process; each handler still runs to completion.
|
|
16
|
+
*
|
|
17
|
+
* Originally inlined in both `a2a/handlers.ts` (`resolveSelfBaseUrl` +
|
|
18
|
+
* `fireProcessTaskDispatch`) and `integrations/webhook-handler.ts`
|
|
19
|
+
* (`resolveBaseUrl` + the dispatch in `enqueueAndDispatch`). Extracted here so
|
|
20
|
+
* A2A, integration webhooks, and Agent Teams sub-agents share one tested
|
|
21
|
+
* implementation.
|
|
22
|
+
*/
|
|
23
|
+
import { withConfiguredAppBasePath } from "./app-base-path.js";
|
|
24
|
+
import { isLocalDatabase } from "../db/client.js";
|
|
25
|
+
import { signInternalToken } from "../integrations/internal-token.js";
|
|
26
|
+
/**
|
|
27
|
+
* On serverless, returning from the dispatching handler before the outbound
|
|
28
|
+
* TCP handshake starts can freeze the function with the dispatch request stuck
|
|
29
|
+
* in the queue. Racing the fetch against a short timer gives the request a
|
|
30
|
+
* chance to leave the box at the cost of a little added latency on the
|
|
31
|
+
* dispatching call. Mirrors the 250ms used by the A2A/webhook paths.
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_DISPATCH_SETTLE_MS = 250;
|
|
34
|
+
function readHeader(event, name) {
|
|
35
|
+
try {
|
|
36
|
+
const headers = event?.node?.req?.headers ?? event?.headers;
|
|
37
|
+
if (!headers)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (typeof headers.get === "function") {
|
|
40
|
+
return headers.get(name) ?? undefined;
|
|
41
|
+
}
|
|
42
|
+
const map = headers;
|
|
43
|
+
return map[name] ?? map[String(name).toLowerCase()];
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the base URL to fire a self-dispatch request at. Prefers explicit env
|
|
51
|
+
* vars (most reliable on serverless, where inbound host headers can be the
|
|
52
|
+
* platform's internal hostname), falling back to the inbound request headers
|
|
53
|
+
* and finally localhost in dev.
|
|
54
|
+
*
|
|
55
|
+
* Throws in production / shared deployments when no env var is set — a silent
|
|
56
|
+
* fallback to a bad host there would drop background work invisibly.
|
|
57
|
+
*/
|
|
58
|
+
export function resolveSelfDispatchBaseUrl(event) {
|
|
59
|
+
const fromEnv = process.env.APP_URL ||
|
|
60
|
+
process.env.URL ||
|
|
61
|
+
process.env.DEPLOY_URL ||
|
|
62
|
+
process.env.BETTER_AUTH_URL;
|
|
63
|
+
if (fromEnv)
|
|
64
|
+
return withConfiguredAppBasePath(String(fromEnv));
|
|
65
|
+
if (process.env.NODE_ENV === "production" || !isLocalDatabase()) {
|
|
66
|
+
throw new Error("Self-dispatch requires APP_URL, URL, DEPLOY_URL, or BETTER_AUTH_URL in " +
|
|
67
|
+
"production/shared deployments so background work can reach this " +
|
|
68
|
+
"deployment's own URL.");
|
|
69
|
+
}
|
|
70
|
+
const proto = readHeader(event, "x-forwarded-proto") || "http";
|
|
71
|
+
const host = readHeader(event, "host") || `localhost:${process.env.PORT || 3000}`;
|
|
72
|
+
return withConfiguredAppBasePath(`${proto}://${host}`);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Fire a fresh, HMAC-signed POST to a processor route on this same deployment.
|
|
76
|
+
* Fire-and-forget: the dispatch is NOT awaited to completion (the processed run
|
|
77
|
+
* may take minutes); it is only raced against a short settle timer so the
|
|
78
|
+
* request reliably leaves a serverless box before it freezes.
|
|
79
|
+
*
|
|
80
|
+
* When `A2A_SECRET` is unset (local dev), the request is sent unsigned — the
|
|
81
|
+
* processor accepts unsigned dispatches in dev and relies on the SQL atomic
|
|
82
|
+
* claim for double-processing protection, mirroring the A2A/webhook flow.
|
|
83
|
+
*/
|
|
84
|
+
export async function fireInternalDispatch(options) {
|
|
85
|
+
const baseUrl = options.baseUrl ?? resolveSelfDispatchBaseUrl(options.event);
|
|
86
|
+
const url = `${baseUrl}${options.path}`;
|
|
87
|
+
const headers = {
|
|
88
|
+
"Content-Type": "application/json",
|
|
89
|
+
};
|
|
90
|
+
try {
|
|
91
|
+
headers["Authorization"] = `Bearer ${signInternalToken(options.taskId)}`;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
// Distinguish the documented "no A2A_SECRET in dev" path from a real
|
|
95
|
+
// signing failure, so a malformed secret doesn't fail invisibly.
|
|
96
|
+
if (err instanceof Error && !/A2A_SECRET/i.test(err.message)) {
|
|
97
|
+
console.error(`[self-dispatch] signInternalToken failed unexpectedly for ${options.taskId}:`, err);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const dispatchPromise = fetch(url, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers,
|
|
103
|
+
body: JSON.stringify({ taskId: options.taskId, ...(options.body ?? {}) }),
|
|
104
|
+
}).catch((err) => {
|
|
105
|
+
console.error(`[self-dispatch] dispatch to ${options.path} failed:`, err);
|
|
106
|
+
});
|
|
107
|
+
const settleMs = options.settleMs ?? DEFAULT_DISPATCH_SETTLE_MS;
|
|
108
|
+
await Promise.race([
|
|
109
|
+
dispatchPromise,
|
|
110
|
+
new Promise((resolve) => setTimeout(resolve, settleMs)),
|
|
111
|
+
]);
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=self-dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-dispatch.js","sourceRoot":"","sources":["../../src/server/self-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAE9C,SAAS,UAAU,CAAC,KAAU,EAAE,IAAY;IAC1C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,IAAI,KAAK,EAAE,OAAO,CAAC;QAC5D,IAAI,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC;QAC/B,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;YACtC,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC;QACxC,CAAC;QACD,MAAM,GAAG,GAAG,OAA6C,CAAC;QAC1D,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAAW;IACpD,MAAM,OAAO,GACX,OAAO,CAAC,GAAG,CAAC,OAAO;QACnB,OAAO,CAAC,GAAG,CAAC,GAAG;QACf,OAAO,CAAC,GAAG,CAAC,UAAU;QACtB,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC9B,IAAI,OAAO;QAAE,OAAO,yBAAyB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAE/D,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CACb,yEAAyE;YACvE,kEAAkE;YAClE,uBAAuB,CAC1B,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,mBAAmB,CAAC,IAAI,MAAM,CAAC;IAC/D,MAAM,IAAI,GACR,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,aAAa,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;IACvE,OAAO,yBAAyB,CAAC,GAAG,KAAK,MAAM,IAAI,EAAE,CAAC,CAAC;AACzD,CAAC;AAiBD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAoC;IAEpC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,0BAA0B,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7E,MAAM,GAAG,GAAG,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IACxC,MAAM,OAAO,GAA2B;QACtC,cAAc,EAAE,kBAAkB;KACnC,CAAC;IACF,IAAI,CAAC;QACH,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;IAC3E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,qEAAqE;QACrE,iEAAiE;QACjE,IAAI,GAAG,YAAY,KAAK,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7D,OAAO,CAAC,KAAK,CACX,6DAA6D,OAAO,CAAC,MAAM,GAAG,EAC9E,GAAG,CACJ,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,eAAe,GAAG,KAAK,CAAC,GAAG,EAAE;QACjC,MAAM,EAAE,MAAM;QACd,OAAO;QACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;KAC1E,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACf,OAAO,CAAC,KAAK,CAAC,+BAA+B,OAAO,CAAC,IAAI,UAAU,EAAE,GAAG,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,0BAA0B,CAAC;IAChE,MAAM,OAAO,CAAC,IAAI,CAAC;QACjB,eAAe;QACf,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;KAC9D,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Shared self-dispatch helper for the framework's serverless background-work\n * pattern: enqueue a unit of work to SQL, then fire a fresh HTTP POST back to\n * this same deployment so the work runs in its own function invocation (with\n * its own full timeout budget) instead of riding on the request that created\n * it.\n *\n * This is the single mechanism that makes background work portable across every\n * host Nitro deploys to:\n * - Netlify Lambda / Vercel Functions / AWS Lambda — the dispatched request\n * hits a fresh function with its own budget; no `waitUntil` needed.\n * - Cloudflare Workers — same (and `waitUntil` still works as a belt-and-\n * suspenders fallback where the in-process path is used).\n * - Self-hosted / long-lived Node — the dispatch comes back as another\n * request to the same process; each handler still runs to completion.\n *\n * Originally inlined in both `a2a/handlers.ts` (`resolveSelfBaseUrl` +\n * `fireProcessTaskDispatch`) and `integrations/webhook-handler.ts`\n * (`resolveBaseUrl` + the dispatch in `enqueueAndDispatch`). Extracted here so\n * A2A, integration webhooks, and Agent Teams sub-agents share one tested\n * implementation.\n */\nimport { withConfiguredAppBasePath } from \"./app-base-path.js\";\nimport { isLocalDatabase } from \"../db/client.js\";\nimport { signInternalToken } from \"../integrations/internal-token.js\";\n\n/**\n * On serverless, returning from the dispatching handler before the outbound\n * TCP handshake starts can freeze the function with the dispatch request stuck\n * in the queue. Racing the fetch against a short timer gives the request a\n * chance to leave the box at the cost of a little added latency on the\n * dispatching call. Mirrors the 250ms used by the A2A/webhook paths.\n */\nexport const DEFAULT_DISPATCH_SETTLE_MS = 250;\n\nfunction readHeader(event: any, name: string): string | undefined {\n try {\n const headers = event?.node?.req?.headers ?? event?.headers;\n if (!headers) return undefined;\n if (typeof headers.get === \"function\") {\n return headers.get(name) ?? undefined;\n }\n const map = headers as Record<string, string | undefined>;\n return map[name] ?? map[String(name).toLowerCase()];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolve the base URL to fire a self-dispatch request at. Prefers explicit env\n * vars (most reliable on serverless, where inbound host headers can be the\n * platform's internal hostname), falling back to the inbound request headers\n * and finally localhost in dev.\n *\n * Throws in production / shared deployments when no env var is set — a silent\n * fallback to a bad host there would drop background work invisibly.\n */\nexport function resolveSelfDispatchBaseUrl(event?: any): string {\n const fromEnv =\n process.env.APP_URL ||\n process.env.URL ||\n process.env.DEPLOY_URL ||\n process.env.BETTER_AUTH_URL;\n if (fromEnv) return withConfiguredAppBasePath(String(fromEnv));\n\n if (process.env.NODE_ENV === \"production\" || !isLocalDatabase()) {\n throw new Error(\n \"Self-dispatch requires APP_URL, URL, DEPLOY_URL, or BETTER_AUTH_URL in \" +\n \"production/shared deployments so background work can reach this \" +\n \"deployment's own URL.\",\n );\n }\n\n const proto = readHeader(event, \"x-forwarded-proto\") || \"http\";\n const host =\n readHeader(event, \"host\") || `localhost:${process.env.PORT || 3000}`;\n return withConfiguredAppBasePath(`${proto}://${host}`);\n}\n\nexport interface FireInternalDispatchOptions {\n /** Base URL of this deployment. Defaults to `resolveSelfDispatchBaseUrl(event)`. */\n baseUrl?: string;\n /** Request event used to derive the base URL when `baseUrl` is omitted. */\n event?: any;\n /** Framework route path to POST to (e.g. \"/_agent-native/agent-teams/_process-run\"). */\n path: string;\n /** Task/run id the processor will claim. Used to sign the HMAC token and as the default body. */\n taskId: string;\n /** Extra fields merged into the JSON body alongside `{ taskId }`. */\n body?: Record<string, unknown>;\n /** Max ms to wait for the outbound request to leave the box. Default 250ms. */\n settleMs?: number;\n}\n\n/**\n * Fire a fresh, HMAC-signed POST to a processor route on this same deployment.\n * Fire-and-forget: the dispatch is NOT awaited to completion (the processed run\n * may take minutes); it is only raced against a short settle timer so the\n * request reliably leaves a serverless box before it freezes.\n *\n * When `A2A_SECRET` is unset (local dev), the request is sent unsigned — the\n * processor accepts unsigned dispatches in dev and relies on the SQL atomic\n * claim for double-processing protection, mirroring the A2A/webhook flow.\n */\nexport async function fireInternalDispatch(\n options: FireInternalDispatchOptions,\n): Promise<void> {\n const baseUrl = options.baseUrl ?? resolveSelfDispatchBaseUrl(options.event);\n const url = `${baseUrl}${options.path}`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n try {\n headers[\"Authorization\"] = `Bearer ${signInternalToken(options.taskId)}`;\n } catch (err) {\n // Distinguish the documented \"no A2A_SECRET in dev\" path from a real\n // signing failure, so a malformed secret doesn't fail invisibly.\n if (err instanceof Error && !/A2A_SECRET/i.test(err.message)) {\n console.error(\n `[self-dispatch] signInternalToken failed unexpectedly for ${options.taskId}:`,\n err,\n );\n }\n }\n\n const dispatchPromise = fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ taskId: options.taskId, ...(options.body ?? {}) }),\n }).catch((err) => {\n console.error(`[self-dispatch] dispatch to ${options.path} failed:`, err);\n });\n\n const settleMs = options.settleMs ?? DEFAULT_DISPATCH_SETTLE_MS;\n await Promise.race([\n dispatchPromise,\n new Promise<void>((resolve) => setTimeout(resolve, settleMs)),\n ]);\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface AgentNativeOgImageInput {
|
|
2
|
+
appName?: string | null;
|
|
3
|
+
title?: string | null;
|
|
4
|
+
accentText?: string | null;
|
|
5
|
+
}
|
|
6
|
+
export declare const AGENT_NATIVE_OG_IMAGE_WIDTH = 1200;
|
|
7
|
+
export declare const AGENT_NATIVE_OG_IMAGE_HEIGHT = 630;
|
|
8
|
+
export declare const AGENT_NATIVE_OG_IMAGE_CACHE_CONTROL = "public, max-age=60, stale-while-revalidate=604800, stale-if-error=3600";
|
|
9
|
+
export declare const AGENT_NATIVE_OG_IMAGE_NETLIFY_CACHE_CONTROL = "public, durable, max-age=60, stale-while-revalidate=604800, stale-if-error=3600";
|
|
10
|
+
export declare function renderAgentNativeOgImageSvg(input?: AgentNativeOgImageInput): string;
|
|
11
|
+
export declare function renderAgentNativeOgImagePng(input?: AgentNativeOgImageInput): Promise<Uint8Array>;
|
|
12
|
+
export declare function agentNativeOgImageResponseHeaders(byteLength?: number): Record<string, string>;
|
|
13
|
+
export declare function createAgentNativeOgImageHandler(options?: AgentNativeOgImageInput): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<Response>>;
|
|
14
|
+
//# sourceMappingURL=social-og-image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"social-og-image.d.ts","sourceRoot":"","sources":["../../src/server/social-og-image.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAChD,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAChD,eAAO,MAAM,mCAAmC,2EAC0B,CAAC;AAC3E,eAAO,MAAM,2CAA2C,oFAC2B,CAAC;AAmOpF,wBAAgB,2BAA2B,CACzC,KAAK,GAAE,uBAA4B,GAClC,MAAM,CAsCR;AAED,wBAAsB,2BAA2B,CAC/C,KAAK,GAAE,uBAA4B,GAClC,OAAO,CAAC,UAAU,CAAC,CAWrB;AAED,wBAAgB,iCAAiC,CAC/C,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAYxB;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,uBAA4B,2FAuBtC"}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { defineEventHandler, getHeader, getMethod, getQuery, getRequestURL, } from "h3";
|
|
2
|
+
import { resolveBuiltInAuthMarketing } from "./auth-marketing.js";
|
|
3
|
+
import { getAppName } from "./app-name.js";
|
|
4
|
+
export const AGENT_NATIVE_OG_IMAGE_WIDTH = 1200;
|
|
5
|
+
export const AGENT_NATIVE_OG_IMAGE_HEIGHT = 630;
|
|
6
|
+
export const AGENT_NATIVE_OG_IMAGE_CACHE_CONTROL = "public, max-age=60, stale-while-revalidate=604800, stale-if-error=3600";
|
|
7
|
+
export const AGENT_NATIVE_OG_IMAGE_NETLIFY_CACHE_CONTROL = "public, durable, max-age=60, stale-while-revalidate=604800, stale-if-error=3600";
|
|
8
|
+
const WIDTH = AGENT_NATIVE_OG_IMAGE_WIDTH;
|
|
9
|
+
const HEIGHT = AGENT_NATIVE_OG_IMAGE_HEIGHT;
|
|
10
|
+
const BRAND_BLUE = "#00B5FF";
|
|
11
|
+
const BRAND_MINT = "#48FFE4";
|
|
12
|
+
const BG = "#000000";
|
|
13
|
+
const FG = "#f5f5f5";
|
|
14
|
+
const FONT_FAMILY = "Inter, Liberation Sans, Arial, Helvetica, system-ui, sans-serif";
|
|
15
|
+
const DEFAULT_ACCENT_TEXT = "100% free and open source";
|
|
16
|
+
const LOGO_MARK = `
|
|
17
|
+
<path d="M24.5537 65.7695H0L15.0859 39.4619L37.708 0L60.4912 39.4619H39.6396L24.5537 65.7695Z" fill="white"/>
|
|
18
|
+
<path d="M89.446 0H114L76.2921 65.7704H51.7383L89.446 0Z" fill="url(#brand)"/>
|
|
19
|
+
`;
|
|
20
|
+
function escapeSvg(value) {
|
|
21
|
+
return value
|
|
22
|
+
.replace(/&/g, "&")
|
|
23
|
+
.replace(/</g, "<")
|
|
24
|
+
.replace(/>/g, ">")
|
|
25
|
+
.replace(/"/g, """);
|
|
26
|
+
}
|
|
27
|
+
function cleanText(value) {
|
|
28
|
+
return String(value ?? "")
|
|
29
|
+
.replace(/\s+/g, " ")
|
|
30
|
+
.trim();
|
|
31
|
+
}
|
|
32
|
+
function titleCase(value) {
|
|
33
|
+
return value
|
|
34
|
+
.split(/[\s._-]+/)
|
|
35
|
+
.filter(Boolean)
|
|
36
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
37
|
+
.join(" ");
|
|
38
|
+
}
|
|
39
|
+
function titleFromAppName(appName) {
|
|
40
|
+
if (appName)
|
|
41
|
+
return appName;
|
|
42
|
+
const basePath = process.env.VITE_APP_BASE_PATH || process.env.APP_BASE_PATH || "";
|
|
43
|
+
const slug = basePath.split("/").filter(Boolean)[0] || "";
|
|
44
|
+
return titleCase(slug) || "Agent-Native";
|
|
45
|
+
}
|
|
46
|
+
function estimateTextWidth(value, fontSize) {
|
|
47
|
+
let units = 0;
|
|
48
|
+
for (const char of value) {
|
|
49
|
+
if (char === " ") {
|
|
50
|
+
units += 0.28;
|
|
51
|
+
}
|
|
52
|
+
else if (/[MW@#%&]/.test(char)) {
|
|
53
|
+
units += 0.86;
|
|
54
|
+
}
|
|
55
|
+
else if (/[A-Z]/.test(char)) {
|
|
56
|
+
units += 0.64;
|
|
57
|
+
}
|
|
58
|
+
else if (/[ilI.,:;|!']/u.test(char)) {
|
|
59
|
+
units += 0.26;
|
|
60
|
+
}
|
|
61
|
+
else if (/[0-9]/.test(char)) {
|
|
62
|
+
units += 0.56;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
units += 0.54;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return units * fontSize;
|
|
69
|
+
}
|
|
70
|
+
function trimTextToWidth(value, fontSize, maxWidth) {
|
|
71
|
+
const ellipsis = "...";
|
|
72
|
+
let trimmed = value.trim();
|
|
73
|
+
while (trimmed.length > 0 &&
|
|
74
|
+
estimateTextWidth(`${trimmed}${ellipsis}`, fontSize) > maxWidth) {
|
|
75
|
+
trimmed = trimmed.slice(0, -1).trimEnd();
|
|
76
|
+
}
|
|
77
|
+
return trimmed ? `${trimmed}${ellipsis}` : ellipsis;
|
|
78
|
+
}
|
|
79
|
+
function wrapTextToWidth(value, fontSize, maxWidth, maxLines) {
|
|
80
|
+
const words = value.split(/\s+/).filter(Boolean);
|
|
81
|
+
const lines = [];
|
|
82
|
+
let current = "";
|
|
83
|
+
let truncated = false;
|
|
84
|
+
for (const word of words) {
|
|
85
|
+
const next = current ? `${current} ${word}` : word;
|
|
86
|
+
if (estimateTextWidth(next, fontSize) <= maxWidth) {
|
|
87
|
+
current = next;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!current) {
|
|
91
|
+
lines.push(trimTextToWidth(word, fontSize, maxWidth));
|
|
92
|
+
truncated = true;
|
|
93
|
+
current = "";
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
lines.push(current);
|
|
97
|
+
current = word;
|
|
98
|
+
}
|
|
99
|
+
if (lines.length === maxLines) {
|
|
100
|
+
truncated = true;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (current && lines.length < maxLines)
|
|
105
|
+
lines.push(current);
|
|
106
|
+
const usedWordCount = lines.join(" ").split(/\s+/).filter(Boolean).length;
|
|
107
|
+
if (usedWordCount < words.length && lines.length > 0) {
|
|
108
|
+
lines[lines.length - 1] = trimTextToWidth(lines[lines.length - 1], fontSize, maxWidth);
|
|
109
|
+
truncated = true;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
lines: lines.length ? lines : [trimTextToWidth(value, fontSize, maxWidth)],
|
|
113
|
+
truncated,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function getTitleLayout(title) {
|
|
117
|
+
const maxTitleWidth = 900;
|
|
118
|
+
if (estimateTextWidth(title, 88) <= maxTitleWidth) {
|
|
119
|
+
return {
|
|
120
|
+
lines: [title],
|
|
121
|
+
fontSize: 88,
|
|
122
|
+
lineHeight: 96,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
for (const fontSize of [76, 70, 64, 58, 52]) {
|
|
126
|
+
const wrapped = wrapTextToWidth(title, fontSize, maxTitleWidth, 2);
|
|
127
|
+
if (!wrapped.truncated) {
|
|
128
|
+
const lineHeight = Math.round(fontSize * 1.1);
|
|
129
|
+
return {
|
|
130
|
+
lines: wrapped.lines,
|
|
131
|
+
fontSize,
|
|
132
|
+
lineHeight,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const fallbackFontSize = 52;
|
|
137
|
+
const wrapped = wrapTextToWidth(title, fallbackFontSize, maxTitleWidth, 2);
|
|
138
|
+
return {
|
|
139
|
+
lines: wrapped.lines,
|
|
140
|
+
fontSize: fallbackFontSize,
|
|
141
|
+
lineHeight: 60,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function textBlock({ lines, x, y, fontSize, lineHeight, weight, fill, anchor = "start", }) {
|
|
145
|
+
return `<text x="${x}" y="${y}" text-anchor="${anchor}" font-family="${FONT_FAMILY}" font-size="${fontSize}" font-weight="${weight}" fill="${fill}">${lines
|
|
146
|
+
.map((line, index) => `<tspan x="${x}" dy="${index === 0 ? 0 : lineHeight}">${escapeSvg(line)}</tspan>`)
|
|
147
|
+
.join("")}</text>`;
|
|
148
|
+
}
|
|
149
|
+
function resolveDefaultAppName(event) {
|
|
150
|
+
const requestHost = event
|
|
151
|
+
? (getHeader(event, "x-forwarded-host") ?? getHeader(event, "host"))
|
|
152
|
+
: undefined;
|
|
153
|
+
const requestPath = event ? getRequestURL(event).pathname : undefined;
|
|
154
|
+
return (getAppName() ??
|
|
155
|
+
resolveBuiltInAuthMarketing({ requestHost, requestPath })?.appName ??
|
|
156
|
+
"Agent-Native");
|
|
157
|
+
}
|
|
158
|
+
function queryStringValue(value, maxLength) {
|
|
159
|
+
if (typeof value !== "string")
|
|
160
|
+
return undefined;
|
|
161
|
+
const clean = cleanText(value).slice(0, maxLength);
|
|
162
|
+
return clean || undefined;
|
|
163
|
+
}
|
|
164
|
+
function pngBody(bytes) {
|
|
165
|
+
const body = new ArrayBuffer(bytes.byteLength);
|
|
166
|
+
new Uint8Array(body).set(bytes);
|
|
167
|
+
return body;
|
|
168
|
+
}
|
|
169
|
+
export function renderAgentNativeOgImageSvg(input = {}) {
|
|
170
|
+
const appName = cleanText(input.appName) || resolveDefaultAppName();
|
|
171
|
+
const title = cleanText(input.title) || titleFromAppName(appName);
|
|
172
|
+
const accentText = cleanText(input.accentText) || DEFAULT_ACCENT_TEXT;
|
|
173
|
+
const titleLayout = getTitleLayout(title);
|
|
174
|
+
const titleY = titleLayout.lines.length > 1 ? 288 : 330;
|
|
175
|
+
const accentY = titleY + titleLayout.lineHeight * (titleLayout.lines.length - 1) + 70;
|
|
176
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${WIDTH}" height="${HEIGHT}" viewBox="0 0 ${WIDTH} ${HEIGHT}">
|
|
177
|
+
<title>${escapeSvg(title)} - Agent-Native preview</title>
|
|
178
|
+
<defs>
|
|
179
|
+
<linearGradient id="brand" x1="101.702" y1="67.4791" x2="113.672" y2="-37.4275" gradientUnits="userSpaceOnUse">
|
|
180
|
+
<stop stop-color="${BRAND_BLUE}"/>
|
|
181
|
+
<stop offset="1" stop-color="${BRAND_MINT}"/>
|
|
182
|
+
</linearGradient>
|
|
183
|
+
<pattern id="grid" width="48" height="48" patternUnits="userSpaceOnUse">
|
|
184
|
+
<path d="M 48 0 L 0 0 0 48" fill="none" stroke="#ffffff" stroke-opacity="0.07" stroke-width="1"/>
|
|
185
|
+
</pattern>
|
|
186
|
+
</defs>
|
|
187
|
+
<rect width="${WIDTH}" height="${HEIGHT}" fill="${BG}"/>
|
|
188
|
+
<rect width="${WIDTH}" height="${HEIGHT}" fill="url(#grid)"/>
|
|
189
|
+
<g transform="translate(80 116) scale(0.94)">
|
|
190
|
+
${LOGO_MARK}
|
|
191
|
+
</g>
|
|
192
|
+
<g>
|
|
193
|
+
${textBlock({
|
|
194
|
+
lines: titleLayout.lines,
|
|
195
|
+
x: 80,
|
|
196
|
+
y: titleY,
|
|
197
|
+
fontSize: titleLayout.fontSize,
|
|
198
|
+
lineHeight: titleLayout.lineHeight,
|
|
199
|
+
weight: 850,
|
|
200
|
+
fill: FG,
|
|
201
|
+
})}
|
|
202
|
+
<text x="84" y="${accentY}" font-family="${FONT_FAMILY}" font-size="34" font-weight="800" fill="${BRAND_BLUE}">${escapeSvg(accentText)}</text>
|
|
203
|
+
</g>
|
|
204
|
+
</svg>`;
|
|
205
|
+
}
|
|
206
|
+
export async function renderAgentNativeOgImagePng(input = {}) {
|
|
207
|
+
const { Resvg } = await import(/* @vite-ignore */ "@resvg/resvg-js");
|
|
208
|
+
const image = new Resvg(renderAgentNativeOgImageSvg(input), {
|
|
209
|
+
fitTo: { mode: "width", value: WIDTH },
|
|
210
|
+
font: {
|
|
211
|
+
loadSystemFonts: true,
|
|
212
|
+
defaultFontFamily: "Arial",
|
|
213
|
+
sansSerifFamily: "Arial",
|
|
214
|
+
},
|
|
215
|
+
}).render();
|
|
216
|
+
return image.asPng();
|
|
217
|
+
}
|
|
218
|
+
export function agentNativeOgImageResponseHeaders(byteLength) {
|
|
219
|
+
const headers = {
|
|
220
|
+
"Content-Type": "image/png",
|
|
221
|
+
"Cache-Control": AGENT_NATIVE_OG_IMAGE_CACHE_CONTROL,
|
|
222
|
+
"CDN-Cache-Control": AGENT_NATIVE_OG_IMAGE_CACHE_CONTROL,
|
|
223
|
+
"Netlify-CDN-Cache-Control": AGENT_NATIVE_OG_IMAGE_NETLIFY_CACHE_CONTROL,
|
|
224
|
+
"Cross-Origin-Resource-Policy": "cross-origin",
|
|
225
|
+
};
|
|
226
|
+
if (typeof byteLength === "number") {
|
|
227
|
+
headers["Content-Length"] = String(byteLength);
|
|
228
|
+
}
|
|
229
|
+
return headers;
|
|
230
|
+
}
|
|
231
|
+
export function createAgentNativeOgImageHandler(options = {}) {
|
|
232
|
+
return defineEventHandler(async (event) => {
|
|
233
|
+
if (getMethod(event) === "HEAD") {
|
|
234
|
+
return new Response(null, {
|
|
235
|
+
headers: agentNativeOgImageResponseHeaders(),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
const query = getQuery(event);
|
|
239
|
+
const appName = cleanText(options.appName) || resolveDefaultAppName(event);
|
|
240
|
+
const png = await renderAgentNativeOgImagePng({
|
|
241
|
+
...options,
|
|
242
|
+
appName,
|
|
243
|
+
title: cleanText(options.title) || queryStringValue(query.title, 140),
|
|
244
|
+
accentText: cleanText(options.accentText) || queryStringValue(query.accentText, 80),
|
|
245
|
+
});
|
|
246
|
+
return new Response(pngBody(png), {
|
|
247
|
+
headers: agentNativeOgImageResponseHeaders(png.byteLength),
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=social-og-image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"social-og-image.js","sourceRoot":"","sources":["../../src/server/social-og-image.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n defineEventHandler,\n getHeader,\n getMethod,\n getQuery,\n getRequestURL,\n type H3Event,\n} from \"h3\";\nimport { resolveBuiltInAuthMarketing } from \"./auth-marketing.js\";\nimport { getAppName } from \"./app-name.js\";\n\nexport interface AgentNativeOgImageInput {\n appName?: string | null;\n title?: string | null;\n accentText?: string | null;\n}\n\nexport const AGENT_NATIVE_OG_IMAGE_WIDTH = 1200;\nexport const AGENT_NATIVE_OG_IMAGE_HEIGHT = 630;\nexport const AGENT_NATIVE_OG_IMAGE_CACHE_CONTROL =\n \"public, max-age=60, stale-while-revalidate=604800, stale-if-error=3600\";\nexport const AGENT_NATIVE_OG_IMAGE_NETLIFY_CACHE_CONTROL =\n \"public, durable, max-age=60, stale-while-revalidate=604800, stale-if-error=3600\";\n\nconst WIDTH = AGENT_NATIVE_OG_IMAGE_WIDTH;\nconst HEIGHT = AGENT_NATIVE_OG_IMAGE_HEIGHT;\nconst BRAND_BLUE = \"#00B5FF\";\nconst BRAND_MINT = \"#48FFE4\";\nconst BG = \"#000000\";\nconst FG = \"#f5f5f5\";\nconst FONT_FAMILY =\n \"Inter, Liberation Sans, Arial, Helvetica, system-ui, sans-serif\";\nconst DEFAULT_ACCENT_TEXT = \"100% free and open source\";\n\nconst LOGO_MARK = `\n <path d=\"M24.5537 65.7695H0L15.0859 39.4619L37.708 0L60.4912 39.4619H39.6396L24.5537 65.7695Z\" fill=\"white\"/>\n <path d=\"M89.446 0H114L76.2921 65.7704H51.7383L89.446 0Z\" fill=\"url(#brand)\"/>\n`;\n\nfunction escapeSvg(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n}\n\nfunction cleanText(value: string | null | undefined): string {\n return String(value ?? \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction titleCase(value: string): string {\n return value\n .split(/[\\s._-]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())\n .join(\" \");\n}\n\nfunction titleFromAppName(appName: string): string {\n if (appName) return appName;\n const basePath =\n process.env.VITE_APP_BASE_PATH || process.env.APP_BASE_PATH || \"\";\n const slug = basePath.split(\"/\").filter(Boolean)[0] || \"\";\n return titleCase(slug) || \"Agent-Native\";\n}\n\ninterface WrappedText {\n lines: string[];\n truncated: boolean;\n}\n\ninterface TitleLayout {\n lines: string[];\n fontSize: number;\n lineHeight: number;\n}\n\nfunction estimateTextWidth(value: string, fontSize: number): number {\n let units = 0;\n for (const char of value) {\n if (char === \" \") {\n units += 0.28;\n } else if (/[MW@#%&]/.test(char)) {\n units += 0.86;\n } else if (/[A-Z]/.test(char)) {\n units += 0.64;\n } else if (/[ilI.,:;|!']/u.test(char)) {\n units += 0.26;\n } else if (/[0-9]/.test(char)) {\n units += 0.56;\n } else {\n units += 0.54;\n }\n }\n return units * fontSize;\n}\n\nfunction trimTextToWidth(\n value: string,\n fontSize: number,\n maxWidth: number,\n): string {\n const ellipsis = \"...\";\n let trimmed = value.trim();\n while (\n trimmed.length > 0 &&\n estimateTextWidth(`${trimmed}${ellipsis}`, fontSize) > maxWidth\n ) {\n trimmed = trimmed.slice(0, -1).trimEnd();\n }\n return trimmed ? `${trimmed}${ellipsis}` : ellipsis;\n}\n\nfunction wrapTextToWidth(\n value: string,\n fontSize: number,\n maxWidth: number,\n maxLines: number,\n): WrappedText {\n const words = value.split(/\\s+/).filter(Boolean);\n const lines: string[] = [];\n let current = \"\";\n let truncated = false;\n\n for (const word of words) {\n const next = current ? `${current} ${word}` : word;\n if (estimateTextWidth(next, fontSize) <= maxWidth) {\n current = next;\n continue;\n }\n if (!current) {\n lines.push(trimTextToWidth(word, fontSize, maxWidth));\n truncated = true;\n current = \"\";\n } else {\n lines.push(current);\n current = word;\n }\n if (lines.length === maxLines) {\n truncated = true;\n break;\n }\n }\n if (current && lines.length < maxLines) lines.push(current);\n\n const usedWordCount = lines.join(\" \").split(/\\s+/).filter(Boolean).length;\n if (usedWordCount < words.length && lines.length > 0) {\n lines[lines.length - 1] = trimTextToWidth(\n lines[lines.length - 1],\n fontSize,\n maxWidth,\n );\n truncated = true;\n }\n\n return {\n lines: lines.length ? lines : [trimTextToWidth(value, fontSize, maxWidth)],\n truncated,\n };\n}\n\nfunction getTitleLayout(title: string): TitleLayout {\n const maxTitleWidth = 900;\n if (estimateTextWidth(title, 88) <= maxTitleWidth) {\n return {\n lines: [title],\n fontSize: 88,\n lineHeight: 96,\n };\n }\n\n for (const fontSize of [76, 70, 64, 58, 52]) {\n const wrapped = wrapTextToWidth(title, fontSize, maxTitleWidth, 2);\n if (!wrapped.truncated) {\n const lineHeight = Math.round(fontSize * 1.1);\n return {\n lines: wrapped.lines,\n fontSize,\n lineHeight,\n };\n }\n }\n\n const fallbackFontSize = 52;\n const wrapped = wrapTextToWidth(title, fallbackFontSize, maxTitleWidth, 2);\n return {\n lines: wrapped.lines,\n fontSize: fallbackFontSize,\n lineHeight: 60,\n };\n}\n\nfunction textBlock({\n lines,\n x,\n y,\n fontSize,\n lineHeight,\n weight,\n fill,\n anchor = \"start\",\n}: {\n lines: string[];\n x: number;\n y: number;\n fontSize: number;\n lineHeight: number;\n weight: number;\n fill: string;\n anchor?: \"start\" | \"middle\";\n}): string {\n return `<text x=\"${x}\" y=\"${y}\" text-anchor=\"${anchor}\" font-family=\"${FONT_FAMILY}\" font-size=\"${fontSize}\" font-weight=\"${weight}\" fill=\"${fill}\">${lines\n .map(\n (line, index) =>\n `<tspan x=\"${x}\" dy=\"${index === 0 ? 0 : lineHeight}\">${escapeSvg(line)}</tspan>`,\n )\n .join(\"\")}</text>`;\n}\n\nfunction resolveDefaultAppName(event?: H3Event): string {\n const requestHost = event\n ? (getHeader(event, \"x-forwarded-host\") ?? getHeader(event, \"host\"))\n : undefined;\n const requestPath = event ? getRequestURL(event).pathname : undefined;\n return (\n getAppName() ??\n resolveBuiltInAuthMarketing({ requestHost, requestPath })?.appName ??\n \"Agent-Native\"\n );\n}\n\nfunction queryStringValue(\n value: unknown,\n maxLength: number,\n): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const clean = cleanText(value).slice(0, maxLength);\n return clean || undefined;\n}\n\nfunction pngBody(bytes: Uint8Array): ArrayBuffer {\n const body = new ArrayBuffer(bytes.byteLength);\n new Uint8Array(body).set(bytes);\n return body;\n}\n\nexport function renderAgentNativeOgImageSvg(\n input: AgentNativeOgImageInput = {},\n): string {\n const appName = cleanText(input.appName) || resolveDefaultAppName();\n const title = cleanText(input.title) || titleFromAppName(appName);\n const accentText = cleanText(input.accentText) || DEFAULT_ACCENT_TEXT;\n const titleLayout = getTitleLayout(title);\n const titleY = titleLayout.lines.length > 1 ? 288 : 330;\n const accentY =\n titleY + titleLayout.lineHeight * (titleLayout.lines.length - 1) + 70;\n\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${WIDTH}\" height=\"${HEIGHT}\" viewBox=\"0 0 ${WIDTH} ${HEIGHT}\">\n <title>${escapeSvg(title)} - Agent-Native preview</title>\n <defs>\n <linearGradient id=\"brand\" x1=\"101.702\" y1=\"67.4791\" x2=\"113.672\" y2=\"-37.4275\" gradientUnits=\"userSpaceOnUse\">\n <stop stop-color=\"${BRAND_BLUE}\"/>\n <stop offset=\"1\" stop-color=\"${BRAND_MINT}\"/>\n </linearGradient>\n <pattern id=\"grid\" width=\"48\" height=\"48\" patternUnits=\"userSpaceOnUse\">\n <path d=\"M 48 0 L 0 0 0 48\" fill=\"none\" stroke=\"#ffffff\" stroke-opacity=\"0.07\" stroke-width=\"1\"/>\n </pattern>\n </defs>\n <rect width=\"${WIDTH}\" height=\"${HEIGHT}\" fill=\"${BG}\"/>\n <rect width=\"${WIDTH}\" height=\"${HEIGHT}\" fill=\"url(#grid)\"/>\n <g transform=\"translate(80 116) scale(0.94)\">\n ${LOGO_MARK}\n </g>\n <g>\n ${textBlock({\n lines: titleLayout.lines,\n x: 80,\n y: titleY,\n fontSize: titleLayout.fontSize,\n lineHeight: titleLayout.lineHeight,\n weight: 850,\n fill: FG,\n })}\n <text x=\"84\" y=\"${accentY}\" font-family=\"${FONT_FAMILY}\" font-size=\"34\" font-weight=\"800\" fill=\"${BRAND_BLUE}\">${escapeSvg(accentText)}</text>\n </g>\n</svg>`;\n}\n\nexport async function renderAgentNativeOgImagePng(\n input: AgentNativeOgImageInput = {},\n): Promise<Uint8Array> {\n const { Resvg } = await import(/* @vite-ignore */ \"@resvg/resvg-js\");\n const image = new Resvg(renderAgentNativeOgImageSvg(input), {\n fitTo: { mode: \"width\", value: WIDTH },\n font: {\n loadSystemFonts: true,\n defaultFontFamily: \"Arial\",\n sansSerifFamily: \"Arial\",\n },\n }).render();\n return image.asPng();\n}\n\nexport function agentNativeOgImageResponseHeaders(\n byteLength?: number,\n): Record<string, string> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"image/png\",\n \"Cache-Control\": AGENT_NATIVE_OG_IMAGE_CACHE_CONTROL,\n \"CDN-Cache-Control\": AGENT_NATIVE_OG_IMAGE_CACHE_CONTROL,\n \"Netlify-CDN-Cache-Control\": AGENT_NATIVE_OG_IMAGE_NETLIFY_CACHE_CONTROL,\n \"Cross-Origin-Resource-Policy\": \"cross-origin\",\n };\n if (typeof byteLength === \"number\") {\n headers[\"Content-Length\"] = String(byteLength);\n }\n return headers;\n}\n\nexport function createAgentNativeOgImageHandler(\n options: AgentNativeOgImageInput = {},\n) {\n return defineEventHandler(async (event) => {\n if (getMethod(event) === \"HEAD\") {\n return new Response(null, {\n headers: agentNativeOgImageResponseHeaders(),\n });\n }\n\n const query = getQuery(event);\n const appName = cleanText(options.appName) || resolveDefaultAppName(event);\n const png = await renderAgentNativeOgImagePng({\n ...options,\n appName,\n title: cleanText(options.title) || queryStringValue(query.title, 140),\n accentText:\n cleanText(options.accentText) || queryStringValue(query.accentText, 80),\n });\n\n return new Response(pngBody(png), {\n headers: agentNativeOgImageResponseHeaders(png.byteLength),\n });\n });\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { DEFAULT_SPECULATION_RULES_HEADER, DEFAULT_SSR_CACHE_CONTROL, } from "../shared/cache-control.js";
|
|
1
|
+
export { DEFAULT_SSR_CACHE_HEADERS, DEFAULT_SPECULATION_RULES_HEADER, DEFAULT_SSR_CACHE_CONTROL, } from "../shared/cache-control.js";
|
|
2
2
|
/**
|
|
3
3
|
* Create an h3 catch-all that hands page routes to React Router and
|
|
4
4
|
* returns 404 for framework / asset paths that React Router doesn't own.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-handler.d.ts","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ssr-handler.d.ts","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"AA4CA,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,yBAAyB,GAC1B,MAAM,4BAA4B,CAAC;AAyWpC;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,2FAyE5E"}
|
|
@@ -22,9 +22,9 @@ import { BETTER_AUTH_COOKIE_PREFIX, COOKIE_NAME, getSession } from "./auth.js";
|
|
|
22
22
|
import { hasAuthContextAccess, runWithRequestContext, } from "./request-context.js";
|
|
23
23
|
import { requestHasEmbedAuthMarker } from "./embed-session.js";
|
|
24
24
|
import { EMBED_SESSION_COOKIE, EMBED_TOKEN_QUERY_PARAM, } from "../shared/embed-auth.js";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
export { DEFAULT_SPECULATION_RULES_HEADER, DEFAULT_SSR_CACHE_CONTROL, } from "../shared/cache-control.js";
|
|
25
|
+
import { AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_NATIVE_SOCIAL_IMAGE_PATH, AGENT_NATIVE_SOCIAL_IMAGE_TYPE, AGENT_NATIVE_SOCIAL_IMAGE_WIDTH, } from "../shared/social-meta.js";
|
|
26
|
+
import { DEFAULT_SSR_CACHE_HEADERS, DEFAULT_SPECULATION_RULES_PATH, } from "../shared/cache-control.js";
|
|
27
|
+
export { DEFAULT_SSR_CACHE_HEADERS, DEFAULT_SPECULATION_RULES_HEADER, DEFAULT_SSR_CACHE_CONTROL, } from "../shared/cache-control.js";
|
|
28
28
|
const ANONYMOUS_SESSION_COOKIE_NAMES = new Set(["an_docs_session"]);
|
|
29
29
|
const BETTER_AUTH_SESSION_COOKIE_RE = /\.session_(?:token|data)$/;
|
|
30
30
|
/**
|
|
@@ -132,20 +132,29 @@ function injectHeadScript(html, script) {
|
|
|
132
132
|
const OG_IMAGE_META_RE = /<meta\b(?=[^>]*\bproperty=(["'])og:image\1)[^>]*>/i;
|
|
133
133
|
const TWITTER_CARD_META_RE = /<meta\b(?=[^>]*\bname=(["'])twitter:card\1)[^>]*>/i;
|
|
134
134
|
const TWITTER_IMAGE_META_RE = /<meta\b(?=[^>]*\bname=(["'])twitter:image\1)[^>]*>/i;
|
|
135
|
-
function
|
|
135
|
+
function defaultSocialImageUrl(requestUrl, basePath) {
|
|
136
|
+
return new URL(prefixMountedPath(AGENT_NATIVE_SOCIAL_IMAGE_PATH, basePath), requestUrl).toString();
|
|
137
|
+
}
|
|
138
|
+
function injectDefaultSocialImageMeta(html, imageUrl) {
|
|
136
139
|
const headCloseIdx = html.indexOf("</head>");
|
|
137
140
|
if (headCloseIdx === -1)
|
|
138
141
|
return html;
|
|
139
142
|
const hasAnySocialImage = OG_IMAGE_META_RE.test(html) || TWITTER_IMAGE_META_RE.test(html);
|
|
140
143
|
const tags = [];
|
|
141
144
|
if (!hasAnySocialImage) {
|
|
142
|
-
tags.push(`<meta property="og:image" content="${
|
|
145
|
+
tags.push(`<meta property="og:image" content="${imageUrl}">`);
|
|
146
|
+
tags.push(`<meta property="og:image:secure_url" content="${imageUrl}">`);
|
|
147
|
+
tags.push(`<meta property="og:image:type" content="${AGENT_NATIVE_SOCIAL_IMAGE_TYPE}">`);
|
|
148
|
+
tags.push(`<meta property="og:image:width" content="${AGENT_NATIVE_SOCIAL_IMAGE_WIDTH}">`);
|
|
149
|
+
tags.push(`<meta property="og:image:height" content="${AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT}">`);
|
|
150
|
+
tags.push(`<meta property="og:image:alt" content="${AGENT_NATIVE_SOCIAL_IMAGE_ALT}">`);
|
|
143
151
|
}
|
|
144
152
|
if (!TWITTER_CARD_META_RE.test(html)) {
|
|
145
153
|
tags.push(`<meta name="twitter:card" content="summary_large_image">`);
|
|
146
154
|
}
|
|
147
155
|
if (!hasAnySocialImage) {
|
|
148
|
-
tags.push(`<meta name="twitter:image" content="${
|
|
156
|
+
tags.push(`<meta name="twitter:image" content="${imageUrl}">`);
|
|
157
|
+
tags.push(`<meta name="twitter:image:alt" content="${AGENT_NATIVE_SOCIAL_IMAGE_ALT}">`);
|
|
149
158
|
}
|
|
150
159
|
if (tags.length === 0)
|
|
151
160
|
return html;
|
|
@@ -221,7 +230,14 @@ function applyDefaultSsrCacheHeader(headers, status, pathname, authContextAccess
|
|
|
221
230
|
if (!shouldUseDefaultSsrCacheHeader(headers, status, pathname, authContextAccessed)) {
|
|
222
231
|
return;
|
|
223
232
|
}
|
|
224
|
-
|
|
233
|
+
// Netlify Functions/proxies are not cached by default, and production docs
|
|
234
|
+
// requests often carry stale auth/doc cookies. Keep all three cache headers:
|
|
235
|
+
// Cache-Control for browsers, CDN-Cache-Control for generic CDNs, and
|
|
236
|
+
// Netlify-CDN-Cache-Control (with durable) so Netlify's shared cache actually
|
|
237
|
+
// serves SSR HTML/.data instead of forwarding every request to origin.
|
|
238
|
+
for (const [name, value] of Object.entries(DEFAULT_SSR_CACHE_HEADERS)) {
|
|
239
|
+
headers.set(name, value);
|
|
240
|
+
}
|
|
225
241
|
}
|
|
226
242
|
function applyDefaultSpeculationRulesHeader(headers, status, basePath) {
|
|
227
243
|
if (status < 200 || status >= 400)
|
|
@@ -256,7 +272,7 @@ function isFrameworkOrAssetPath(pathname) {
|
|
|
256
272
|
pathname === "/favicon.png" ||
|
|
257
273
|
(/\.\w+$/.test(pathname) && !pathname.endsWith(".data")));
|
|
258
274
|
}
|
|
259
|
-
async function rewriteMountedResponse(response, basePath, pathname, requestContext) {
|
|
275
|
+
async function rewriteMountedResponse(response, basePath, pathname, requestUrl, requestContext) {
|
|
260
276
|
const sentryClientConfigScript = getSentryClientConfigScript();
|
|
261
277
|
const headers = new Headers(response.headers);
|
|
262
278
|
applyDefaultSsrCacheHeader(headers, response.status, pathname, hasAuthContextAccess(requestContext));
|
|
@@ -275,7 +291,7 @@ async function rewriteMountedResponse(response, basePath, pathname, requestConte
|
|
|
275
291
|
}
|
|
276
292
|
const html = await response.text();
|
|
277
293
|
headers.delete("content-length");
|
|
278
|
-
return new Response(injectHeadScript(injectDefaultSocialImageMeta(prefixMountedHtml(html, basePath)), sentryClientConfigScript), {
|
|
294
|
+
return new Response(injectHeadScript(injectDefaultSocialImageMeta(prefixMountedHtml(html, basePath), defaultSocialImageUrl(requestUrl, basePath)), sentryClientConfigScript), {
|
|
279
295
|
status: response.status,
|
|
280
296
|
statusText: response.statusText,
|
|
281
297
|
headers,
|
|
@@ -326,9 +342,9 @@ export function createH3SSRHandler(getBuild) {
|
|
|
326
342
|
status: response.status,
|
|
327
343
|
statusText: response.statusText,
|
|
328
344
|
headers: response.headers,
|
|
329
|
-
}), basePath, p, ctx);
|
|
345
|
+
}), basePath, p, request.url, ctx);
|
|
330
346
|
}
|
|
331
|
-
return await rewriteMountedResponse(await runWithRequestContext(ctx, () => handler(request)), basePath, p, ctx);
|
|
347
|
+
return await rewriteMountedResponse(await runWithRequestContext(ctx, () => handler(request)), basePath, p, request.url, ctx);
|
|
332
348
|
}
|
|
333
349
|
catch (err) {
|
|
334
350
|
// Log the full stack server-side, but never leak it to the client.
|