@authon/svelte 0.3.0 → 0.3.1

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.ko.md CHANGED
@@ -2,140 +2,49 @@
2
2
 
3
3
  # @authon/svelte
4
4
 
5
- [Authon](https://authon.dev)을 위한 Svelte SDK store, 액션, 컴포넌트를 제공합니다.
5
+ > Svelte 인증 -- 반응형 스토어 -- 셀프 호스팅 Clerk 대안
6
6
 
7
7
  ## 설치
8
8
 
9
9
  ```bash
10
10
  npm install @authon/svelte
11
- # 또는
12
- pnpm add @authon/svelte
13
11
  ```
14
12
 
15
- `svelte >= 4.0.0`이 필요합니다.
16
-
17
13
  ## 빠른 시작
18
14
 
19
- ### 1. 초기화
20
-
21
- ```ts
22
- // src/lib/authon.ts
23
- import { initAuthon } from '@authon/svelte';
24
-
25
- export const authon = initAuthon({
26
- publishableKey: 'pk_live_...',
27
- });
28
- ```
29
-
30
- ### 2. Store 사용
31
-
32
15
  ```svelte
33
- <script>
34
- import { user, isSignedIn, isLoading } from '@authon/svelte';
35
- import { openSignIn, signOut } from '@authon/svelte';
16
+ <!-- +layout.svelte -->
17
+ <script lang="ts">
18
+ import { initAuthon } from '@authon/svelte';
19
+ import { onDestroy } from 'svelte';
20
+ const authon = initAuthon('pk_live_...', { apiUrl: 'https://your-authon-server.com' });
21
+ onDestroy(() => authon.destroy());
36
22
  </script>
37
-
38
- {#if $isLoading}
39
- <p>Loading...</p>
40
- {:else if $isSignedIn}
41
- <p>Welcome, {$user?.displayName}</p>
42
- <button on:click={signOut}>Sign Out</button>
43
- {:else}
44
- <button on:click={openSignIn}>Sign In</button>
45
- {/if}
23
+ <slot />
46
24
  ```
47
25
 
48
- ### 3. 컴포넌트 사용
49
-
50
26
  ```svelte
51
- <script>
52
- import { SignedIn, SignedOut, UserButton } from '@authon/svelte';
53
- </script>
54
-
55
- <SignedIn>
56
- <UserButton />
57
- </SignedIn>
58
- <SignedOut>
59
- <button on:click={openSignIn}>Sign In</button>
60
- </SignedOut>
61
- ```
62
-
63
- ## API 레퍼런스
64
-
65
- ### Store
66
-
67
- | Store | 타입 | 설명 |
68
- |-------|------|------|
69
- | `user` | `Readable<AuthonUser \| null>` | 현재 사용자 |
70
- | `isSignedIn` | `Readable<boolean>` | 로그인 여부 |
71
- | `isLoading` | `Readable<boolean>` | 인증 상태 로딩 여부 |
72
-
73
- ### 액션
74
-
75
- | 함수 | 반환값 | 설명 |
76
- |------|--------|------|
77
- | `openSignIn()` | `Promise<void>` | 로그인 모달 열기 |
78
- | `openSignUp()` | `Promise<void>` | 회원가입 모달 열기 |
79
- | `signOut()` | `Promise<void>` | 로그아웃 |
80
- | `getToken()` | `string \| null` | 현재 액세스 토큰 반환 |
81
-
82
- ### 컴포넌트
83
-
84
- | 컴포넌트 | 설명 |
85
- |----------|------|
86
- | `<SignedIn>` | 로그인 상태일 때만 슬롯 렌더링 |
87
- | `<SignedOut>` | 로그아웃 상태일 때만 슬롯 렌더링 |
88
- | `<UserButton>` | 로그아웃 기능이 포함된 아바타 드롭다운 |
89
-
90
- ## 다중 인증 (MFA)
91
-
92
- Authon 클라이언트 인스턴스를 통해 MFA에 접근합니다.
93
-
94
- ```svelte
95
- <script>
27
+ <!-- +page.svelte -->
28
+ <script lang="ts">
96
29
  import { getAuthon } from '@authon/svelte';
97
- import { AuthonMfaRequiredError } from '@authon/js';
98
-
99
- const authon = getAuthon();
100
- let qrSvg = '';
101
- let mfaToken = '';
102
-
103
- async function enableMfa() {
104
- const setup = await authon.client.setupMfa();
105
- qrSvg = setup.qrCodeSvg; // Display QR for authenticator app
106
- }
107
-
108
- async function verifySetup(code) {
109
- await authon.client.verifyMfaSetup(code);
110
- }
111
-
112
- async function signIn(email, password) {
113
- try {
114
- await authon.client.signInWithEmail(email, password);
115
- } catch (err) {
116
- if (err instanceof AuthonMfaRequiredError) {
117
- mfaToken = err.mfaToken; // Show TOTP input
118
- }
119
- }
120
- }
121
-
122
- async function verifyMfa(code) {
123
- await authon.client.verifyMfa(mfaToken, code);
124
- }
30
+ const { user, isSignedIn, openSignIn, signOut } = getAuthon();
125
31
  </script>
126
32
 
127
- {#if qrSvg}
128
- {@html qrSvg}
129
- <p>Scan with your authenticator app</p>
33
+ {#if $isSignedIn}
34
+ <p>환영합니다, {$user?.displayName}</p>
35
+ <button on:click={signOut}>로그아웃</button>
36
+ {:else}
37
+ <button on:click={openSignIn}>로그인</button>
130
38
  {/if}
131
39
  ```
132
40
 
133
- 전체 API 레퍼런스는 [`@authon/js` MFA 문서](../js/README.md#multi-factor-authentication-mfa)를 참고하세요.
134
-
135
- ## 문서
41
+ ## 환경 변수
136
42
 
137
- [authon.dev/docs](https://authon.dev/docs)
43
+ | 변수 | 필수 | 설명 |
44
+ |------|------|------|
45
+ | `PUBLIC_AUTHON_API_URL` | Yes | Authon 서버 URL |
46
+ | `PUBLIC_AUTHON_PUBLISHABLE_KEY` | Yes | 퍼블리셔블 키 |
138
47
 
139
48
  ## 라이선스
140
49
 
141
- [MIT](../../LICENSE)
50
+ MIT
package/README.md CHANGED
@@ -2,45 +2,59 @@
2
2
 
3
3
  # @authon/svelte
4
4
 
5
- Svelte integration for [Authon](https://authon.dev) reactive stores, context-based setup, and social login buttons.
5
+ > Drop-in Svelte authentication with reactive stores — self-hosted Clerk alternative, Auth0 alternative
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@authon/svelte?color=6d28d9)](https://www.npmjs.com/package/@authon/svelte)
8
+ [![License](https://img.shields.io/badge/license-MIT-blue)](../../LICENSE)
9
+
10
+ ## Prerequisites
11
+
12
+ Before installing the SDK, create an Authon project and get your API keys:
13
+
14
+ 1. **Create a project** at [Authon Dashboard](https://authon.dev/dashboard/overview)
15
+ - Click "Create Project" and enter your app name
16
+ - Select the authentication methods you want (Email/Password, OAuth providers, etc.)
17
+
18
+ 2. **Get your API keys** from Project Settings → API Keys
19
+ - **Publishable Key** (`pk_live_...` or `pk_test_...`) — safe to use in client-side code
20
+ - **Secret Key** (`sk_live_...` or `sk_test_...`) — server-side only, never expose to clients
21
+
22
+ 3. **Configure OAuth providers** (optional) in Project Settings → OAuth
23
+ - Add Google, Apple, GitHub, etc. with their respective Client ID and Secret
24
+ - Set the redirect URL to `https://api.authon.dev/v1/auth/oauth/redirect`
25
+
26
+ > **Test vs Live keys:** Use `pk_test_...` during development. Switch to `pk_live_...` before deploying to production. Test keys use a sandbox environment with no rate limits.
6
27
 
7
28
  ## Install
8
29
 
9
30
  ```bash
10
- npm install @authon/svelte @authon/js
31
+ npm install @authon/svelte
11
32
  ```
12
33
 
13
- Requires `svelte >= 4.0.0`.
14
-
15
- ## Setup
16
-
17
- Initialize Authon in your root layout component and provide it to the component tree via Svelte context:
34
+ ## Quick Start
18
35
 
19
36
  ```svelte
20
37
  <!-- src/routes/+layout.svelte -->
21
38
  <script lang="ts">
22
- import { initAuthon } from '@authon/svelte'
23
- import { onDestroy } from 'svelte'
39
+ import { initAuthon } from '@authon/svelte';
40
+ import { onDestroy } from 'svelte';
24
41
 
25
- const authon = initAuthon('pk_live_...', {
42
+ const authon = initAuthon('pk_live_YOUR_PUBLISHABLE_KEY', {
43
+ apiUrl: 'https://your-authon-server.com',
26
44
  theme: 'auto',
27
- locale: 'en',
28
- })
45
+ });
29
46
 
30
- onDestroy(() => authon.destroy())
47
+ onDestroy(() => authon.destroy());
31
48
  </script>
32
49
 
33
50
  <slot />
34
51
  ```
35
52
 
36
- Then access the store in any child component:
37
-
38
53
  ```svelte
39
54
  <!-- src/routes/+page.svelte -->
40
55
  <script lang="ts">
41
- import { getAuthon } from '@authon/svelte'
42
-
43
- const { user, isSignedIn, isLoading, openSignIn, signOut } = getAuthon()
56
+ import { getAuthon } from '@authon/svelte';
57
+ const { user, isSignedIn, isLoading, openSignIn, signOut } = getAuthon();
44
58
  </script>
45
59
 
46
60
  {#if $isLoading}
@@ -53,412 +67,138 @@ Then access the store in any child component:
53
67
  {/if}
54
68
  ```
55
69
 
56
- ## API Reference
57
-
58
- ### `initAuthon(publishableKey, config?)`
59
-
60
- Creates an `AuthonStore` and registers it in Svelte context. Call this once in your root layout.
61
-
62
- ```ts
63
- import { initAuthon } from '@authon/svelte'
64
-
65
- const authon = initAuthon('pk_live_...', {
66
- theme: 'auto',
67
- locale: 'en',
68
- })
69
- ```
70
-
71
- ### `getAuthon()`
70
+ ## Common Tasks
72
71
 
73
- Retrieves the `AuthonStore` from Svelte context. Must be called within a component that is a descendant of the component where `initAuthon` was called.
74
-
75
- ```ts
76
- import { getAuthon } from '@authon/svelte'
77
-
78
- const authon = getAuthon()
79
- ```
80
-
81
- ### `createAuthonStore(publishableKey, config?)`
82
-
83
- Low-level store factory. Use `initAuthon` / `getAuthon` for most cases; use this directly if you need a store without Svelte context.
84
-
85
- ### `AuthonStore`
86
-
87
- ```ts
88
- interface AuthonStore {
89
- user: Readable<AuthonUser | null>
90
- isSignedIn: Readable<boolean>
91
- isLoading: Readable<boolean>
92
- client: Authon // @authon/js Authon instance
93
- signOut: () => Promise<void>
94
- openSignIn: () => Promise<void>
95
- openSignUp: () => Promise<void>
96
- getToken: () => string | null
97
- destroy: () => void
98
- }
99
- ```
100
-
101
- All store values are Svelte `Readable` stores — subscribe with the `$` prefix in templates.
102
-
103
- ### `renderSocialButtons(options)`
104
-
105
- Renders branded OAuth provider buttons into a DOM element. Returns a cleanup function.
106
-
107
- **Options:**
108
-
109
- | Option | Type | Default | Description |
110
- |---|---|---|---|
111
- | `client` | `Authon` | required | Authon client instance |
112
- | `container` | `HTMLElement` | required | Target DOM element |
113
- | `onSuccess` | `() => void` | — | Called after successful OAuth sign-in |
114
- | `onError` | `(error: Error) => void` | — | Called on OAuth error |
115
- | `compact` | `boolean` | `false` | Icon-only square buttons in a row |
116
- | `gap` | `number` | `10` / `12` | Gap between buttons in px |
117
- | `labels` | `Record<provider, string>` | — | Override button labels per provider |
118
- | `borderRadius` | `number` | `10` | Button border radius in px |
119
- | `height` | `number` | `48` | Button height in px |
120
- | `size` | `number` | `48` | Icon button size in px (compact mode) |
121
-
122
- ## Examples
123
-
124
- ### Basic auth state
72
+ ### Add Google OAuth Login
125
73
 
126
74
  ```svelte
127
75
  <script lang="ts">
128
- import { getAuthon } from '@authon/svelte'
129
-
130
- const { user, isSignedIn, isLoading, openSignIn, signOut } = getAuthon()
76
+ import { getAuthon } from '@authon/svelte';
77
+ const { client } = getAuthon();
131
78
  </script>
132
79
 
133
- {#if $isLoading}
134
- <p>Loading...</p>
135
- {:else if $isSignedIn}
136
- <p>Hello, {$user?.displayName ?? $user?.email}</p>
137
- <button on:click={signOut}>Sign out</button>
138
- {:else}
139
- <button on:click={openSignIn}>Sign in</button>
140
- {/if}
141
- ```
142
-
143
- ### Email + password sign-in
144
-
145
- ```svelte
146
- <script lang="ts">
147
- import { getAuthon } from '@authon/svelte'
148
-
149
- const { client } = getAuthon()
150
-
151
- let email = ''
152
- let password = ''
153
- let loading = false
154
- let error = ''
155
-
156
- async function handleSignIn() {
157
- loading = true
158
- error = ''
159
- try {
160
- await client.signInWithEmail(email, password)
161
- } catch (e: any) {
162
- error = e.message
163
- } finally {
164
- loading = false
165
- }
166
- }
167
- </script>
168
-
169
- <form on:submit|preventDefault={handleSignIn}>
170
- <input bind:value={email} type="email" placeholder="Email" />
171
- <input bind:value={password} type="password" placeholder="Password" />
172
- <button type="submit" disabled={loading}>Sign in</button>
173
- {#if error}<p>{error}</p>{/if}
174
- </form>
80
+ <button on:click={() => client.signInWithOAuth('google')}>Sign in with Google</button>
175
81
  ```
176
82
 
177
- ### OAuth sign-in
83
+ ### Protect a Route
178
84
 
179
85
  ```svelte
86
+ <!-- src/routes/dashboard/+page.svelte -->
180
87
  <script lang="ts">
181
- import { getAuthon, renderSocialButtons } from '@authon/svelte'
182
- import { onMount, onDestroy } from 'svelte'
88
+ import { getAuthon } from '@authon/svelte';
89
+ import { goto } from '$app/navigation';
90
+ import { onMount } from 'svelte';
183
91
 
184
- const { client } = getAuthon()
185
- let container: HTMLElement
186
- let cleanup: (() => void) | undefined
92
+ const { isSignedIn, isLoading } = getAuthon();
187
93
 
188
94
  onMount(() => {
189
- cleanup = renderSocialButtons({
190
- client,
191
- container,
192
- onSuccess: () => window.location.href = '/dashboard',
193
- onError: (e) => console.error(e),
194
- })
195
- })
196
-
197
- onDestroy(() => cleanup?.())
198
-
199
- // Or trigger a single provider directly
200
- async function signInWithGoogle() {
201
- await client.signInWithOAuth('google')
202
- }
203
- </script>
204
-
205
- <div bind:this={container} />
206
- <button on:click={signInWithGoogle}>Sign in with Google</button>
207
- ```
208
-
209
- ### MFA setup
210
-
211
- ```svelte
212
- <script lang="ts">
213
- import { getAuthon } from '@authon/svelte'
214
-
215
- const { client } = getAuthon()
216
-
217
- let qrCodeSvg = ''
218
- let secret = ''
219
- let backupCodes: string[] = []
220
- let verifyCode = ''
221
-
222
- async function initMfaSetup() {
223
- const res = await client.setupMfa()
224
- qrCodeSvg = res.qrCodeSvg // SVG string for display
225
- secret = res.secret
226
- backupCodes = res.backupCodes
227
- }
228
-
229
- async function confirmSetup() {
230
- await client.verifyMfaSetup(verifyCode)
231
- alert('MFA enabled')
232
- }
95
+ if (!$isLoading && !$isSignedIn) goto('/sign-in');
96
+ });
233
97
  </script>
234
98
 
235
- <button on:click={initMfaSetup}>Enable MFA</button>
236
-
237
- {#if qrCodeSvg}
238
- {@html qrCodeSvg}
239
- <p>Scan with your authenticator app</p>
240
- <p>Secret: {secret}</p>
241
- <ul>{#each backupCodes as code}<li>{code}</li>{/each}</ul>
242
- <input bind:value={verifyCode} placeholder="6-digit code" />
243
- <button on:click={confirmSetup}>Verify</button>
99
+ {#if $isSignedIn}
100
+ <h1>Dashboard</h1>
244
101
  {/if}
245
102
  ```
246
103
 
247
- ### MFA verification on sign-in
248
-
249
- ```svelte
250
- <script lang="ts">
251
- import { getAuthon } from '@authon/svelte'
252
- import { AuthonMfaRequiredError } from '@authon/js'
253
-
254
- const { client } = getAuthon()
255
-
256
- let mfaToken = ''
257
- let totpCode = ''
258
-
259
- async function signIn(email: string, password: string) {
260
- try {
261
- await client.signInWithEmail(email, password)
262
- } catch (e) {
263
- if (e instanceof AuthonMfaRequiredError) {
264
- mfaToken = e.mfaToken // show TOTP input
265
- }
266
- }
267
- }
268
-
269
- async function verifyMfa() {
270
- await client.verifyMfa(mfaToken, totpCode)
271
- }
272
- </script>
273
- ```
274
-
275
- ### Passwordless — magic link
104
+ ### Get Current User
276
105
 
277
106
  ```svelte
278
107
  <script lang="ts">
279
- import { getAuthon } from '@authon/svelte'
280
-
281
- const { client } = getAuthon()
282
- let email = ''
283
- let sent = false
284
-
285
- async function sendMagicLink() {
286
- await client.sendMagicLink(email)
287
- sent = true
288
- }
108
+ import { getAuthon } from '@authon/svelte';
109
+ const { user, isLoading } = getAuthon();
289
110
  </script>
290
111
 
291
- {#if sent}
292
- <p>Check your inbox for a sign-in link.</p>
112
+ {#if $isLoading}
113
+ <p>Loading...</p>
114
+ {:else if $user}
115
+ <p>Email: {$user.email}</p>
116
+ <p>Name: {$user.displayName}</p>
293
117
  {:else}
294
- <input bind:value={email} type="email" placeholder="Email" />
295
- <button on:click={sendMagicLink}>Send magic link</button>
118
+ <p>Not signed in</p>
296
119
  {/if}
297
120
  ```
298
121
 
299
- ### Passwordless email OTP
122
+ ### Add Email/Password Auth
300
123
 
301
124
  ```svelte
302
125
  <script lang="ts">
303
- import { getAuthon } from '@authon/svelte'
126
+ import { getAuthon } from '@authon/svelte';
127
+ const { client } = getAuthon();
128
+ let email = '';
129
+ let password = '';
304
130
 
305
- const { client } = getAuthon()
306
- let email = ''
307
- let otp = ''
308
- let step: 'email' | 'verify' = 'email'
309
-
310
- async function sendOtp() {
311
- await client.sendEmailOtp(email)
312
- step = 'verify'
313
- }
314
-
315
- async function verifyOtp() {
316
- const user = await client.verifyPasswordless({ email, code: otp })
317
- console.log('Signed in as:', user.email)
131
+ async function handleSignIn() {
132
+ await client.signInWithEmail(email, password);
318
133
  }
319
134
  </script>
320
135
 
321
- {#if step === 'email'}
136
+ <form on:submit|preventDefault={handleSignIn}>
322
137
  <input bind:value={email} type="email" placeholder="Email" />
323
- <button on:click={sendOtp}>Send code</button>
324
- {:else}
325
- <input bind:value={otp} placeholder="6-digit code" />
326
- <button on:click={verifyOtp}>Verify</button>
327
- {/if}
328
- ```
329
-
330
- ### Passkeys
331
-
332
- ```svelte
333
- <script lang="ts">
334
- import { getAuthon } from '@authon/svelte'
335
-
336
- const { client } = getAuthon()
337
-
338
- // Register (user must be signed in)
339
- async function registerPasskey() {
340
- const credential = await client.registerPasskey('My Device')
341
- console.log('Registered:', credential.id)
342
- }
343
-
344
- // Authenticate
345
- async function loginWithPasskey() {
346
- const user = await client.authenticateWithPasskey()
347
- console.log('Signed in as:', user.email)
348
- }
349
-
350
- // List registered passkeys
351
- async function listPasskeys() {
352
- const keys = await client.listPasskeys()
353
- console.log(keys)
354
- }
355
- </script>
356
- ```
357
-
358
- ### Web3 wallet authentication
359
-
360
- ```svelte
361
- <script lang="ts">
362
- import { getAuthon } from '@authon/svelte'
363
-
364
- const { client } = getAuthon()
365
-
366
- async function signInWithWallet() {
367
- const address = '0xYourWalletAddress'
368
-
369
- // 1. Get a nonce + signable message from Authon
370
- const { nonce, message } = await client.web3GetNonce(address, 'evm', 'metamask')
371
-
372
- // 2. Sign the message with the wallet
373
- const signature = await window.ethereum.request({
374
- method: 'personal_sign',
375
- params: [message, address],
376
- })
377
-
378
- // 3. Verify the signature and sign in
379
- const user = await client.web3Verify(message, signature, address, 'evm', 'metamask')
380
- console.log('Signed in as:', user.email)
381
- }
382
-
383
- async function listLinkedWallets() {
384
- const wallets = await client.listWallets()
385
- console.log(wallets)
386
- }
387
- </script>
138
+ <input bind:value={password} type="password" placeholder="Password" />
139
+ <button type="submit">Sign in</button>
140
+ </form>
388
141
  ```
389
142
 
390
- ### Profile update
143
+ ### Handle Sign Out
391
144
 
392
145
  ```svelte
393
146
  <script lang="ts">
394
- import { getAuthon } from '@authon/svelte'
395
-
396
- const { client } = getAuthon()
397
-
398
- async function saveProfile() {
399
- const updated = await client.updateProfile({
400
- displayName: 'Jane Doe',
401
- avatarUrl: 'https://example.com/avatar.png',
402
- phone: '+1234567890',
403
- publicMetadata: { role: 'admin' },
404
- })
405
- console.log('Updated user:', updated)
406
- }
147
+ import { getAuthon } from '@authon/svelte';
148
+ const { signOut } = getAuthon();
407
149
  </script>
408
- ```
409
-
410
- ### Session management
411
-
412
- ```svelte
413
- <script lang="ts">
414
- import { getAuthon } from '@authon/svelte'
415
- import { onMount } from 'svelte'
416
- import type { SessionInfo } from '@authon/shared'
417
-
418
- const { client } = getAuthon()
419
- let sessions: SessionInfo[] = []
420
150
 
421
- onMount(async () => {
422
- sessions = await client.listSessions()
423
- })
424
-
425
- async function revokeSession(sessionId: string) {
426
- await client.revokeSession(sessionId)
427
- sessions = sessions.filter(s => s.id !== sessionId)
428
- }
429
- </script>
430
-
431
- <ul>
432
- {#each sessions as session (session.id)}
433
- <li>
434
- {session.userAgent} — {session.createdAt}
435
- <button on:click={() => revokeSession(session.id)}>Revoke</button>
436
- </li>
437
- {/each}
438
- </ul>
151
+ <button on:click={signOut}>Sign Out</button>
439
152
  ```
440
153
 
441
- ## Store options
154
+ ## Environment Variables
442
155
 
443
- | Option | Type | Default | Description |
444
- |---|---|---|---|
445
- | `publishableKey` | `string` | required | Your Authon publishable key |
446
- | `config.theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | UI theme |
447
- | `config.locale` | `string` | `'en'` | Language code |
448
- | `config.apiUrl` | `string` | `'https://api.authon.dev'` | Custom API base URL |
449
- | `config.appearance` | `Partial<BrandingConfig>` | — | Override branding colors and logo |
156
+ | Variable | Required | Description |
157
+ |----------|----------|-------------|
158
+ | `PUBLIC_AUTHON_API_URL` | Yes | Your Authon server URL |
159
+ | `PUBLIC_AUTHON_PUBLISHABLE_KEY` | Yes | Project publishable key |
450
160
 
451
- ## TypeScript
452
-
453
- ```ts
454
- import type { AuthonStore, SocialButtonsOptions } from '@authon/svelte'
455
- import type { AuthonUser, SessionInfo, PasskeyCredential, Web3Wallet } from '@authon/shared'
456
- ```
457
-
458
- ## Documentation
161
+ ## API Reference
459
162
 
460
- [authon.dev/docs](https://authon.dev/docs)
163
+ ### Setup
164
+
165
+ | Function | Description |
166
+ |----------|-------------|
167
+ | `initAuthon(key, config?)` | Create store and set in Svelte context (call in root layout) |
168
+ | `getAuthon()` | Get `AuthonStore` from context (call in child components) |
169
+ | `createAuthonStore(key, config?)` | Low-level store factory (without context) |
170
+
171
+ ### AuthonStore
172
+
173
+ | Property / Method | Type |
174
+ |-------------------|------|
175
+ | `user` | `Readable<AuthonUser \| null>` |
176
+ | `isSignedIn` | `Readable<boolean>` |
177
+ | `isLoading` | `Readable<boolean>` |
178
+ | `client` | `Authon` |
179
+ | `signOut()` | `Promise<void>` |
180
+ | `openSignIn()` | `Promise<void>` |
181
+ | `openSignUp()` | `Promise<void>` |
182
+ | `getToken()` | `string \| null` |
183
+ | `web3GetNonce(...)` | Web3 nonce request |
184
+ | `web3Verify(...)` | Web3 sign-in |
185
+ | `passwordlessSendCode(...)` | Send magic link or OTP |
186
+ | `passwordlessVerifyCode(...)` | Verify OTP |
187
+ | `passkeyRegister(...)` | Register passkey |
188
+ | `passkeyAuthenticate(...)` | Auth with passkey |
189
+ | `destroy()` | Cleanup |
190
+
191
+ ## Comparison
192
+
193
+ | Feature | Authon | Clerk | Auth.js |
194
+ |---------|--------|-------|---------|
195
+ | Self-hosted | Yes | No | Partial |
196
+ | Pricing | Free | $25/mo+ | Free |
197
+ | OAuth providers | 10+ | 20+ | 80+ |
198
+ | ShadowDOM modal | Yes | No | No |
199
+ | MFA/Passkeys | Yes | Yes | Plugin |
200
+ | Web3 auth | Yes | No | No |
461
201
 
462
202
  ## License
463
203
 
464
- [MIT](../../LICENSE)
204
+ MIT
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/store.ts","../src/context.ts","../src/SocialButtons.ts"],"sourcesContent":["export { createAuthonStore } from './store';\nexport type { AuthonStore } from './store';\nexport { initAuthon, getAuthon } from './context';\nexport { renderSocialButtons } from './SocialButtons';\nexport type { SocialButtonsOptions } from './SocialButtons';\nexport type {\n PasskeyCredential,\n Web3Chain,\n Web3NonceResponse,\n Web3Wallet,\n Web3WalletType,\n} from '@authon/shared';\n","import { writable, derived, type Readable } from 'svelte/store';\nimport { Authon } from '@authon/js';\nimport type { AuthonConfig } from '@authon/js';\nimport type {\n AuthonUser,\n PasskeyCredential,\n Web3Chain,\n Web3NonceResponse,\n Web3Wallet,\n Web3WalletType,\n} from '@authon/shared';\n\nexport interface AuthonStore {\n user: Readable<AuthonUser | null>;\n isSignedIn: Readable<boolean>;\n isLoading: Readable<boolean>;\n signOut: () => Promise<void>;\n openSignIn: () => Promise<void>;\n openSignUp: () => Promise<void>;\n getToken: () => string | null;\n destroy: () => void;\n client: Authon;\n // Web3\n web3GetNonce: (address: string, chain: Web3Chain, walletType: Web3WalletType, chainId?: number) => Promise<Web3NonceResponse>;\n web3Verify: (message: string, signature: string, address: string, chain: Web3Chain, walletType: Web3WalletType) => Promise<AuthonUser>;\n web3LinkWallet: (params: { address: string; chain: Web3Chain; walletType: Web3WalletType; chainId?: number; message: string; signature: string }) => Promise<Web3Wallet>;\n web3UnlinkWallet: (walletId: string) => Promise<void>;\n web3GetWallets: () => Promise<Web3Wallet[]>;\n // Passwordless\n passwordlessSendCode: (email: string, type?: 'magic-link' | 'otp') => Promise<void>;\n passwordlessVerifyCode: (email: string, code: string) => Promise<AuthonUser>;\n // Passkeys\n passkeyRegister: (name?: string) => Promise<PasskeyCredential>;\n passkeyAuthenticate: (email?: string) => Promise<AuthonUser>;\n passkeyList: () => Promise<PasskeyCredential[]>;\n passkeyDelete: (credentialId: string) => Promise<void>;\n}\n\n/**\n * Creates an Authon store with reactive Svelte stores.\n *\n * Usage:\n * ```ts\n * import { createAuthonStore } from '@authon/svelte'\n *\n * const authon = createAuthonStore('pk_live_...')\n *\n * // In your component:\n * $: user = $authon.user\n * $: isSignedIn = $authon.isSignedIn\n * ```\n */\nexport function createAuthonStore(\n publishableKey: string,\n config?: Omit<AuthonConfig, 'mode'>,\n): AuthonStore {\n const client = new Authon(publishableKey, config);\n const userStore = writable<AuthonUser | null>(null);\n const isLoadingStore = writable(true);\n\n const isSignedIn = derived(userStore, ($user) => $user !== null);\n\n client.on('signedIn', (user) => {\n userStore.set(user as AuthonUser);\n isLoadingStore.set(false);\n });\n\n client.on('signedOut', () => {\n userStore.set(null);\n });\n\n client.on('error', () => {\n isLoadingStore.set(false);\n });\n\n const existingUser = client.getUser();\n if (existingUser) {\n userStore.set(existingUser);\n }\n isLoadingStore.set(false);\n\n return {\n user: { subscribe: userStore.subscribe },\n isSignedIn,\n isLoading: { subscribe: isLoadingStore.subscribe },\n signOut: async () => {\n await client.signOut();\n userStore.set(null);\n },\n openSignIn: () => client.openSignIn(),\n openSignUp: () => client.openSignUp(),\n getToken: () => client.getToken(),\n destroy: () => client.destroy(),\n client,\n // Web3\n web3GetNonce: (address, chain, walletType, chainId?) =>\n client.web3GetNonce(address, chain, walletType, chainId),\n web3Verify: (message, signature, address, chain, walletType) =>\n client.web3Verify(message, signature, address, chain, walletType),\n web3LinkWallet: (params) => client.linkWallet(params),\n web3UnlinkWallet: (walletId) => client.unlinkWallet(walletId),\n web3GetWallets: () => client.listWallets(),\n // Passwordless\n passwordlessSendCode: (email, type = 'otp') =>\n type === 'magic-link' ? client.sendMagicLink(email) : client.sendEmailOtp(email),\n passwordlessVerifyCode: (email, code) => client.verifyPasswordless({ email, code }),\n // Passkeys\n passkeyRegister: (name?) => client.registerPasskey(name),\n passkeyAuthenticate: (email?) => client.authenticateWithPasskey(email),\n passkeyList: () => client.listPasskeys(),\n passkeyDelete: (credentialId) => client.revokePasskey(credentialId),\n };\n}\n","import { setContext, getContext } from 'svelte';\nimport type { AuthonConfig } from '@authon/js';\nimport { createAuthonStore, type AuthonStore } from './store';\n\nconst AUTHON_CONTEXT_KEY = Symbol('authon');\n\n/**\n * Initialize Authon in a Svelte component tree.\n * Call this in your root layout or top-level component.\n *\n * Usage in +layout.svelte:\n * ```svelte\n * <script>\n * import { initAuthon } from '@authon/svelte'\n * const authon = initAuthon('pk_live_...')\n * </script>\n * ```\n */\nexport function initAuthon(\n publishableKey: string,\n config?: Omit<AuthonConfig, 'mode'>,\n): AuthonStore {\n const store = createAuthonStore(publishableKey, config);\n setContext(AUTHON_CONTEXT_KEY, store);\n return store;\n}\n\n/**\n * Get the Authon store from Svelte context.\n * Must be called within a component tree where `initAuthon` was called.\n *\n * Usage:\n * ```svelte\n * <script>\n * import { getAuthon } from '@authon/svelte'\n * const { user, isSignedIn, signOut } = getAuthon()\n * </script>\n * ```\n */\nexport function getAuthon(): AuthonStore {\n const store = getContext<AuthonStore | undefined>(AUTHON_CONTEXT_KEY);\n if (!store) {\n throw new Error('getAuthon() must be called within a component tree where initAuthon() was called.');\n }\n return store;\n}\n","import { PROVIDER_COLORS, PROVIDER_DISPLAY_NAMES, type OAuthProviderType } from '@authon/shared';\nimport { getProviderButtonConfig, type Authon } from '@authon/js';\n\nexport interface SocialButtonsOptions {\n /** Authon client instance */\n client: Authon;\n /** Target container element */\n container: HTMLElement;\n /** Called after successful OAuth sign-in */\n onSuccess?: () => void;\n /** Called on OAuth error */\n onError?: (error: Error) => void;\n /** Compact mode — icon-only square buttons in a row (default: false) */\n compact?: boolean;\n /** Gap between buttons in px (default: 10, compact default: 12) */\n gap?: number;\n /** Custom labels per provider */\n labels?: Partial<Record<OAuthProviderType, string>>;\n /** Icon size (default: 20, compact default: 24) */\n iconSize?: number;\n /** Border radius in px (default: 10) */\n borderRadius?: number;\n /** Button height in px (default: 48) */\n height?: number;\n /** Button size for compact mode in px (default: 48) */\n size?: number;\n}\n\n/**\n * Render social login buttons into a container element.\n *\n * Usage in +page.svelte:\n * ```svelte\n * <script>\n * import { getAuthon } from '@authon/svelte'\n * import { renderSocialButtons } from '@authon/svelte'\n * import { onMount } from 'svelte'\n *\n * const { client } = getAuthon()\n * let container: HTMLElement\n *\n * onMount(() => {\n * const cleanup = renderSocialButtons({\n * client,\n * container,\n * compact: true,\n * onError: (err) => console.error(err),\n * })\n * return cleanup\n * })\n * </script>\n *\n * <div bind:this={container}></div>\n * ```\n */\nexport function renderSocialButtons(options: SocialButtonsOptions): () => void {\n const {\n client,\n container,\n onSuccess,\n onError,\n compact = false,\n gap,\n labels,\n iconSize,\n borderRadius = 10,\n height = 48,\n size = 48,\n } = options;\n\n const resolvedGap = gap ?? (compact ? 12 : 10);\n const resolvedIconSize = iconSize ?? (compact ? 24 : 20);\n let loadingProvider: string | null = null;\n let buttons: HTMLButtonElement[] = [];\n\n const handleClick = async (provider: OAuthProviderType, btn: HTMLButtonElement) => {\n if (loadingProvider) return;\n loadingProvider = provider;\n btn.innerHTML = '<span style=\"display:inline-block;width:16px;height:16px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:authon-spin 0.6s linear infinite\"></span>';\n buttons.forEach((b) => (b.disabled = true));\n\n try {\n await client.signInWithOAuth(provider);\n onSuccess?.();\n } catch (e: any) {\n const error = e instanceof Error ? e : new Error(String(e));\n onError?.(error);\n } finally {\n loadingProvider = null;\n renderButtons(providers);\n }\n };\n\n let providers: OAuthProviderType[] = [];\n\n function renderButtons(providerList: OAuthProviderType[]) {\n container.innerHTML = '';\n buttons = [];\n\n // Inject keyframe if not present\n if (!document.getElementById('authon-spin-style')) {\n const style = document.createElement('style');\n style.id = 'authon-spin-style';\n style.textContent = '@keyframes authon-spin{to{transform:rotate(360deg)}}';\n document.head.appendChild(style);\n }\n\n const wrapper = document.createElement('div');\n wrapper.style.display = 'flex';\n wrapper.style.gap = `${resolvedGap}px`;\n\n if (compact) {\n wrapper.style.flexDirection = 'row';\n wrapper.style.flexWrap = 'wrap';\n wrapper.style.justifyContent = 'center';\n } else {\n wrapper.style.flexDirection = 'column';\n }\n\n for (const provider of providerList) {\n const colors = PROVIDER_COLORS[provider] || { bg: '#333', text: '#fff' };\n const displayName = PROVIDER_DISPLAY_NAMES[provider] || provider;\n const config = getProviderButtonConfig(provider);\n const iconSvg = config.iconSvg\n .replace(/width=\"\\d+\"/, `width=\"${resolvedIconSize}\"`)\n .replace(/height=\"\\d+\"/, `height=\"${resolvedIconSize}\"`);\n const needsBorder = colors.bg.toLowerCase() === '#ffffff';\n\n const btn = document.createElement('button');\n btn.setAttribute('aria-label', `Sign in with ${displayName}`);\n btn.style.display = 'flex';\n btn.style.alignItems = 'center';\n btn.style.justifyContent = 'center';\n btn.style.border = needsBorder ? '1px solid #dadce0' : 'none';\n btn.style.cursor = 'pointer';\n btn.style.backgroundColor = colors.bg;\n btn.style.color = colors.text;\n btn.style.borderRadius = `${borderRadius}px`;\n btn.style.transition = 'opacity 0.15s';\n btn.style.fontFamily = 'inherit';\n\n if (compact) {\n btn.style.width = `${size}px`;\n btn.style.height = `${size}px`;\n btn.style.padding = '0';\n btn.innerHTML = `<span style=\"display:flex;align-items:center\">${iconSvg}</span>`;\n } else {\n btn.style.width = '100%';\n btn.style.height = `${height}px`;\n btn.style.gap = '10px';\n btn.style.paddingLeft = '16px';\n btn.style.paddingRight = '16px';\n const buttonLabel = labels?.[provider] ?? `Continue with ${displayName}`;\n btn.innerHTML = `<span style=\"display:flex;align-items:center;flex-shrink:0\">${iconSvg}</span><span style=\"font-size:15px;font-weight:600;white-space:nowrap\">${buttonLabel}</span>`;\n }\n\n btn.addEventListener('click', () => handleClick(provider, btn));\n btn.addEventListener('mouseenter', () => (btn.style.opacity = '0.85'));\n btn.addEventListener('mouseleave', () => (btn.style.opacity = '1'));\n\n buttons.push(btn);\n wrapper.appendChild(btn);\n }\n\n container.appendChild(wrapper);\n }\n\n // Init\n client.getProviders().then((p: OAuthProviderType[]) => {\n providers = p;\n if (providers.length > 0) {\n renderButtons(providers);\n }\n });\n\n // Cleanup\n return () => {\n container.innerHTML = '';\n buttons = [];\n };\n}\n"],"mappings":"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","names":["import_js"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/store.ts","../src/context.ts","../src/SocialButtons.ts"],"sourcesContent":["export { createAuthonStore } from './store';\nexport type { AuthonStore } from './store';\nexport { initAuthon, getAuthon } from './context';\nexport { renderSocialButtons } from './SocialButtons';\nexport type { SocialButtonsOptions } from './SocialButtons';\nexport type {\n PasskeyCredential,\n Web3Chain,\n Web3NonceResponse,\n Web3Wallet,\n Web3WalletType,\n} from '@authon/shared';\n\n// Svelte components are exported as raw .svelte source for the consumer's compiler.\n// Import them directly:\n// import SignIn from '@authon/svelte/SignIn.svelte'\n// import SignUp from '@authon/svelte/SignUp.svelte'\n","import { writable, derived, type Readable } from 'svelte/store';\nimport { Authon } from '@authon/js';\nimport type { AuthonConfig } from '@authon/js';\nimport type {\n AuthonUser,\n PasskeyCredential,\n Web3Chain,\n Web3NonceResponse,\n Web3Wallet,\n Web3WalletType,\n} from '@authon/shared';\n\nexport interface AuthonStore {\n user: Readable<AuthonUser | null>;\n isSignedIn: Readable<boolean>;\n isLoading: Readable<boolean>;\n signOut: () => Promise<void>;\n openSignIn: () => Promise<void>;\n openSignUp: () => Promise<void>;\n getToken: () => string | null;\n destroy: () => void;\n client: Authon;\n // Web3\n web3GetNonce: (address: string, chain: Web3Chain, walletType: Web3WalletType, chainId?: number) => Promise<Web3NonceResponse>;\n web3Verify: (message: string, signature: string, address: string, chain: Web3Chain, walletType: Web3WalletType) => Promise<AuthonUser>;\n web3LinkWallet: (params: { address: string; chain: Web3Chain; walletType: Web3WalletType; chainId?: number; message: string; signature: string }) => Promise<Web3Wallet>;\n web3UnlinkWallet: (walletId: string) => Promise<void>;\n web3GetWallets: () => Promise<Web3Wallet[]>;\n // Passwordless\n passwordlessSendCode: (email: string, type?: 'magic-link' | 'otp') => Promise<void>;\n passwordlessVerifyCode: (email: string, code: string) => Promise<AuthonUser>;\n // Passkeys\n passkeyRegister: (name?: string) => Promise<PasskeyCredential>;\n passkeyAuthenticate: (email?: string) => Promise<AuthonUser>;\n passkeyList: () => Promise<PasskeyCredential[]>;\n passkeyDelete: (credentialId: string) => Promise<void>;\n}\n\n/**\n * Creates an Authon store with reactive Svelte stores.\n *\n * Usage:\n * ```ts\n * import { createAuthonStore } from '@authon/svelte'\n *\n * const authon = createAuthonStore('pk_live_...')\n *\n * // In your component:\n * $: user = $authon.user\n * $: isSignedIn = $authon.isSignedIn\n * ```\n */\nexport function createAuthonStore(\n publishableKey: string,\n config?: Omit<AuthonConfig, 'mode'>,\n): AuthonStore {\n const client = new Authon(publishableKey, config);\n const userStore = writable<AuthonUser | null>(null);\n const isLoadingStore = writable(true);\n\n const isSignedIn = derived(userStore, ($user) => $user !== null);\n\n client.on('signedIn', (user) => {\n userStore.set(user as AuthonUser);\n isLoadingStore.set(false);\n });\n\n client.on('signedOut', () => {\n userStore.set(null);\n });\n\n client.on('error', () => {\n isLoadingStore.set(false);\n });\n\n const existingUser = client.getUser();\n if (existingUser) {\n userStore.set(existingUser);\n }\n isLoadingStore.set(false);\n\n return {\n user: { subscribe: userStore.subscribe },\n isSignedIn,\n isLoading: { subscribe: isLoadingStore.subscribe },\n signOut: async () => {\n await client.signOut();\n userStore.set(null);\n },\n openSignIn: () => client.openSignIn(),\n openSignUp: () => client.openSignUp(),\n getToken: () => client.getToken(),\n destroy: () => client.destroy(),\n client,\n // Web3\n web3GetNonce: (address, chain, walletType, chainId?) =>\n client.web3GetNonce(address, chain, walletType, chainId),\n web3Verify: (message, signature, address, chain, walletType) =>\n client.web3Verify(message, signature, address, chain, walletType),\n web3LinkWallet: (params) => client.linkWallet(params),\n web3UnlinkWallet: (walletId) => client.unlinkWallet(walletId),\n web3GetWallets: () => client.listWallets(),\n // Passwordless\n passwordlessSendCode: (email, type = 'otp') =>\n type === 'magic-link' ? client.sendMagicLink(email) : client.sendEmailOtp(email),\n passwordlessVerifyCode: (email, code) => client.verifyPasswordless({ email, code }),\n // Passkeys\n passkeyRegister: (name?) => client.registerPasskey(name),\n passkeyAuthenticate: (email?) => client.authenticateWithPasskey(email),\n passkeyList: () => client.listPasskeys(),\n passkeyDelete: (credentialId) => client.revokePasskey(credentialId),\n };\n}\n","import { setContext, getContext } from 'svelte';\nimport type { AuthonConfig } from '@authon/js';\nimport { createAuthonStore, type AuthonStore } from './store';\n\nconst AUTHON_CONTEXT_KEY = Symbol('authon');\n\n/**\n * Initialize Authon in a Svelte component tree.\n * Call this in your root layout or top-level component.\n *\n * Usage in +layout.svelte:\n * ```svelte\n * <script>\n * import { initAuthon } from '@authon/svelte'\n * const authon = initAuthon('pk_live_...')\n * </script>\n * ```\n */\nexport function initAuthon(\n publishableKey: string,\n config?: Omit<AuthonConfig, 'mode'>,\n): AuthonStore {\n const store = createAuthonStore(publishableKey, config);\n setContext(AUTHON_CONTEXT_KEY, store);\n return store;\n}\n\n/**\n * Get the Authon store from Svelte context.\n * Must be called within a component tree where `initAuthon` was called.\n *\n * Usage:\n * ```svelte\n * <script>\n * import { getAuthon } from '@authon/svelte'\n * const { user, isSignedIn, signOut } = getAuthon()\n * </script>\n * ```\n */\nexport function getAuthon(): AuthonStore {\n const store = getContext<AuthonStore | undefined>(AUTHON_CONTEXT_KEY);\n if (!store) {\n throw new Error('getAuthon() must be called within a component tree where initAuthon() was called.');\n }\n return store;\n}\n","import { PROVIDER_COLORS, PROVIDER_DISPLAY_NAMES, type OAuthProviderType } from '@authon/shared';\nimport { getProviderButtonConfig, type Authon } from '@authon/js';\n\nexport interface SocialButtonsOptions {\n /** Authon client instance */\n client: Authon;\n /** Target container element */\n container: HTMLElement;\n /** Called after successful OAuth sign-in */\n onSuccess?: () => void;\n /** Called on OAuth error */\n onError?: (error: Error) => void;\n /** Compact mode — icon-only square buttons in a row (default: false) */\n compact?: boolean;\n /** Gap between buttons in px (default: 10, compact default: 12) */\n gap?: number;\n /** Custom labels per provider */\n labels?: Partial<Record<OAuthProviderType, string>>;\n /** Icon size (default: 20, compact default: 24) */\n iconSize?: number;\n /** Border radius in px (default: 10) */\n borderRadius?: number;\n /** Button height in px (default: 48) */\n height?: number;\n /** Button size for compact mode in px (default: 48) */\n size?: number;\n}\n\n/**\n * Render social login buttons into a container element.\n *\n * Usage in +page.svelte:\n * ```svelte\n * <script>\n * import { getAuthon } from '@authon/svelte'\n * import { renderSocialButtons } from '@authon/svelte'\n * import { onMount } from 'svelte'\n *\n * const { client } = getAuthon()\n * let container: HTMLElement\n *\n * onMount(() => {\n * const cleanup = renderSocialButtons({\n * client,\n * container,\n * compact: true,\n * onError: (err) => console.error(err),\n * })\n * return cleanup\n * })\n * </script>\n *\n * <div bind:this={container}></div>\n * ```\n */\nexport function renderSocialButtons(options: SocialButtonsOptions): () => void {\n const {\n client,\n container,\n onSuccess,\n onError,\n compact = false,\n gap,\n labels,\n iconSize,\n borderRadius = 10,\n height = 48,\n size = 48,\n } = options;\n\n const resolvedGap = gap ?? (compact ? 12 : 10);\n const resolvedIconSize = iconSize ?? (compact ? 24 : 20);\n let loadingProvider: string | null = null;\n let buttons: HTMLButtonElement[] = [];\n\n const handleClick = async (provider: OAuthProviderType, btn: HTMLButtonElement) => {\n if (loadingProvider) return;\n loadingProvider = provider;\n btn.innerHTML = '<span style=\"display:inline-block;width:16px;height:16px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:authon-spin 0.6s linear infinite\"></span>';\n buttons.forEach((b) => (b.disabled = true));\n\n try {\n await client.signInWithOAuth(provider);\n onSuccess?.();\n } catch (e: any) {\n const error = e instanceof Error ? e : new Error(String(e));\n onError?.(error);\n } finally {\n loadingProvider = null;\n renderButtons(providers);\n }\n };\n\n let providers: OAuthProviderType[] = [];\n\n function renderButtons(providerList: OAuthProviderType[]) {\n container.innerHTML = '';\n buttons = [];\n\n // Inject keyframe if not present\n if (!document.getElementById('authon-spin-style')) {\n const style = document.createElement('style');\n style.id = 'authon-spin-style';\n style.textContent = '@keyframes authon-spin{to{transform:rotate(360deg)}}';\n document.head.appendChild(style);\n }\n\n const wrapper = document.createElement('div');\n wrapper.style.display = 'flex';\n wrapper.style.gap = `${resolvedGap}px`;\n\n if (compact) {\n wrapper.style.flexDirection = 'row';\n wrapper.style.flexWrap = 'wrap';\n wrapper.style.justifyContent = 'center';\n } else {\n wrapper.style.flexDirection = 'column';\n }\n\n for (const provider of providerList) {\n const colors = PROVIDER_COLORS[provider] || { bg: '#333', text: '#fff' };\n const displayName = PROVIDER_DISPLAY_NAMES[provider] || provider;\n const config = getProviderButtonConfig(provider);\n const iconSvg = config.iconSvg\n .replace(/width=\"\\d+\"/, `width=\"${resolvedIconSize}\"`)\n .replace(/height=\"\\d+\"/, `height=\"${resolvedIconSize}\"`);\n const needsBorder = colors.bg.toLowerCase() === '#ffffff';\n\n const btn = document.createElement('button');\n btn.setAttribute('aria-label', `Sign in with ${displayName}`);\n btn.style.display = 'flex';\n btn.style.alignItems = 'center';\n btn.style.justifyContent = 'center';\n btn.style.border = needsBorder ? '1px solid #dadce0' : 'none';\n btn.style.cursor = 'pointer';\n btn.style.backgroundColor = colors.bg;\n btn.style.color = colors.text;\n btn.style.borderRadius = `${borderRadius}px`;\n btn.style.transition = 'opacity 0.15s';\n btn.style.fontFamily = 'inherit';\n\n if (compact) {\n btn.style.width = `${size}px`;\n btn.style.height = `${size}px`;\n btn.style.padding = '0';\n btn.innerHTML = `<span style=\"display:flex;align-items:center\">${iconSvg}</span>`;\n } else {\n btn.style.width = '100%';\n btn.style.height = `${height}px`;\n btn.style.gap = '10px';\n btn.style.paddingLeft = '16px';\n btn.style.paddingRight = '16px';\n const buttonLabel = labels?.[provider] ?? `Continue with ${displayName}`;\n btn.innerHTML = `<span style=\"display:flex;align-items:center;flex-shrink:0\">${iconSvg}</span><span style=\"font-size:15px;font-weight:600;white-space:nowrap\">${buttonLabel}</span>`;\n }\n\n btn.addEventListener('click', () => handleClick(provider, btn));\n btn.addEventListener('mouseenter', () => (btn.style.opacity = '0.85'));\n btn.addEventListener('mouseleave', () => (btn.style.opacity = '1'));\n\n buttons.push(btn);\n wrapper.appendChild(btn);\n }\n\n container.appendChild(wrapper);\n }\n\n // Init\n client.getProviders().then((p: OAuthProviderType[]) => {\n providers = p;\n if (providers.length > 0) {\n renderButtons(providers);\n }\n });\n\n // Cleanup\n return () => {\n container.innerHTML = '';\n buttons = [];\n };\n}\n"],"mappings":"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","names":["import_js"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authon/svelte",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Authon Svelte SDK — stores and components",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -11,10 +11,13 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.js",
13
13
  "require": "./dist/index.cjs"
14
- }
14
+ },
15
+ "./SignIn.svelte": "./src/components/SignIn.svelte",
16
+ "./SignUp.svelte": "./src/components/SignUp.svelte"
15
17
  },
16
18
  "files": [
17
- "dist"
19
+ "dist",
20
+ "src/components"
18
21
  ],
19
22
  "scripts": {
20
23
  "build": "tsup",
@@ -0,0 +1,39 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy, createEventDispatcher } from 'svelte';
3
+ import { Authon } from '@authon/js';
4
+ import type { AuthonUser } from '@authon/shared';
5
+
6
+ export let publishableKey: string;
7
+ export let apiUrl: string = 'https://api.authon.dev';
8
+ export let theme: 'light' | 'dark' | 'auto' = 'auto';
9
+ export let locale: string | undefined = undefined;
10
+
11
+ const dispatch = createEventDispatcher<{ signIn: { user: AuthonUser } }>();
12
+
13
+ let container: HTMLDivElement;
14
+ let authonInstance: Authon | null = null;
15
+ let unsubscribe: (() => void) | null = null;
16
+ const containerId = `authon-signin-${Math.random().toString(36).slice(2, 8)}`;
17
+
18
+ onMount(() => {
19
+ container.id = containerId;
20
+ authonInstance = new Authon(publishableKey, {
21
+ mode: 'embedded',
22
+ containerId,
23
+ apiUrl,
24
+ theme,
25
+ locale,
26
+ });
27
+ unsubscribe = authonInstance.on('signedIn', (user) => {
28
+ dispatch('signIn', { user: user as AuthonUser });
29
+ });
30
+ authonInstance.openSignIn();
31
+ });
32
+
33
+ onDestroy(() => {
34
+ unsubscribe?.();
35
+ authonInstance?.destroy();
36
+ });
37
+ </script>
38
+
39
+ <div bind:this={container}></div>
@@ -0,0 +1,39 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy, createEventDispatcher } from 'svelte';
3
+ import { Authon } from '@authon/js';
4
+ import type { AuthonUser } from '@authon/shared';
5
+
6
+ export let publishableKey: string;
7
+ export let apiUrl: string = 'https://api.authon.dev';
8
+ export let theme: 'light' | 'dark' | 'auto' = 'auto';
9
+ export let locale: string | undefined = undefined;
10
+
11
+ const dispatch = createEventDispatcher<{ signUp: { user: AuthonUser } }>();
12
+
13
+ let container: HTMLDivElement;
14
+ let authonInstance: Authon | null = null;
15
+ let unsubscribe: (() => void) | null = null;
16
+ const containerId = `authon-signup-${Math.random().toString(36).slice(2, 8)}`;
17
+
18
+ onMount(() => {
19
+ container.id = containerId;
20
+ authonInstance = new Authon(publishableKey, {
21
+ mode: 'embedded',
22
+ containerId,
23
+ apiUrl,
24
+ theme,
25
+ locale,
26
+ });
27
+ unsubscribe = authonInstance.on('signedIn', (user) => {
28
+ dispatch('signUp', { user: user as AuthonUser });
29
+ });
30
+ authonInstance.openSignUp();
31
+ });
32
+
33
+ onDestroy(() => {
34
+ unsubscribe?.();
35
+ authonInstance?.destroy();
36
+ });
37
+ </script>
38
+
39
+ <div bind:this={container}></div>