@businessdash/sdk 0.9.60

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 (268) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +899 -0
  3. package/dist/adapters-revalidate.d.ts +44 -0
  4. package/dist/adapters-revalidate.d.ts.map +1 -0
  5. package/dist/adapters-revalidate.js +68 -0
  6. package/dist/adapters-revalidate.js.map +1 -0
  7. package/dist/alpha.d.ts +21 -0
  8. package/dist/alpha.d.ts.map +1 -0
  9. package/dist/alpha.js +21 -0
  10. package/dist/alpha.js.map +1 -0
  11. package/dist/analytics-core.d.ts +34 -0
  12. package/dist/analytics-core.d.ts.map +1 -0
  13. package/dist/analytics-core.js +138 -0
  14. package/dist/analytics-core.js.map +1 -0
  15. package/dist/analytics-server.d.ts +54 -0
  16. package/dist/analytics-server.d.ts.map +1 -0
  17. package/dist/analytics-server.js +78 -0
  18. package/dist/analytics-server.js.map +1 -0
  19. package/dist/attribution-logo.d.ts +2 -0
  20. package/dist/attribution-logo.d.ts.map +1 -0
  21. package/dist/attribution-logo.js +5 -0
  22. package/dist/attribution-logo.js.map +1 -0
  23. package/dist/auth-handler.d.ts +92 -0
  24. package/dist/auth-handler.d.ts.map +1 -0
  25. package/dist/auth-handler.js +277 -0
  26. package/dist/auth-handler.js.map +1 -0
  27. package/dist/biab-forms.css +484 -0
  28. package/dist/cli.d.ts +46 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.js +1824 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/client.d.ts +1681 -0
  33. package/dist/client.d.ts.map +1 -0
  34. package/dist/client.js +2845 -0
  35. package/dist/client.js.map +1 -0
  36. package/dist/collections.d.ts +13 -0
  37. package/dist/collections.d.ts.map +1 -0
  38. package/dist/collections.js +13 -0
  39. package/dist/collections.js.map +1 -0
  40. package/dist/contracts.d.ts +14609 -0
  41. package/dist/contracts.d.ts.map +1 -0
  42. package/dist/contracts.js +2428 -0
  43. package/dist/contracts.js.map +1 -0
  44. package/dist/data-model-schema.d.ts +962 -0
  45. package/dist/data-model-schema.d.ts.map +1 -0
  46. package/dist/data-model-schema.js +2295 -0
  47. package/dist/data-model-schema.js.map +1 -0
  48. package/dist/distribution.d.ts +38 -0
  49. package/dist/distribution.d.ts.map +1 -0
  50. package/dist/distribution.js +61 -0
  51. package/dist/distribution.js.map +1 -0
  52. package/dist/element.d.ts +121 -0
  53. package/dist/element.d.ts.map +1 -0
  54. package/dist/element.js +276 -0
  55. package/dist/element.js.map +1 -0
  56. package/dist/enums.d.ts +25 -0
  57. package/dist/enums.d.ts.map +1 -0
  58. package/dist/enums.js +38 -0
  59. package/dist/enums.js.map +1 -0
  60. package/dist/errors.d.ts +195 -0
  61. package/dist/errors.d.ts.map +1 -0
  62. package/dist/errors.js +188 -0
  63. package/dist/errors.js.map +1 -0
  64. package/dist/forms-core/autocomplete.d.ts +37 -0
  65. package/dist/forms-core/autocomplete.d.ts.map +1 -0
  66. package/dist/forms-core/autocomplete.js +82 -0
  67. package/dist/forms-core/autocomplete.js.map +1 -0
  68. package/dist/forms-core/business-function.d.ts +61 -0
  69. package/dist/forms-core/business-function.d.ts.map +1 -0
  70. package/dist/forms-core/business-function.js +149 -0
  71. package/dist/forms-core/business-function.js.map +1 -0
  72. package/dist/forms-core/calculation.d.ts +72 -0
  73. package/dist/forms-core/calculation.d.ts.map +1 -0
  74. package/dist/forms-core/calculation.js +103 -0
  75. package/dist/forms-core/calculation.js.map +1 -0
  76. package/dist/forms-core/controller.d.ts +249 -0
  77. package/dist/forms-core/controller.d.ts.map +1 -0
  78. package/dist/forms-core/controller.js +590 -0
  79. package/dist/forms-core/controller.js.map +1 -0
  80. package/dist/forms-core/elements.d.ts +44 -0
  81. package/dist/forms-core/elements.d.ts.map +1 -0
  82. package/dist/forms-core/elements.js +68 -0
  83. package/dist/forms-core/elements.js.map +1 -0
  84. package/dist/forms-core/email-domains.d.ts +10 -0
  85. package/dist/forms-core/email-domains.d.ts.map +1 -0
  86. package/dist/forms-core/email-domains.js +69 -0
  87. package/dist/forms-core/email-domains.js.map +1 -0
  88. package/dist/forms-core/file-rules.d.ts +51 -0
  89. package/dist/forms-core/file-rules.d.ts.map +1 -0
  90. package/dist/forms-core/file-rules.js +105 -0
  91. package/dist/forms-core/file-rules.js.map +1 -0
  92. package/dist/forms-core/formula.d.ts +114 -0
  93. package/dist/forms-core/formula.d.ts.map +1 -0
  94. package/dist/forms-core/formula.js +1170 -0
  95. package/dist/forms-core/formula.js.map +1 -0
  96. package/dist/forms-core/index.d.ts +24 -0
  97. package/dist/forms-core/index.d.ts.map +1 -0
  98. package/dist/forms-core/index.js +28 -0
  99. package/dist/forms-core/index.js.map +1 -0
  100. package/dist/forms-core/units.d.ts +49 -0
  101. package/dist/forms-core/units.d.ts.map +1 -0
  102. package/dist/forms-core/units.js +255 -0
  103. package/dist/forms-core/units.js.map +1 -0
  104. package/dist/forms-core/us-states.d.ts +14 -0
  105. package/dist/forms-core/us-states.d.ts.map +1 -0
  106. package/dist/forms-core/us-states.js +66 -0
  107. package/dist/forms-core/us-states.js.map +1 -0
  108. package/dist/forms-core/validation.d.ts +266 -0
  109. package/dist/forms-core/validation.d.ts.map +1 -0
  110. package/dist/forms-core/validation.js +850 -0
  111. package/dist/forms-core/validation.js.map +1 -0
  112. package/dist/forms.d.ts +19 -0
  113. package/dist/forms.d.ts.map +1 -0
  114. package/dist/forms.js +29 -0
  115. package/dist/forms.js.map +1 -0
  116. package/dist/gallery-types.d.ts +32 -0
  117. package/dist/gallery-types.d.ts.map +1 -0
  118. package/dist/gallery-types.js +2 -0
  119. package/dist/gallery-types.js.map +1 -0
  120. package/dist/graphql.d.ts +97 -0
  121. package/dist/graphql.d.ts.map +1 -0
  122. package/dist/graphql.js +212 -0
  123. package/dist/graphql.js.map +1 -0
  124. package/dist/index.cjs +19448 -0
  125. package/dist/index.d.cts +19 -0
  126. package/dist/index.d.mts +19 -0
  127. package/dist/index.d.ts +19 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +20 -0
  130. package/dist/index.js.map +1 -0
  131. package/dist/marketing-schema.d.ts +216 -0
  132. package/dist/marketing-schema.d.ts.map +1 -0
  133. package/dist/marketing-schema.js +200 -0
  134. package/dist/marketing-schema.js.map +1 -0
  135. package/dist/mcp.d.ts +42 -0
  136. package/dist/mcp.d.ts.map +1 -0
  137. package/dist/mcp.js +87 -0
  138. package/dist/mcp.js.map +1 -0
  139. package/dist/native.d.ts +45 -0
  140. package/dist/native.d.ts.map +1 -0
  141. package/dist/native.js +43 -0
  142. package/dist/native.js.map +1 -0
  143. package/dist/next-revalidate.d.ts +43 -0
  144. package/dist/next-revalidate.d.ts.map +1 -0
  145. package/dist/next-revalidate.js +84 -0
  146. package/dist/next-revalidate.js.map +1 -0
  147. package/dist/parallel-pages.d.ts +102 -0
  148. package/dist/parallel-pages.d.ts.map +1 -0
  149. package/dist/parallel-pages.js +108 -0
  150. package/dist/parallel-pages.js.map +1 -0
  151. package/dist/payment-icons.d.ts +66 -0
  152. package/dist/payment-icons.d.ts.map +1 -0
  153. package/dist/payment-icons.js +160 -0
  154. package/dist/payment-icons.js.map +1 -0
  155. package/dist/proxy.d.ts +67 -0
  156. package/dist/proxy.d.ts.map +1 -0
  157. package/dist/proxy.js +97 -0
  158. package/dist/proxy.js.map +1 -0
  159. package/dist/react-analytics.d.ts +39 -0
  160. package/dist/react-analytics.d.ts.map +1 -0
  161. package/dist/react-analytics.js +59 -0
  162. package/dist/react-analytics.js.map +1 -0
  163. package/dist/react-attribution.d.ts +99 -0
  164. package/dist/react-attribution.d.ts.map +1 -0
  165. package/dist/react-attribution.js +52 -0
  166. package/dist/react-attribution.js.map +1 -0
  167. package/dist/react-marketing.d.ts +181 -0
  168. package/dist/react-marketing.d.ts.map +1 -0
  169. package/dist/react-marketing.js +215 -0
  170. package/dist/react-marketing.js.map +1 -0
  171. package/dist/react.d.ts +727 -0
  172. package/dist/react.d.ts.map +1 -0
  173. package/dist/react.js +2418 -0
  174. package/dist/react.js.map +1 -0
  175. package/dist/reserved-paths.d.ts +24 -0
  176. package/dist/reserved-paths.d.ts.map +1 -0
  177. package/dist/reserved-paths.js +60 -0
  178. package/dist/reserved-paths.js.map +1 -0
  179. package/dist/revalidate-verify.d.ts +30 -0
  180. package/dist/revalidate-verify.d.ts.map +1 -0
  181. package/dist/revalidate-verify.js +74 -0
  182. package/dist/revalidate-verify.js.map +1 -0
  183. package/dist/sdk.d.ts +678 -0
  184. package/dist/sdk.d.ts.map +1 -0
  185. package/dist/sdk.js +801 -0
  186. package/dist/sdk.js.map +1 -0
  187. package/dist/seo.d.ts +218 -0
  188. package/dist/seo.d.ts.map +1 -0
  189. package/dist/seo.js +309 -0
  190. package/dist/seo.js.map +1 -0
  191. package/dist/socials.d.ts +47 -0
  192. package/dist/socials.d.ts.map +1 -0
  193. package/dist/socials.js +172 -0
  194. package/dist/socials.js.map +1 -0
  195. package/dist/static.d.ts +14 -0
  196. package/dist/static.d.ts.map +1 -0
  197. package/dist/static.js +14 -0
  198. package/dist/static.js.map +1 -0
  199. package/dist/url-pattern.d.ts +47 -0
  200. package/dist/url-pattern.d.ts.map +1 -0
  201. package/dist/url-pattern.js +124 -0
  202. package/dist/url-pattern.js.map +1 -0
  203. package/dist/vanilla.d.ts +77 -0
  204. package/dist/vanilla.d.ts.map +1 -0
  205. package/dist/vanilla.js +903 -0
  206. package/dist/vanilla.js.map +1 -0
  207. package/openapi.json +13779 -0
  208. package/package.json +204 -0
  209. package/src/adapters-revalidate.ts +88 -0
  210. package/src/alpha.ts +89 -0
  211. package/src/analytics-core.ts +175 -0
  212. package/src/analytics-server.ts +99 -0
  213. package/src/angular/biab-form.component.ts +1443 -0
  214. package/src/angular/index.ts +26 -0
  215. package/src/attribution-logo.ts +5 -0
  216. package/src/auth-handler.ts +352 -0
  217. package/src/cli.ts +2517 -0
  218. package/src/client.ts +3578 -0
  219. package/src/collections.ts +101 -0
  220. package/src/contracts.ts +3390 -0
  221. package/src/data-model-schema.ts +4042 -0
  222. package/src/distribution.ts +73 -0
  223. package/src/element.ts +321 -0
  224. package/src/enums.ts +65 -0
  225. package/src/errors.ts +338 -0
  226. package/src/forms-core/autocomplete.ts +97 -0
  227. package/src/forms-core/business-function.ts +209 -0
  228. package/src/forms-core/calculation.ts +167 -0
  229. package/src/forms-core/controller.ts +922 -0
  230. package/src/forms-core/elements.ts +86 -0
  231. package/src/forms-core/email-domains.ts +68 -0
  232. package/src/forms-core/file-rules.ts +123 -0
  233. package/src/forms-core/formula.ts +1339 -0
  234. package/src/forms-core/index.ts +122 -0
  235. package/src/forms-core/units.ts +308 -0
  236. package/src/forms-core/us-states.ts +68 -0
  237. package/src/forms-core/validation.ts +1037 -0
  238. package/src/forms.ts +63 -0
  239. package/src/gallery-types.ts +31 -0
  240. package/src/graphql.ts +295 -0
  241. package/src/index.ts +505 -0
  242. package/src/marketing-schema.ts +395 -0
  243. package/src/mcp.ts +108 -0
  244. package/src/native.ts +73 -0
  245. package/src/next-revalidate.ts +124 -0
  246. package/src/parallel-pages.ts +192 -0
  247. package/src/payment-icons.ts +205 -0
  248. package/src/proxy.ts +173 -0
  249. package/src/qwik.tsx +1234 -0
  250. package/src/react-analytics.tsx +76 -0
  251. package/src/react-attribution.tsx +192 -0
  252. package/src/react-marketing.tsx +338 -0
  253. package/src/react.tsx +5495 -0
  254. package/src/reserved-paths.ts +65 -0
  255. package/src/revalidate-verify.ts +98 -0
  256. package/src/sdk.ts +1671 -0
  257. package/src/seo.ts +488 -0
  258. package/src/socials.ts +230 -0
  259. package/src/solid.tsx +1466 -0
  260. package/src/static.ts +21 -0
  261. package/src/styles/biab-forms.css +484 -0
  262. package/src/svelte/BiabForm.svelte +1069 -0
  263. package/src/svelte/context.ts +57 -0
  264. package/src/svelte/index.ts +79 -0
  265. package/src/url-pattern.ts +153 -0
  266. package/src/vanilla.ts +1290 -0
  267. package/src/vue/BiabForm.vue +1538 -0
  268. package/src/vue/index.ts +119 -0
@@ -0,0 +1,352 @@
1
+ /**
2
+ * SDK auth handler — the file a tenant developer mounts on their own site
3
+ * to handle the platform auth-provider callback. Cookie lives on the tenant's
4
+ * domain so the tenant's SSR can read it via standard cookie APIs.
5
+ *
6
+ * Usage (Next.js App Router):
7
+ *
8
+ * // app/api/biab-auth/[...biab]/route.ts
9
+ * import { createAuthHandler } from "@businessdash/sdk";
10
+ *
11
+ * const handler = createAuthHandler({
12
+ * baseUrl: process.env.BIAB_PACKAGE_API_BASE_URL!,
13
+ * apiKey: process.env.BIAB_API_KEY!,
14
+ * callbackUrl: "https://my-site.com/api/biab-auth/callback",
15
+ * });
16
+ *
17
+ * export const GET = handler.GET;
18
+ * export const POST = handler.POST;
19
+ *
20
+ * Then drop <SignIn />, <SignUp />, <SignOut /> into your pages — they call
21
+ * `/api/biab-auth/sign-in?intent=sign-in` etc., which 302s through the
22
+ * platform-hosted auth page and back to `callbackUrl`. The handler exchanges
23
+ * the code,
24
+ * sets the session cookie, and redirects the user to `returnTo`.
25
+ */
26
+
27
+ import { Temporal } from "@js-temporal/polyfill";
28
+
29
+ import { BiabDevClient, type BiabDevClientOptions } from "./client.js";
30
+ import { BiabApiError } from "./errors.js";
31
+
32
+ export const DEFAULT_AUTH_COOKIE_NAME = "biab_session";
33
+ export const DEFAULT_AUTH_BASE_PATH = "/api/biab-auth";
34
+
35
+ export type CreateAuthHandlerOptions = {
36
+ /** BIAB platform base URL — e.g. https://app.biab.com/api/package/v1 */
37
+ baseUrl: string;
38
+ /** Org-bound package API key (server-side secret). */
39
+ apiKey: string;
40
+ /**
41
+ * Public, fully-qualified URL of THIS handler's `/callback` route on the
42
+ * tenant's domain. Must be registered as a redirect URI with the platform.
43
+ */
44
+ callbackUrl: string;
45
+ /** Where to send the user after successful sign-in if no `returnTo` was passed. */
46
+ defaultReturnTo?: string;
47
+ /** Where to send the user after sign-out. Defaults to "/". */
48
+ signOutReturnTo?: string;
49
+ /** Cookie name. Defaults to "biab_session". */
50
+ cookieName?: string;
51
+ /**
52
+ * Cookie domain. Leave undefined to scope to current host. Set if you want
53
+ * to share the session across subdomains (e.g. ".my-site.com").
54
+ */
55
+ cookieDomain?: string;
56
+ /**
57
+ * The base path this handler is mounted at — used to derive sub-routes.
58
+ * Defaults to "/api/biab-auth". Must match the route file location.
59
+ */
60
+ basePath?: string;
61
+ /** Optional fetch override (testing, edge runtimes). */
62
+ fetch?: BiabDevClientOptions["fetch"];
63
+ /**
64
+ * Origin sent on every server-to-server request to the platform. Defaults
65
+ * to the origin of `callbackUrl`. Override only when your dev/prod hosts
66
+ * differ from the callback (rare).
67
+ */
68
+ siteOrigin?: string;
69
+ };
70
+
71
+ type SimpleHandler = (request: Request) => Promise<Response>;
72
+
73
+ export type AuthHandlerRoutes = {
74
+ GET: SimpleHandler;
75
+ POST: SimpleHandler;
76
+ };
77
+
78
+ function buildSetCookie(
79
+ name: string,
80
+ value: string,
81
+ opts: {
82
+ expiresAt?: Date | undefined;
83
+ domain?: string | undefined;
84
+ clear?: boolean | undefined;
85
+ },
86
+ ): string {
87
+ const parts = [`${name}=${value}`, "Path=/", "HttpOnly", "SameSite=Lax"];
88
+ parts.push("Secure");
89
+ if (opts.domain) parts.push(`Domain=${opts.domain}`);
90
+ if (opts.clear) {
91
+ parts.push("Max-Age=0");
92
+ } else if (opts.expiresAt) {
93
+ parts.push(`Expires=${opts.expiresAt.toUTCString()}`);
94
+ }
95
+ return parts.join("; ");
96
+ }
97
+
98
+ function readCookie(request: Request, name: string): string | null {
99
+ const header = request.headers.get("cookie");
100
+ if (!header) return null;
101
+ for (const part of header.split(";")) {
102
+ const [k, ...rest] = part.trim().split("=");
103
+ if (k === name) return rest.join("=");
104
+ }
105
+ return null;
106
+ }
107
+
108
+ /**
109
+ * `auth/start` expects `returnTo` as an absolute URL. Plain paths like `/` come
110
+ * from `defaultReturnTo` and from `<SignIn returnTo="/dashboard" />`.
111
+ */
112
+ function absolutizeReturnTo(returnTo: string, requestUrl: string): string {
113
+ try {
114
+ return new URL(returnTo, requestUrl).href;
115
+ } catch {
116
+ return new URL("/", requestUrl).href;
117
+ }
118
+ }
119
+
120
+ function decodeReturnTo(state: string | null): string | null {
121
+ if (!state) return null;
122
+ try {
123
+ const decoded = Buffer.from(state, "base64url").toString("utf8");
124
+ const parsed = JSON.parse(decoded) as { returnTo?: unknown };
125
+ if (typeof parsed.returnTo === "string") return parsed.returnTo;
126
+ } catch {
127
+ /* ignore */
128
+ }
129
+ return null;
130
+ }
131
+
132
+ /**
133
+ * Returned as plain text so failed redirects show a readable page instead of
134
+ * Next.js's empty 500 from an uncaught `BiabApiError`.
135
+ */
136
+ function authStartFailureResponse(error: unknown): Response {
137
+ let detail = "";
138
+ if (error instanceof BiabApiError) {
139
+ const fromBody =
140
+ error.body &&
141
+ typeof error.body === "object" &&
142
+ "message" in error.body &&
143
+ typeof (error.body as { message: unknown }).message === "string"
144
+ ? (error.body as { message: string }).message
145
+ : error.message;
146
+ detail = fromBody ? `${fromBody}\n\n` : "";
147
+ const needsTenantAuthHint =
148
+ error.status === 403 &&
149
+ (typeof fromBody === "string"
150
+ ? /tenant_auth|missing_scope/i.test(fromBody)
151
+ : false);
152
+ if (needsTenantAuthHint) {
153
+ detail +=
154
+ "Signing in requires API scope tenant_auth:public (it is on by default in Site Builder → Developer).\n";
155
+ detail +=
156
+ "If scope is included but this still fails, deploy the latest BIAB app (pinned-site keys were broken on tenant auth routes) or use an org-wide key (no “Pin to current site”).\n";
157
+ detail +=
158
+ "Platform operators issuing keys separately: include tenant_auth under Scopes. Re-issue the key, set BIAB_API_KEY, redeploy.";
159
+ }
160
+ const status =
161
+ error.status >= 400 && error.status <= 599 ? error.status : 502;
162
+ return new Response(`${detail || "Tenant auth request failed."}`.trim(), {
163
+ status,
164
+ headers: { "Content-Type": "text/plain; charset=utf-8" },
165
+ });
166
+ }
167
+ const fallback =
168
+ error instanceof Error
169
+ ? error.message
170
+ : "Unexpected error during tenant auth.";
171
+ return new Response(`Tenant auth request failed.\n\n${fallback}`.trim(), {
172
+ status: 502,
173
+ headers: { "Content-Type": "text/plain; charset=utf-8" },
174
+ });
175
+ }
176
+
177
+ export function createAuthHandler(
178
+ options: CreateAuthHandlerOptions,
179
+ ): AuthHandlerRoutes {
180
+ const cookieName = options.cookieName ?? DEFAULT_AUTH_COOKIE_NAME;
181
+ const basePath = (options.basePath ?? DEFAULT_AUTH_BASE_PATH).replace(
182
+ /\/$/,
183
+ "",
184
+ );
185
+ const defaultReturnTo = options.defaultReturnTo ?? "/";
186
+ const signOutReturnTo = options.signOutReturnTo ?? "/";
187
+ const derivedOrigin =
188
+ options.siteOrigin ??
189
+ (() => {
190
+ try {
191
+ return new URL(options.callbackUrl).origin;
192
+ } catch {
193
+ return undefined;
194
+ }
195
+ })();
196
+ const client = new BiabDevClient({
197
+ baseUrl: options.baseUrl,
198
+ apiKey: options.apiKey,
199
+ ...(options.fetch ? { fetch: options.fetch } : {}),
200
+ ...(derivedOrigin ? { siteOrigin: derivedOrigin } : {}),
201
+ });
202
+
203
+ async function handle(request: Request): Promise<Response> {
204
+ const url = new URL(request.url);
205
+ const subroute = url.pathname.startsWith(basePath)
206
+ ? url.pathname.slice(basePath.length).replace(/^\//, "")
207
+ : url.pathname;
208
+
209
+ // /sign-in?returnTo=... and /sign-up?returnTo=...
210
+ if (subroute === "sign-in" || subroute === "sign-up") {
211
+ const rawReturnTo = url.searchParams.get("returnTo") ?? defaultReturnTo;
212
+ try {
213
+ const start = await client.auth.start({
214
+ intent: subroute === "sign-up" ? "sign-up" : "sign-in",
215
+ returnTo: absolutizeReturnTo(rawReturnTo, request.url),
216
+ redirectUri: options.callbackUrl,
217
+ loginHint: url.searchParams.get("loginHint") ?? undefined,
218
+ });
219
+ return Response.redirect(start.url, 302);
220
+ } catch (err) {
221
+ return authStartFailureResponse(err);
222
+ }
223
+ }
224
+
225
+ // /callback?code=...&state=...
226
+ if (subroute === "callback") {
227
+ const code = url.searchParams.get("code");
228
+ if (!code) {
229
+ return new Response("Missing code", { status: 400 });
230
+ }
231
+ const oauthState = url.searchParams.get("state");
232
+ if (!oauthState?.trim()) {
233
+ return new Response("Missing OAuth state", { status: 400 });
234
+ }
235
+ try {
236
+ const session = await client.auth.exchange({
237
+ code,
238
+ state: oauthState,
239
+ redirectUri: options.callbackUrl,
240
+ });
241
+ const returnTo =
242
+ decodeReturnTo(url.searchParams.get("state")) ?? defaultReturnTo;
243
+ return new Response(null, {
244
+ status: 302,
245
+ headers: {
246
+ Location: returnTo,
247
+ "Set-Cookie": buildSetCookie(cookieName, session.sessionToken, {
248
+ expiresAt: new Date(
249
+ Temporal.Instant.from(session.expiresAt).epochMilliseconds,
250
+ ),
251
+ domain: options.cookieDomain,
252
+ }),
253
+ },
254
+ });
255
+ } catch (err) {
256
+ const message = err instanceof Error ? err.message : "exchange failed";
257
+ return new Response(`Sign-in failed: ${message}`, { status: 400 });
258
+ }
259
+ }
260
+
261
+ // /sign-out (GET → redirect, POST → JSON response for programmatic calls)
262
+ if (subroute === "sign-out") {
263
+ await client.auth.signOut().catch(() => undefined);
264
+ const clearCookieHeader = buildSetCookie(cookieName, "", {
265
+ clear: true,
266
+ domain: options.cookieDomain,
267
+ });
268
+ if (request.method === "POST") {
269
+ return new Response(JSON.stringify({ ok: true }), {
270
+ status: 200,
271
+ headers: {
272
+ "Content-Type": "application/json",
273
+ "Set-Cookie": clearCookieHeader,
274
+ },
275
+ });
276
+ }
277
+ return new Response(null, {
278
+ status: 302,
279
+ headers: {
280
+ Location: signOutReturnTo,
281
+ "Set-Cookie": clearCookieHeader,
282
+ },
283
+ });
284
+ }
285
+
286
+ // /password-reset (POST { email } → 200 ok). Always succeeds from the
287
+ // caller's perspective; the BIAB endpoint only emails when the address
288
+ // belongs to a member of the org bound to this handler's API key.
289
+ if (subroute === "password-reset" && request.method === "POST") {
290
+ let payload: { email?: unknown; returnTo?: unknown };
291
+ try {
292
+ payload = (await request.json()) as typeof payload;
293
+ } catch {
294
+ return new Response("Bad JSON", { status: 400 });
295
+ }
296
+ const email =
297
+ typeof payload.email === "string" ? payload.email.trim() : "";
298
+ if (!email) {
299
+ return new Response("email is required", { status: 400 });
300
+ }
301
+ try {
302
+ const result = await client.auth.requestPasswordReset({
303
+ email,
304
+ ...(typeof payload.returnTo === "string"
305
+ ? { returnTo: payload.returnTo }
306
+ : {}),
307
+ });
308
+ return Response.json(result);
309
+ } catch (err) {
310
+ return authStartFailureResponse(err);
311
+ }
312
+ }
313
+
314
+ // /me — returns the current session (or null) as JSON, scoped to this cookie.
315
+ if (subroute === "me") {
316
+ const token = readCookie(request, cookieName);
317
+ if (!token) {
318
+ return Response.json(null);
319
+ }
320
+ const me = await client.auth
321
+ .me({ sessionToken: token })
322
+ .catch(() => null);
323
+ return Response.json(me);
324
+ }
325
+
326
+ return new Response("Not found", { status: 404 });
327
+ }
328
+
329
+ return { GET: handle, POST: handle };
330
+ }
331
+
332
+ /**
333
+ * Server-side helper for tenant SSR: read the current session from the cookie.
334
+ * Returns null when the user isn't signed in.
335
+ */
336
+ export async function getTenantSession(input: {
337
+ cookieValue: string | null | undefined;
338
+ baseUrl: string;
339
+ apiKey: string;
340
+ /** Required when the API key has Allowed Host set; see {@link BiabDevClientOptions.siteOrigin}. */
341
+ siteOrigin?: string;
342
+ fetch?: BiabDevClientOptions["fetch"];
343
+ }) {
344
+ if (!input.cookieValue) return null;
345
+ const client = new BiabDevClient({
346
+ baseUrl: input.baseUrl,
347
+ apiKey: input.apiKey,
348
+ ...(input.fetch ? { fetch: input.fetch } : {}),
349
+ ...(input.siteOrigin ? { siteOrigin: input.siteOrigin } : {}),
350
+ });
351
+ return client.auth.me({ sessionToken: input.cookieValue }).catch(() => null);
352
+ }