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/README.md +61 -5
- package/package.json +6 -5
- package/src/anonymous-server.js +2013 -70
- package/src/anonymous.js +14 -5
- package/src/auth.js +155 -0
- package/src/cli.js +135 -53
- package/src/client.d.ts +55 -0
- package/src/client.js +445 -9
- package/src/server.d.ts +7 -0
package/src/client.js
CHANGED
|
@@ -1,16 +1,54 @@
|
|
|
1
|
+
import { h } from "preact";
|
|
1
2
|
import { useEffect, useState } from "preact/hooks";
|
|
2
3
|
|
|
4
|
+
const DEFAULT_SHOO_BASE_URL = "https://shoo.dev";
|
|
5
|
+
const AUTH_STORAGE_KEY = "lakebed_identity";
|
|
6
|
+
const LEGACY_SHOO_STORAGE_KEY = "shoo_identity";
|
|
7
|
+
const PKCE_STORAGE_KEY = "lakebed_google_pkce";
|
|
8
|
+
const RETURN_TO_STORAGE_KEY = "lakebed_google_return_to";
|
|
9
|
+
const PKCE_MAX_AGE_MS = 10 * 60 * 1000;
|
|
10
|
+
const encoder = new TextEncoder();
|
|
11
|
+
|
|
3
12
|
let socket = null;
|
|
4
13
|
let nextRequestId = 1;
|
|
5
|
-
let auth =
|
|
6
|
-
userId: "guest:local",
|
|
7
|
-
displayName: "Local Guest"
|
|
8
|
-
};
|
|
14
|
+
let auth = createGuestAuth("local");
|
|
9
15
|
const authListeners = new Set();
|
|
10
16
|
const queryValues = new Map();
|
|
11
17
|
const queryListeners = new Map();
|
|
12
18
|
const pending = new Map();
|
|
13
19
|
const activeSubscriptions = new Set();
|
|
20
|
+
let authInitPromise = null;
|
|
21
|
+
let authInitialized = false;
|
|
22
|
+
|
|
23
|
+
function toGuestName(name) {
|
|
24
|
+
return (
|
|
25
|
+
String(name ?? "local")
|
|
26
|
+
.replace(/^guest:/, "")
|
|
27
|
+
.trim()
|
|
28
|
+
.replace(/[^a-zA-Z0-9_.-]+/g, "-")
|
|
29
|
+
.replace(/^-+|-+$/g, "")
|
|
30
|
+
.toLowerCase() || "local"
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function toDisplayName(name) {
|
|
35
|
+
return toGuestName(name)
|
|
36
|
+
.split(/[-_\s.]+/)
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
|
|
39
|
+
.join(" ");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createGuestAuth(name) {
|
|
43
|
+
const guestName = toGuestName(name);
|
|
44
|
+
return {
|
|
45
|
+
displayName: toDisplayName(guestName),
|
|
46
|
+
isAuthenticated: false,
|
|
47
|
+
isGuest: true,
|
|
48
|
+
provider: "guest",
|
|
49
|
+
userId: `guest:${guestName}`
|
|
50
|
+
};
|
|
51
|
+
}
|
|
14
52
|
|
|
15
53
|
function emitAuth() {
|
|
16
54
|
for (const listener of authListeners) {
|
|
@@ -47,6 +85,308 @@ function send(message) {
|
|
|
47
85
|
);
|
|
48
86
|
}
|
|
49
87
|
|
|
88
|
+
function basePath() {
|
|
89
|
+
return window.__LAKEBED_BASE_PATH__ ?? "";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function authConfig() {
|
|
93
|
+
return window.__LAKEBED_AUTH__ ?? {};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function callbackPath() {
|
|
97
|
+
return `${basePath()}/auth/callback`.replace(/\/{2,}/g, "/");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function currentRoute() {
|
|
101
|
+
return `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function normalizeReturnTo(value) {
|
|
105
|
+
if (!value) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const parsed = new URL(value, window.location.origin);
|
|
111
|
+
if (parsed.origin !== window.location.origin) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const route = `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
116
|
+
if (!route.startsWith("/") || route.startsWith("//")) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return route;
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function fallbackRoute() {
|
|
127
|
+
return basePath() || "/";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function deriveRedirectUri(path) {
|
|
131
|
+
return new URL(path, window.location.origin).toString();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function deriveClientIdFromRedirectUri(redirectUri) {
|
|
135
|
+
return `origin:${new URL(redirectUri).origin}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function resolveGoogleAuthOptions(options = {}) {
|
|
139
|
+
const resolvedCallbackPath = normalizeReturnTo(options.callbackPath) ?? callbackPath();
|
|
140
|
+
const redirectUri = options.redirectUri ?? deriveRedirectUri(resolvedCallbackPath);
|
|
141
|
+
return {
|
|
142
|
+
callbackPath: resolvedCallbackPath,
|
|
143
|
+
clientId: options.clientId ?? deriveClientIdFromRedirectUri(redirectUri),
|
|
144
|
+
redirectUri,
|
|
145
|
+
returnTo: normalizeReturnTo(options.returnTo) ?? currentRoute(),
|
|
146
|
+
shooBaseUrl: String(options.shooBaseUrl ?? authConfig().shooBaseUrl ?? DEFAULT_SHOO_BASE_URL).replace(/\/+$/g, "")
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function randomString(length = 64) {
|
|
151
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
152
|
+
const random = crypto.getRandomValues(new Uint8Array(length));
|
|
153
|
+
let value = "";
|
|
154
|
+
for (let index = 0; index < random.length; index += 1) {
|
|
155
|
+
value += chars[random[index] % chars.length];
|
|
156
|
+
}
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function bytesToBase64Url(bytes) {
|
|
161
|
+
let binary = "";
|
|
162
|
+
for (const byte of bytes) {
|
|
163
|
+
binary += String.fromCharCode(byte);
|
|
164
|
+
}
|
|
165
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function decodeBase64Url(value) {
|
|
169
|
+
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
170
|
+
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
|
|
171
|
+
return atob(padded);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function parseJson(value) {
|
|
175
|
+
try {
|
|
176
|
+
return JSON.parse(value);
|
|
177
|
+
} catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function decodeIdentityClaims(idToken) {
|
|
183
|
+
if (!idToken) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const parts = idToken.split(".");
|
|
188
|
+
if (parts.length < 2) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return parseJson(decodeBase64Url(parts[1]));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function readStoredIdentity() {
|
|
196
|
+
const raw = window.localStorage.getItem(AUTH_STORAGE_KEY) ?? window.localStorage.getItem(LEGACY_SHOO_STORAGE_KEY);
|
|
197
|
+
if (!raw) {
|
|
198
|
+
return { userId: null };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const parsed = parseJson(raw);
|
|
202
|
+
if (!parsed || typeof parsed !== "object") {
|
|
203
|
+
return { userId: null };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const token = typeof parsed.token === "string" ? parsed.token : undefined;
|
|
207
|
+
const claims = decodeIdentityClaims(token);
|
|
208
|
+
if (typeof claims?.exp === "number" && claims.exp * 1000 <= Date.now()) {
|
|
209
|
+
clearStoredIdentity();
|
|
210
|
+
return { userId: null };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
token,
|
|
215
|
+
userId: typeof parsed.userId === "string" ? parsed.userId : (typeof parsed.pairwiseSub === "string" ? parsed.pairwiseSub : null)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function persistIdentity(userId, token, expiresIn) {
|
|
220
|
+
window.localStorage.setItem(
|
|
221
|
+
AUTH_STORAGE_KEY,
|
|
222
|
+
JSON.stringify({
|
|
223
|
+
expiresIn,
|
|
224
|
+
receivedAt: Date.now(),
|
|
225
|
+
token,
|
|
226
|
+
userId
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function clearStoredIdentity() {
|
|
232
|
+
window.localStorage.removeItem(AUTH_STORAGE_KEY);
|
|
233
|
+
window.localStorage.removeItem(LEGACY_SHOO_STORAGE_KEY);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function storedAuthToken() {
|
|
237
|
+
return readStoredIdentity().token ?? "";
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function createGoogleAuthFromToken(token) {
|
|
241
|
+
const claims = decodeIdentityClaims(token);
|
|
242
|
+
const pairwiseSub = claims?.pairwise_sub ?? claims?.sub;
|
|
243
|
+
if (!pairwiseSub) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
displayName: claims.name ?? claims.email ?? "Google User",
|
|
249
|
+
email: claims.email,
|
|
250
|
+
emailVerified: claims.email_verified,
|
|
251
|
+
isAuthenticated: true,
|
|
252
|
+
isGuest: false,
|
|
253
|
+
name: claims.name,
|
|
254
|
+
picture: claims.picture,
|
|
255
|
+
provider: "google",
|
|
256
|
+
userId: `google:${pairwiseSub}`
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function createPkceBundle() {
|
|
261
|
+
const verifier = randomString(64);
|
|
262
|
+
const state = randomString(32);
|
|
263
|
+
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(verifier));
|
|
264
|
+
return {
|
|
265
|
+
challenge: bytesToBase64Url(new Uint8Array(digest)),
|
|
266
|
+
state,
|
|
267
|
+
verifier
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function createSignInUrl(options, bundle) {
|
|
272
|
+
const url = new URL("/authorize", options.shooBaseUrl);
|
|
273
|
+
url.searchParams.set("client_id", options.clientId);
|
|
274
|
+
url.searchParams.set("redirect_uri", options.redirectUri);
|
|
275
|
+
url.searchParams.set("state", bundle.state);
|
|
276
|
+
url.searchParams.set("code_challenge", bundle.challenge);
|
|
277
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
278
|
+
url.searchParams.set("pii", "true");
|
|
279
|
+
return url.toString();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function parseCallback(url = window.location.href) {
|
|
283
|
+
const parsed = new URL(url);
|
|
284
|
+
const code = parsed.searchParams.get("code");
|
|
285
|
+
const state = parsed.searchParams.get("state");
|
|
286
|
+
if (!code || !state) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
return { code, state };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function clearCallbackParams(url = window.location.href) {
|
|
293
|
+
const next = new URL(url);
|
|
294
|
+
next.searchParams.delete("code");
|
|
295
|
+
next.searchParams.delete("state");
|
|
296
|
+
next.searchParams.delete("error");
|
|
297
|
+
window.history.replaceState({}, "", next.toString());
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function popReturnTo() {
|
|
301
|
+
const value = normalizeReturnTo(window.sessionStorage.getItem(RETURN_TO_STORAGE_KEY));
|
|
302
|
+
window.sessionStorage.removeItem(RETURN_TO_STORAGE_KEY);
|
|
303
|
+
return value;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function exchangeCode({ code, codeVerifier, options }) {
|
|
307
|
+
const body = new URLSearchParams({
|
|
308
|
+
client_id: options.clientId,
|
|
309
|
+
code,
|
|
310
|
+
code_verifier: codeVerifier,
|
|
311
|
+
grant_type: "authorization_code",
|
|
312
|
+
redirect_uri: options.redirectUri
|
|
313
|
+
});
|
|
314
|
+
const response = await fetch(new URL("/token", options.shooBaseUrl), {
|
|
315
|
+
body,
|
|
316
|
+
headers: {
|
|
317
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
318
|
+
},
|
|
319
|
+
method: "POST"
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
if (!response.ok) {
|
|
323
|
+
const details = await response.text();
|
|
324
|
+
throw new Error(`Google sign-in token exchange failed (${response.status}): ${details || "no details"}`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return response.json();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function handleGoogleCallback() {
|
|
331
|
+
const callback = parseCallback();
|
|
332
|
+
if (!callback) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const rawPkce = window.sessionStorage.getItem(PKCE_STORAGE_KEY);
|
|
337
|
+
const parsedPkce = rawPkce ? parseJson(rawPkce) : null;
|
|
338
|
+
if (!parsedPkce?.state || !parsedPkce?.verifier) {
|
|
339
|
+
throw new Error("Missing Google sign-in verifier. Start sign-in again.");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (typeof parsedPkce.createdAt === "number" && Date.now() - parsedPkce.createdAt > PKCE_MAX_AGE_MS) {
|
|
343
|
+
window.sessionStorage.removeItem(PKCE_STORAGE_KEY);
|
|
344
|
+
throw new Error("Google sign-in verifier expired. Start sign-in again.");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (parsedPkce.state !== callback.state) {
|
|
348
|
+
throw new Error("Google sign-in state mismatch.");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const options = resolveGoogleAuthOptions();
|
|
352
|
+
const token = await exchangeCode({
|
|
353
|
+
code: callback.code,
|
|
354
|
+
codeVerifier: parsedPkce.verifier,
|
|
355
|
+
options
|
|
356
|
+
});
|
|
357
|
+
if (!token?.id_token || !token?.pairwise_sub) {
|
|
358
|
+
throw new Error("Google sign-in token response was missing identity claims.");
|
|
359
|
+
}
|
|
360
|
+
persistIdentity(token.pairwise_sub, token.id_token, token.expires_in);
|
|
361
|
+
window.sessionStorage.removeItem(PKCE_STORAGE_KEY);
|
|
362
|
+
|
|
363
|
+
const localAuth = createGoogleAuthFromToken(token.id_token);
|
|
364
|
+
if (localAuth) {
|
|
365
|
+
auth = localAuth;
|
|
366
|
+
emitAuth();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const returnTo = popReturnTo() ?? fallbackRoute();
|
|
370
|
+
clearCallbackParams();
|
|
371
|
+
window.location.replace(returnTo);
|
|
372
|
+
return token;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function ensureAuthInitialized() {
|
|
376
|
+
if (authInitialized) {
|
|
377
|
+
return Promise.resolve();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
authInitPromise ??= handleGoogleCallback()
|
|
381
|
+
.catch((error) => {
|
|
382
|
+
console.error("[lakebed] Google sign-in failed", error);
|
|
383
|
+
})
|
|
384
|
+
.finally(() => {
|
|
385
|
+
authInitialized = true;
|
|
386
|
+
});
|
|
387
|
+
return authInitPromise;
|
|
388
|
+
}
|
|
389
|
+
|
|
50
390
|
function request(op, payload) {
|
|
51
391
|
const id = nextRequestId++;
|
|
52
392
|
send({ id, op, ...payload });
|
|
@@ -61,24 +401,30 @@ function connect() {
|
|
|
61
401
|
return socket;
|
|
62
402
|
}
|
|
63
403
|
|
|
404
|
+
void ensureAuthInitialized();
|
|
405
|
+
|
|
64
406
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
65
|
-
const
|
|
66
|
-
const url = new URL(`${protocol}//${window.location.host}${basePath}/__lakebed/ws`);
|
|
407
|
+
const url = new URL(`${protocol}//${window.location.host}${basePath()}/__lakebed/ws`);
|
|
67
408
|
const guestName = new URLSearchParams(window.location.search).get("lakebed_guest");
|
|
68
409
|
if (guestName) {
|
|
69
410
|
url.searchParams.set("lakebed_guest", guestName);
|
|
70
411
|
}
|
|
412
|
+
const token = storedAuthToken();
|
|
413
|
+
if (token) {
|
|
414
|
+
url.searchParams.set("lakebed_token", token);
|
|
415
|
+
}
|
|
71
416
|
|
|
72
417
|
socket = new WebSocket(url);
|
|
418
|
+
const currentSocket = socket;
|
|
73
419
|
|
|
74
|
-
|
|
420
|
+
currentSocket.addEventListener("open", () => {
|
|
75
421
|
send({ op: "auth.get" });
|
|
76
422
|
for (const name of activeSubscriptions) {
|
|
77
423
|
send({ op: "query.subscribe", name });
|
|
78
424
|
}
|
|
79
425
|
});
|
|
80
426
|
|
|
81
|
-
|
|
427
|
+
currentSocket.addEventListener("message", (event) => {
|
|
82
428
|
const message = JSON.parse(String(event.data));
|
|
83
429
|
|
|
84
430
|
if (message.op === "auth.result") {
|
|
@@ -104,8 +450,16 @@ function connect() {
|
|
|
104
450
|
}
|
|
105
451
|
});
|
|
106
452
|
|
|
107
|
-
|
|
453
|
+
currentSocket.addEventListener("close", () => {
|
|
454
|
+
if (socket !== currentSocket) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
108
458
|
window.setTimeout(() => {
|
|
459
|
+
if (socket !== currentSocket) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
109
463
|
socket = null;
|
|
110
464
|
connect();
|
|
111
465
|
}, 500);
|
|
@@ -114,10 +468,19 @@ function connect() {
|
|
|
114
468
|
return socket;
|
|
115
469
|
}
|
|
116
470
|
|
|
471
|
+
function reconnect() {
|
|
472
|
+
if (socket && socket.readyState !== WebSocket.CLOSED && socket.readyState !== WebSocket.CLOSING) {
|
|
473
|
+
socket.close();
|
|
474
|
+
}
|
|
475
|
+
socket = null;
|
|
476
|
+
connect();
|
|
477
|
+
}
|
|
478
|
+
|
|
117
479
|
export function useAuth() {
|
|
118
480
|
const [value, setValue] = useState(auth);
|
|
119
481
|
|
|
120
482
|
useEffect(() => {
|
|
483
|
+
void ensureAuthInitialized();
|
|
121
484
|
connect();
|
|
122
485
|
authListeners.add(setValue);
|
|
123
486
|
return () => {
|
|
@@ -128,6 +491,79 @@ export function useAuth() {
|
|
|
128
491
|
return value;
|
|
129
492
|
}
|
|
130
493
|
|
|
494
|
+
export async function signInWithGoogle(options = {}) {
|
|
495
|
+
const resolved = resolveGoogleAuthOptions(options);
|
|
496
|
+
const bundle = await createPkceBundle();
|
|
497
|
+
window.sessionStorage.setItem(
|
|
498
|
+
PKCE_STORAGE_KEY,
|
|
499
|
+
JSON.stringify({
|
|
500
|
+
createdAt: Date.now(),
|
|
501
|
+
state: bundle.state,
|
|
502
|
+
verifier: bundle.verifier
|
|
503
|
+
})
|
|
504
|
+
);
|
|
505
|
+
window.sessionStorage.setItem(
|
|
506
|
+
RETURN_TO_STORAGE_KEY,
|
|
507
|
+
normalizeReturnTo(resolved.returnTo) === resolved.callbackPath ? fallbackRoute() : (normalizeReturnTo(resolved.returnTo) ?? fallbackRoute())
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
const url = createSignInUrl(resolved, bundle);
|
|
511
|
+
window.location.assign(url);
|
|
512
|
+
return { bundle, url };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export function signOut() {
|
|
516
|
+
clearStoredIdentity();
|
|
517
|
+
auth = createGuestAuth(new URLSearchParams(window.location.search).get("lakebed_guest") ?? "local");
|
|
518
|
+
emitAuth();
|
|
519
|
+
reconnect();
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
export function getIdentity() {
|
|
523
|
+
return readStoredIdentity();
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export function SignInWithGoogle({
|
|
527
|
+
children = "Sign in with Google",
|
|
528
|
+
className = "",
|
|
529
|
+
clientId,
|
|
530
|
+
callbackPath,
|
|
531
|
+
disabled,
|
|
532
|
+
onClick,
|
|
533
|
+
redirectUri,
|
|
534
|
+
requestPii: _requestPii,
|
|
535
|
+
requestProfile: _requestProfile,
|
|
536
|
+
returnTo,
|
|
537
|
+
shooBaseUrl,
|
|
538
|
+
type = "button",
|
|
539
|
+
...props
|
|
540
|
+
} = {}) {
|
|
541
|
+
return h(
|
|
542
|
+
"button",
|
|
543
|
+
{
|
|
544
|
+
className,
|
|
545
|
+
disabled,
|
|
546
|
+
onClick: (event) => {
|
|
547
|
+
onClick?.(event);
|
|
548
|
+
if (event.defaultPrevented || disabled) {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
void signInWithGoogle({
|
|
553
|
+
callbackPath,
|
|
554
|
+
clientId,
|
|
555
|
+
redirectUri,
|
|
556
|
+
returnTo,
|
|
557
|
+
shooBaseUrl
|
|
558
|
+
});
|
|
559
|
+
},
|
|
560
|
+
type,
|
|
561
|
+
...props
|
|
562
|
+
},
|
|
563
|
+
children
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
131
567
|
export function useQuery(name) {
|
|
132
568
|
const [value, setValue] = useState(queryValues.get(name) ?? []);
|
|
133
569
|
|
package/src/server.d.ts
CHANGED
|
@@ -12,6 +12,13 @@ export type TableDefinition = {
|
|
|
12
12
|
export type AuthContext = {
|
|
13
13
|
userId: string;
|
|
14
14
|
displayName: string;
|
|
15
|
+
provider: "guest" | "google";
|
|
16
|
+
isGuest: boolean;
|
|
17
|
+
isAuthenticated: boolean;
|
|
18
|
+
email?: string;
|
|
19
|
+
emailVerified?: boolean;
|
|
20
|
+
name?: string;
|
|
21
|
+
picture?: string;
|
|
15
22
|
};
|
|
16
23
|
|
|
17
24
|
export type LogContext = {
|