@agent-native/core 0.116.0 → 0.117.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +21 -0
  3. package/corpus/core/package.json +4 -1
  4. package/corpus/core/src/client/analytics.ts +7 -0
  5. package/corpus/core/src/client/use-db-sync.ts +354 -15
  6. package/corpus/core/src/collab/client.ts +26 -2
  7. package/corpus/core/src/db/ddl-guard.ts +49 -10
  8. package/corpus/core/src/deploy/build.ts +25 -2
  9. package/corpus/core/src/deploy/workspace-deploy.ts +14 -4
  10. package/corpus/core/src/realtime-protocol.ts +92 -0
  11. package/corpus/core/src/server/core-routes-plugin.ts +7 -0
  12. package/corpus/core/src/server/poll-events.ts +29 -8
  13. package/corpus/core/src/server/poll.ts +1123 -865
  14. package/corpus/core/src/server/realtime-token.ts +106 -0
  15. package/corpus/core/src/server/sentry-config.ts +40 -0
  16. package/corpus/core/src/server/short-lived-token.ts +156 -0
  17. package/corpus/core/src/server/sse.ts +15 -0
  18. package/corpus/core/src/server/ssr-handler.ts +9 -3
  19. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +12 -0
  20. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  21. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +566 -299
  22. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +14 -0
  23. package/corpus/templates/content/app/i18n-data.ts +90 -0
  24. package/dist/client/analytics.d.ts +10 -0
  25. package/dist/client/analytics.d.ts.map +1 -1
  26. package/dist/client/analytics.js.map +1 -1
  27. package/dist/client/use-db-sync.d.ts +1 -1
  28. package/dist/client/use-db-sync.d.ts.map +1 -1
  29. package/dist/client/use-db-sync.js +315 -15
  30. package/dist/client/use-db-sync.js.map +1 -1
  31. package/dist/collab/client.d.ts.map +1 -1
  32. package/dist/collab/client.js +26 -2
  33. package/dist/collab/client.js.map +1 -1
  34. package/dist/collab/routes.d.ts +1 -1
  35. package/dist/collab/struct-routes.d.ts +1 -1
  36. package/dist/db/ddl-guard.d.ts +7 -2
  37. package/dist/db/ddl-guard.d.ts.map +1 -1
  38. package/dist/db/ddl-guard.js +21 -9
  39. package/dist/db/ddl-guard.js.map +1 -1
  40. package/dist/deploy/build.d.ts.map +1 -1
  41. package/dist/deploy/build.js +25 -2
  42. package/dist/deploy/build.js.map +1 -1
  43. package/dist/deploy/workspace-deploy.js +14 -4
  44. package/dist/deploy/workspace-deploy.js.map +1 -1
  45. package/dist/notifications/routes.d.ts +1 -1
  46. package/dist/observability/routes.d.ts +1 -1
  47. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  48. package/dist/provider-api/actions/provider-api.d.ts +6 -6
  49. package/dist/realtime-protocol.d.ts +40 -0
  50. package/dist/realtime-protocol.d.ts.map +1 -0
  51. package/dist/realtime-protocol.js +69 -0
  52. package/dist/realtime-protocol.js.map +1 -0
  53. package/dist/resources/handlers.d.ts +1 -1
  54. package/dist/secrets/routes.d.ts +3 -3
  55. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  56. package/dist/server/core-routes-plugin.js +3 -0
  57. package/dist/server/core-routes-plugin.js.map +1 -1
  58. package/dist/server/poll-events.d.ts +7 -1
  59. package/dist/server/poll-events.d.ts.map +1 -1
  60. package/dist/server/poll-events.js +24 -7
  61. package/dist/server/poll-events.js.map +1 -1
  62. package/dist/server/poll.d.ts +223 -42
  63. package/dist/server/poll.d.ts.map +1 -1
  64. package/dist/server/poll.js +849 -737
  65. package/dist/server/poll.js.map +1 -1
  66. package/dist/server/realtime-token.d.ts +34 -0
  67. package/dist/server/realtime-token.d.ts.map +1 -0
  68. package/dist/server/realtime-token.js +85 -0
  69. package/dist/server/realtime-token.js.map +1 -0
  70. package/dist/server/sentry-config.d.ts +12 -0
  71. package/dist/server/sentry-config.d.ts.map +1 -1
  72. package/dist/server/sentry-config.js +34 -0
  73. package/dist/server/sentry-config.js.map +1 -1
  74. package/dist/server/short-lived-token.d.ts +46 -0
  75. package/dist/server/short-lived-token.d.ts.map +1 -1
  76. package/dist/server/short-lived-token.js +104 -0
  77. package/dist/server/short-lived-token.js.map +1 -1
  78. package/dist/server/sse.d.ts +1 -1
  79. package/dist/server/sse.d.ts.map +1 -1
  80. package/dist/server/sse.js +4 -0
  81. package/dist/server/sse.js.map +1 -1
  82. package/dist/server/ssr-handler.d.ts.map +1 -1
  83. package/dist/server/ssr-handler.js +5 -3
  84. package/dist/server/ssr-handler.js.map +1 -1
  85. package/package.json +4 -1
  86. package/src/client/analytics.ts +7 -0
  87. package/src/client/use-db-sync.ts +354 -15
  88. package/src/collab/client.ts +26 -2
  89. package/src/db/ddl-guard.ts +49 -10
  90. package/src/deploy/build.ts +25 -2
  91. package/src/deploy/workspace-deploy.ts +14 -4
  92. package/src/realtime-protocol.ts +92 -0
  93. package/src/server/core-routes-plugin.ts +7 -0
  94. package/src/server/poll-events.ts +29 -8
  95. package/src/server/poll.ts +1123 -865
  96. package/src/server/realtime-token.ts +106 -0
  97. package/src/server/sentry-config.ts +40 -0
  98. package/src/server/short-lived-token.ts +156 -0
  99. package/src/server/sse.ts +15 -0
  100. package/src/server/ssr-handler.ts +9 -3
@@ -1 +1 @@
1
- {"version":3,"file":"sentry-config.js","sourceRoot":"","sources":["../../src/server/sentry-config.ts"],"names":[],"mappings":"AAAA,SAAS,aAAa,CACpB,GAAG,MAAiC;IAEpC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;QAC9B,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;IAC9B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,8BAA8B;IACrC,MAAM,GAAG,GAAG,aAAa,CACvB,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAC7B,OAAO,CAAC,GAAG,CAAC,sBAAsB,CACnC,CAAC;IACF,MAAM,SAAS,GAAG,aAAa,CAC7B,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAC7B,OAAO,CAAC,GAAG,CAAC,sBAAsB,CACnC,CAAC;IACF,MAAM,IAAI,GAAG,aAAa,CACxB,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAC9B,OAAO,CAAC,GAAG,CAAC,uBAAuB,CACpC,CAAC;IACF,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAClD,OAAO,WAAW,GAAG,IAAI,IAAI,IAAI,SAAS,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,OAAO,CACL,aAAa,CACX,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAC9B,OAAO,CAAC,GAAG,CAAC,eAAe,EAC3B,OAAO,CAAC,GAAG,CAAC,UAAU,EACtB,OAAO,CAAC,GAAG,CAAC,QAAQ,CACrB,IAAI,YAAY,CAClB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,CACL,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;QACpE,8BAA8B,EAAE,CACjC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,CACL,aAAa,CACX,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAC7B,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAClC,OAAO,CAAC,GAAG,CAAC,eAAe,EAC3B,OAAO,CAAC,GAAG,CAAC,UAAU,CACvB,IAAI,8BAA8B,EAAE,CACtC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,2BAA2B;IACzC,MAAM,GAAG,GAAG,sBAAsB,EAAE,CAAC;IACrC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,MAAM,MAAM,GAAG;QACb,SAAS,EAAE,GAAG;QACd,iBAAiB,EAAE,wBAAwB,EAAE;KAC9C,CAAC;IAEF,OAAO;QACL,0CAA0C;QAC1C,iFAAiF;QACjF,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;QACtB,IAAI;QACJ,WAAW;KACZ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACb,CAAC","sourcesContent":["function firstNonEmpty(\n ...values: Array<string | undefined>\n): string | undefined {\n for (const value of values) {\n const trimmed = value?.trim();\n if (trimmed) return trimmed;\n }\n return undefined;\n}\n\nfunction resolveSentryDsnFromKeyProject(): string | undefined {\n const key = firstNonEmpty(\n process.env.SENTRY_CLIENT_KEY,\n process.env.VITE_SENTRY_CLIENT_KEY,\n );\n const projectId = firstNonEmpty(\n process.env.SENTRY_PROJECT_ID,\n process.env.VITE_SENTRY_PROJECT_ID,\n );\n const host = firstNonEmpty(\n process.env.SENTRY_INGEST_HOST,\n process.env.VITE_SENTRY_INGEST_HOST,\n );\n if (!key || !projectId || !host) return undefined;\n return `https://${key}@${host}/${projectId}`;\n}\n\nexport function resolveSentryEnvironment(): string {\n return (\n firstNonEmpty(\n process.env.SENTRY_ENVIRONMENT,\n process.env.NETLIFY_CONTEXT,\n process.env.VERCEL_ENV,\n process.env.NODE_ENV,\n ) ?? \"production\"\n );\n}\n\nexport function resolveServerSentryDsn(): string | undefined {\n return (\n firstNonEmpty(process.env.SENTRY_SERVER_DSN, process.env.SENTRY_DSN) ??\n resolveSentryDsnFromKeyProject()\n );\n}\n\nexport function resolvePublicSentryDsn(): string | undefined {\n return (\n firstNonEmpty(\n process.env.SENTRY_CLIENT_DSN,\n process.env.VITE_SENTRY_CLIENT_DSN,\n process.env.VITE_SENTRY_DSN,\n process.env.SENTRY_DSN,\n ) ?? resolveSentryDsnFromKeyProject()\n );\n}\n\nexport function getSentryClientConfigScript(): string | null {\n const dsn = resolvePublicSentryDsn();\n if (!dsn) return null;\n\n const config = {\n sentryDsn: dsn,\n sentryEnvironment: resolveSentryEnvironment(),\n };\n\n return [\n \"<script data-agent-native-sentry-config>\",\n \"window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,\",\n JSON.stringify(config),\n \");\",\n \"</script>\",\n ].join(\"\");\n}\n"]}
1
+ {"version":3,"file":"sentry-config.js","sourceRoot":"","sources":["../../src/server/sentry-config.ts"],"names":[],"mappings":"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","sourcesContent":["function firstNonEmpty(\n ...values: Array<string | undefined>\n): string | undefined {\n for (const value of values) {\n const trimmed = value?.trim();\n if (trimmed) return trimmed;\n }\n return undefined;\n}\n\nfunction resolveSentryDsnFromKeyProject(): string | undefined {\n const key = firstNonEmpty(\n process.env.SENTRY_CLIENT_KEY,\n process.env.VITE_SENTRY_CLIENT_KEY,\n );\n const projectId = firstNonEmpty(\n process.env.SENTRY_PROJECT_ID,\n process.env.VITE_SENTRY_PROJECT_ID,\n );\n const host = firstNonEmpty(\n process.env.SENTRY_INGEST_HOST,\n process.env.VITE_SENTRY_INGEST_HOST,\n );\n if (!key || !projectId || !host) return undefined;\n return `https://${key}@${host}/${projectId}`;\n}\n\nexport function resolveSentryEnvironment(): string {\n return (\n firstNonEmpty(\n process.env.SENTRY_ENVIRONMENT,\n process.env.NETLIFY_CONTEXT,\n process.env.VERCEL_ENV,\n process.env.NODE_ENV,\n ) ?? \"production\"\n );\n}\n\nexport function resolveServerSentryDsn(): string | undefined {\n return (\n firstNonEmpty(process.env.SENTRY_SERVER_DSN, process.env.SENTRY_DSN) ??\n resolveSentryDsnFromKeyProject()\n );\n}\n\nexport function resolvePublicSentryDsn(): string | undefined {\n return (\n firstNonEmpty(\n process.env.SENTRY_CLIENT_DSN,\n process.env.VITE_SENTRY_CLIENT_DSN,\n process.env.VITE_SENTRY_DSN,\n process.env.SENTRY_DSN,\n ) ?? resolveSentryDsnFromKeyProject()\n );\n}\n\nexport function getSentryClientConfigScript(): string | null {\n const dsn = resolvePublicSentryDsn();\n if (!dsn) return null;\n\n const config = {\n sentryDsn: dsn,\n sentryEnvironment: resolveSentryEnvironment(),\n };\n\n return [\n \"<script data-agent-native-sentry-config>\",\n \"window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,\",\n JSON.stringify(config),\n \");\",\n \"</script>\",\n ].join(\"\");\n}\n\n/**\n * Hosted Realtime Gateway config for the client, or null for the in-process\n * (local) transport. Values are env-derived and identical for every visitor,\n * so this is safe inside the CDN-cached SSR shell (see `guard:ssr-cache-shell`).\n * The per-user subscribe token is NOT here — it is minted client-side after\n * load from `/_agent-native/realtime-token`.\n */\nexport function resolveRealtimeClientConfig(): {\n transport: \"hosted\";\n gatewayBaseUrl: string;\n} | null {\n // Fail closed: emit hosted config only when BOTH the transport is hosted AND\n // an explicit gateway URL is set. No production default — this ships into the\n // CDN-cached shell served to every visitor, so a mis-set staging/preview/\n // self-hosted env must stay on the local transport rather than silently\n // point every browser at api.builder.io. This gating is mirrored byte-for-\n // byte in the worker emitter in `deploy/build.ts` (kept in sync deliberately).\n if (firstNonEmpty(process.env.AGENT_NATIVE_REALTIME_TRANSPORT) !== \"hosted\") {\n return null;\n }\n const gatewayBaseUrl = firstNonEmpty(\n process.env.AGENT_NATIVE_REALTIME_GATEWAY_URL,\n );\n if (!gatewayBaseUrl) return null;\n return { transport: \"hosted\", gatewayBaseUrl };\n}\n\nexport function getRealtimeClientConfigScript(): string | null {\n const realtime = resolveRealtimeClientConfig();\n if (!realtime) return null;\n\n return [\n \"<script data-agent-native-realtime-config>\",\n \"window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,\",\n JSON.stringify({ realtime }),\n \");\",\n \"</script>\",\n ].join(\"\");\n}\n"]}
@@ -60,4 +60,50 @@ export declare function signShortLivedToken(claims: ShortLivedTokenClaims): stri
60
60
  * not surface the reason to viewers (it's useful for server-side logs only).
61
61
  */
62
62
  export declare function verifyShortLivedToken(token: string, expectedResourceId: string): VerifyResult;
63
+ /** Payload `typ` discriminator for realtime subscribe tokens. */
64
+ export declare const REALTIME_SUBSCRIBE_TOKEN_TYPE = "rt-subscribe";
65
+ /** Inputs for {@link signRealtimeSubscribeToken}. */
66
+ export interface RealtimeSubscribeClaims {
67
+ /** Channel — one Neon project per app. Verified at connect, not just carried. */
68
+ projectId: string;
69
+ /**
70
+ * App end-user session email (NOT a Builder.io account — see the tech spec).
71
+ * Fed to `canSeeChangeForUser`. Present in v0.
72
+ */
73
+ owner?: string;
74
+ /** Framework org id for the app's end-user. */
75
+ orgId?: string;
76
+ /** Override default TTL (seconds). */
77
+ ttlSeconds?: number;
78
+ }
79
+ /**
80
+ * Result of {@link verifyRealtimeSubscribeToken}. On success it returns the
81
+ * identity claims the gateway uses to scope delivery.
82
+ */
83
+ export type RealtimeVerifyResult = {
84
+ ok: true;
85
+ projectId: string;
86
+ owner?: string;
87
+ orgId?: string;
88
+ exp: number;
89
+ } | {
90
+ ok: false;
91
+ reason: string;
92
+ };
93
+ /**
94
+ * Mint a realtime subscribe token for `claims.projectId`, signed with the
95
+ * app's per-project `key`. Safe to place on the connect query string (short
96
+ * TTL, single-purpose, channel-bound). Verified by
97
+ * {@link verifyRealtimeSubscribeToken} at connect.
98
+ */
99
+ export declare function signRealtimeSubscribeToken(claims: RealtimeSubscribeClaims, key: string): string;
100
+ /**
101
+ * Verify a realtime subscribe token against the app's per-project `key` and the
102
+ * connect channel `projectId`. Returns the identity claims only when the shape,
103
+ * signature (constant-time), `typ`, `exp`, and `projectId` binding all hold.
104
+ */
105
+ export declare function verifyRealtimeSubscribeToken(token: string, expected: {
106
+ projectId: string;
107
+ key: string;
108
+ }): RealtimeVerifyResult;
63
109
  //# sourceMappingURL=short-lived-token.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"short-lived-token.d.ts","sourceRoot":"","sources":["../../src/server/short-lived-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAQD;;;GAGG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAuClC;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAazE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,kBAAkB,EAAE,MAAM,GACzB,YAAY,CA0Cd"}
1
+ {"version":3,"file":"short-lived-token.d.ts","sourceRoot":"","sources":["../../src/server/short-lived-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAQD;;;GAGG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAuClC;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAazE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,kBAAkB,EAAE,MAAM,GACzB,YAAY,CA0Cd;AAmBD,iEAAiE;AACjE,eAAO,MAAM,6BAA6B,iBAAiB,CAAC;AAG5D,qDAAqD;AACrD,MAAM,WAAW,uBAAuB;IACtC,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAkBlC;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,uBAAuB,EAC/B,GAAG,EAAE,MAAM,GACV,MAAM,CAsBR;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC3C,oBAAoB,CAuCtB"}
@@ -119,4 +119,108 @@ export function verifyShortLivedToken(token, expectedResourceId) {
119
119
  }
120
120
  return { ok: true, viewerEmail: claims.viewerEmail };
121
121
  }
122
+ // ── Realtime subscribe tokens ────────────────────────────────────────────────
123
+ //
124
+ // An identity-bearing extension of the same HMAC discipline, used by the hosted
125
+ // Realtime Gateway. Differs from the media token above in three ways the gateway
126
+ // depends on:
127
+ // 1. It is signed with a caller-supplied PER-PROJECT key (the app's HMAC
128
+ // secret), not the single deployment-wide `getSigningKey()`. The app server
129
+ // mints with it; the gateway (a second issuer for stream rotation) verifies
130
+ // and re-mints with the same per-project key. A token minted for project A
131
+ // fails signature verification against project B's key.
132
+ // 2. It carries authorization-bearing identity (`owner`/`orgId`) that the
133
+ // gateway feeds to `canSeeChangeForUser` — the media token's `viewerEmail`
134
+ // is audit-only by contract, so a new field is required, not repurposed.
135
+ // 3. It binds `projectId` as the channel and stamps a `typ` discriminator, so
136
+ // it is verified against the connect channel and cannot be replayed as a
137
+ // media token (or vice-versa) even if keys ever overlapped.
138
+ /** Payload `typ` discriminator for realtime subscribe tokens. */
139
+ export const REALTIME_SUBSCRIBE_TOKEN_TYPE = "rt-subscribe";
140
+ const DEFAULT_REALTIME_TTL_SECONDS = 600;
141
+ function hmacB64(payloadStr, key) {
142
+ return base64UrlEncode(crypto.createHmac("sha256", key).update(payloadStr).digest());
143
+ }
144
+ function timingSafeEqualB64(sig, expected) {
145
+ const sigBuf = Buffer.from(sig, "utf8");
146
+ const expBuf = Buffer.from(expected, "utf8");
147
+ if (sigBuf.length !== expBuf.length) {
148
+ crypto.timingSafeEqual(expBuf, expBuf); // burn ~equal cycles
149
+ return false;
150
+ }
151
+ return crypto.timingSafeEqual(sigBuf, expBuf);
152
+ }
153
+ /**
154
+ * Mint a realtime subscribe token for `claims.projectId`, signed with the
155
+ * app's per-project `key`. Safe to place on the connect query string (short
156
+ * TTL, single-purpose, channel-bound). Verified by
157
+ * {@link verifyRealtimeSubscribeToken} at connect.
158
+ */
159
+ export function signRealtimeSubscribeToken(claims, key) {
160
+ if (!key)
161
+ throw new Error("signRealtimeSubscribeToken requires a key");
162
+ // Fail closed: a token with neither owner nor orgId carries no authorization
163
+ // identity, so canSeeChangeForUser would only ever match global/unowned
164
+ // events. Every issuer (mint endpoint + the gateway's rotation re-mint) must
165
+ // supply at least one identity claim.
166
+ if (!claims.owner && !claims.orgId) {
167
+ throw new Error("signRealtimeSubscribeToken requires an owner or orgId claim");
168
+ }
169
+ const ttl = claims.ttlSeconds ?? DEFAULT_REALTIME_TTL_SECONDS;
170
+ const payload = {
171
+ typ: REALTIME_SUBSCRIBE_TOKEN_TYPE,
172
+ projectId: claims.projectId,
173
+ exp: Math.floor(Date.now() / 1000) + ttl,
174
+ };
175
+ if (claims.owner)
176
+ payload.owner = claims.owner;
177
+ if (claims.orgId)
178
+ payload.orgId = claims.orgId;
179
+ const payloadStr = base64UrlEncode(JSON.stringify(payload));
180
+ return `${payloadStr}.${hmacB64(payloadStr, key)}`;
181
+ }
182
+ /**
183
+ * Verify a realtime subscribe token against the app's per-project `key` and the
184
+ * connect channel `projectId`. Returns the identity claims only when the shape,
185
+ * signature (constant-time), `typ`, `exp`, and `projectId` binding all hold.
186
+ */
187
+ export function verifyRealtimeSubscribeToken(token, expected) {
188
+ if (!expected.key)
189
+ return { ok: false, reason: "no_key" };
190
+ if (typeof token !== "string" || !token.includes(".")) {
191
+ return { ok: false, reason: "malformed" };
192
+ }
193
+ const [payloadStr, sig] = token.split(".", 2);
194
+ if (!payloadStr || !sig)
195
+ return { ok: false, reason: "malformed" };
196
+ if (!timingSafeEqualB64(sig, hmacB64(payloadStr, expected.key))) {
197
+ return { ok: false, reason: "bad_signature" };
198
+ }
199
+ let claims;
200
+ try {
201
+ claims = JSON.parse(base64UrlDecode(payloadStr).toString("utf8"));
202
+ }
203
+ catch {
204
+ return { ok: false, reason: "bad_payload" };
205
+ }
206
+ if (claims.typ !== REALTIME_SUBSCRIBE_TOKEN_TYPE) {
207
+ return { ok: false, reason: "wrong_type" };
208
+ }
209
+ if (typeof claims.exp !== "number") {
210
+ return { ok: false, reason: "bad_payload" };
211
+ }
212
+ if (claims.exp * 1000 < Date.now()) {
213
+ return { ok: false, reason: "expired" };
214
+ }
215
+ if (claims.projectId !== expected.projectId) {
216
+ return { ok: false, reason: "wrong_project" };
217
+ }
218
+ return {
219
+ ok: true,
220
+ projectId: claims.projectId,
221
+ owner: claims.owner,
222
+ orgId: claims.orgId,
223
+ exp: claims.exp,
224
+ };
225
+ }
122
226
  //# sourceMappingURL=short-lived-token.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"short-lived-token.js","sourceRoot":"","sources":["../../src/server/short-lived-token.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Short-lived HMAC-signed access tokens for media URLs.\n *\n * Used by clips and calls to mint a single-use bearer token after a password\n * gate passes, then bake `?t=<token>` into the video/blob URL handed to the\n * `<video>` element — instead of `?password=<plaintext>` (which ends up in\n * browser history, CDN logs, and Referer headers).\n *\n * Token shape: `<payloadB64Url>.<sigB64Url>`\n * payload = base64url(JSON.stringify({ resourceId, viewerEmail?, exp }))\n * sig = base64url(HMAC-SHA256(payload, key))\n *\n * Key resolution mirrors `google-oauth.ts:getStateSigningKey`:\n * 1. OAUTH_STATE_SECRET (preferred — dedicated to short-lived signing)\n * 2. BETTER_AUTH_SECRET (already used as a server secret)\n * 3. Hosted workspace deploys derive a per-purpose key from A2A_SECRET\n * 4. In dev only, an ephemeral random key (per-process)\n *\n * In production, throws if no usable server secret is set.\n */\n\nimport crypto from \"node:crypto\";\n\nimport { getWorkspaceA2ADerivedSecret } from \"./derived-secret.js\";\n\n/** Default token TTL, in seconds. 10 minutes covers a typical video session. */\nconst DEFAULT_TTL_SECONDS = 600;\n\n/**\n * Inputs for {@link signShortLivedToken}.\n */\nexport interface ShortLivedTokenClaims {\n /** Resource id the token authorises (recording id, call id, snippet id, …). */\n resourceId: string;\n /** Optional viewer email for audit / analytics — not used for authorisation. */\n viewerEmail?: string;\n /** Override default TTL (seconds). */\n ttlSeconds?: number;\n}\n\ninterface DecodedClaims {\n resourceId: string;\n viewerEmail?: string;\n exp: number;\n}\n\n/**\n * Result of {@link verifyShortLivedToken}. Discriminated by the literal\n * `ok` field so callers can `if (!result.ok) return …`.\n */\nexport type VerifyResult =\n | { ok: true; viewerEmail?: string }\n | { ok: false; reason: string };\n\nlet _devSigningKey: string | undefined;\n\nfunction getSigningKey(): string {\n const secret =\n process.env.OAUTH_STATE_SECRET ||\n process.env.BETTER_AUTH_SECRET ||\n getWorkspaceA2ADerivedSecret(\"short-lived-token\");\n if (secret) return secret;\n\n if (process.env.NODE_ENV === \"production\") {\n throw new Error(\n \"Short-lived token signing requires a server secret. \" +\n \"Set OAUTH_STATE_SECRET, BETTER_AUTH_SECRET, or A2A_SECRET in production workspace deploys.\",\n );\n }\n\n if (!_devSigningKey) {\n _devSigningKey = crypto.randomBytes(32).toString(\"hex\");\n }\n return _devSigningKey;\n}\n\nfunction base64UrlEncode(buf: Buffer | string): string {\n const b = typeof buf === \"string\" ? Buffer.from(buf, \"utf8\") : buf;\n return b\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/g, \"\");\n}\n\nfunction base64UrlDecode(s: string): Buffer {\n // Re-pad to a multiple of 4 so Buffer.from('base64') decodes cleanly.\n const padded = s + \"=\".repeat((4 - (s.length % 4)) % 4);\n return Buffer.from(padded.replace(/-/g, \"+\").replace(/_/g, \"/\"), \"base64\");\n}\n\n/**\n * Mint a signed token authorising read access to `claims.resourceId` until\n * `exp = now + ttl`. The result is safe to drop into a query string —\n * `?t=<token>` — and verified by {@link verifyShortLivedToken} on the\n * downstream route.\n */\nexport function signShortLivedToken(claims: ShortLivedTokenClaims): string {\n const ttl = claims.ttlSeconds ?? DEFAULT_TTL_SECONDS;\n const payload: DecodedClaims = {\n resourceId: claims.resourceId,\n exp: Math.floor(Date.now() / 1000) + ttl,\n };\n if (claims.viewerEmail) payload.viewerEmail = claims.viewerEmail;\n\n const payloadStr = base64UrlEncode(JSON.stringify(payload));\n const sig = base64UrlEncode(\n crypto.createHmac(\"sha256\", getSigningKey()).update(payloadStr).digest(),\n );\n return `${payloadStr}.${sig}`;\n}\n\n/**\n * Verify a token previously produced by {@link signShortLivedToken}.\n *\n * Returns `{ ok: true, viewerEmail? }` only when:\n * - the token has the expected shape (`<payload>.<sig>`),\n * - the signature matches via constant-time comparison,\n * - the token has not expired,\n * - the embedded `resourceId` matches `expectedResourceId`.\n *\n * Otherwise returns `{ ok: false, reason: <error string> }`. Callers should\n * not surface the reason to viewers (it's useful for server-side logs only).\n */\nexport function verifyShortLivedToken(\n token: string,\n expectedResourceId: string,\n): VerifyResult {\n if (typeof token !== \"string\" || !token.includes(\".\")) {\n return { ok: false, reason: \"malformed\" };\n }\n const [payloadStr, sig] = token.split(\".\", 2);\n if (!payloadStr || !sig) return { ok: false, reason: \"malformed\" };\n\n const expected = base64UrlEncode(\n crypto.createHmac(\"sha256\", getSigningKey()).update(payloadStr).digest(),\n );\n\n // Constant-time compare. Length-mismatched inputs would throw under\n // `crypto.timingSafeEqual`, so we check length first and fall back to a\n // dummy compare to keep timing roughly constant on the failure path.\n const sigBuf = Buffer.from(sig, \"utf8\");\n const expBuf = Buffer.from(expected, \"utf8\");\n if (sigBuf.length !== expBuf.length) {\n crypto.timingSafeEqual(expBuf, expBuf); // burn ~equal cycles\n return { ok: false, reason: \"bad_signature\" };\n }\n if (!crypto.timingSafeEqual(sigBuf, expBuf)) {\n return { ok: false, reason: \"bad_signature\" };\n }\n\n let claims: DecodedClaims;\n try {\n claims = JSON.parse(base64UrlDecode(payloadStr).toString(\"utf8\"));\n } catch {\n return { ok: false, reason: \"bad_payload\" };\n }\n\n if (typeof claims.exp !== \"number\") {\n return { ok: false, reason: \"bad_payload\" };\n }\n if (claims.exp * 1000 < Date.now()) {\n return { ok: false, reason: \"expired\" };\n }\n if (claims.resourceId !== expectedResourceId) {\n return { ok: false, reason: \"wrong_resource\" };\n }\n\n return { ok: true, viewerEmail: claims.viewerEmail };\n}\n"]}
1
+ {"version":3,"file":"short-lived-token.js","sourceRoot":"","sources":["../../src/server/short-lived-token.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Short-lived HMAC-signed access tokens for media URLs.\n *\n * Used by clips and calls to mint a single-use bearer token after a password\n * gate passes, then bake `?t=<token>` into the video/blob URL handed to the\n * `<video>` element — instead of `?password=<plaintext>` (which ends up in\n * browser history, CDN logs, and Referer headers).\n *\n * Token shape: `<payloadB64Url>.<sigB64Url>`\n * payload = base64url(JSON.stringify({ resourceId, viewerEmail?, exp }))\n * sig = base64url(HMAC-SHA256(payload, key))\n *\n * Key resolution mirrors `google-oauth.ts:getStateSigningKey`:\n * 1. OAUTH_STATE_SECRET (preferred — dedicated to short-lived signing)\n * 2. BETTER_AUTH_SECRET (already used as a server secret)\n * 3. Hosted workspace deploys derive a per-purpose key from A2A_SECRET\n * 4. In dev only, an ephemeral random key (per-process)\n *\n * In production, throws if no usable server secret is set.\n */\n\nimport crypto from \"node:crypto\";\n\nimport { getWorkspaceA2ADerivedSecret } from \"./derived-secret.js\";\n\n/** Default token TTL, in seconds. 10 minutes covers a typical video session. */\nconst DEFAULT_TTL_SECONDS = 600;\n\n/**\n * Inputs for {@link signShortLivedToken}.\n */\nexport interface ShortLivedTokenClaims {\n /** Resource id the token authorises (recording id, call id, snippet id, …). */\n resourceId: string;\n /** Optional viewer email for audit / analytics — not used for authorisation. */\n viewerEmail?: string;\n /** Override default TTL (seconds). */\n ttlSeconds?: number;\n}\n\ninterface DecodedClaims {\n resourceId: string;\n viewerEmail?: string;\n exp: number;\n}\n\n/**\n * Result of {@link verifyShortLivedToken}. Discriminated by the literal\n * `ok` field so callers can `if (!result.ok) return …`.\n */\nexport type VerifyResult =\n | { ok: true; viewerEmail?: string }\n | { ok: false; reason: string };\n\nlet _devSigningKey: string | undefined;\n\nfunction getSigningKey(): string {\n const secret =\n process.env.OAUTH_STATE_SECRET ||\n process.env.BETTER_AUTH_SECRET ||\n getWorkspaceA2ADerivedSecret(\"short-lived-token\");\n if (secret) return secret;\n\n if (process.env.NODE_ENV === \"production\") {\n throw new Error(\n \"Short-lived token signing requires a server secret. \" +\n \"Set OAUTH_STATE_SECRET, BETTER_AUTH_SECRET, or A2A_SECRET in production workspace deploys.\",\n );\n }\n\n if (!_devSigningKey) {\n _devSigningKey = crypto.randomBytes(32).toString(\"hex\");\n }\n return _devSigningKey;\n}\n\nfunction base64UrlEncode(buf: Buffer | string): string {\n const b = typeof buf === \"string\" ? Buffer.from(buf, \"utf8\") : buf;\n return b\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/g, \"\");\n}\n\nfunction base64UrlDecode(s: string): Buffer {\n // Re-pad to a multiple of 4 so Buffer.from('base64') decodes cleanly.\n const padded = s + \"=\".repeat((4 - (s.length % 4)) % 4);\n return Buffer.from(padded.replace(/-/g, \"+\").replace(/_/g, \"/\"), \"base64\");\n}\n\n/**\n * Mint a signed token authorising read access to `claims.resourceId` until\n * `exp = now + ttl`. The result is safe to drop into a query string —\n * `?t=<token>` — and verified by {@link verifyShortLivedToken} on the\n * downstream route.\n */\nexport function signShortLivedToken(claims: ShortLivedTokenClaims): string {\n const ttl = claims.ttlSeconds ?? DEFAULT_TTL_SECONDS;\n const payload: DecodedClaims = {\n resourceId: claims.resourceId,\n exp: Math.floor(Date.now() / 1000) + ttl,\n };\n if (claims.viewerEmail) payload.viewerEmail = claims.viewerEmail;\n\n const payloadStr = base64UrlEncode(JSON.stringify(payload));\n const sig = base64UrlEncode(\n crypto.createHmac(\"sha256\", getSigningKey()).update(payloadStr).digest(),\n );\n return `${payloadStr}.${sig}`;\n}\n\n/**\n * Verify a token previously produced by {@link signShortLivedToken}.\n *\n * Returns `{ ok: true, viewerEmail? }` only when:\n * - the token has the expected shape (`<payload>.<sig>`),\n * - the signature matches via constant-time comparison,\n * - the token has not expired,\n * - the embedded `resourceId` matches `expectedResourceId`.\n *\n * Otherwise returns `{ ok: false, reason: <error string> }`. Callers should\n * not surface the reason to viewers (it's useful for server-side logs only).\n */\nexport function verifyShortLivedToken(\n token: string,\n expectedResourceId: string,\n): VerifyResult {\n if (typeof token !== \"string\" || !token.includes(\".\")) {\n return { ok: false, reason: \"malformed\" };\n }\n const [payloadStr, sig] = token.split(\".\", 2);\n if (!payloadStr || !sig) return { ok: false, reason: \"malformed\" };\n\n const expected = base64UrlEncode(\n crypto.createHmac(\"sha256\", getSigningKey()).update(payloadStr).digest(),\n );\n\n // Constant-time compare. Length-mismatched inputs would throw under\n // `crypto.timingSafeEqual`, so we check length first and fall back to a\n // dummy compare to keep timing roughly constant on the failure path.\n const sigBuf = Buffer.from(sig, \"utf8\");\n const expBuf = Buffer.from(expected, \"utf8\");\n if (sigBuf.length !== expBuf.length) {\n crypto.timingSafeEqual(expBuf, expBuf); // burn ~equal cycles\n return { ok: false, reason: \"bad_signature\" };\n }\n if (!crypto.timingSafeEqual(sigBuf, expBuf)) {\n return { ok: false, reason: \"bad_signature\" };\n }\n\n let claims: DecodedClaims;\n try {\n claims = JSON.parse(base64UrlDecode(payloadStr).toString(\"utf8\"));\n } catch {\n return { ok: false, reason: \"bad_payload\" };\n }\n\n if (typeof claims.exp !== \"number\") {\n return { ok: false, reason: \"bad_payload\" };\n }\n if (claims.exp * 1000 < Date.now()) {\n return { ok: false, reason: \"expired\" };\n }\n if (claims.resourceId !== expectedResourceId) {\n return { ok: false, reason: \"wrong_resource\" };\n }\n\n return { ok: true, viewerEmail: claims.viewerEmail };\n}\n\n// ── Realtime subscribe tokens ────────────────────────────────────────────────\n//\n// An identity-bearing extension of the same HMAC discipline, used by the hosted\n// Realtime Gateway. Differs from the media token above in three ways the gateway\n// depends on:\n// 1. It is signed with a caller-supplied PER-PROJECT key (the app's HMAC\n// secret), not the single deployment-wide `getSigningKey()`. The app server\n// mints with it; the gateway (a second issuer for stream rotation) verifies\n// and re-mints with the same per-project key. A token minted for project A\n// fails signature verification against project B's key.\n// 2. It carries authorization-bearing identity (`owner`/`orgId`) that the\n// gateway feeds to `canSeeChangeForUser` — the media token's `viewerEmail`\n// is audit-only by contract, so a new field is required, not repurposed.\n// 3. It binds `projectId` as the channel and stamps a `typ` discriminator, so\n// it is verified against the connect channel and cannot be replayed as a\n// media token (or vice-versa) even if keys ever overlapped.\n\n/** Payload `typ` discriminator for realtime subscribe tokens. */\nexport const REALTIME_SUBSCRIBE_TOKEN_TYPE = \"rt-subscribe\";\nconst DEFAULT_REALTIME_TTL_SECONDS = 600;\n\n/** Inputs for {@link signRealtimeSubscribeToken}. */\nexport interface RealtimeSubscribeClaims {\n /** Channel — one Neon project per app. Verified at connect, not just carried. */\n projectId: string;\n /**\n * App end-user session email (NOT a Builder.io account — see the tech spec).\n * Fed to `canSeeChangeForUser`. Present in v0.\n */\n owner?: string;\n /** Framework org id for the app's end-user. */\n orgId?: string;\n /** Override default TTL (seconds). */\n ttlSeconds?: number;\n}\n\ninterface DecodedRealtimeClaims {\n typ: string;\n projectId: string;\n owner?: string;\n orgId?: string;\n exp: number;\n}\n\n/**\n * Result of {@link verifyRealtimeSubscribeToken}. On success it returns the\n * identity claims the gateway uses to scope delivery.\n */\nexport type RealtimeVerifyResult =\n | {\n ok: true;\n projectId: string;\n owner?: string;\n orgId?: string;\n exp: number;\n }\n | { ok: false; reason: string };\n\nfunction hmacB64(payloadStr: string, key: string): string {\n return base64UrlEncode(\n crypto.createHmac(\"sha256\", key).update(payloadStr).digest(),\n );\n}\n\nfunction timingSafeEqualB64(sig: string, expected: string): boolean {\n const sigBuf = Buffer.from(sig, \"utf8\");\n const expBuf = Buffer.from(expected, \"utf8\");\n if (sigBuf.length !== expBuf.length) {\n crypto.timingSafeEqual(expBuf, expBuf); // burn ~equal cycles\n return false;\n }\n return crypto.timingSafeEqual(sigBuf, expBuf);\n}\n\n/**\n * Mint a realtime subscribe token for `claims.projectId`, signed with the\n * app's per-project `key`. Safe to place on the connect query string (short\n * TTL, single-purpose, channel-bound). Verified by\n * {@link verifyRealtimeSubscribeToken} at connect.\n */\nexport function signRealtimeSubscribeToken(\n claims: RealtimeSubscribeClaims,\n key: string,\n): string {\n if (!key) throw new Error(\"signRealtimeSubscribeToken requires a key\");\n // Fail closed: a token with neither owner nor orgId carries no authorization\n // identity, so canSeeChangeForUser would only ever match global/unowned\n // events. Every issuer (mint endpoint + the gateway's rotation re-mint) must\n // supply at least one identity claim.\n if (!claims.owner && !claims.orgId) {\n throw new Error(\n \"signRealtimeSubscribeToken requires an owner or orgId claim\",\n );\n }\n const ttl = claims.ttlSeconds ?? DEFAULT_REALTIME_TTL_SECONDS;\n const payload: DecodedRealtimeClaims = {\n typ: REALTIME_SUBSCRIBE_TOKEN_TYPE,\n projectId: claims.projectId,\n exp: Math.floor(Date.now() / 1000) + ttl,\n };\n if (claims.owner) payload.owner = claims.owner;\n if (claims.orgId) payload.orgId = claims.orgId;\n\n const payloadStr = base64UrlEncode(JSON.stringify(payload));\n return `${payloadStr}.${hmacB64(payloadStr, key)}`;\n}\n\n/**\n * Verify a realtime subscribe token against the app's per-project `key` and the\n * connect channel `projectId`. Returns the identity claims only when the shape,\n * signature (constant-time), `typ`, `exp`, and `projectId` binding all hold.\n */\nexport function verifyRealtimeSubscribeToken(\n token: string,\n expected: { projectId: string; key: string },\n): RealtimeVerifyResult {\n if (!expected.key) return { ok: false, reason: \"no_key\" };\n if (typeof token !== \"string\" || !token.includes(\".\")) {\n return { ok: false, reason: \"malformed\" };\n }\n const [payloadStr, sig] = token.split(\".\", 2);\n if (!payloadStr || !sig) return { ok: false, reason: \"malformed\" };\n\n if (!timingSafeEqualB64(sig, hmacB64(payloadStr, expected.key))) {\n return { ok: false, reason: \"bad_signature\" };\n }\n\n let claims: DecodedRealtimeClaims;\n try {\n claims = JSON.parse(base64UrlDecode(payloadStr).toString(\"utf8\"));\n } catch {\n return { ok: false, reason: \"bad_payload\" };\n }\n\n if (claims.typ !== REALTIME_SUBSCRIBE_TOKEN_TYPE) {\n return { ok: false, reason: \"wrong_type\" };\n }\n if (typeof claims.exp !== \"number\") {\n return { ok: false, reason: \"bad_payload\" };\n }\n if (claims.exp * 1000 < Date.now()) {\n return { ok: false, reason: \"expired\" };\n }\n if (claims.projectId !== expected.projectId) {\n return { ok: false, reason: \"wrong_project\" };\n }\n\n return {\n ok: true,\n projectId: claims.projectId,\n owner: claims.owner,\n orgId: claims.orgId,\n exp: claims.exp,\n };\n}\n"]}
@@ -1,3 +1,4 @@
1
+ export { buildHandshakeFrame, parseHandshakeFrame, parseTokenFrame, REALTIME_CAP_NO_AWARENESS, REALTIME_PROTOCOL_VERSION, REALTIME_SSE_HANDSHAKE_EVENT, REALTIME_SSE_TOKEN_EVENT, type RealtimeHandshake, type RealtimeTokenFrame, } from "../realtime-protocol.js";
1
2
  /** Any object with on/off methods (compatible with EventEmitter, TypedEventEmitter, etc.). */
2
3
  interface EventLike {
3
4
  on(event: string, listener: (...args: any[]) => void): any;
@@ -19,5 +20,4 @@ export interface SSEHandlerOptions {
19
20
  * router.get("/_agent-native/events", createSSEHandler({ extraEmitters }));
20
21
  */
21
22
  export declare function createSSEHandler(options?: SSEHandlerOptions): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<BodyInit>>;
22
- export {};
23
23
  //# sourceMappingURL=sse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../src/server/sse.ts"],"names":[],"mappings":"AAEA,8FAA8F;AAC9F,UAAU,SAAS;IAEjB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,GAAG,CAAC;IAE3D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,GAAG,CAAC;CAC7D;AAED,MAAM,WAAW,iBAAiB;IAChC,8EAA8E;IAC9E,aAAa,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9D;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,iBAAsB,2FA8E/D"}
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../src/server/sse.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,yBAAyB,EACzB,yBAAyB,EACzB,4BAA4B,EAC5B,wBAAwB,EACxB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,yBAAyB,CAAC;AAEjC,8FAA8F;AAC9F,UAAU,SAAS;IAEjB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,GAAG,CAAC;IAE3D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,GAAG,CAAC;CAC7D;AAED,MAAM,WAAW,iBAAiB;IAChC,8EAA8E;IAC9E,aAAa,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9D;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,iBAAsB,2FA8E/D"}
@@ -1,4 +1,8 @@
1
1
  import { defineEventHandler, createEventStream } from "h3";
2
+ // Re-export the wire protocol so server consumers (the hosted Realtime Gateway
3
+ // in ai-services) get the frame contract from the same `./server/sse` subpath.
4
+ // The browser client imports it directly from `../realtime-protocol.js`.
5
+ export { buildHandshakeFrame, parseHandshakeFrame, parseTokenFrame, REALTIME_CAP_NO_AWARENESS, REALTIME_PROTOCOL_VERSION, REALTIME_SSE_HANDSHAKE_EVENT, REALTIME_SSE_TOKEN_EVENT, } from "../realtime-protocol.js";
2
6
  /**
3
7
  * Create an H3 event handler that streams Server-Sent Events.
4
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/server/sse.ts"],"names":[],"mappings":"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","sourcesContent":["import { defineEventHandler, createEventStream } from \"h3\";\n\n/** Any object with on/off methods (compatible with EventEmitter, TypedEventEmitter, etc.). */\ninterface EventLike {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n on(event: string, listener: (...args: any[]) => void): any;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n off(event: string, listener: (...args: any[]) => void): any;\n}\n\nexport interface SSEHandlerOptions {\n /** Additional EventEmitters to stream events from (e.g. DB change events). */\n extraEmitters?: Array<{ emitter: EventLike; event: string }>;\n}\n\n/**\n * Create an H3 event handler that streams Server-Sent Events.\n *\n * Streams events from DB change emitters (application state, settings).\n *\n * Usage:\n * router.get(\"/_agent-native/events\", createSSEHandler({ extraEmitters }));\n */\nexport function createSSEHandler(options: SSEHandlerOptions = {}) {\n return defineEventHandler(async (event) => {\n const stream = createEventStream(event);\n\n let closed = false;\n\n // --- Batch mode for startup sync bursts ---\n let batchMode = false;\n const pending: unknown[] = [];\n let flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n const safePush = (data: string) => {\n if (closed) return;\n try {\n stream.push(data);\n } catch {\n // Connection dead — events lost for this client, EventSource will reconnect\n }\n };\n\n const flush = () => {\n flushTimer = null;\n if (closed || pending.length === 0) return;\n const batch = pending.splice(0);\n safePush(JSON.stringify({ type: \"batch\", events: batch }));\n };\n\n const send = (evt: unknown) => {\n if (closed) return;\n if (batchMode) {\n pending.push(evt);\n if (!flushTimer) flushTimer = setTimeout(flush, 150);\n } else {\n safePush(JSON.stringify(evt));\n }\n };\n\n const cleanups: Array<() => void> = [];\n\n // Subscribe to extra emitters (DB change events)\n for (const { emitter, event: evtName } of options.extraEmitters ?? []) {\n const handler = (data: unknown) => {\n send(data);\n };\n emitter.on(evtName, handler);\n cleanups.push(() => emitter.off(evtName, handler));\n }\n\n // Listen for batch mode signals from sync engine\n for (const { emitter } of options.extraEmitters ?? []) {\n const startBatch = () => {\n batchMode = true;\n };\n const endBatch = () => {\n batchMode = false;\n if (flushTimer) {\n clearTimeout(flushTimer);\n flushTimer = null;\n }\n flush();\n };\n emitter.on(\"sync-burst-start\", startBatch);\n emitter.on(\"sync-burst-end\", endBatch);\n cleanups.push(() => {\n emitter.off(\"sync-burst-start\", startBatch);\n emitter.off(\"sync-burst-end\", endBatch);\n });\n }\n\n stream.onClosed(() => {\n closed = true;\n if (flushTimer) clearTimeout(flushTimer);\n pending.length = 0;\n for (const cleanup of cleanups) cleanup();\n });\n\n return stream.send();\n });\n}\n"]}
1
+ {"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/server/sse.ts"],"names":[],"mappings":"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","sourcesContent":["import { defineEventHandler, createEventStream } from \"h3\";\n\n// Re-export the wire protocol so server consumers (the hosted Realtime Gateway\n// in ai-services) get the frame contract from the same `./server/sse` subpath.\n// The browser client imports it directly from `../realtime-protocol.js`.\nexport {\n buildHandshakeFrame,\n parseHandshakeFrame,\n parseTokenFrame,\n REALTIME_CAP_NO_AWARENESS,\n REALTIME_PROTOCOL_VERSION,\n REALTIME_SSE_HANDSHAKE_EVENT,\n REALTIME_SSE_TOKEN_EVENT,\n type RealtimeHandshake,\n type RealtimeTokenFrame,\n} from \"../realtime-protocol.js\";\n\n/** Any object with on/off methods (compatible with EventEmitter, TypedEventEmitter, etc.). */\ninterface EventLike {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n on(event: string, listener: (...args: any[]) => void): any;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n off(event: string, listener: (...args: any[]) => void): any;\n}\n\nexport interface SSEHandlerOptions {\n /** Additional EventEmitters to stream events from (e.g. DB change events). */\n extraEmitters?: Array<{ emitter: EventLike; event: string }>;\n}\n\n/**\n * Create an H3 event handler that streams Server-Sent Events.\n *\n * Streams events from DB change emitters (application state, settings).\n *\n * Usage:\n * router.get(\"/_agent-native/events\", createSSEHandler({ extraEmitters }));\n */\nexport function createSSEHandler(options: SSEHandlerOptions = {}) {\n return defineEventHandler(async (event) => {\n const stream = createEventStream(event);\n\n let closed = false;\n\n // --- Batch mode for startup sync bursts ---\n let batchMode = false;\n const pending: unknown[] = [];\n let flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n const safePush = (data: string) => {\n if (closed) return;\n try {\n stream.push(data);\n } catch {\n // Connection dead — events lost for this client, EventSource will reconnect\n }\n };\n\n const flush = () => {\n flushTimer = null;\n if (closed || pending.length === 0) return;\n const batch = pending.splice(0);\n safePush(JSON.stringify({ type: \"batch\", events: batch }));\n };\n\n const send = (evt: unknown) => {\n if (closed) return;\n if (batchMode) {\n pending.push(evt);\n if (!flushTimer) flushTimer = setTimeout(flush, 150);\n } else {\n safePush(JSON.stringify(evt));\n }\n };\n\n const cleanups: Array<() => void> = [];\n\n // Subscribe to extra emitters (DB change events)\n for (const { emitter, event: evtName } of options.extraEmitters ?? []) {\n const handler = (data: unknown) => {\n send(data);\n };\n emitter.on(evtName, handler);\n cleanups.push(() => emitter.off(evtName, handler));\n }\n\n // Listen for batch mode signals from sync engine\n for (const { emitter } of options.extraEmitters ?? []) {\n const startBatch = () => {\n batchMode = true;\n };\n const endBatch = () => {\n batchMode = false;\n if (flushTimer) {\n clearTimeout(flushTimer);\n flushTimer = null;\n }\n flush();\n };\n emitter.on(\"sync-burst-start\", startBatch);\n emitter.on(\"sync-burst-end\", endBatch);\n cleanups.push(() => {\n emitter.off(\"sync-burst-start\", startBatch);\n emitter.off(\"sync-burst-end\", endBatch);\n });\n }\n\n stream.onClosed(() => {\n closed = true;\n if (flushTimer) clearTimeout(flushTimer);\n pending.length = 0;\n for (const cleanup of cleanups) cleanup();\n });\n\n return stream.send();\n });\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-handler.d.ts","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"AAyCA,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,yBAAyB,GAC1B,MAAM,4BAA4B,CAAC;AAwVpC;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,2FAwE5E"}
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;AA2VpC;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,2FAwE5E"}
@@ -23,7 +23,7 @@ import { AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_
23
23
  import { getAppBasePathFromViteEnv, stripAppBasePath as canonicalStripAppBasePath, } from "./app-base-path.js";
24
24
  import { captureError } from "./capture-error.js";
25
25
  import { runWithRequestContext } from "./request-context.js";
26
- import { getSentryClientConfigScript } from "./sentry-config.js";
26
+ import { getRealtimeClientConfigScript, getSentryClientConfigScript, } from "./sentry-config.js";
27
27
  export { DEFAULT_SSR_CACHE_HEADERS, DEFAULT_SPECULATION_RULES_HEADER, DEFAULT_SSR_CACHE_CONTROL, } from "../shared/cache-control.js";
28
28
  function getAppBasePath() {
29
29
  return getAppBasePathFromViteEnv();
@@ -266,7 +266,9 @@ function isFrameworkOrAssetPath(pathname) {
266
266
  (/\.\w+$/.test(pathname) && !pathname.endsWith(".data")));
267
267
  }
268
268
  async function rewriteMountedResponse(response, basePath, pathname, requestUrl) {
269
- const sentryClientConfigScript = getSentryClientConfigScript();
269
+ const clientConfigScript = [getSentryClientConfigScript(), getRealtimeClientConfigScript()]
270
+ .filter(Boolean)
271
+ .join("") || null;
270
272
  const headers = new Headers(response.headers);
271
273
  applyDefaultSsrCacheHeader(headers, response.status, pathname);
272
274
  applyDefaultSpeculationRulesHeader(headers, response.status, basePath);
@@ -292,7 +294,7 @@ async function rewriteMountedResponse(response, basePath, pathname, requestUrl)
292
294
  }
293
295
  const html = await response.text();
294
296
  headers.delete("content-length");
295
- return new Response(injectHeadScript(injectDefaultSocialImageMeta(prefixMountedHtml(html, basePath), defaultSocialImageUrl(requestUrl, basePath)), sentryClientConfigScript), {
297
+ return new Response(injectHeadScript(injectDefaultSocialImageMeta(prefixMountedHtml(html, basePath), defaultSocialImageUrl(requestUrl, basePath)), clientConfigScript), {
296
298
  status: response.status,
297
299
  statusText: response.statusText,
298
300
  headers,
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-handler.js","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"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","sourcesContent":["import { defineEventHandler } from \"h3\";\n/**\n * Shared SSR catch-all handler for React Router framework mode.\n *\n * Templates wire this up via:\n *\n * // server/routes/[...page].get.ts\n * import { createH3SSRHandler } from \"@agent-native/core/server/ssr-handler\";\n * export default createH3SSRHandler(\n * () => import(\"virtual:react-router/server-build\"),\n * );\n *\n * The `getBuild` callback MUST live in the template's own source so Vite's\n * @react-router/dev plugin can resolve the `virtual:` module. Pulling the\n * import into core (e.g. via a re-export) puts it in node_modules where\n * Vite's SSR externalizer leaves it untouched and Node's ESM loader rejects\n * the unknown scheme — silently 302'ing every request to \"/\".\n */\nimport { createRequestHandler } from \"react-router\";\n\nimport { isMcpPublicPath } from \"../mcp/route-paths.js\";\nimport {\n DEFAULT_SSR_CACHE_HEADERS,\n DEFAULT_SPECULATION_RULES_PATH,\n} from \"../shared/cache-control.js\";\nimport {\n AGENT_NATIVE_SOCIAL_IMAGE_ALT,\n AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT,\n AGENT_NATIVE_SOCIAL_IMAGE_PATH,\n AGENT_NATIVE_SOCIAL_IMAGE_TYPE,\n AGENT_NATIVE_SOCIAL_IMAGE_WIDTH,\n withAgentNativeSocialImageCacheBuster,\n} from \"../shared/social-meta.js\";\nimport {\n getAppBasePathFromViteEnv,\n stripAppBasePath as canonicalStripAppBasePath,\n} from \"./app-base-path.js\";\nimport { captureError } from \"./capture-error.js\";\nimport { runWithRequestContext } from \"./request-context.js\";\nimport { getSentryClientConfigScript } from \"./sentry-config.js\";\n\nexport {\n DEFAULT_SSR_CACHE_HEADERS,\n DEFAULT_SPECULATION_RULES_HEADER,\n DEFAULT_SSR_CACHE_CONTROL,\n} from \"../shared/cache-control.js\";\n\nfunction getAppBasePath(): string {\n return getAppBasePathFromViteEnv();\n}\n\nfunction stripAppBasePath(pathname: string): string {\n return canonicalStripAppBasePath(pathname, getAppBasePath());\n}\n\nfunction stripBasePath(pathname: string, basePath: string): string {\n if (!basePath) return pathname;\n if (pathname === basePath) return \"/\";\n if (pathname.startsWith(`${basePath}/`)) {\n return pathname.slice(basePath.length) || \"/\";\n }\n return pathname;\n}\n\nfunction requestWithPathname(\n request: Request,\n pathname: string,\n basePath: string,\n): Request {\n const url = new URL(request.url);\n let changed = false;\n if (basePath && pathname === \"/__manifest\") {\n const paths = url.searchParams.get(\"paths\");\n if (paths) {\n const strippedPaths = paths\n .split(\",\")\n .map((path) => stripBasePath(path, basePath))\n .join(\",\");\n if (strippedPaths !== paths) {\n url.searchParams.set(\"paths\", strippedPaths);\n changed = true;\n }\n }\n }\n if (url.pathname !== pathname) {\n url.pathname = pathname;\n changed = true;\n }\n if (!changed) return request;\n const init: RequestInit & { duplex?: \"half\" } = {\n method: request.method,\n headers: request.headers,\n signal: request.signal,\n };\n if (request.body && ![\"GET\", \"HEAD\"].includes(request.method.toUpperCase())) {\n init.body = request.body;\n init.duplex = \"half\";\n }\n return new Request(url, init);\n}\n\nfunction requestForAnonymousSsr(request: Request): Request {\n const headers = new Headers(request.headers);\n headers.delete(\"cookie\");\n headers.delete(\"authorization\");\n const init: RequestInit & { duplex?: \"half\" } = {\n method: request.method,\n headers,\n signal: request.signal,\n };\n if (request.body && ![\"GET\", \"HEAD\"].includes(request.method.toUpperCase())) {\n init.body = request.body;\n init.duplex = \"half\";\n }\n return new Request(request.url, init);\n}\n\nfunction prefixMountedPath(path: string, basePath: string): string {\n if (!basePath || !path.startsWith(\"/\") || path.startsWith(\"//\")) return path;\n if (path === basePath || path.startsWith(`${basePath}/`)) return path;\n return `${basePath}${path}`;\n}\n\nfunction prefixMountedHtml(html: string, basePath: string): string {\n if (!basePath) return html;\n const prefixedHtml = html\n .replace(\n /\\b(href|src|action|formaction|poster)=([\"'])(\\/(?!\\/)[^\"']*)\\2/g,\n (_match, attr: string, quote: string, path: string) =>\n `${attr}=${quote}${prefixMountedPath(path, basePath)}${quote}`,\n )\n .replace(/url\\(([\"']?)(\\/(?!\\/)[^)'\" ]+)\\1\\)/g, (_match, quote, path) => {\n const q = quote || \"\";\n return `url(${q}${prefixMountedPath(path, basePath)}${q})`;\n });\n\n // React Router serializes the server-side basename into its hydration\n // context. The request above is deliberately rendered mount-relative, so\n // that value is normally \"/\" even though the browser URL is mounted at a\n // workspace prefix such as \"/analytics\". If the client hydrates that\n // context unchanged, the mounted pathname no longer matches the route tree:\n // index redirects can stall and child pages can fall through to the 404.\n // Keep the serialized router state consistent with the URLs we just\n // prefixed. Template entry clients also set this defensively for older\n // responses, but the initial hydration state must be correct at the source.\n return prefixedHtml.replace(\n /(window\\.__reactRouterContext\\s*=\\s*\\{\\s*\"basename\"\\s*:\\s*)\"(?:\\\\.|[^\"\\\\])*\"/,\n `$1${JSON.stringify(basePath)}`,\n );\n}\n\nfunction injectHeadScript(html: string, script: string | null): string {\n if (!script) return html;\n const headCloseIdx = html.indexOf(\"</head>\");\n if (headCloseIdx === -1) return html;\n return html.slice(0, headCloseIdx) + script + html.slice(headCloseIdx);\n}\n\nconst OG_IMAGE_META_RE = /<meta\\b(?=[^>]*\\bproperty=([\"'])og:image\\1)[^>]*>/i;\nconst TWITTER_CARD_META_RE =\n /<meta\\b(?=[^>]*\\bname=([\"'])twitter:card\\1)[^>]*>/i;\nconst TWITTER_IMAGE_META_RE =\n /<meta\\b(?=[^>]*\\bname=([\"'])twitter:image\\1)[^>]*>/i;\n\nfunction defaultSocialImageUrl(requestUrl: string, basePath: string): string {\n return withAgentNativeSocialImageCacheBuster(\n new URL(\n prefixMountedPath(AGENT_NATIVE_SOCIAL_IMAGE_PATH, basePath),\n requestUrl,\n ).toString(),\n );\n}\n\nfunction injectDefaultSocialImageMeta(html: string, imageUrl: string): string {\n const headCloseIdx = html.indexOf(\"</head>\");\n if (headCloseIdx === -1) return html;\n\n const hasAnySocialImage =\n OG_IMAGE_META_RE.test(html) || TWITTER_IMAGE_META_RE.test(html);\n const tags: string[] = [];\n\n if (!hasAnySocialImage) {\n tags.push(`<meta property=\"og:image\" content=\"${imageUrl}\">`);\n tags.push(`<meta property=\"og:image:secure_url\" content=\"${imageUrl}\">`);\n tags.push(\n `<meta property=\"og:image:type\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_TYPE}\">`,\n );\n tags.push(\n `<meta property=\"og:image:width\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_WIDTH}\">`,\n );\n tags.push(\n `<meta property=\"og:image:height\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT}\">`,\n );\n tags.push(\n `<meta property=\"og:image:alt\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_ALT}\">`,\n );\n }\n if (!TWITTER_CARD_META_RE.test(html)) {\n tags.push(`<meta name=\"twitter:card\" content=\"summary_large_image\">`);\n }\n if (!hasAnySocialImage) {\n tags.push(`<meta name=\"twitter:image\" content=\"${imageUrl}\">`);\n tags.push(\n `<meta name=\"twitter:image:alt\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_ALT}\">`,\n );\n }\n\n if (tags.length === 0) return html;\n return html.slice(0, headCloseIdx) + tags.join(\"\") + html.slice(headCloseIdx);\n}\n\nfunction isSsrHtmlOrDataResponse(\n headers: Headers,\n status: number,\n pathname: string,\n): boolean {\n if (status < 200 || status >= 400) return false;\n const contentType = headers.get(\"content-type\")?.toLowerCase() ?? \"\";\n if (contentType.includes(\"text/html\")) return true;\n return pathname.endsWith(\".data\") && contentType.includes(\"text/x-script\");\n}\n\n/**\n * Apply the SSR cache policy to the response headers.\n *\n * ┌──────────────────────────────────────────────────────────────────────────┐\n * │ SSR IS A PUBLIC, HARD-CDN-CACHED SHELL — SERVED IDENTICALLY TO EVERYONE. │\n * │ │\n * │ Every SSR HTML / React Router `.data` response gets the same public │\n * │ stale-while-revalidate policy for ALL visitors, authenticated or not, so │\n * │ the edge serves one shared copy and never stampedes origin. │\n * │ │\n * │ DO NOT reintroduce per-user / cookie-based cache variation here (no │\n * │ `private`, no `no-store`, no `Vary: Cookie`, no \"authenticated → don't │\n * │ cache\" branch). That makes pages uncacheable for every logged-in visitor, │\n * │ which is slow and expensive — exactly the regression this guardrail │\n * │ prevents. The reason it is SAFE to hard-cache is that the SSR response is │\n * │ impersonal: `createH3SSRHandler` renders without reading the request's │\n * │ session/cookies, so there is no per-user data baked into the HTML. ALL │\n * │ per-user state (who's logged in, private records, access checks) is │\n * │ resolved CLIENT-SIDE after load. Keep it that way: if you need the SSR │\n * │ output to differ per user, the fix is to move that work client-side, not │\n * │ to disable caching here. │\n * └──────────────────────────────────────────────────────────────────────────┘\n */\nfunction applyDefaultSsrCacheHeader(\n headers: Headers,\n status: number,\n pathname: string,\n) {\n if (!isSsrHtmlOrDataResponse(headers, status, pathname)) return;\n\n // A public shell must never set a viewer cookie or vary by credentials.\n // Preserve harmless content-negotiation dimensions such as Accept-Encoding.\n headers.delete(\"set-cookie\");\n const vary = headers.get(\"vary\");\n if (vary) {\n const publicVary = vary\n .split(\",\")\n .map((value) => value.trim())\n .filter((value) => {\n const normalized = value.toLowerCase();\n return (\n normalized &&\n normalized !== \"*\" &&\n normalized !== \"cookie\" &&\n normalized !== \"authorization\"\n );\n });\n if (publicVary.length > 0) headers.set(\"vary\", publicVary.join(\", \"));\n else headers.delete(\"vary\");\n }\n\n // Netlify Functions/proxies are not cached by default. Set all three cache\n // headers: Cache-Control for browsers, CDN-Cache-Control for generic CDNs,\n // and Netlify-CDN-Cache-Control (with durable) so Netlify's shared cache\n // actually serves SSR HTML/.data from the edge instead of forwarding every\n // request to origin — for every visitor, authenticated or not.\n for (const [name, value] of Object.entries(DEFAULT_SSR_CACHE_HEADERS)) {\n headers.set(name, value);\n }\n}\n\nfunction applyDefaultSpeculationRulesHeader(\n headers: Headers,\n status: number,\n basePath: string,\n) {\n if (status < 200 || status >= 400) return;\n if (headers.has(\"speculation-rules\")) return;\n\n const contentType = headers.get(\"content-type\")?.toLowerCase() ?? \"\";\n if (!contentType.includes(\"text/html\")) return;\n\n // Cloudflare Speed Brain injects its own Speculation-Rules header when the\n // origin omits one. Those browser prefetches carry `Sec-Purpose: prefetch`,\n // and Cloudflare refuses cache-ineligible dynamic pages with a 503 before\n // the request can reach Netlify/origin. We publish an explicit no-op ruleset\n // by default so Cloudflare does not inject its edge prefetch rules. Preserve\n // an app-provided Speculation-Rules header above if a template deliberately\n // owns this behavior.\n const rulesPath = prefixMountedPath(DEFAULT_SPECULATION_RULES_PATH, basePath);\n headers.set(\"speculation-rules\", `\"${rulesPath}\"`);\n}\n\n/**\n * Strip document-level CSP from app HTML responses.\n *\n * Hosted templates inject framework bootstrap scripts, analytics, Sentry config,\n * and app-owned inline scripts whose exact bytes vary by build/template. Any\n * shared CSP header, even Report-Only, can block or noisily report Google Tag\n * Manager and those bootstraps. Extension iframes and webviews keep their own\n * route-specific sandboxes; normal app documents deliberately do not emit CSP.\n */\nfunction removeDocumentCsp(headers: Headers): void {\n headers.delete(\"content-security-policy\");\n headers.delete(\"content-security-policy-report-only\");\n}\n\nfunction isFrameworkOrAssetPath(pathname: string): boolean {\n return (\n isMcpPublicPath(pathname) ||\n pathname.startsWith(\"/.well-known/\") ||\n pathname.startsWith(\"/_agent_native/\") ||\n pathname.startsWith(\"/_agent-native/\") ||\n pathname.startsWith(\"/api/\") ||\n pathname.startsWith(\"/@vite/\") ||\n pathname.startsWith(\"/@id/\") ||\n pathname.startsWith(\"/@fs/\") ||\n pathname === \"/@react-refresh\" ||\n pathname === \"/__vite_ping\" ||\n pathname === \"/__open-in-editor\" ||\n pathname === \"/favicon.ico\" ||\n pathname === \"/favicon.png\" ||\n (/\\.\\w+$/.test(pathname) && !pathname.endsWith(\".data\"))\n );\n}\n\nasync function rewriteMountedResponse(\n response: Response,\n basePath: string,\n pathname: string,\n requestUrl: string,\n): Promise<Response> {\n const sentryClientConfigScript = getSentryClientConfigScript();\n const headers = new Headers(response.headers);\n applyDefaultSsrCacheHeader(headers, response.status, pathname);\n applyDefaultSpeculationRulesHeader(headers, response.status, basePath);\n\n const location = headers.get(\"location\");\n if (location?.startsWith(\"/\") && !location.startsWith(\"//\")) {\n headers.set(\"location\", prefixMountedPath(location, basePath));\n }\n\n const contentType = headers.get(\"content-type\") ?? \"\";\n if (!contentType.toLowerCase().includes(\"text/html\")) {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n removeDocumentCsp(headers);\n if (!response.body) {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n\n const html = await response.text();\n headers.delete(\"content-length\");\n return new Response(\n injectHeadScript(\n injectDefaultSocialImageMeta(\n prefixMountedHtml(html, basePath),\n defaultSocialImageUrl(requestUrl, basePath),\n ),\n sentryClientConfigScript,\n ),\n {\n status: response.status,\n statusText: response.statusText,\n headers,\n },\n );\n}\n\n/**\n * Create an h3 catch-all that hands page routes to React Router and\n * returns 404 for framework / asset paths that React Router doesn't own.\n */\nexport function createH3SSRHandler(getBuild: () => Promise<unknown> | unknown) {\n const handler = createRequestHandler(getBuild as any);\n return defineEventHandler(async (event) => {\n const basePath = getAppBasePath();\n const p = stripAppBasePath(event.url.pathname);\n if (isFrameworkOrAssetPath(p)) {\n return new Response(null, { status: 404 });\n }\n try {\n const request = requestForAnonymousSsr(\n requestWithPathname(event.req as Request, p, basePath),\n );\n // SSR renders an IMPERSONAL public shell — we deliberately do NOT read the\n // request's session/cookies here, and pin an explicitly anonymous request\n // context. That keeps the SSR HTML/.data identical for every visitor so it\n // can be hard-cached at the CDN for everyone (see applyDefaultSsrCacheHeader).\n //\n // Consequence: SSR loaders that call `getRequestUserEmail()` / `accessFilter()`\n // always see the unauthenticated branch and render public content only. Any\n // per-user view (private records, share-grant access, who's logged in) MUST\n // be resolved CLIENT-SIDE after load, never baked into SSR. Do not re-pin the\n // session here to \"fix\" a per-user page — that silently makes the page\n // uncacheable and/or leaks one user's data into another's cached copy.\n const ctx = { userEmail: undefined, orgId: undefined };\n if (request.method === \"HEAD\") {\n const getRequest = new Request(request.url, {\n method: \"GET\",\n headers: request.headers,\n signal: request.signal,\n });\n const response = await runWithRequestContext(ctx, () =>\n handler(getRequest),\n );\n return await rewriteMountedResponse(\n new Response(null, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n }),\n basePath,\n p,\n request.url,\n );\n }\n return await rewriteMountedResponse(\n await runWithRequestContext(ctx, () => handler(request)),\n basePath,\n p,\n request.url,\n );\n } catch (err) {\n // Log the full stack server-side, but never leak it to the client.\n // Stack traces expose file paths, library versions, and code structure\n // that aid reconnaissance attacks. In dev we surface the message text\n // so devtools shows something useful; in prod we return a bare 500.\n console.error(\"[ssr-handler] SSR error:\", err);\n captureError(err, {\n route: p,\n method: event.req.method,\n userAgent: event.req.headers.get(\"user-agent\") ?? undefined,\n tags: { renderMode: \"anonymous-public\", surface: \"ssr\" },\n });\n const isProd = process.env.NODE_ENV === \"production\";\n const body = isProd\n ? \"Internal Server Error\"\n : `Internal Server Error: ${(err as Error)?.message ?? err}`;\n return new Response(body, {\n status: 500,\n headers: { \"content-type\": \"text/plain\" },\n });\n }\n });\n}\n"]}
1
+ {"version":3,"file":"ssr-handler.js","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"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","sourcesContent":["import { defineEventHandler } from \"h3\";\n/**\n * Shared SSR catch-all handler for React Router framework mode.\n *\n * Templates wire this up via:\n *\n * // server/routes/[...page].get.ts\n * import { createH3SSRHandler } from \"@agent-native/core/server/ssr-handler\";\n * export default createH3SSRHandler(\n * () => import(\"virtual:react-router/server-build\"),\n * );\n *\n * The `getBuild` callback MUST live in the template's own source so Vite's\n * @react-router/dev plugin can resolve the `virtual:` module. Pulling the\n * import into core (e.g. via a re-export) puts it in node_modules where\n * Vite's SSR externalizer leaves it untouched and Node's ESM loader rejects\n * the unknown scheme — silently 302'ing every request to \"/\".\n */\nimport { createRequestHandler } from \"react-router\";\n\nimport { isMcpPublicPath } from \"../mcp/route-paths.js\";\nimport {\n DEFAULT_SSR_CACHE_HEADERS,\n DEFAULT_SPECULATION_RULES_PATH,\n} from \"../shared/cache-control.js\";\nimport {\n AGENT_NATIVE_SOCIAL_IMAGE_ALT,\n AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT,\n AGENT_NATIVE_SOCIAL_IMAGE_PATH,\n AGENT_NATIVE_SOCIAL_IMAGE_TYPE,\n AGENT_NATIVE_SOCIAL_IMAGE_WIDTH,\n withAgentNativeSocialImageCacheBuster,\n} from \"../shared/social-meta.js\";\nimport {\n getAppBasePathFromViteEnv,\n stripAppBasePath as canonicalStripAppBasePath,\n} from \"./app-base-path.js\";\nimport { captureError } from \"./capture-error.js\";\nimport { runWithRequestContext } from \"./request-context.js\";\nimport {\n getRealtimeClientConfigScript,\n getSentryClientConfigScript,\n} from \"./sentry-config.js\";\n\nexport {\n DEFAULT_SSR_CACHE_HEADERS,\n DEFAULT_SPECULATION_RULES_HEADER,\n DEFAULT_SSR_CACHE_CONTROL,\n} from \"../shared/cache-control.js\";\n\nfunction getAppBasePath(): string {\n return getAppBasePathFromViteEnv();\n}\n\nfunction stripAppBasePath(pathname: string): string {\n return canonicalStripAppBasePath(pathname, getAppBasePath());\n}\n\nfunction stripBasePath(pathname: string, basePath: string): string {\n if (!basePath) return pathname;\n if (pathname === basePath) return \"/\";\n if (pathname.startsWith(`${basePath}/`)) {\n return pathname.slice(basePath.length) || \"/\";\n }\n return pathname;\n}\n\nfunction requestWithPathname(\n request: Request,\n pathname: string,\n basePath: string,\n): Request {\n const url = new URL(request.url);\n let changed = false;\n if (basePath && pathname === \"/__manifest\") {\n const paths = url.searchParams.get(\"paths\");\n if (paths) {\n const strippedPaths = paths\n .split(\",\")\n .map((path) => stripBasePath(path, basePath))\n .join(\",\");\n if (strippedPaths !== paths) {\n url.searchParams.set(\"paths\", strippedPaths);\n changed = true;\n }\n }\n }\n if (url.pathname !== pathname) {\n url.pathname = pathname;\n changed = true;\n }\n if (!changed) return request;\n const init: RequestInit & { duplex?: \"half\" } = {\n method: request.method,\n headers: request.headers,\n signal: request.signal,\n };\n if (request.body && ![\"GET\", \"HEAD\"].includes(request.method.toUpperCase())) {\n init.body = request.body;\n init.duplex = \"half\";\n }\n return new Request(url, init);\n}\n\nfunction requestForAnonymousSsr(request: Request): Request {\n const headers = new Headers(request.headers);\n headers.delete(\"cookie\");\n headers.delete(\"authorization\");\n const init: RequestInit & { duplex?: \"half\" } = {\n method: request.method,\n headers,\n signal: request.signal,\n };\n if (request.body && ![\"GET\", \"HEAD\"].includes(request.method.toUpperCase())) {\n init.body = request.body;\n init.duplex = \"half\";\n }\n return new Request(request.url, init);\n}\n\nfunction prefixMountedPath(path: string, basePath: string): string {\n if (!basePath || !path.startsWith(\"/\") || path.startsWith(\"//\")) return path;\n if (path === basePath || path.startsWith(`${basePath}/`)) return path;\n return `${basePath}${path}`;\n}\n\nfunction prefixMountedHtml(html: string, basePath: string): string {\n if (!basePath) return html;\n const prefixedHtml = html\n .replace(\n /\\b(href|src|action|formaction|poster)=([\"'])(\\/(?!\\/)[^\"']*)\\2/g,\n (_match, attr: string, quote: string, path: string) =>\n `${attr}=${quote}${prefixMountedPath(path, basePath)}${quote}`,\n )\n .replace(/url\\(([\"']?)(\\/(?!\\/)[^)'\" ]+)\\1\\)/g, (_match, quote, path) => {\n const q = quote || \"\";\n return `url(${q}${prefixMountedPath(path, basePath)}${q})`;\n });\n\n // React Router serializes the server-side basename into its hydration\n // context. The request above is deliberately rendered mount-relative, so\n // that value is normally \"/\" even though the browser URL is mounted at a\n // workspace prefix such as \"/analytics\". If the client hydrates that\n // context unchanged, the mounted pathname no longer matches the route tree:\n // index redirects can stall and child pages can fall through to the 404.\n // Keep the serialized router state consistent with the URLs we just\n // prefixed. Template entry clients also set this defensively for older\n // responses, but the initial hydration state must be correct at the source.\n return prefixedHtml.replace(\n /(window\\.__reactRouterContext\\s*=\\s*\\{\\s*\"basename\"\\s*:\\s*)\"(?:\\\\.|[^\"\\\\])*\"/,\n `$1${JSON.stringify(basePath)}`,\n );\n}\n\nfunction injectHeadScript(html: string, script: string | null): string {\n if (!script) return html;\n const headCloseIdx = html.indexOf(\"</head>\");\n if (headCloseIdx === -1) return html;\n return html.slice(0, headCloseIdx) + script + html.slice(headCloseIdx);\n}\n\nconst OG_IMAGE_META_RE = /<meta\\b(?=[^>]*\\bproperty=([\"'])og:image\\1)[^>]*>/i;\nconst TWITTER_CARD_META_RE =\n /<meta\\b(?=[^>]*\\bname=([\"'])twitter:card\\1)[^>]*>/i;\nconst TWITTER_IMAGE_META_RE =\n /<meta\\b(?=[^>]*\\bname=([\"'])twitter:image\\1)[^>]*>/i;\n\nfunction defaultSocialImageUrl(requestUrl: string, basePath: string): string {\n return withAgentNativeSocialImageCacheBuster(\n new URL(\n prefixMountedPath(AGENT_NATIVE_SOCIAL_IMAGE_PATH, basePath),\n requestUrl,\n ).toString(),\n );\n}\n\nfunction injectDefaultSocialImageMeta(html: string, imageUrl: string): string {\n const headCloseIdx = html.indexOf(\"</head>\");\n if (headCloseIdx === -1) return html;\n\n const hasAnySocialImage =\n OG_IMAGE_META_RE.test(html) || TWITTER_IMAGE_META_RE.test(html);\n const tags: string[] = [];\n\n if (!hasAnySocialImage) {\n tags.push(`<meta property=\"og:image\" content=\"${imageUrl}\">`);\n tags.push(`<meta property=\"og:image:secure_url\" content=\"${imageUrl}\">`);\n tags.push(\n `<meta property=\"og:image:type\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_TYPE}\">`,\n );\n tags.push(\n `<meta property=\"og:image:width\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_WIDTH}\">`,\n );\n tags.push(\n `<meta property=\"og:image:height\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT}\">`,\n );\n tags.push(\n `<meta property=\"og:image:alt\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_ALT}\">`,\n );\n }\n if (!TWITTER_CARD_META_RE.test(html)) {\n tags.push(`<meta name=\"twitter:card\" content=\"summary_large_image\">`);\n }\n if (!hasAnySocialImage) {\n tags.push(`<meta name=\"twitter:image\" content=\"${imageUrl}\">`);\n tags.push(\n `<meta name=\"twitter:image:alt\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_ALT}\">`,\n );\n }\n\n if (tags.length === 0) return html;\n return html.slice(0, headCloseIdx) + tags.join(\"\") + html.slice(headCloseIdx);\n}\n\nfunction isSsrHtmlOrDataResponse(\n headers: Headers,\n status: number,\n pathname: string,\n): boolean {\n if (status < 200 || status >= 400) return false;\n const contentType = headers.get(\"content-type\")?.toLowerCase() ?? \"\";\n if (contentType.includes(\"text/html\")) return true;\n return pathname.endsWith(\".data\") && contentType.includes(\"text/x-script\");\n}\n\n/**\n * Apply the SSR cache policy to the response headers.\n *\n * ┌──────────────────────────────────────────────────────────────────────────┐\n * │ SSR IS A PUBLIC, HARD-CDN-CACHED SHELL — SERVED IDENTICALLY TO EVERYONE. │\n * │ │\n * │ Every SSR HTML / React Router `.data` response gets the same public │\n * │ stale-while-revalidate policy for ALL visitors, authenticated or not, so │\n * │ the edge serves one shared copy and never stampedes origin. │\n * │ │\n * │ DO NOT reintroduce per-user / cookie-based cache variation here (no │\n * │ `private`, no `no-store`, no `Vary: Cookie`, no \"authenticated → don't │\n * │ cache\" branch). That makes pages uncacheable for every logged-in visitor, │\n * │ which is slow and expensive — exactly the regression this guardrail │\n * │ prevents. The reason it is SAFE to hard-cache is that the SSR response is │\n * │ impersonal: `createH3SSRHandler` renders without reading the request's │\n * │ session/cookies, so there is no per-user data baked into the HTML. ALL │\n * │ per-user state (who's logged in, private records, access checks) is │\n * │ resolved CLIENT-SIDE after load. Keep it that way: if you need the SSR │\n * │ output to differ per user, the fix is to move that work client-side, not │\n * │ to disable caching here. │\n * └──────────────────────────────────────────────────────────────────────────┘\n */\nfunction applyDefaultSsrCacheHeader(\n headers: Headers,\n status: number,\n pathname: string,\n) {\n if (!isSsrHtmlOrDataResponse(headers, status, pathname)) return;\n\n // A public shell must never set a viewer cookie or vary by credentials.\n // Preserve harmless content-negotiation dimensions such as Accept-Encoding.\n headers.delete(\"set-cookie\");\n const vary = headers.get(\"vary\");\n if (vary) {\n const publicVary = vary\n .split(\",\")\n .map((value) => value.trim())\n .filter((value) => {\n const normalized = value.toLowerCase();\n return (\n normalized &&\n normalized !== \"*\" &&\n normalized !== \"cookie\" &&\n normalized !== \"authorization\"\n );\n });\n if (publicVary.length > 0) headers.set(\"vary\", publicVary.join(\", \"));\n else headers.delete(\"vary\");\n }\n\n // Netlify Functions/proxies are not cached by default. Set all three cache\n // headers: Cache-Control for browsers, CDN-Cache-Control for generic CDNs,\n // and Netlify-CDN-Cache-Control (with durable) so Netlify's shared cache\n // actually serves SSR HTML/.data from the edge instead of forwarding every\n // request to origin — for every visitor, authenticated or not.\n for (const [name, value] of Object.entries(DEFAULT_SSR_CACHE_HEADERS)) {\n headers.set(name, value);\n }\n}\n\nfunction applyDefaultSpeculationRulesHeader(\n headers: Headers,\n status: number,\n basePath: string,\n) {\n if (status < 200 || status >= 400) return;\n if (headers.has(\"speculation-rules\")) return;\n\n const contentType = headers.get(\"content-type\")?.toLowerCase() ?? \"\";\n if (!contentType.includes(\"text/html\")) return;\n\n // Cloudflare Speed Brain injects its own Speculation-Rules header when the\n // origin omits one. Those browser prefetches carry `Sec-Purpose: prefetch`,\n // and Cloudflare refuses cache-ineligible dynamic pages with a 503 before\n // the request can reach Netlify/origin. We publish an explicit no-op ruleset\n // by default so Cloudflare does not inject its edge prefetch rules. Preserve\n // an app-provided Speculation-Rules header above if a template deliberately\n // owns this behavior.\n const rulesPath = prefixMountedPath(DEFAULT_SPECULATION_RULES_PATH, basePath);\n headers.set(\"speculation-rules\", `\"${rulesPath}\"`);\n}\n\n/**\n * Strip document-level CSP from app HTML responses.\n *\n * Hosted templates inject framework bootstrap scripts, analytics, Sentry config,\n * and app-owned inline scripts whose exact bytes vary by build/template. Any\n * shared CSP header, even Report-Only, can block or noisily report Google Tag\n * Manager and those bootstraps. Extension iframes and webviews keep their own\n * route-specific sandboxes; normal app documents deliberately do not emit CSP.\n */\nfunction removeDocumentCsp(headers: Headers): void {\n headers.delete(\"content-security-policy\");\n headers.delete(\"content-security-policy-report-only\");\n}\n\nfunction isFrameworkOrAssetPath(pathname: string): boolean {\n return (\n isMcpPublicPath(pathname) ||\n pathname.startsWith(\"/.well-known/\") ||\n pathname.startsWith(\"/_agent_native/\") ||\n pathname.startsWith(\"/_agent-native/\") ||\n pathname.startsWith(\"/api/\") ||\n pathname.startsWith(\"/@vite/\") ||\n pathname.startsWith(\"/@id/\") ||\n pathname.startsWith(\"/@fs/\") ||\n pathname === \"/@react-refresh\" ||\n pathname === \"/__vite_ping\" ||\n pathname === \"/__open-in-editor\" ||\n pathname === \"/favicon.ico\" ||\n pathname === \"/favicon.png\" ||\n (/\\.\\w+$/.test(pathname) && !pathname.endsWith(\".data\"))\n );\n}\n\nasync function rewriteMountedResponse(\n response: Response,\n basePath: string,\n pathname: string,\n requestUrl: string,\n): Promise<Response> {\n const clientConfigScript =\n [getSentryClientConfigScript(), getRealtimeClientConfigScript()]\n .filter(Boolean)\n .join(\"\") || null;\n const headers = new Headers(response.headers);\n applyDefaultSsrCacheHeader(headers, response.status, pathname);\n applyDefaultSpeculationRulesHeader(headers, response.status, basePath);\n\n const location = headers.get(\"location\");\n if (location?.startsWith(\"/\") && !location.startsWith(\"//\")) {\n headers.set(\"location\", prefixMountedPath(location, basePath));\n }\n\n const contentType = headers.get(\"content-type\") ?? \"\";\n if (!contentType.toLowerCase().includes(\"text/html\")) {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n removeDocumentCsp(headers);\n if (!response.body) {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n\n const html = await response.text();\n headers.delete(\"content-length\");\n return new Response(\n injectHeadScript(\n injectDefaultSocialImageMeta(\n prefixMountedHtml(html, basePath),\n defaultSocialImageUrl(requestUrl, basePath),\n ),\n clientConfigScript,\n ),\n {\n status: response.status,\n statusText: response.statusText,\n headers,\n },\n );\n}\n\n/**\n * Create an h3 catch-all that hands page routes to React Router and\n * returns 404 for framework / asset paths that React Router doesn't own.\n */\nexport function createH3SSRHandler(getBuild: () => Promise<unknown> | unknown) {\n const handler = createRequestHandler(getBuild as any);\n return defineEventHandler(async (event) => {\n const basePath = getAppBasePath();\n const p = stripAppBasePath(event.url.pathname);\n if (isFrameworkOrAssetPath(p)) {\n return new Response(null, { status: 404 });\n }\n try {\n const request = requestForAnonymousSsr(\n requestWithPathname(event.req as Request, p, basePath),\n );\n // SSR renders an IMPERSONAL public shell — we deliberately do NOT read the\n // request's session/cookies here, and pin an explicitly anonymous request\n // context. That keeps the SSR HTML/.data identical for every visitor so it\n // can be hard-cached at the CDN for everyone (see applyDefaultSsrCacheHeader).\n //\n // Consequence: SSR loaders that call `getRequestUserEmail()` / `accessFilter()`\n // always see the unauthenticated branch and render public content only. Any\n // per-user view (private records, share-grant access, who's logged in) MUST\n // be resolved CLIENT-SIDE after load, never baked into SSR. Do not re-pin the\n // session here to \"fix\" a per-user page — that silently makes the page\n // uncacheable and/or leaks one user's data into another's cached copy.\n const ctx = { userEmail: undefined, orgId: undefined };\n if (request.method === \"HEAD\") {\n const getRequest = new Request(request.url, {\n method: \"GET\",\n headers: request.headers,\n signal: request.signal,\n });\n const response = await runWithRequestContext(ctx, () =>\n handler(getRequest),\n );\n return await rewriteMountedResponse(\n new Response(null, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n }),\n basePath,\n p,\n request.url,\n );\n }\n return await rewriteMountedResponse(\n await runWithRequestContext(ctx, () => handler(request)),\n basePath,\n p,\n request.url,\n );\n } catch (err) {\n // Log the full stack server-side, but never leak it to the client.\n // Stack traces expose file paths, library versions, and code structure\n // that aid reconnaissance attacks. In dev we surface the message text\n // so devtools shows something useful; in prod we return a bare 500.\n console.error(\"[ssr-handler] SSR error:\", err);\n captureError(err, {\n route: p,\n method: event.req.method,\n userAgent: event.req.headers.get(\"user-agent\") ?? undefined,\n tags: { renderMode: \"anonymous-public\", surface: \"ssr\" },\n });\n const isProd = process.env.NODE_ENV === \"production\";\n const body = isProd\n ? \"Internal Server Error\"\n : `Internal Server Error: ${(err as Error)?.message ?? err}`;\n return new Response(body, {\n status: 500,\n headers: { \"content-type\": \"text/plain\" },\n });\n }\n });\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.116.0",
3
+ "version": "0.117.1",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -68,6 +68,9 @@
68
68
  "./server/agent-discovery": "./dist/server/agent-discovery.js",
69
69
  "./server/request-context": "./dist/server/request-context.js",
70
70
  "./server/design-token-utils": "./dist/server/design-token-utils.js",
71
+ "./server/poll": "./dist/server/poll.js",
72
+ "./server/sse": "./dist/server/sse.js",
73
+ "./server/short-lived-token": "./dist/server/short-lived-token.js",
71
74
  "./integrations": "./dist/integrations/index.js",
72
75
  "./integrations/runtime": "./dist/integrations/runtime.js",
73
76
  "./ingestion": "./dist/ingestion/index.js",
@@ -50,6 +50,13 @@ declare global {
50
50
  __AGENT_NATIVE_CONFIG__?: {
51
51
  sentryDsn?: string;
52
52
  sentryEnvironment?: string;
53
+ /**
54
+ * Hosted Realtime Gateway config. Impersonal (same for every visitor),
55
+ * so it is safe inside the CDN-cached SSR shell — unlike the per-user
56
+ * subscribe token, which is minted client-side after load. Absent when
57
+ * the app uses the in-process (local) transport.
58
+ */
59
+ realtime?: { transport?: string; gatewayBaseUrl?: string };
53
60
  };
54
61
  }
55
62
  }