@wordkey/sdk 1.0.0 → 1.1.0
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 +72 -6
- package/dist/index.d.mts +41 -4
- package/dist/index.d.ts +41 -4
- package/dist/index.js +359 -66
- package/dist/index.mjs +356 -64
- package/dist/server.d.mts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/{types-Be6QFOjD.d.mts → types-WFjkfsy6.d.mts} +17 -1
- package/dist/{types-Be6QFOjD.d.ts → types-WFjkfsy6.d.ts} +17 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -30,12 +30,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
|
-
WordKeyButton: () => WordKeyButton
|
|
33
|
+
WordKeyButton: () => WordKeyButton,
|
|
34
|
+
WordKeyModal: () => WordKeyModal
|
|
34
35
|
});
|
|
35
36
|
module.exports = __toCommonJS(src_exports);
|
|
36
37
|
|
|
37
38
|
// src/WordKeyButton.tsx
|
|
38
|
-
var
|
|
39
|
+
var import_react2 = require("react");
|
|
39
40
|
|
|
40
41
|
// src/types.ts
|
|
41
42
|
var FIELD_LABELS = {
|
|
@@ -46,6 +47,9 @@ var FIELD_LABELS = {
|
|
|
46
47
|
};
|
|
47
48
|
var DEFAULT_API_URL = "https://app.wordkey.app";
|
|
48
49
|
|
|
50
|
+
// src/WordKeyModal.tsx
|
|
51
|
+
var import_react = require("react");
|
|
52
|
+
|
|
49
53
|
// src/fields.ts
|
|
50
54
|
var STANDARD = {
|
|
51
55
|
name: { key: "name", label: FIELD_LABELS.name, type: "text", isStandard: true },
|
|
@@ -110,6 +114,17 @@ var DeviceApprovalRequiredError = class extends Error {
|
|
|
110
114
|
this.name = "DeviceApprovalRequiredError";
|
|
111
115
|
}
|
|
112
116
|
};
|
|
117
|
+
function decodeToken(token) {
|
|
118
|
+
try {
|
|
119
|
+
const part = token.split(".")[1];
|
|
120
|
+
if (!part) return null;
|
|
121
|
+
const b64 = part.replace(/-/g, "+").replace(/_/g, "/");
|
|
122
|
+
const json = typeof atob === "function" ? atob(b64) : Buffer.from(b64, "base64").toString("utf8");
|
|
123
|
+
return JSON.parse(json);
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
113
128
|
async function verifyWord(apiUrl = DEFAULT_API_URL, publishableKey, word) {
|
|
114
129
|
const wordHash = await hashWord(word);
|
|
115
130
|
const res = await fetch(`${apiUrl}/api/auth/verify`, {
|
|
@@ -244,9 +259,11 @@ async function beginRedirectShare(input2) {
|
|
|
244
259
|
const publicJwk = await crypto.subtle.exportKey("jwk", pair.publicKey);
|
|
245
260
|
const privateKeyJwk = await crypto.subtle.exportKey("jwk", pair.privateKey);
|
|
246
261
|
const pending = {
|
|
262
|
+
kind: "login",
|
|
247
263
|
requestId: input2.requestId,
|
|
248
264
|
userId: input2.userId,
|
|
249
265
|
isNewUser: input2.isNewUser,
|
|
266
|
+
token: input2.token,
|
|
250
267
|
fields: input2.fields,
|
|
251
268
|
privateKeyJwk
|
|
252
269
|
};
|
|
@@ -282,10 +299,12 @@ async function consumePendingRedirect() {
|
|
|
282
299
|
}
|
|
283
300
|
if (envelope.requestId !== pending.requestId) return null;
|
|
284
301
|
sessionStorage.removeItem(PENDING_STORE);
|
|
285
|
-
if (envelope.
|
|
286
|
-
|
|
302
|
+
if (!envelope.epk || !envelope.iv || !envelope.ct) {
|
|
303
|
+
if (envelope.granted !== true) {
|
|
304
|
+
return { pending, result: { granted: false, values: {} } };
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
287
307
|
}
|
|
288
|
-
if (!envelope.epk || !envelope.iv || !envelope.ct) return null;
|
|
289
308
|
try {
|
|
290
309
|
const privateKey = await crypto.subtle.importKey(
|
|
291
310
|
"jwk",
|
|
@@ -316,13 +335,135 @@ async function consumePendingRedirect() {
|
|
|
316
335
|
const parsed = JSON.parse(new TextDecoder().decode(plain));
|
|
317
336
|
return {
|
|
318
337
|
pending,
|
|
319
|
-
result: {
|
|
338
|
+
result: {
|
|
339
|
+
granted: envelope.granted === true,
|
|
340
|
+
values: parsed.values ?? {}
|
|
341
|
+
},
|
|
342
|
+
signup: typeof parsed.signup?.token === "string" ? { token: parsed.signup.token } : void 0
|
|
320
343
|
};
|
|
321
344
|
} catch {
|
|
322
345
|
return null;
|
|
323
346
|
}
|
|
324
347
|
}
|
|
325
348
|
|
|
349
|
+
// src/signup-popup.ts
|
|
350
|
+
var SIGNUP_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
351
|
+
function openSignupWindow(apiUrl, publishableKey) {
|
|
352
|
+
const w = 440;
|
|
353
|
+
const h = 700;
|
|
354
|
+
const left = Math.max(0, (window.screenX ?? 0) + (window.outerWidth - w) / 2);
|
|
355
|
+
const top = Math.max(0, (window.screenY ?? 0) + (window.outerHeight - h) / 3);
|
|
356
|
+
try {
|
|
357
|
+
return window.open(
|
|
358
|
+
`${apiUrl}/welcome?wk_sdk=1&business=${encodeURIComponent(publishableKey)}`,
|
|
359
|
+
"wordkey-signup",
|
|
360
|
+
`popup=yes,width=${w},height=${h},left=${left},top=${top}`
|
|
361
|
+
);
|
|
362
|
+
} catch {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function runSignupHandshake(input2) {
|
|
367
|
+
const { popup, apiOrigin, requestId, signal } = input2;
|
|
368
|
+
return new Promise((resolve) => {
|
|
369
|
+
let settled = false;
|
|
370
|
+
const cleanup = () => {
|
|
371
|
+
window.removeEventListener("message", onMsg);
|
|
372
|
+
clearInterval(closedPoll);
|
|
373
|
+
clearTimeout(timer);
|
|
374
|
+
signal?.removeEventListener("abort", onAbort);
|
|
375
|
+
};
|
|
376
|
+
const finish = (r) => {
|
|
377
|
+
if (settled) return;
|
|
378
|
+
settled = true;
|
|
379
|
+
cleanup();
|
|
380
|
+
resolve(r);
|
|
381
|
+
};
|
|
382
|
+
const onAbort = () => {
|
|
383
|
+
try {
|
|
384
|
+
popup.close();
|
|
385
|
+
} catch {
|
|
386
|
+
}
|
|
387
|
+
finish({ ok: false, cancelled: true });
|
|
388
|
+
};
|
|
389
|
+
const onMsg = (e) => {
|
|
390
|
+
if (e.origin !== apiOrigin || e.source !== popup) return;
|
|
391
|
+
const data = e.data;
|
|
392
|
+
if (!data || typeof data !== "object") return;
|
|
393
|
+
if (data.type === "wordkey:signup:ready") {
|
|
394
|
+
popup.postMessage(
|
|
395
|
+
{
|
|
396
|
+
type: "wordkey:signup:init",
|
|
397
|
+
requestId,
|
|
398
|
+
publishableKey: input2.publishableKey,
|
|
399
|
+
sdkDeviceId: input2.sdkDeviceId,
|
|
400
|
+
fields: input2.fields
|
|
401
|
+
},
|
|
402
|
+
apiOrigin
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
if (data.type === "wordkey:signup:result" && data.requestId === requestId) {
|
|
406
|
+
finish({
|
|
407
|
+
ok: data.ok === true && typeof data.token === "string",
|
|
408
|
+
token: typeof data.token === "string" ? data.token : void 0,
|
|
409
|
+
profileTicket: typeof data.profileTicket === "string" ? data.profileTicket : void 0
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
window.addEventListener("message", onMsg);
|
|
414
|
+
const closedPoll = setInterval(() => {
|
|
415
|
+
if (popup.closed) finish({ ok: false, closed: true });
|
|
416
|
+
}, 400);
|
|
417
|
+
const timer = setTimeout(
|
|
418
|
+
() => finish({ ok: false, closed: true }),
|
|
419
|
+
SIGNUP_TIMEOUT_MS
|
|
420
|
+
);
|
|
421
|
+
if (signal) {
|
|
422
|
+
if (signal.aborted) onAbort();
|
|
423
|
+
else signal.addEventListener("abort", onAbort);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
async function beginSignupRedirect(input2) {
|
|
428
|
+
const pair = await crypto.subtle.generateKey(
|
|
429
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
430
|
+
true,
|
|
431
|
+
["deriveKey"]
|
|
432
|
+
);
|
|
433
|
+
const publicJwk = await crypto.subtle.exportKey("jwk", pair.publicKey);
|
|
434
|
+
const privateKeyJwk = await crypto.subtle.exportKey("jwk", pair.privateKey);
|
|
435
|
+
const pending = {
|
|
436
|
+
kind: "signup",
|
|
437
|
+
requestId: input2.requestId,
|
|
438
|
+
fields: input2.fields,
|
|
439
|
+
privateKeyJwk
|
|
440
|
+
};
|
|
441
|
+
sessionStorage.setItem(PENDING_STORE, JSON.stringify(pending));
|
|
442
|
+
const returnUrl = window.location.href.split("#")[0];
|
|
443
|
+
const request = {
|
|
444
|
+
v: 1,
|
|
445
|
+
requestId: input2.requestId,
|
|
446
|
+
publishableKey: input2.publishableKey,
|
|
447
|
+
origin: window.location.origin,
|
|
448
|
+
returnUrl,
|
|
449
|
+
sdkPub: publicJwk,
|
|
450
|
+
sdkDeviceId: input2.sdkDeviceId,
|
|
451
|
+
fields: input2.fields
|
|
452
|
+
};
|
|
453
|
+
const b64 = encodeSignupRequest(request);
|
|
454
|
+
window.location.assign(
|
|
455
|
+
`${input2.apiUrl}/welcome?wk_sdk=1&business=${encodeURIComponent(
|
|
456
|
+
input2.publishableKey
|
|
457
|
+
)}#wksignup=${b64}`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
function encodeSignupRequest(value) {
|
|
461
|
+
const bytes = new TextEncoder().encode(JSON.stringify(value));
|
|
462
|
+
let bin = "";
|
|
463
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
464
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
465
|
+
}
|
|
466
|
+
|
|
326
467
|
// src/fonts.ts
|
|
327
468
|
var loaded = false;
|
|
328
469
|
function ensureFont() {
|
|
@@ -419,19 +560,18 @@ var ghostBtn = {
|
|
|
419
560
|
fontFamily: FONT_BODY
|
|
420
561
|
};
|
|
421
562
|
|
|
422
|
-
// src/
|
|
563
|
+
// src/WordKeyModal.tsx
|
|
423
564
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
424
|
-
|
|
425
|
-
function WordKeyButton({
|
|
565
|
+
function WordKeyModal({
|
|
426
566
|
publishableKey,
|
|
567
|
+
open,
|
|
568
|
+
onClose,
|
|
427
569
|
onSuccess,
|
|
570
|
+
mode = "login",
|
|
428
571
|
onError,
|
|
429
|
-
size = "md",
|
|
430
|
-
label = "Word?",
|
|
431
572
|
requestedFields,
|
|
432
573
|
apiUrl = DEFAULT_API_URL
|
|
433
574
|
}) {
|
|
434
|
-
const [open, setOpen] = (0, import_react.useState)(false);
|
|
435
575
|
(0, import_react.useEffect)(() => {
|
|
436
576
|
ensureFont();
|
|
437
577
|
}, []);
|
|
@@ -441,55 +581,151 @@ function WordKeyButton({
|
|
|
441
581
|
resumed.current = true;
|
|
442
582
|
void consumePendingRedirect().then((r) => {
|
|
443
583
|
if (!r) return;
|
|
584
|
+
if (r.pending.kind === "signup") {
|
|
585
|
+
const token = r.signup?.token;
|
|
586
|
+
if (!token) {
|
|
587
|
+
onError?.({
|
|
588
|
+
code: "signup_incomplete",
|
|
589
|
+
message: "Signup did not complete. Please try again."
|
|
590
|
+
});
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const claims = decodeToken(token);
|
|
594
|
+
onSuccess({
|
|
595
|
+
id: claims?.userId ?? "",
|
|
596
|
+
isNewUser: claims?.isNewUser ?? true,
|
|
597
|
+
authenticatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
598
|
+
token,
|
|
599
|
+
profile: r.pending.fields.length ? buildProfile(r.pending.fields, r.result.granted ? r.result.values : {}) : void 0
|
|
600
|
+
});
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
444
603
|
const profile = buildProfile(
|
|
445
604
|
r.pending.fields,
|
|
446
605
|
r.result.granted ? r.result.values : {}
|
|
447
606
|
);
|
|
448
607
|
onSuccess({
|
|
449
|
-
id: r.pending.userId,
|
|
450
|
-
isNewUser: r.pending.isNewUser,
|
|
608
|
+
id: r.pending.userId ?? "",
|
|
609
|
+
isNewUser: r.pending.isNewUser ?? false,
|
|
451
610
|
authenticatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
611
|
+
token: r.pending.token,
|
|
452
612
|
profile
|
|
453
613
|
});
|
|
454
614
|
});
|
|
455
615
|
}, []);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
616
|
+
if (!open) return null;
|
|
617
|
+
const common = {
|
|
618
|
+
publishableKey,
|
|
619
|
+
apiUrl,
|
|
620
|
+
requestedFields,
|
|
621
|
+
onClose,
|
|
622
|
+
onSuccess: (u) => {
|
|
623
|
+
onClose();
|
|
624
|
+
onSuccess(u);
|
|
625
|
+
},
|
|
626
|
+
onError: (e) => onError?.(e)
|
|
627
|
+
};
|
|
628
|
+
return mode === "signup" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SignupModal, { ...common }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthModal, { ...common });
|
|
629
|
+
}
|
|
630
|
+
function SignupModal({
|
|
631
|
+
publishableKey,
|
|
632
|
+
apiUrl,
|
|
633
|
+
requestedFields,
|
|
634
|
+
onClose,
|
|
635
|
+
onSuccess,
|
|
636
|
+
onError
|
|
637
|
+
}) {
|
|
638
|
+
const fields = (0, import_react.useMemo)(
|
|
639
|
+
() => requestedFields ? resolveFields(requestedFields) : [],
|
|
640
|
+
[requestedFields]
|
|
641
|
+
);
|
|
642
|
+
const apiOrigin = (0, import_react.useMemo)(() => {
|
|
643
|
+
try {
|
|
644
|
+
return new URL(apiUrl).origin;
|
|
645
|
+
} catch {
|
|
646
|
+
return apiUrl;
|
|
647
|
+
}
|
|
648
|
+
}, [apiUrl]);
|
|
649
|
+
const [failed, setFailed] = (0, import_react.useState)(null);
|
|
650
|
+
const abortRef = (0, import_react.useRef)(null);
|
|
651
|
+
if (!abortRef.current) abortRef.current = new AbortController();
|
|
652
|
+
(0, import_react.useEffect)(() => () => abortRef.current?.abort(), []);
|
|
653
|
+
const started = (0, import_react.useRef)(false);
|
|
654
|
+
(0, import_react.useEffect)(() => {
|
|
655
|
+
if (started.current) return;
|
|
656
|
+
started.current = true;
|
|
657
|
+
const popup = openSignupWindow(apiUrl, publishableKey);
|
|
658
|
+
if (!popup) {
|
|
659
|
+
void beginSignupRedirect({
|
|
482
660
|
apiUrl,
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
661
|
+
requestId: newRequestId(),
|
|
662
|
+
publishableKey,
|
|
663
|
+
sdkDeviceId: getDeviceId(),
|
|
664
|
+
fields
|
|
665
|
+
});
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const requestId = newRequestId();
|
|
669
|
+
void (async () => {
|
|
670
|
+
const hs = await runSignupHandshake({
|
|
671
|
+
popup,
|
|
672
|
+
apiOrigin,
|
|
673
|
+
requestId,
|
|
674
|
+
publishableKey,
|
|
675
|
+
sdkDeviceId: getDeviceId(),
|
|
676
|
+
fields,
|
|
677
|
+
signal: abortRef.current?.signal
|
|
678
|
+
});
|
|
679
|
+
if (hs.cancelled) return;
|
|
680
|
+
if (hs.closed) {
|
|
681
|
+
onClose();
|
|
682
|
+
return;
|
|
490
683
|
}
|
|
491
|
-
|
|
492
|
-
|
|
684
|
+
if (!hs.ok || !hs.token) {
|
|
685
|
+
setFailed("Signup could not be completed. Please try again.");
|
|
686
|
+
onError({
|
|
687
|
+
code: "signup_failed",
|
|
688
|
+
message: "Signup could not be completed"
|
|
689
|
+
});
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
const claims = decodeToken(hs.token);
|
|
693
|
+
const finish = (profile) => onSuccess({
|
|
694
|
+
id: claims?.userId ?? "",
|
|
695
|
+
isNewUser: claims?.isNewUser ?? true,
|
|
696
|
+
authenticatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
697
|
+
token: hs.token,
|
|
698
|
+
profile
|
|
699
|
+
});
|
|
700
|
+
if (fields.length === 0) {
|
|
701
|
+
finish();
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const share = await runPopupFlow({
|
|
705
|
+
popup,
|
|
706
|
+
apiOrigin,
|
|
707
|
+
requestId,
|
|
708
|
+
publishableKey,
|
|
709
|
+
ticket: hs.profileTicket,
|
|
710
|
+
fields,
|
|
711
|
+
signal: abortRef.current?.signal
|
|
712
|
+
});
|
|
713
|
+
if (share.cancelled) return;
|
|
714
|
+
finish(buildProfile(fields, share.granted ? share.values : {}));
|
|
715
|
+
})();
|
|
716
|
+
}, []);
|
|
717
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: overlay, role: "dialog", "aria-modal": "true", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: panel, onClick: (e) => e.stopPropagation(), children: [
|
|
718
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Header, {}),
|
|
719
|
+
failed ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
720
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorText, { children: failed }),
|
|
721
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: ghostBtn, onClick: onClose, children: "Close" }) })
|
|
722
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { textAlign: "center", padding: "12px 0" }, children: [
|
|
723
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
|
|
724
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { style: { fontFamily: FONT_DISPLAY, fontSize: 22, margin: "4px 0" }, children: "Get your word in the WordKey window" }),
|
|
725
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { color: C.muted, fontSize: 13 }, children: "Finish setting up in the window that just opened. Closing it cancels \u2014 nothing is created without you." }),
|
|
726
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: ghostBtn, onClick: onClose, children: "Cancel" }) })
|
|
727
|
+
] })
|
|
728
|
+
] }) });
|
|
493
729
|
}
|
|
494
730
|
function AuthModal({
|
|
495
731
|
publishableKey,
|
|
@@ -524,7 +760,12 @@ function AuthModal({
|
|
|
524
760
|
setStep("authenticating");
|
|
525
761
|
try {
|
|
526
762
|
const result = await verifyWord(apiUrl, publishableKey, word);
|
|
527
|
-
await completeAuth(
|
|
763
|
+
await completeAuth(
|
|
764
|
+
result.userId,
|
|
765
|
+
result.isNewUser,
|
|
766
|
+
result.token,
|
|
767
|
+
result.profileTicket
|
|
768
|
+
);
|
|
528
769
|
} catch (e) {
|
|
529
770
|
if (e instanceof DeviceApprovalRequiredError) {
|
|
530
771
|
await startDeviceApproval();
|
|
@@ -536,11 +777,12 @@ function AuthModal({
|
|
|
536
777
|
onError({ code: "auth_failed", message });
|
|
537
778
|
}
|
|
538
779
|
}
|
|
539
|
-
async function completeAuth(userId, isNewUser, profileTicket) {
|
|
780
|
+
async function completeAuth(userId, isNewUser, token, profileTicket) {
|
|
540
781
|
const finish = (profile) => onSuccess({
|
|
541
782
|
id: userId,
|
|
542
783
|
isNewUser,
|
|
543
784
|
authenticatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
785
|
+
token,
|
|
544
786
|
profile
|
|
545
787
|
});
|
|
546
788
|
if (fields.length === 0) {
|
|
@@ -557,7 +799,8 @@ function AuthModal({
|
|
|
557
799
|
ticket: profileTicket,
|
|
558
800
|
fields,
|
|
559
801
|
userId,
|
|
560
|
-
isNewUser
|
|
802
|
+
isNewUser,
|
|
803
|
+
token
|
|
561
804
|
});
|
|
562
805
|
return;
|
|
563
806
|
}
|
|
@@ -629,11 +872,12 @@ function AuthModal({
|
|
|
629
872
|
const userId = claims?.userId ?? "";
|
|
630
873
|
const isNewUser = claims?.isNewUser ?? false;
|
|
631
874
|
if (fields.length === 0) {
|
|
632
|
-
await completeAuth(userId, isNewUser, data.profileTicket);
|
|
875
|
+
await completeAuth(userId, isNewUser, data.token, data.profileTicket);
|
|
633
876
|
} else {
|
|
634
877
|
pendingApproval.current = {
|
|
635
878
|
userId,
|
|
636
879
|
isNewUser,
|
|
880
|
+
token: data.token,
|
|
637
881
|
profileTicket: data.profileTicket
|
|
638
882
|
};
|
|
639
883
|
setStep("approved");
|
|
@@ -738,7 +982,8 @@ function AuthModal({
|
|
|
738
982
|
style: primaryBtn(false),
|
|
739
983
|
onClick: () => {
|
|
740
984
|
const p = pendingApproval.current;
|
|
741
|
-
if (p)
|
|
985
|
+
if (p)
|
|
986
|
+
void completeAuth(p.userId, p.isNewUser, p.token, p.profileTicket);
|
|
742
987
|
},
|
|
743
988
|
children: "Continue"
|
|
744
989
|
}
|
|
@@ -797,17 +1042,6 @@ function valuePresent(v) {
|
|
|
797
1042
|
if (typeof v === "string") return v.trim().length > 0;
|
|
798
1043
|
return Object.values(v).some((x) => x && String(x).trim().length > 0);
|
|
799
1044
|
}
|
|
800
|
-
function decodeToken(token) {
|
|
801
|
-
try {
|
|
802
|
-
const part = token.split(".")[1];
|
|
803
|
-
if (!part) return null;
|
|
804
|
-
const b64 = part.replace(/-/g, "+").replace(/_/g, "/");
|
|
805
|
-
const json = typeof atob === "function" ? atob(b64) : Buffer.from(b64, "base64").toString("utf8");
|
|
806
|
-
return JSON.parse(json);
|
|
807
|
-
} catch {
|
|
808
|
-
return null;
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
1045
|
function buildProfile(fields, granted) {
|
|
812
1046
|
const profile = {};
|
|
813
1047
|
const custom = {};
|
|
@@ -856,7 +1090,66 @@ function Spinner() {
|
|
|
856
1090
|
}
|
|
857
1091
|
);
|
|
858
1092
|
}
|
|
1093
|
+
|
|
1094
|
+
// src/WordKeyButton.tsx
|
|
1095
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1096
|
+
var sizePx = { sm: 20, md: 28, lg: 38 };
|
|
1097
|
+
function WordKeyButton({
|
|
1098
|
+
publishableKey,
|
|
1099
|
+
onSuccess,
|
|
1100
|
+
onError,
|
|
1101
|
+
size = "md",
|
|
1102
|
+
label,
|
|
1103
|
+
mode = "login",
|
|
1104
|
+
requestedFields,
|
|
1105
|
+
apiUrl = DEFAULT_API_URL
|
|
1106
|
+
}) {
|
|
1107
|
+
const [open, setOpen] = (0, import_react2.useState)(false);
|
|
1108
|
+
(0, import_react2.useEffect)(() => {
|
|
1109
|
+
ensureFont();
|
|
1110
|
+
}, []);
|
|
1111
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1112
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1113
|
+
"button",
|
|
1114
|
+
{
|
|
1115
|
+
onClick: () => setOpen(true),
|
|
1116
|
+
style: {
|
|
1117
|
+
background: "transparent",
|
|
1118
|
+
border: "none",
|
|
1119
|
+
padding: 0,
|
|
1120
|
+
cursor: "pointer",
|
|
1121
|
+
fontFamily: FONT_DISPLAY,
|
|
1122
|
+
fontWeight: 600,
|
|
1123
|
+
fontSize: sizePx[size],
|
|
1124
|
+
lineHeight: 1
|
|
1125
|
+
},
|
|
1126
|
+
"aria-label": label ?? (mode === "signup" ? "Get your word" : "Word?"),
|
|
1127
|
+
children: mode === "signup" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1128
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: C.purple }, children: "Get your " }),
|
|
1129
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: C.purpleL }, children: "word" })
|
|
1130
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1131
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: C.purple }, children: "Word" }),
|
|
1132
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: C.purpleL }, children: "?" })
|
|
1133
|
+
] })
|
|
1134
|
+
}
|
|
1135
|
+
),
|
|
1136
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1137
|
+
WordKeyModal,
|
|
1138
|
+
{
|
|
1139
|
+
publishableKey,
|
|
1140
|
+
open,
|
|
1141
|
+
mode,
|
|
1142
|
+
onClose: () => setOpen(false),
|
|
1143
|
+
onSuccess,
|
|
1144
|
+
onError,
|
|
1145
|
+
requestedFields,
|
|
1146
|
+
apiUrl
|
|
1147
|
+
}
|
|
1148
|
+
)
|
|
1149
|
+
] });
|
|
1150
|
+
}
|
|
859
1151
|
// Annotate the CommonJS export names for ESM import in node:
|
|
860
1152
|
0 && (module.exports = {
|
|
861
|
-
WordKeyButton
|
|
1153
|
+
WordKeyButton,
|
|
1154
|
+
WordKeyModal
|
|
862
1155
|
});
|