@edgespark/web 0.0.0-beta.920378e
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 +281 -0
- package/auth/styles/styles.css +270 -0
- package/dist/api/fetch.d.ts +8 -0
- package/dist/api/types.d.ts +13 -0
- package/dist/auth/create-auth.d.ts +18 -0
- package/dist/auth/types.d.ts +84 -0
- package/dist/auth-ui/ManagedAuthUI.d.ts +85 -0
- package/dist/auth-ui/appearance.d.ts +64 -0
- package/dist/auth-ui/config.d.ts +33 -0
- package/dist/auth-ui/default-labels.d.ts +2 -0
- package/dist/auth-ui/index.d.ts +4 -0
- package/dist/auth-ui/mount.d.ts +4 -0
- package/dist/auth-ui/runtime.d.ts +7 -0
- package/dist/auth-ui/types.d.ts +156 -0
- package/dist/browser.d.ts +2 -0
- package/dist/client.d.ts +43 -0
- package/dist/errors.d.ts +32 -0
- package/dist/index.cjs +147 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.es.js +2272 -0
- package/dist/locales/ar.d.ts +2 -0
- package/dist/locales/de.d.ts +2 -0
- package/dist/locales/en-US.d.ts +2 -0
- package/dist/locales/es.d.ts +2 -0
- package/dist/locales/fr.d.ts +2 -0
- package/dist/locales/hi.d.ts +2 -0
- package/dist/locales/index.d.ts +21 -0
- package/dist/locales/it.d.ts +2 -0
- package/dist/locales/ja.d.ts +2 -0
- package/dist/locales/ko.d.ts +2 -0
- package/dist/locales/pt-BR.d.ts +2 -0
- package/dist/locales/ru.d.ts +2 -0
- package/dist/locales/zh-CN.d.ts +2 -0
- package/dist/redirect.d.ts +5 -0
- package/dist/shared/types.d.ts +6 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# `@edgespark/web`
|
|
2
|
+
|
|
3
|
+
EdgeSpark browser SDK.
|
|
4
|
+
|
|
5
|
+
This package is intentionally:
|
|
6
|
+
|
|
7
|
+
- browser-only
|
|
8
|
+
- same-origin only
|
|
9
|
+
- cookie-session only
|
|
10
|
+
- zero-config
|
|
11
|
+
- optimized for agent-written code
|
|
12
|
+
|
|
13
|
+
Recommended browser auth path:
|
|
14
|
+
|
|
15
|
+
- use `@edgespark/web` for all browser auth
|
|
16
|
+
- prefer `authUI.mount()` for managed auth UI
|
|
17
|
+
- use `es.auth` for custom flows
|
|
18
|
+
- do not implement app auth by calling `/api/_es/auth/*` manually
|
|
19
|
+
|
|
20
|
+
## Use in this repo
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm install
|
|
24
|
+
pnpm --filter @edgespark/web build
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createEdgeSpark } from "@edgespark/web";
|
|
31
|
+
import "@edgespark/web/styles.css";
|
|
32
|
+
|
|
33
|
+
const es = createEdgeSpark();
|
|
34
|
+
const container = document.getElementById("auth")!;
|
|
35
|
+
|
|
36
|
+
es.authUI.mount(container, {
|
|
37
|
+
redirectTo: "/dashboard",
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Auto-detected labels are used by default. The managed UI picks the best preset
|
|
42
|
+
from the browser's language preferences automatically.
|
|
43
|
+
|
|
44
|
+
## Public surface
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const es = createEdgeSpark();
|
|
48
|
+
const container = document.getElementById("auth")!;
|
|
49
|
+
|
|
50
|
+
es.auth;
|
|
51
|
+
es.api.fetch("/api/todos");
|
|
52
|
+
es.authUI.mount(container, { redirectTo: "/dashboard" });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `auth`
|
|
56
|
+
|
|
57
|
+
`es.auth` keeps better-auth's API shape and adds two EdgeSpark helpers:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const session = await es.auth.requireSession();
|
|
61
|
+
|
|
62
|
+
const unsubscribe = es.auth.onSessionChange((nextSession) => {
|
|
63
|
+
console.log(nextSession?.user.email ?? "signed out");
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`requireSession()` throws `UNAUTHENTICATED` only when no session exists. If the
|
|
68
|
+
session lookup itself fails, it preserves the upstream auth error code/status
|
|
69
|
+
so route guards can distinguish backend failures from a real sign-out.
|
|
70
|
+
|
|
71
|
+
`onSessionChange()` is hydration-safe: it waits for the first session lookup to
|
|
72
|
+
complete before emitting, so apps do not mistake initial loading for a real
|
|
73
|
+
signed-out state. Successful sign-in/sign-up/sign-out mutations also prompt
|
|
74
|
+
other tabs to refetch their session state.
|
|
75
|
+
|
|
76
|
+
Current platform note:
|
|
77
|
+
- `changeEmail()`
|
|
78
|
+
- `deleteUser()`
|
|
79
|
+
- `deleteUserCallback()`
|
|
80
|
+
|
|
81
|
+
still appear on the familiar better-auth-shaped surface, but EdgeSpark does
|
|
82
|
+
not currently enable those platform capabilities. These methods fail fast with
|
|
83
|
+
`UNSUPPORTED_AUTH_METHOD`. For normal browser auth flows, stay on the supported
|
|
84
|
+
paths: `authUI.mount()` for managed UI and `es.auth` for supported custom flows.
|
|
85
|
+
|
|
86
|
+
Use the familiar auth methods directly:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
await es.auth.signIn.email({ email, password });
|
|
90
|
+
await es.auth.signUp.email({ name, email, password });
|
|
91
|
+
await es.auth.signOut();
|
|
92
|
+
await es.auth.getSession();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The platform-managed `/api/_es/auth/*` routes exist underneath the SDK, but normal app code should not call them directly. Treat those routes as implementation details unless you are debugging auth at the HTTP layer.
|
|
96
|
+
|
|
97
|
+
### `api`
|
|
98
|
+
|
|
99
|
+
`es.api.fetch(...)` is the same-origin fetch wrapper for authenticated app requests.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const response = await es.api.fetch("/api/todos");
|
|
103
|
+
const todos = await response.json();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The wrapper always uses cookie credentials, rejects cross-origin URLs, and does not accept a `credentials` override in its `init` type.
|
|
107
|
+
|
|
108
|
+
### `authUI`
|
|
109
|
+
|
|
110
|
+
The managed auth UI supports exactly two post-auth strategies.
|
|
111
|
+
|
|
112
|
+
#### Redirect mode
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const container = document.getElementById("auth")!;
|
|
116
|
+
|
|
117
|
+
const ui = es.authUI.mount(container, {
|
|
118
|
+
redirectTo: "/dashboard",
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Controlled mode
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const container = document.getElementById("auth")!;
|
|
126
|
+
|
|
127
|
+
const ui = es.authUI.mount(container, {
|
|
128
|
+
onSuccess(event) {
|
|
129
|
+
if (event.action === "password-reset") {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
window.location.assign("/dashboard");
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`ui.destroy()` removes the mounted UI and clears timers/listeners.
|
|
139
|
+
|
|
140
|
+
`onError` always receives a normalized `EdgeSparkAuthError`, so callback code
|
|
141
|
+
can safely read `error.code`, `error.message`, and `error.status` without
|
|
142
|
+
defensive `unknown` handling.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
es.authUI.mount(container, {
|
|
146
|
+
onSuccess(event) {
|
|
147
|
+
if (event.action !== "password-reset") {
|
|
148
|
+
window.location.assign("/dashboard");
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
onError(error) {
|
|
152
|
+
console.error(error.code, error.message);
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Password reset has one special rule:
|
|
158
|
+
|
|
159
|
+
- in controlled mode, `onSuccess({ action: "password-reset" })` fires and the caller owns the next step
|
|
160
|
+
- in redirect mode, the UI returns to sign-in with a success notice instead of redirecting to an authenticated page
|
|
161
|
+
- if the platform keeps JWT session cookie cache enabled, reset-time session revocation remains eventually consistent for already-open browser contexts and may take up to the cache TTL to become visible
|
|
162
|
+
|
|
163
|
+
In controlled mode, `authUI` stops mutating the container after `onSuccess`
|
|
164
|
+
runs. If the app wants to close, replace, or reuse that container, it owns that
|
|
165
|
+
teardown explicitly.
|
|
166
|
+
|
|
167
|
+
#### Appearance
|
|
168
|
+
|
|
169
|
+
Managed auth supports a small, explicit appearance contract. If `appearance` is
|
|
170
|
+
omitted, the UI keeps its built-in default styling.
|
|
171
|
+
|
|
172
|
+
- `theme` selects the built-in starting preset
|
|
173
|
+
- `variables` override specific design tokens on top of that preset
|
|
174
|
+
- `className` adds a stable root hook for app-scoped CSS
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import {
|
|
178
|
+
AUTH_UI_APPEARANCE_VARIABLE,
|
|
179
|
+
AUTH_UI_THEME,
|
|
180
|
+
type AuthUIAppearance,
|
|
181
|
+
} from "@edgespark/web";
|
|
182
|
+
|
|
183
|
+
const appearance = {
|
|
184
|
+
theme: AUTH_UI_THEME.DARK,
|
|
185
|
+
className: "my-auth-surface",
|
|
186
|
+
variables: {
|
|
187
|
+
[AUTH_UI_APPEARANCE_VARIABLE.PRIMARY]: "#d97d4a",
|
|
188
|
+
[AUTH_UI_APPEARANCE_VARIABLE.FONT]: '"Manrope", sans-serif',
|
|
189
|
+
},
|
|
190
|
+
} satisfies AuthUIAppearance;
|
|
191
|
+
|
|
192
|
+
es.authUI.mount(container, {
|
|
193
|
+
redirectTo: "/dashboard",
|
|
194
|
+
appearance,
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Supported appearance fields:
|
|
199
|
+
|
|
200
|
+
- `theme`: `"light"` or `"dark"` (`"light"` is the default)
|
|
201
|
+
- `className`: applied to the managed auth UI root container
|
|
202
|
+
- `variables`: stable CSS custom properties for light-touch theming
|
|
203
|
+
|
|
204
|
+
`appearance.variables` are applied as inline CSS custom properties on the
|
|
205
|
+
managed auth root, so explicit overrides win over the built-in stylesheet
|
|
206
|
+
defaults without requiring consumers to manage CSS load order.
|
|
207
|
+
|
|
208
|
+
When the appearance object is assigned to a variable before calling
|
|
209
|
+
`authUI.mount(...)`, prefer `satisfies AuthUIAppearance` so `tsc` rejects
|
|
210
|
+
unsupported theme values, unsupported token keys, and misspelled appearance
|
|
211
|
+
fields during type-checking.
|
|
212
|
+
|
|
213
|
+
Supported `appearance.variables` keys:
|
|
214
|
+
|
|
215
|
+
| Variable | Controls | Light default |
|
|
216
|
+
|----------|----------|---------------|
|
|
217
|
+
| `--edgespark-auth-bg` | Page/input background, status mix base | `#ffffff` |
|
|
218
|
+
| `--edgespark-auth-bg-muted` | Secondary button hover, muted status bg, modal close hover | `#f7f7f8` |
|
|
219
|
+
| `--edgespark-auth-card-bg` | Card background (accepts gradient or flat color) | `linear-gradient(180deg, #ffffff, #fcfcfc)` |
|
|
220
|
+
| `--edgespark-auth-border` | Input border, secondary button border, divider line | `rgba(15,23,42,0.08)` |
|
|
221
|
+
| `--edgespark-auth-text` | All body text | `#111827` |
|
|
222
|
+
| `--edgespark-auth-text-muted` | Subtitles, meta text, divider label, modal close icon | `#6b7280` |
|
|
223
|
+
| `--edgespark-auth-divider-bg` | Divider pill background (masks the line behind the label) | inherits `--edgespark-auth-bg` |
|
|
224
|
+
| `--edgespark-auth-primary` | Primary button bg, links, input focus ring + border | `#0f766e` |
|
|
225
|
+
| `--edgespark-auth-primary-hover` | Primary button hover bg | `#115e59` |
|
|
226
|
+
| `--edgespark-auth-primary-contrast` | Primary button text | `#ffffff` |
|
|
227
|
+
| `--edgespark-auth-error` | Error status text + tinted bg | `#b91c1c` |
|
|
228
|
+
| `--edgespark-auth-info` | Info status text + tinted bg | `#0f766e` |
|
|
229
|
+
| `--edgespark-auth-radius-card` | Card border radius | `20px` |
|
|
230
|
+
| `--edgespark-auth-radius-control` | Input border radius | `12px` |
|
|
231
|
+
| `--edgespark-auth-shadow` | Card box shadow | `0 24px 60px rgba(15,23,42,0.08)` |
|
|
232
|
+
| `--edgespark-auth-font` | Root font family | `"IBM Plex Sans", "Inter", ui-sans-serif, system-ui, sans-serif` |
|
|
233
|
+
| `--edgespark-auth-modal-backdrop` | OAuth modal overlay background | `rgba(15,23,42,0.22)` |
|
|
234
|
+
| `--edgespark-auth-modal-card-bg` | OAuth modal card background | `#ffffff` |
|
|
235
|
+
| `--edgespark-auth-modal-shadow` | OAuth modal card box shadow | `0 24px 60px rgba(15,23,42,0.18)` |
|
|
236
|
+
|
|
237
|
+
Only the root container hook, the theme attribute, and the documented CSS
|
|
238
|
+
variables above are part of the managed auth appearance contract. Internal
|
|
239
|
+
`.edgespark-auth__*` classes remain implementation details and should not be
|
|
240
|
+
treated as a stable public API.
|
|
241
|
+
|
|
242
|
+
## Flows covered by the managed UI
|
|
243
|
+
|
|
244
|
+
- email/password sign-in
|
|
245
|
+
- email/password sign-up
|
|
246
|
+
- OAuth sign-in
|
|
247
|
+
- email verification instructions + resend
|
|
248
|
+
- forgot password
|
|
249
|
+
- reset password with email OTP
|
|
250
|
+
|
|
251
|
+
The forgot-password path only appears when the project config enables email-OTP password reset.
|
|
252
|
+
|
|
253
|
+
## Locale presets
|
|
254
|
+
|
|
255
|
+
Managed auth UI labels auto-detect by default, so most apps do not need to
|
|
256
|
+
import locale helpers manually.
|
|
257
|
+
|
|
258
|
+
Import a preset only when the app wants to force a specific language or build
|
|
259
|
+
on top of a known base:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import { createEdgeSpark, fr } from "@edgespark/web";
|
|
263
|
+
|
|
264
|
+
const es = createEdgeSpark();
|
|
265
|
+
|
|
266
|
+
es.authUI.mount(container, {
|
|
267
|
+
redirectTo: "/dashboard",
|
|
268
|
+
labels: fr,
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
`detectLocale()` is exported for advanced cases that want the same browser
|
|
273
|
+
language matching outside the managed auth UI.
|
|
274
|
+
|
|
275
|
+
## Design notes
|
|
276
|
+
|
|
277
|
+
- `createEdgeSpark()` takes no options
|
|
278
|
+
- current page origin is used internally
|
|
279
|
+
- auth base path is internal
|
|
280
|
+
- credentials mode is internal
|
|
281
|
+
- local dev with separate web/backend ports should use a dev proxy so the browser still sees same-origin requests
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
[data-edgespark-auth] {
|
|
2
|
+
--edgespark-auth-bg: #ffffff;
|
|
3
|
+
--edgespark-auth-bg-muted: #f7f7f8;
|
|
4
|
+
--edgespark-auth-card-bg: linear-gradient(180deg, #ffffff 0%, #fcfcfc 100%);
|
|
5
|
+
--edgespark-auth-border: rgba(15, 23, 42, 0.08);
|
|
6
|
+
--edgespark-auth-text: #111827;
|
|
7
|
+
--edgespark-auth-text-muted: #6b7280;
|
|
8
|
+
--edgespark-auth-divider-bg: var(--edgespark-auth-bg);
|
|
9
|
+
--edgespark-auth-primary: #0f766e;
|
|
10
|
+
--edgespark-auth-primary-hover: #115e59;
|
|
11
|
+
--edgespark-auth-primary-contrast: #ffffff;
|
|
12
|
+
--edgespark-auth-error: #b91c1c;
|
|
13
|
+
--edgespark-auth-info: #0f766e;
|
|
14
|
+
--edgespark-auth-radius-card: 20px;
|
|
15
|
+
--edgespark-auth-radius-control: 12px;
|
|
16
|
+
--edgespark-auth-shadow: 0 24px 60px rgba(15, 23, 42, 0.08);
|
|
17
|
+
--edgespark-auth-font: "IBM Plex Sans", "Inter", ui-sans-serif, system-ui, sans-serif;
|
|
18
|
+
--edgespark-auth-modal-backdrop: rgba(15, 23, 42, 0.22);
|
|
19
|
+
--edgespark-auth-modal-card-bg: #ffffff;
|
|
20
|
+
--edgespark-auth-modal-shadow: 0 24px 60px rgba(15, 23, 42, 0.18);
|
|
21
|
+
color: var(--edgespark-auth-text);
|
|
22
|
+
font-family: var(--edgespark-auth-font);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
[data-edgespark-auth][data-edgespark-auth-theme="dark"] {
|
|
26
|
+
--edgespark-auth-bg: #14181f;
|
|
27
|
+
--edgespark-auth-bg-muted: #1d2330;
|
|
28
|
+
--edgespark-auth-card-bg: linear-gradient(180deg, #171d27 0%, #121823 100%);
|
|
29
|
+
--edgespark-auth-border: rgba(226, 232, 240, 0.12);
|
|
30
|
+
--edgespark-auth-text: #f8fafc;
|
|
31
|
+
--edgespark-auth-text-muted: #94a3b8;
|
|
32
|
+
--edgespark-auth-divider-bg: var(--edgespark-auth-bg);
|
|
33
|
+
--edgespark-auth-primary: #14b8a6;
|
|
34
|
+
--edgespark-auth-primary-hover: #0f9488;
|
|
35
|
+
--edgespark-auth-primary-contrast: #f8fafc;
|
|
36
|
+
--edgespark-auth-error: #fca5a5;
|
|
37
|
+
--edgespark-auth-info: #5eead4;
|
|
38
|
+
--edgespark-auth-shadow: 0 24px 60px rgba(2, 6, 23, 0.32);
|
|
39
|
+
--edgespark-auth-modal-backdrop: rgba(2, 6, 23, 0.56);
|
|
40
|
+
--edgespark-auth-modal-card-bg: #101722;
|
|
41
|
+
--edgespark-auth-modal-shadow: 0 24px 60px rgba(2, 6, 23, 0.48);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[data-edgespark-auth] *,
|
|
45
|
+
[data-edgespark-auth] *::before,
|
|
46
|
+
[data-edgespark-auth] *::after {
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.edgespark-auth__shell {
|
|
51
|
+
width: 100%;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.edgespark-auth__card {
|
|
55
|
+
display: grid;
|
|
56
|
+
gap: 16px;
|
|
57
|
+
width: 100%;
|
|
58
|
+
max-width: 420px;
|
|
59
|
+
padding: 24px;
|
|
60
|
+
border: 1px solid var(--edgespark-auth-border);
|
|
61
|
+
border-radius: var(--edgespark-auth-radius-card);
|
|
62
|
+
background: var(--edgespark-auth-card-bg);
|
|
63
|
+
box-shadow: var(--edgespark-auth-shadow);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.edgespark-auth__section,
|
|
67
|
+
.edgespark-auth__form,
|
|
68
|
+
.edgespark-auth__stack {
|
|
69
|
+
display: grid;
|
|
70
|
+
gap: 14px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.edgespark-auth__header {
|
|
74
|
+
display: grid;
|
|
75
|
+
gap: 8px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.edgespark-auth__title {
|
|
79
|
+
margin: 0;
|
|
80
|
+
font-size: 1.75rem;
|
|
81
|
+
line-height: 1.1;
|
|
82
|
+
font-weight: 700;
|
|
83
|
+
letter-spacing: -0.03em;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.edgespark-auth__copy,
|
|
87
|
+
.edgespark-auth__meta {
|
|
88
|
+
margin: 0;
|
|
89
|
+
color: var(--edgespark-auth-text-muted);
|
|
90
|
+
font-size: 0.95rem;
|
|
91
|
+
line-height: 1.5;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.edgespark-auth__field {
|
|
95
|
+
display: grid;
|
|
96
|
+
gap: 8px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.edgespark-auth__label {
|
|
100
|
+
font-size: 0.9rem;
|
|
101
|
+
font-weight: 600;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.edgespark-auth__input {
|
|
105
|
+
width: 100%;
|
|
106
|
+
min-height: 48px;
|
|
107
|
+
padding: 0 14px;
|
|
108
|
+
border: 1px solid var(--edgespark-auth-border);
|
|
109
|
+
border-radius: var(--edgespark-auth-radius-control);
|
|
110
|
+
background: var(--edgespark-auth-bg);
|
|
111
|
+
color: var(--edgespark-auth-text);
|
|
112
|
+
font: inherit;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.edgespark-auth__input:focus {
|
|
116
|
+
outline: 2px solid color-mix(in srgb, var(--edgespark-auth-primary) 35%, transparent);
|
|
117
|
+
outline-offset: 1px;
|
|
118
|
+
border-color: var(--edgespark-auth-primary);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.edgespark-auth__button,
|
|
122
|
+
.edgespark-auth__link {
|
|
123
|
+
font: inherit;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.edgespark-auth__button {
|
|
127
|
+
display: inline-flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
justify-content: center;
|
|
130
|
+
min-height: 48px;
|
|
131
|
+
padding: 0 16px;
|
|
132
|
+
border-radius: 999px;
|
|
133
|
+
border: 1px solid transparent;
|
|
134
|
+
cursor: pointer;
|
|
135
|
+
transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease, opacity 120ms ease;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.edgespark-auth__button:disabled,
|
|
139
|
+
.edgespark-auth__link:disabled {
|
|
140
|
+
cursor: not-allowed;
|
|
141
|
+
opacity: 0.6;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.edgespark-auth__button--primary {
|
|
145
|
+
background: var(--edgespark-auth-primary);
|
|
146
|
+
color: var(--edgespark-auth-primary-contrast);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.edgespark-auth__button--primary:hover:not(:disabled) {
|
|
150
|
+
background: var(--edgespark-auth-primary-hover);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.edgespark-auth__button--secondary {
|
|
154
|
+
border-color: var(--edgespark-auth-border);
|
|
155
|
+
background: var(--edgespark-auth-bg);
|
|
156
|
+
color: var(--edgespark-auth-text);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.edgespark-auth__button--secondary:hover:not(:disabled) {
|
|
160
|
+
background: var(--edgespark-auth-bg-muted);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.edgespark-auth__actions {
|
|
164
|
+
display: grid;
|
|
165
|
+
gap: 10px;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.edgespark-auth__link {
|
|
169
|
+
justify-self: start;
|
|
170
|
+
padding: 0;
|
|
171
|
+
border: 0;
|
|
172
|
+
background: transparent;
|
|
173
|
+
color: var(--edgespark-auth-primary);
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.edgespark-auth__divider {
|
|
178
|
+
position: relative;
|
|
179
|
+
text-align: center;
|
|
180
|
+
color: var(--edgespark-auth-text-muted);
|
|
181
|
+
font-size: 0.85rem;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.edgespark-auth__divider::before {
|
|
185
|
+
content: "";
|
|
186
|
+
position: absolute;
|
|
187
|
+
left: 0;
|
|
188
|
+
right: 0;
|
|
189
|
+
top: 50%;
|
|
190
|
+
border-top: 1px solid var(--edgespark-auth-border);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.edgespark-auth__divider span {
|
|
194
|
+
position: relative;
|
|
195
|
+
padding: 0 10px;
|
|
196
|
+
background: var(--edgespark-auth-divider-bg);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.edgespark-auth__status {
|
|
200
|
+
padding: 12px 14px;
|
|
201
|
+
border-radius: 14px;
|
|
202
|
+
font-size: 0.92rem;
|
|
203
|
+
line-height: 1.5;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.edgespark-auth__status--error {
|
|
207
|
+
color: var(--edgespark-auth-error);
|
|
208
|
+
background: color-mix(in srgb, var(--edgespark-auth-error) 10%, var(--edgespark-auth-bg));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.edgespark-auth__status--info {
|
|
212
|
+
color: var(--edgespark-auth-info);
|
|
213
|
+
background: color-mix(in srgb, var(--edgespark-auth-info) 10%, var(--edgespark-auth-bg));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.edgespark-auth__status--muted {
|
|
217
|
+
color: var(--edgespark-auth-text-muted);
|
|
218
|
+
background: var(--edgespark-auth-bg-muted);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.edgespark-auth__iframe-modal {
|
|
222
|
+
position: fixed;
|
|
223
|
+
inset: 0;
|
|
224
|
+
z-index: 9999;
|
|
225
|
+
display: flex;
|
|
226
|
+
align-items: center;
|
|
227
|
+
justify-content: center;
|
|
228
|
+
padding: 24px;
|
|
229
|
+
background: var(--edgespark-auth-modal-backdrop);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.edgespark-auth__iframe-modal-card {
|
|
233
|
+
position: relative;
|
|
234
|
+
display: grid;
|
|
235
|
+
gap: 16px;
|
|
236
|
+
width: min(520px, 100%);
|
|
237
|
+
padding: 28px;
|
|
238
|
+
border-radius: 24px;
|
|
239
|
+
background: var(--edgespark-auth-modal-card-bg);
|
|
240
|
+
box-shadow: var(--edgespark-auth-modal-shadow);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.edgespark-auth__iframe-modal-close {
|
|
244
|
+
position: absolute;
|
|
245
|
+
top: 12px;
|
|
246
|
+
right: 12px;
|
|
247
|
+
width: 36px;
|
|
248
|
+
height: 36px;
|
|
249
|
+
border: 0;
|
|
250
|
+
border-radius: 999px;
|
|
251
|
+
background: transparent;
|
|
252
|
+
color: var(--edgespark-auth-text-muted);
|
|
253
|
+
font-size: 1.5rem;
|
|
254
|
+
cursor: pointer;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.edgespark-auth__iframe-modal-close:hover {
|
|
258
|
+
background: var(--edgespark-auth-bg-muted);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@media (max-width: 480px) {
|
|
262
|
+
.edgespark-auth__card {
|
|
263
|
+
padding: 18px;
|
|
264
|
+
border-radius: 16px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.edgespark-auth__title {
|
|
268
|
+
font-size: 1.45rem;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { EdgeSparkApi } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Creates the API module used by EdgeSpark apps.
|
|
4
|
+
*
|
|
5
|
+
* The wrapper keeps cookie credentials enabled and rejects cross-origin URLs so
|
|
6
|
+
* the SDK stays honest about its same-origin product boundary.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createApiModule(): EdgeSparkApi;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type EdgeSparkRequestInit = Omit<RequestInit, "credentials">;
|
|
2
|
+
/**
|
|
3
|
+
* Same-origin fetch wrapper for authenticated app requests.
|
|
4
|
+
*/
|
|
5
|
+
export interface EdgeSparkApi {
|
|
6
|
+
/**
|
|
7
|
+
* Same-origin `fetch` with cookie credentials always enabled.
|
|
8
|
+
*
|
|
9
|
+
* `credentials` is intentionally omitted from the accepted init shape because
|
|
10
|
+
* the SDK never allows callers to disable cookie auth.
|
|
11
|
+
*/
|
|
12
|
+
fetch(input: RequestInfo | URL, init?: EdgeSparkRequestInit): Promise<Response>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BetterAuthClientInstance, EdgeSparkAuth } from './types';
|
|
2
|
+
export declare const AUTH_BASE_PATH: "/api/_es/auth";
|
|
3
|
+
export declare function syncEdgeSparkAuthSession(authClient: BetterAuthClientInstance): Promise<void>;
|
|
4
|
+
/**
|
|
5
|
+
* Creates the headless auth module.
|
|
6
|
+
*
|
|
7
|
+
* The exposed API intentionally keeps better-auth's shape so existing upstream
|
|
8
|
+
* knowledge transfers directly. EdgeSpark adds session helpers and sync
|
|
9
|
+
* semantics on top of that familiar surface.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createEdgeSparkAuth(): EdgeSparkAuth;
|
|
12
|
+
/**
|
|
13
|
+
* Creates the internal auth client used by the managed UI.
|
|
14
|
+
*
|
|
15
|
+
* Redirect handling is intentionally disabled here so `authUI` can own
|
|
16
|
+
* callback timing, loading state, and post-auth navigation semantics.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createManagedAuthUIClient(): BetterAuthClientInstance;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createAuthClient } from 'better-auth/client';
|
|
2
|
+
/**
|
|
3
|
+
* Direct better-auth client instance returned by createAuthClient().
|
|
4
|
+
*/
|
|
5
|
+
export type BetterAuthClientInstance = ReturnType<typeof createAuthClient>;
|
|
6
|
+
export type SessionResult = Awaited<ReturnType<BetterAuthClientInstance["getSession"]>>;
|
|
7
|
+
export type AuthSession = NonNullable<SessionResult["data"]>;
|
|
8
|
+
export type AuthUser = AuthSession["user"];
|
|
9
|
+
export type SessionChangeCallback = (session: AuthSession | null) => void;
|
|
10
|
+
export interface AuthEndpointError {
|
|
11
|
+
code?: string;
|
|
12
|
+
message?: string;
|
|
13
|
+
status?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface AuthEndpointResult<TData = unknown> {
|
|
16
|
+
data?: TData | null;
|
|
17
|
+
error?: AuthEndpointError | null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* EdgeSpark keeps the auth API shape aligned with better-auth while adding a
|
|
21
|
+
* small set of session helpers on top.
|
|
22
|
+
*
|
|
23
|
+
* Most upstream auth methods remain available on the familiar better-auth
|
|
24
|
+
* shape. Some upstream methods may still be intentionally unsupported by the
|
|
25
|
+
* current EdgeSpark platform capability set even though they exist on the
|
|
26
|
+
* underlying better-auth client surface. In those cases, the facade throws an
|
|
27
|
+
* `EdgeSparkAuthError` with code `UNSUPPORTED_AUTH_METHOD` instead of deferring
|
|
28
|
+
* to a platform-specific 400/404 response.
|
|
29
|
+
*/
|
|
30
|
+
export type EdgeSparkAuth = BetterAuthClientInstance & {
|
|
31
|
+
/**
|
|
32
|
+
* Resolves with the current session, or throws when the browser is not authenticated.
|
|
33
|
+
*
|
|
34
|
+
* Use this for route guards and other flows where nullable session checks add noise.
|
|
35
|
+
*
|
|
36
|
+
* If the session lookup itself fails (offline, 5xx, etc.), this throws an
|
|
37
|
+
* `EdgeSparkAuthError` that preserves the upstream error code/status instead
|
|
38
|
+
* of collapsing the failure into `UNAUTHENTICATED`.
|
|
39
|
+
*/
|
|
40
|
+
requireSession(): Promise<AuthSession>;
|
|
41
|
+
/**
|
|
42
|
+
* Subscribes to session changes for the current browser context.
|
|
43
|
+
*
|
|
44
|
+
* The first emission is hydration-safe: it waits for the initial server
|
|
45
|
+
* session lookup to complete before firing, so subscribers never receive a
|
|
46
|
+
* premature `null` while a cookie session is still settling.
|
|
47
|
+
*
|
|
48
|
+
* After hydration, successful session atom updates are forwarded immediately.
|
|
49
|
+
* Successful auth mutations that create or clear a session in the current
|
|
50
|
+
* tab also prompt other tabs to refetch their session state, so logout and
|
|
51
|
+
* same-tab sign-in/sign-up converge quickly across tabs.
|
|
52
|
+
*
|
|
53
|
+
* Transient lookup failures that temporarily clear the atom to `null` are
|
|
54
|
+
* ignored so subscribers do not mistake a backend error for a real sign-out.
|
|
55
|
+
*
|
|
56
|
+
* Returns an unsubscribe function.
|
|
57
|
+
*/
|
|
58
|
+
onSessionChange(callback: SessionChangeCallback): () => void;
|
|
59
|
+
/**
|
|
60
|
+
* Signs out and then best-effort synchronizes session state for the current
|
|
61
|
+
* tab and other open tabs.
|
|
62
|
+
*
|
|
63
|
+
* The underlying better-auth result is returned unchanged.
|
|
64
|
+
*/
|
|
65
|
+
signOut(...args: Parameters<BetterAuthClientInstance["signOut"]>): ReturnType<BetterAuthClientInstance["signOut"]>;
|
|
66
|
+
/**
|
|
67
|
+
* Currently unsupported by EdgeSpark platform auth.
|
|
68
|
+
*
|
|
69
|
+
* The method remains present to preserve the familiar better-auth API shape,
|
|
70
|
+
* but the facade throws `UNSUPPORTED_AUTH_METHOD` until the platform enables
|
|
71
|
+
* this capability. For browser auth, prefer supported `client.auth` methods
|
|
72
|
+
* or `authUI.mount()` instead of dropping to raw `/api/_es/auth/*` routes.
|
|
73
|
+
*/
|
|
74
|
+
changeEmail(...args: Parameters<BetterAuthClientInstance["changeEmail"]>): ReturnType<BetterAuthClientInstance["changeEmail"]>;
|
|
75
|
+
/**
|
|
76
|
+
* Currently unsupported by EdgeSpark platform auth.
|
|
77
|
+
*
|
|
78
|
+
* The method remains present to preserve the familiar better-auth API shape,
|
|
79
|
+
* but the facade throws `UNSUPPORTED_AUTH_METHOD` until the platform enables
|
|
80
|
+
* this capability. Handle account removal through your own app flow until the
|
|
81
|
+
* platform adds first-class support.
|
|
82
|
+
*/
|
|
83
|
+
deleteUser(...args: Parameters<BetterAuthClientInstance["deleteUser"]>): ReturnType<BetterAuthClientInstance["deleteUser"]>;
|
|
84
|
+
};
|