lakebed 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/anonymous.js CHANGED
@@ -16,6 +16,7 @@ export const DEFAULT_ANONYMOUS_LIMITS = {
16
16
  };
17
17
 
18
18
  const expressionOps = new Set(["arg", "auth", "call", "row"]);
19
+ const authFields = new Set(["displayName", "email", "emailVerified", "isAuthenticated", "isGuest", "name", "picture", "provider", "userId"]);
19
20
 
20
21
  export class AnonymousCompilerError extends Error {
21
22
  constructor(diagnostics) {
@@ -131,9 +132,17 @@ function createSymbolicArg(index) {
131
132
  }
132
133
 
133
134
  function createSymbolicAuth() {
134
- const userId = new SymbolicValue(["auth", "userId"], "guest:trace");
135
- const displayName = new SymbolicValue(["auth", "displayName"], "Trace Guest");
136
- return { displayName, userId };
135
+ return {
136
+ displayName: new SymbolicValue(["auth", "displayName"], "Trace Guest"),
137
+ email: new SymbolicValue(["auth", "email"], "trace@example.test"),
138
+ emailVerified: new SymbolicValue(["auth", "emailVerified"], true),
139
+ isAuthenticated: new SymbolicValue(["auth", "isAuthenticated"], true),
140
+ isGuest: new SymbolicValue(["auth", "isGuest"], false),
141
+ name: new SymbolicValue(["auth", "name"], "Trace Guest"),
142
+ picture: new SymbolicValue(["auth", "picture"], "https://example.test/avatar.png"),
143
+ provider: new SymbolicValue(["auth", "provider"], "google"),
144
+ userId: new SymbolicValue(["auth", "userId"], "guest:trace")
145
+ };
137
146
  }
138
147
 
139
148
  function createSymbolicRow({ auth, idExpr, scanId, schema, tableName }) {
@@ -484,7 +493,7 @@ function compileServerToIr(app, schema) {
484
493
  return { diagnostics, mutations, queries };
485
494
  }
486
495
 
487
- export async function createAnonymousArtifact({ app, clientOut, sourceStore, version = "0.0.2" }) {
496
+ export async function createAnonymousArtifact({ app, clientOut, sourceStore, version = "0.0.3" }) {
488
497
  const sourceFiles = await readSourceFiles(sourceStore);
489
498
  const diagnostics = forbiddenSourceDiagnostics(sourceFiles);
490
499
  const { diagnostics: schemaDiagnostics, schema } = serializeSchema(app.schema);
@@ -561,7 +570,7 @@ function validateExpression(expr, path, diagnostics) {
561
570
  }
562
571
 
563
572
  if (op === "auth") {
564
- if ((expr[1] !== "userId" && expr[1] !== "displayName") || expr.length !== 2) {
573
+ if (!authFields.has(expr[1]) || expr.length !== 2) {
565
574
  diagnostics.push(diagnostic(path, "Invalid auth expression."));
566
575
  }
567
576
  return;
package/src/auth.js ADDED
@@ -0,0 +1,155 @@
1
+ const DEFAULT_SHOO_BASE_URL = "https://shoo.dev";
2
+
3
+ export function shooBaseUrlFromEnv(env = process.env) {
4
+ return String(env.LAKEBED_SHOO_BASE_URL ?? env.SHOO_BASE_URL ?? DEFAULT_SHOO_BASE_URL).replace(/\/+$/g, "");
5
+ }
6
+
7
+ export function toGuestName(name) {
8
+ return (
9
+ String(name ?? "local")
10
+ .replace(/^guest:/, "")
11
+ .trim()
12
+ .replace(/[^a-zA-Z0-9_.-]+/g, "-")
13
+ .replace(/^-+|-+$/g, "")
14
+ .toLowerCase() || "local"
15
+ );
16
+ }
17
+
18
+ export function toDisplayName(name) {
19
+ return toGuestName(name)
20
+ .split(/[-_\s.]+/)
21
+ .filter(Boolean)
22
+ .map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
23
+ .join(" ");
24
+ }
25
+
26
+ export function createGuestAuth(name) {
27
+ const guestName = toGuestName(name);
28
+ return {
29
+ displayName: toDisplayName(guestName),
30
+ isAuthenticated: false,
31
+ isGuest: true,
32
+ provider: "guest",
33
+ userId: `guest:${guestName}`
34
+ };
35
+ }
36
+
37
+ export function requestOrigin(req, fallbackUrl) {
38
+ const forwardedHost = String(req.headers["x-forwarded-host"] ?? "")
39
+ .split(",")[0]
40
+ .trim();
41
+ const host = forwardedHost || req.headers.host || (fallbackUrl ? new URL(fallbackUrl).host : "localhost");
42
+ const forwardedProto = String(req.headers["x-forwarded-proto"] ?? "")
43
+ .split(",")[0]
44
+ .trim();
45
+ const protocol = forwardedProto || (fallbackUrl ? new URL(fallbackUrl).protocol.replace(/:$/g, "") : "http");
46
+ return `${protocol}://${host}`;
47
+ }
48
+
49
+ function decodeBase64UrlJson(value) {
50
+ try {
51
+ return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ export function decodeIdentityClaims(idToken) {
58
+ if (!idToken || typeof idToken !== "string") {
59
+ return null;
60
+ }
61
+
62
+ const parts = idToken.split(".");
63
+ if (parts.length < 2) {
64
+ return null;
65
+ }
66
+
67
+ return decodeBase64UrlJson(parts[1]);
68
+ }
69
+
70
+ function stringClaim(claims, name) {
71
+ return typeof claims?.[name] === "string" ? claims[name] : undefined;
72
+ }
73
+
74
+ function booleanClaim(claims, name) {
75
+ return typeof claims?.[name] === "boolean" ? claims[name] : undefined;
76
+ }
77
+
78
+ function authFromClaims(claims) {
79
+ const pairwiseSub = stringClaim(claims, "pairwise_sub") ?? stringClaim(claims, "sub");
80
+ if (!pairwiseSub) {
81
+ return null;
82
+ }
83
+
84
+ const name = stringClaim(claims, "name");
85
+ const email = stringClaim(claims, "email");
86
+ return {
87
+ displayName: name ?? email ?? "Google User",
88
+ email,
89
+ emailVerified: booleanClaim(claims, "email_verified"),
90
+ isAuthenticated: true,
91
+ isGuest: false,
92
+ name,
93
+ picture: stringClaim(claims, "picture"),
94
+ provider: "google",
95
+ userId: `google:${pairwiseSub}`
96
+ };
97
+ }
98
+
99
+ function isLocallyPlausibleShooToken(claims, origin, shooBaseUrl) {
100
+ if (!claims || typeof claims !== "object") {
101
+ return false;
102
+ }
103
+
104
+ if (claims.aud !== `origin:${new URL(origin).origin}`) {
105
+ return false;
106
+ }
107
+
108
+ if (typeof claims.iss !== "string" || claims.iss.replace(/\/+$/g, "") !== shooBaseUrl.replace(/\/+$/g, "")) {
109
+ return false;
110
+ }
111
+
112
+ return typeof claims.exp !== "number" || claims.exp * 1000 > Date.now();
113
+ }
114
+
115
+ export async function verifyShooAuth({ origin, shooBaseUrl = shooBaseUrlFromEnv(), token }) {
116
+ if (!token) {
117
+ return null;
118
+ }
119
+
120
+ const response = await fetch(new URL("/session/check", shooBaseUrl), {
121
+ headers: {
122
+ Authorization: `Bearer ${token}`,
123
+ Origin: new URL(origin).origin
124
+ },
125
+ method: "POST"
126
+ });
127
+
128
+ if (!response.ok) {
129
+ return null;
130
+ }
131
+
132
+ const claims = decodeIdentityClaims(token);
133
+ if (!isLocallyPlausibleShooToken(claims, origin, shooBaseUrl)) {
134
+ return null;
135
+ }
136
+
137
+ return authFromClaims(claims);
138
+ }
139
+
140
+ export async function authFromUrl({ defaultAuth = createGuestAuth("local"), onError, origin, shooBaseUrl, url }) {
141
+ const token = url.searchParams.get("lakebed_token") ?? url.searchParams.get("auth_token") ?? "";
142
+ if (token) {
143
+ try {
144
+ const shooAuth = await verifyShooAuth({ origin, shooBaseUrl, token });
145
+ if (shooAuth) {
146
+ return shooAuth;
147
+ }
148
+ } catch (error) {
149
+ await onError?.(error);
150
+ }
151
+ }
152
+
153
+ const guestName = url.searchParams.get("lakebed_guest") ?? url.searchParams.get("guest");
154
+ return guestName ? createGuestAuth(guestName) : defaultAuth;
155
+ }
package/src/cli.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  stableStringify
15
15
  } from "./anonymous.js";
16
16
  import { startAnonymousServer } from "./anonymous-server.js";
17
+ import { authFromUrl as resolveAuthFromUrl, createGuestAuth, requestOrigin, shooBaseUrlFromEnv } from "./auth.js";
17
18
  import { LogBuffer, StateCell } from "./runtime.js";
18
19
  import { createMemorySourceStoreFromDirectory, sourcePathDirname, sourcePathJoin } from "./source-store.js";
19
20
 
@@ -30,8 +31,8 @@ Usage:
30
31
  lakebed new <name> [--template todo]
31
32
  lakebed dev <capsule-dir> [--port 3000]
32
33
  lakebed build <capsule-dir> --target anonymous [--out .lakebed/artifacts/app.json] [--json]
33
- lakebed deploy <capsule-dir> [--ttl 7d] [--api <url>] [--json]
34
- lakebed anonymous-server [--port 8787] [--public-root-url <url>]
34
+ lakebed deploy [capsule-dir] [--ttl 7d] [--api <url>] [--json]
35
+ lakebed anonymous-server [--port 8787] [--public-root-url <url>] [--app-base-domain <domain>]
35
36
  lakebed inspect <deploy-id-or-url> [--api <url>] [--json]
36
37
  lakebed run-many <capsule-dir> [--count 20] [--base-port 4000]
37
38
  lakebed auth as <name>
@@ -91,31 +92,6 @@ function authFile() {
91
92
  return resolve(root, ".lakebed/auth.json");
92
93
  }
93
94
 
94
- function toGuestName(name) {
95
- return String(name ?? "local")
96
- .replace(/^guest:/, "")
97
- .trim()
98
- .replace(/[^a-zA-Z0-9_.-]+/g, "-")
99
- .replace(/^-+|-+$/g, "")
100
- .toLowerCase() || "local";
101
- }
102
-
103
- function toDisplayName(name) {
104
- return toGuestName(name)
105
- .split(/[-_\s.]+/)
106
- .filter(Boolean)
107
- .map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
108
- .join(" ");
109
- }
110
-
111
- function createGuestAuth(name) {
112
- const guestName = toGuestName(name);
113
- return {
114
- userId: `guest:${guestName}`,
115
- displayName: toDisplayName(guestName)
116
- };
117
- }
118
-
119
95
  async function readAuth() {
120
96
  try {
121
97
  return JSON.parse(await readFile(authFile(), "utf8"));
@@ -129,11 +105,6 @@ async function writeAuth(auth) {
129
105
  await writeFile(authFile(), `${JSON.stringify(auth, null, 2)}\n`);
130
106
  }
131
107
 
132
- function authFromUrl(url, defaultAuth) {
133
- const guestName = url.searchParams.get("lakebed_guest") ?? url.searchParams.get("guest");
134
- return guestName ? createGuestAuth(guestName) : defaultAuth;
135
- }
136
-
137
108
  function isBareSpecifier(path) {
138
109
  return !path.startsWith(".") && !path.startsWith("/") && !/^[a-zA-Z]:/.test(path);
139
110
  }
@@ -332,7 +303,7 @@ export async function buildCapsule({ capsuleDir, sourceStore, capsuleId = "dev"
332
303
  };
333
304
  }
334
305
 
335
- function html(title) {
306
+ function html(title, { shooBaseUrl } = {}) {
336
307
  return `<!doctype html>
337
308
  <html lang="en">
338
309
  <head>
@@ -342,6 +313,7 @@ function html(title) {
342
313
  </head>
343
314
  <body>
344
315
  <div id="app"></div>
316
+ <script>window.__LAKEBED_AUTH__ = ${JSON.stringify({ shooBaseUrl })};</script>
345
317
  <script type="module" src="/client.js"></script>
346
318
  <script>
347
319
  const tailwind = document.createElement("script");
@@ -394,7 +366,14 @@ async function runMutation({ app, stateCell, auth, logs, env, name, args }) {
394
366
  );
395
367
  }
396
368
 
397
- export async function startDevServer({ capsuleDir, sourceStore, port = 3000, capsuleId = "dev", quiet = false } = {}) {
369
+ export async function startDevServer({
370
+ capsuleDir,
371
+ sourceStore,
372
+ port = 3000,
373
+ capsuleId = "dev",
374
+ quiet = false,
375
+ shooBaseUrl = shooBaseUrlFromEnv()
376
+ } = {}) {
398
377
  const resolvedCapsuleDir = resolveCapsuleDir(capsuleDir);
399
378
  const built = await buildCapsule({ capsuleDir: resolvedCapsuleDir, sourceStore, capsuleId });
400
379
  const defaultAuth = await readAuth();
@@ -406,9 +385,9 @@ export async function startDevServer({ capsuleDir, sourceStore, port = 3000, cap
406
385
  try {
407
386
  const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
408
387
 
409
- if (requestUrl.pathname === "/" || requestUrl.pathname === "/index.html") {
388
+ if (requestUrl.pathname === "/" || requestUrl.pathname === "/index.html" || requestUrl.pathname === "/auth/callback") {
410
389
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
411
- res.end(html(built.app.name ?? "Lakebed Capsule"));
390
+ res.end(html(built.app.name ?? "Lakebed Capsule", { shooBaseUrl }));
412
391
  return;
413
392
  }
414
393
 
@@ -529,14 +508,22 @@ export async function startDevServer({ capsuleDir, sourceStore, port = 3000, cap
529
508
  });
530
509
  });
531
510
 
532
- server.on("upgrade", (req, socket, head) => {
511
+ server.on("upgrade", async (req, socket, head) => {
533
512
  const requestUrl = new URL(req.url ?? "/", "http://lakebed.local");
534
513
  if (requestUrl.pathname !== "/__lakebed/ws") {
535
514
  socket.destroy();
536
515
  return;
537
516
  }
538
517
 
539
- const auth = authFromUrl(requestUrl, defaultAuth);
518
+ const auth = await resolveAuthFromUrl({
519
+ defaultAuth,
520
+ onError: (error) => {
521
+ logs.append("warn", "google auth verification failed", { error: error instanceof Error ? error.message : String(error) });
522
+ },
523
+ origin: requestOrigin(req),
524
+ shooBaseUrl,
525
+ url: requestUrl
526
+ });
540
527
  wss.handleUpgrade(req, socket, head, (ws) => {
541
528
  wss.emit("connection", ws, req, auth);
542
529
  });
@@ -615,6 +602,55 @@ function defaultArtifactPath(capsuleDir) {
615
602
  return resolve(root, ".lakebed/artifacts", `${basename(capsuleDir)}.anonymous.json`);
616
603
  }
617
604
 
605
+ function deployMetadataPath(capsuleDir) {
606
+ return resolve(capsuleDir, ".lakebed/deploy.json");
607
+ }
608
+
609
+ async function readDeployMetadata(capsuleDir) {
610
+ try {
611
+ return JSON.parse(await readFile(deployMetadataPath(capsuleDir), "utf8"));
612
+ } catch (error) {
613
+ if (error?.code === "ENOENT") {
614
+ return null;
615
+ }
616
+
617
+ throw new Error(`Unable to read Lakebed deploy metadata: ${error instanceof Error ? error.message : String(error)}`);
618
+ }
619
+ }
620
+
621
+ async function writeDeployMetadata(capsuleDir, metadata) {
622
+ const path = deployMetadataPath(capsuleDir);
623
+ await mkdir(dirname(path), { recursive: true });
624
+ await writeFile(path, `${JSON.stringify(metadata, null, 2)}\n`);
625
+ }
626
+
627
+ function claimTokenFromDeployResponse(deployed) {
628
+ if (!deployed?.claimUrl || !deployed?.deployId) {
629
+ return null;
630
+ }
631
+
632
+ try {
633
+ const url = new URL(deployed.claimUrl);
634
+ const segments = url.pathname.split("/").filter(Boolean);
635
+ if (segments[0] === "claim" && segments[1] === deployed.deployId) {
636
+ return segments[2] ?? null;
637
+ }
638
+ } catch {
639
+ return null;
640
+ }
641
+
642
+ return null;
643
+ }
644
+
645
+ function deployRequestBody(envelope, ttl) {
646
+ return JSON.stringify({
647
+ artifact: envelope.artifact,
648
+ clientBundle: envelope.clientBundle,
649
+ clientVersion: "0.0.3",
650
+ requestedTtlSeconds: ttl
651
+ });
652
+ }
653
+
618
654
  async function buildCommand(args) {
619
655
  const [capsuleArg] = positionals(args);
620
656
  const target = readArg(args, "--target", "anonymous");
@@ -663,32 +699,65 @@ async function readResponseJson(response) {
663
699
 
664
700
  async function deployCommand(args) {
665
701
  const [capsuleArg] = positionals(args);
666
- const envelope = await buildAnonymousEnvelope(capsuleArg);
702
+ const capsuleDir = capsuleArg ? resolveCapsuleDir(capsuleArg) : root;
703
+ const envelope = await buildAnonymousEnvelope(capsuleDir);
667
704
  const ttl = parseTtlSeconds(readArg(args, "--ttl", "7d"));
668
705
  const api = deployApiUrl(args);
669
- const response = await fetch(`${api}/v1/anonymous-deploys`, {
670
- body: JSON.stringify({
671
- artifact: envelope.artifact,
672
- clientBundle: envelope.clientBundle,
673
- clientVersion: "0.0.2",
674
- requestedTtlSeconds: ttl
675
- }),
706
+ const body = deployRequestBody(envelope, ttl);
707
+ const metadata = await readDeployMetadata(capsuleDir);
708
+ const canUpdate =
709
+ metadata?.api === api && typeof metadata?.deployId === "string" && typeof metadata?.claimToken === "string";
710
+ let mode = "created";
711
+ let response;
712
+
713
+ if (canUpdate) {
714
+ response = await fetch(`${api}/v1/deploys/${encodeURIComponent(metadata.deployId)}`, {
715
+ body,
716
+ headers: {
717
+ "Authorization": `Bearer ${metadata.claimToken}`,
718
+ "Content-Type": "application/json"
719
+ },
720
+ method: "PUT"
721
+ });
722
+
723
+ if (response.status === 404 || response.status === 410) {
724
+ mode = "created";
725
+ response = null;
726
+ } else {
727
+ mode = "updated";
728
+ }
729
+ }
730
+
731
+ response ??= await fetch(`${api}/v1/anonymous-deploys`, {
732
+ body,
676
733
  headers: {
677
734
  "Content-Type": "application/json"
678
735
  },
679
736
  method: "POST"
680
737
  });
681
738
  const deployed = await readResponseJson(response);
739
+ const claimToken = claimTokenFromDeployResponse(deployed) ?? metadata?.claimToken;
740
+ if (claimToken) {
741
+ await writeDeployMetadata(capsuleDir, {
742
+ api,
743
+ claimToken,
744
+ deployId: deployed.deployId,
745
+ updatedAt: new Date().toISOString(),
746
+ url: deployed.url
747
+ });
748
+ }
682
749
 
683
750
  if (hasFlag(args, "--json")) {
684
751
  console.log(JSON.stringify(deployed, null, 2));
685
752
  return;
686
753
  }
687
754
 
688
- console.log("Deploying anonymous preview...\n");
755
+ console.log(`${mode === "updated" ? "Updated" : "Created"} anonymous preview.\n`);
689
756
  console.log(`App: ${deployed.url}`);
690
757
  console.log(`Expires: ${deployed.expiresAt}`);
691
- console.log(`Claim: ${deployed.claimUrl}`);
758
+ if (deployed.claimUrl) {
759
+ console.log(`Claim: ${deployed.claimUrl}`);
760
+ }
692
761
  console.log(`Inspect: lakebed inspect ${deployed.deployId}`);
693
762
  console.log("\nLimits:");
694
763
  console.log(` source/artifact: ${deployed.limits.artifactBytes} bytes`);
@@ -833,7 +902,7 @@ lakebed dev .
833
902
  Deploy:
834
903
 
835
904
  \`\`\`sh
836
- lakebed deploy .
905
+ lakebed deploy
837
906
  \`\`\`
838
907
 
839
908
  Inspect local state while \`lakebed dev\` is running:
@@ -852,14 +921,15 @@ lakebed logs --port 3000
852
921
  - Do not use Node built-ins in app code.
853
922
  - Use Tailwind classes directly in JSX.
854
923
  - Do not add a CSS, PostCSS, or Tailwind build pipeline.
855
- - Use guest auth through \`ctx.auth\` on the server and \`useAuth()\` on the client.
924
+ - Use auth through \`ctx.auth\` on the server and \`useAuth()\` on the client.
925
+ - Add Google sign-in with \`<SignInWithGoogle />\` or \`signInWithGoogle()\` from \`lakebed/client\`.
856
926
  - Keep \`shared/\` free of DOM, Node, env, and Lakebed runtime imports.
857
927
 
858
928
  ## Current Limits
859
929
 
860
930
  - One server entry.
861
931
  - One client entry.
862
- - Guest auth only.
932
+ - Guest auth locally, with built-in Google sign-in through Shoo.
863
933
  - No file storage.
864
934
  - No outbound fetch in anonymous deploys.
865
935
  - Local state resets when \`lakebed dev\` restarts.
@@ -905,13 +975,14 @@ export default capsule({
905
975
  }
906
976
  });
907
977
  `,
908
- "client/index.tsx": `import { useAuth, useMutation, useQuery } from "lakebed/client";
978
+ "client/index.tsx": `import { SignInWithGoogle, signOut, useAuth, useMutation, useQuery } from "lakebed/client";
909
979
  import { cleanTodoText, type Todo } from "../shared/todo";
910
980
 
911
981
  export function App() {
912
982
  const auth = useAuth();
913
983
  const todos = useQuery<Todo[]>("todos");
914
984
  const addTodo = useMutation<[text: string], void>("addTodo");
985
+ const authLabel = auth.email ?? auth.displayName;
915
986
 
916
987
  async function onSubmit(event: SubmitEvent) {
917
988
  event.preventDefault();
@@ -929,7 +1000,16 @@ export function App() {
929
1000
  return (
930
1001
  <main className="min-h-screen bg-black px-6 py-10 text-white">
931
1002
  <section className="mx-auto max-w-2xl">
932
- <p className="mb-3 font-mono text-sm text-neutral-500">signed in as {auth.userId}</p>
1003
+ <div className="mb-3 flex items-center justify-between gap-3">
1004
+ <p className="font-mono text-sm text-neutral-500">signed in as {authLabel}</p>
1005
+ {auth.isGuest ? (
1006
+ <SignInWithGoogle className="border border-neutral-700 px-3 py-1.5 text-sm font-medium text-neutral-200 hover:border-white hover:text-white" />
1007
+ ) : (
1008
+ <button className="text-sm text-neutral-400 hover:text-white" type="button" onClick={() => signOut()}>
1009
+ Sign out
1010
+ </button>
1011
+ )}
1012
+ </div>
933
1013
  <h1 className="mb-8 text-5xl font-bold tracking-tight">${title}</h1>
934
1014
  <form className="mb-8 flex gap-3" onSubmit={(event) => void onSubmit(event)}>
935
1015
  <input className="min-w-0 flex-1 border border-neutral-700 bg-black px-3 py-2 text-white outline-none focus:border-white" name="text" placeholder="Add a todo" />
@@ -957,6 +1037,8 @@ export function App() {
957
1037
  export function cleanTodoText(value: string): string {
958
1038
  return value.trim().slice(0, 160);
959
1039
  }
1040
+ `,
1041
+ ".gitignore": `.lakebed/
960
1042
  `,
961
1043
  "README.md": `# ${title}
962
1044
 
package/src/client.d.ts CHANGED
@@ -1,8 +1,63 @@
1
+ import type { ComponentChildren, JSX } from "preact";
2
+
1
3
  export type Auth = {
2
4
  userId: string;
3
5
  displayName: string;
6
+ provider: "guest" | "google";
7
+ isGuest: boolean;
8
+ isAuthenticated: boolean;
9
+ email?: string;
10
+ emailVerified?: boolean;
11
+ name?: string;
12
+ picture?: string;
13
+ };
14
+
15
+ export type Identity = {
16
+ userId: string | null;
17
+ token?: string;
18
+ };
19
+
20
+ export type IdentityClaims = {
21
+ iss?: string;
22
+ aud?: string;
23
+ sub?: string;
24
+ iat?: number;
25
+ exp?: number;
26
+ jti?: string;
27
+ pairwise_sub?: string;
28
+ email?: string;
29
+ email_verified?: boolean;
30
+ name?: string;
31
+ picture?: string;
4
32
  };
5
33
 
34
+ export type SignInWithGoogleOptions = {
35
+ callbackPath?: string;
36
+ clientId?: string;
37
+ redirectUri?: string;
38
+ returnTo?: string;
39
+ shooBaseUrl?: string;
40
+ };
41
+
42
+ export type SignInWithGoogleResult = {
43
+ url: string;
44
+ bundle: {
45
+ state: string;
46
+ verifier: string;
47
+ challenge: string;
48
+ };
49
+ };
50
+
51
+ export type SignInWithGoogleProps = Omit<JSX.ButtonHTMLAttributes<HTMLButtonElement>, "children"> &
52
+ SignInWithGoogleOptions & {
53
+ children?: ComponentChildren;
54
+ };
55
+
6
56
  export function useAuth(): Auth;
57
+ export function signInWithGoogle(options?: SignInWithGoogleOptions): Promise<SignInWithGoogleResult>;
58
+ export function signOut(): void;
59
+ export function getIdentity(): Identity;
60
+ export function decodeIdentityClaims(idToken?: string): IdentityClaims | null;
61
+ export function SignInWithGoogle(props?: SignInWithGoogleProps): JSX.Element;
7
62
  export function useQuery<T>(name: string): T;
8
63
  export function useMutation<TArgs extends unknown[], TResult>(name: string): (...args: TArgs) => Promise<TResult>;