@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 CHANGED
@@ -10,16 +10,77 @@ npm install @wordkey/sdk
10
10
 
11
11
  ## 1. Add the button
12
12
 
13
+ Signup and login are different user moments, so the SDK gives you both:
14
+
15
+ - **`mode="signup"`** — *Get your word*: a brand-new user gets a word in
16
+ WordKey's onboarding window, this device is enrolled, and they come back
17
+ signed in to your site — one flow.
18
+ - **`mode="login"`** (default) — *Word?*: a returning user types the word
19
+ they already have.
20
+
21
+ Both deliver the identical `onSuccess` user, so your backend handles one
22
+ contract.
23
+
13
24
  ```jsx
14
25
  import { WordKeyButton } from "@wordkey/sdk";
15
26
 
27
+ const handleUser = async (user) => {
28
+ // user.token is a single-use proof of authentication. Hand it to
29
+ // YOUR backend and verify it there (step 2) before creating a
30
+ // session — never trust user.id straight from the browser.
31
+ await fetch("/api/wordkey-login", {
32
+ method: "POST",
33
+ headers: { "Content-Type": "application/json" },
34
+ body: JSON.stringify({ token: user.token }),
35
+ });
36
+ };
37
+
16
38
  export function LoginPage() {
17
39
  return (
18
- <WordKeyButton
19
- publishableKey="wk_pub_xxxxxxxxxxxx"
20
- onSuccess={(user) => console.log("Authenticated:", user)}
21
- onError={(err) => console.error(err)}
22
- />
40
+ <>
41
+ <WordKeyButton
42
+ publishableKey="wk_pub_xxxxxxxxxxxx"
43
+ mode="signup"
44
+ onSuccess={handleUser}
45
+ />
46
+ <WordKeyButton
47
+ publishableKey="wk_pub_xxxxxxxxxxxx"
48
+ onSuccess={handleUser}
49
+ onError={(err) => console.error(err)}
50
+ />
51
+ </>
52
+ );
53
+ }
54
+ ```
55
+
56
+ ### Use your own buttons (WordKeyModal)
57
+
58
+ `WordKeyButton` is sugar over the controlled `WordKeyModal`. Render the modal
59
+ once, unconditionally, and open either flow from any UI you like:
60
+
61
+ ```jsx
62
+ import { WordKeyModal } from "@wordkey/sdk";
63
+ import { useState } from "react";
64
+
65
+ export function AuthButtons() {
66
+ const [flow, setFlow] = useState(null); // null | "signup" | "login"
67
+ return (
68
+ <>
69
+ <button className="cta" onClick={() => setFlow("signup")}>
70
+ Get Your Word
71
+ </button>
72
+ <a onClick={() => setFlow("login")}>Already have a word?</a>
73
+
74
+ {/* Always mounted — toggle `open`, don't conditionally render: the
75
+ modal also resumes the popup-blocked redirect fallback on mount. */}
76
+ <WordKeyModal
77
+ publishableKey="wk_pub_xxxxxxxxxxxx"
78
+ open={flow !== null}
79
+ mode={flow ?? "login"}
80
+ onClose={() => setFlow(null)}
81
+ onSuccess={handleUser}
82
+ />
83
+ </>
23
84
  );
24
85
  }
25
86
  ```
@@ -64,6 +125,10 @@ SDK automatically falls back to a full-page redirect and resumes your
64
125
 
65
126
  ## 2. Verify server-side
66
127
 
128
+ The token your page received in `onSuccess` is single-use and business-bound:
129
+ your server exchanges it with WordKey exactly once, over an authenticated
130
+ server-to-server call. A replayed, expired, or foreign-business token throws.
131
+
67
132
  ```ts
68
133
  import { WordKey } from "@wordkey/sdk/server";
69
134
 
@@ -71,9 +136,10 @@ const wk = new WordKey({ secretKey: process.env.WORDKEY_SECRET_KEY! });
71
136
 
72
137
  export async function POST(req: Request) {
73
138
  const { token } = await req.json();
74
- const user = await wk.verify(token);
139
+ const user = await wk.verify(token); // throws if invalid, reused, or expired
75
140
  // user.id — permanent unique id for this WordKey user
76
141
  // user.isNewUser — true if this is their first authentication
142
+ // Safe to create your session for user.id now.
77
143
  }
78
144
  ```
79
145
 
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
- import { W as WordKeyUser, a as WordKeyError, R as RequestedField } from './types-Be6QFOjD.mjs';
3
- export { A as Address, b as AuthEvent, C as CustomField, P as ProfileField, c as WordKeyProfile } from './types-Be6QFOjD.mjs';
2
+ import { W as WordKeyUser, a as WordKeyError, b as WordKeyMode, R as RequestedField } from './types-WFjkfsy6.mjs';
3
+ export { A as Address, c as AuthEvent, C as CustomField, P as ProfileField, d as WordKeyProfile } from './types-WFjkfsy6.mjs';
4
4
 
5
5
  interface WordKeyButtonProps {
6
6
  publishableKey: string;
@@ -9,11 +9,48 @@ interface WordKeyButtonProps {
9
9
  theme?: "dark" | "light" | "auto";
10
10
  size?: "sm" | "md" | "lg";
11
11
  label?: string;
12
+ /**
13
+ * Which flow this button opens. "login" (default, the 1.0.x behavior)
14
+ * renders the Word? mark for returning users; "signup" renders
15
+ * "Get your word" and opens WordKey onboarding for brand-new users.
16
+ * Render one button of each mode for the two user moments — or drive
17
+ * WordKeyModal yourself from any UI you like.
18
+ */
19
+ mode?: WordKeyMode;
12
20
  /** Profile fields to request. Standard names or custom field descriptors. */
13
21
  requestedFields?: RequestedField[];
14
22
  /** Override the protocol host (defaults to app.wordkey.app). */
15
23
  apiUrl?: string;
16
24
  }
17
- declare function WordKeyButton({ publishableKey, onSuccess, onError, size, label, requestedFields, apiUrl, }: WordKeyButtonProps): react.JSX.Element;
25
+ declare function WordKeyButton({ publishableKey, onSuccess, onError, size, label, mode, requestedFields, apiUrl, }: WordKeyButtonProps): react.JSX.Element;
18
26
 
19
- export { RequestedField, WordKeyButton, type WordKeyButtonProps, WordKeyError, WordKeyUser };
27
+ /**
28
+ * The controlled WordKey flow. Render it ONCE, unconditionally (toggle `open`,
29
+ * don't conditionally mount it): the redirect-fallback return leg is consumed
30
+ * in this component's mount effect, and a conditionally rendered modal would
31
+ * miss it on the page load that carries the result fragment.
32
+ *
33
+ * mode "login" — the Word? flow for returning users (the 1.0.x behavior).
34
+ * mode "signup" — the Get-your-word flow: opens WordKey's first-party
35
+ * onboarding page in a popup, which assigns a word, enrolls this device, and
36
+ * hands back the same single-use token login yields — so `onSuccess` has ONE
37
+ * contract across both modes and every WordKeyButton stays a thin wrapper.
38
+ */
39
+ interface WordKeyModalProps {
40
+ publishableKey: string;
41
+ /** Controlled visibility. The modal calls `onClose` when it dismisses itself
42
+ * (cancel, overlay click, or right before delivering `onSuccess`). */
43
+ open: boolean;
44
+ onClose: () => void;
45
+ onSuccess: (user: WordKeyUser) => void;
46
+ /** Which flow to open. Defaults to "login". */
47
+ mode?: WordKeyMode;
48
+ onError?: (error: WordKeyError) => void;
49
+ /** Profile fields to request. Standard names or custom field descriptors. */
50
+ requestedFields?: RequestedField[];
51
+ /** Override the protocol host (defaults to app.wordkey.app). */
52
+ apiUrl?: string;
53
+ }
54
+ declare function WordKeyModal({ publishableKey, open, onClose, onSuccess, mode, onError, requestedFields, apiUrl, }: WordKeyModalProps): react.JSX.Element | null;
55
+
56
+ export { RequestedField, WordKeyButton, type WordKeyButtonProps, WordKeyError, WordKeyModal, type WordKeyModalProps, WordKeyMode, WordKeyUser };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
- import { W as WordKeyUser, a as WordKeyError, R as RequestedField } from './types-Be6QFOjD.js';
3
- export { A as Address, b as AuthEvent, C as CustomField, P as ProfileField, c as WordKeyProfile } from './types-Be6QFOjD.js';
2
+ import { W as WordKeyUser, a as WordKeyError, b as WordKeyMode, R as RequestedField } from './types-WFjkfsy6.js';
3
+ export { A as Address, c as AuthEvent, C as CustomField, P as ProfileField, d as WordKeyProfile } from './types-WFjkfsy6.js';
4
4
 
5
5
  interface WordKeyButtonProps {
6
6
  publishableKey: string;
@@ -9,11 +9,48 @@ interface WordKeyButtonProps {
9
9
  theme?: "dark" | "light" | "auto";
10
10
  size?: "sm" | "md" | "lg";
11
11
  label?: string;
12
+ /**
13
+ * Which flow this button opens. "login" (default, the 1.0.x behavior)
14
+ * renders the Word? mark for returning users; "signup" renders
15
+ * "Get your word" and opens WordKey onboarding for brand-new users.
16
+ * Render one button of each mode for the two user moments — or drive
17
+ * WordKeyModal yourself from any UI you like.
18
+ */
19
+ mode?: WordKeyMode;
12
20
  /** Profile fields to request. Standard names or custom field descriptors. */
13
21
  requestedFields?: RequestedField[];
14
22
  /** Override the protocol host (defaults to app.wordkey.app). */
15
23
  apiUrl?: string;
16
24
  }
17
- declare function WordKeyButton({ publishableKey, onSuccess, onError, size, label, requestedFields, apiUrl, }: WordKeyButtonProps): react.JSX.Element;
25
+ declare function WordKeyButton({ publishableKey, onSuccess, onError, size, label, mode, requestedFields, apiUrl, }: WordKeyButtonProps): react.JSX.Element;
18
26
 
19
- export { RequestedField, WordKeyButton, type WordKeyButtonProps, WordKeyError, WordKeyUser };
27
+ /**
28
+ * The controlled WordKey flow. Render it ONCE, unconditionally (toggle `open`,
29
+ * don't conditionally mount it): the redirect-fallback return leg is consumed
30
+ * in this component's mount effect, and a conditionally rendered modal would
31
+ * miss it on the page load that carries the result fragment.
32
+ *
33
+ * mode "login" — the Word? flow for returning users (the 1.0.x behavior).
34
+ * mode "signup" — the Get-your-word flow: opens WordKey's first-party
35
+ * onboarding page in a popup, which assigns a word, enrolls this device, and
36
+ * hands back the same single-use token login yields — so `onSuccess` has ONE
37
+ * contract across both modes and every WordKeyButton stays a thin wrapper.
38
+ */
39
+ interface WordKeyModalProps {
40
+ publishableKey: string;
41
+ /** Controlled visibility. The modal calls `onClose` when it dismisses itself
42
+ * (cancel, overlay click, or right before delivering `onSuccess`). */
43
+ open: boolean;
44
+ onClose: () => void;
45
+ onSuccess: (user: WordKeyUser) => void;
46
+ /** Which flow to open. Defaults to "login". */
47
+ mode?: WordKeyMode;
48
+ onError?: (error: WordKeyError) => void;
49
+ /** Profile fields to request. Standard names or custom field descriptors. */
50
+ requestedFields?: RequestedField[];
51
+ /** Override the protocol host (defaults to app.wordkey.app). */
52
+ apiUrl?: string;
53
+ }
54
+ declare function WordKeyModal({ publishableKey, open, onClose, onSuccess, mode, onError, requestedFields, apiUrl, }: WordKeyModalProps): react.JSX.Element | null;
55
+
56
+ export { RequestedField, WordKeyButton, type WordKeyButtonProps, WordKeyError, WordKeyModal, type WordKeyModalProps, WordKeyMode, WordKeyUser };