@authrim/sveltekit 0.1.0 → 0.1.2
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/LICENSE +191 -191
- package/README.md +527 -531
- package/dist/components/AuthProvider.svelte +56 -56
- package/dist/components/ProtectedRoute.svelte +71 -71
- package/dist/components/SignInButton.svelte +93 -93
- package/dist/components/SignOutButton.svelte +72 -72
- package/dist/components/UserProfile.svelte +71 -71
- package/dist/ui/account/LinkAccountButton.svelte +133 -133
- package/dist/ui/account/LinkedAccountsList.svelte +233 -233
- package/dist/ui/account/UnlinkAccountButton.svelte +179 -179
- package/dist/ui/forms/EmailCodeForm.svelte +224 -224
- package/dist/ui/forms/PasskeyConditionalInput.svelte +173 -173
- package/dist/ui/forms/SocialLoginButtons.svelte +209 -209
- package/dist/ui/helpers/AuthError.svelte +124 -124
- package/dist/ui/helpers/AuthLoading.svelte +83 -83
- package/dist/ui/helpers/OTPInput.svelte +214 -214
- package/dist/ui/helpers/ResendCodeButton.svelte +140 -140
- package/dist/ui/passkey/PasskeyDeleteButton.svelte +177 -177
- package/dist/ui/passkey/PasskeyList.svelte +225 -225
- package/dist/ui/passkey/PasskeyRegisterButton.svelte +52 -52
- package/dist/ui/session/SessionExpiryIndicator.svelte +109 -109
- package/dist/ui/session/SessionList.svelte +231 -231
- package/dist/ui/session/SessionRevokeButton.svelte +72 -72
- package/dist/ui/shared/Badge.svelte +100 -100
- package/dist/ui/shared/Button.svelte +213 -213
- package/dist/ui/shared/Card.svelte +85 -85
- package/dist/ui/shared/Input.svelte +192 -192
- package/dist/ui/shared/Spinner.svelte +75 -75
- package/dist/ui/styles/base.css +168 -168
- package/dist/ui/styles/theme.css +279 -279
- package/dist/ui/templates/AccountSettingsTemplate.svelte +205 -205
- package/dist/ui/templates/LoginTemplate.svelte +234 -234
- package/dist/ui/templates/SignUpTemplate.svelte +345 -345
- package/package.json +112 -111
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
AuthProvider Component
|
|
3
|
-
|
|
4
|
-
Responsibilities (strictly limited):
|
|
5
|
-
- Wire auth instance into Svelte context
|
|
6
|
-
- Sync initial session from SSR (synchronously to avoid hydration mismatch)
|
|
7
|
-
|
|
8
|
-
It must NOT implement business logic.
|
|
9
|
-
-->
|
|
10
|
-
<script lang="ts">
|
|
11
|
-
import { onMount, onDestroy } from 'svelte';
|
|
12
|
-
import { setAuthContext } from '../utils/context.js';
|
|
13
|
-
import type { AuthrimClient } from '../types.js';
|
|
14
|
-
import type { Session, User } from '@authrim/core';
|
|
15
|
-
|
|
16
|
-
/** Authrim client instance */
|
|
17
|
-
export let auth: AuthrimClient;
|
|
18
|
-
|
|
19
|
-
/** Initial session from SSR (optional) */
|
|
20
|
-
export let initialSession: Session | null = null;
|
|
21
|
-
|
|
22
|
-
/** Initial user from SSR (optional) */
|
|
23
|
-
export let initialUser: User | null = null;
|
|
24
|
-
|
|
25
|
-
// Set auth context for child components
|
|
26
|
-
setAuthContext(auth);
|
|
27
|
-
|
|
28
|
-
// Sync initial session from SSR synchronously to avoid hydration mismatch
|
|
29
|
-
// This must happen before the first render
|
|
30
|
-
if (initialSession && initialUser) {
|
|
31
|
-
auth._syncFromSSR(initialSession, initialUser);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Optionally validate session in the background after hydration
|
|
35
|
-
onMount(() => {
|
|
36
|
-
if (initialSession && initialUser) {
|
|
37
|
-
// Session already synced above, optionally revalidate in background
|
|
38
|
-
// This ensures the session is still valid on the server
|
|
39
|
-
auth.session.get().catch((error) => {
|
|
40
|
-
console.warn('[Authrim] Failed to revalidate session:', error);
|
|
41
|
-
});
|
|
42
|
-
} else if (!initialSession && !initialUser) {
|
|
43
|
-
// No SSR session provided, check if there's a session in storage
|
|
44
|
-
auth.session.get().catch((error) => {
|
|
45
|
-
console.warn('[Authrim] Failed to fetch session:', error);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// Cleanup on destroy
|
|
51
|
-
onDestroy(() => {
|
|
52
|
-
auth.destroy();
|
|
53
|
-
});
|
|
54
|
-
</script>
|
|
55
|
-
|
|
56
|
-
<slot />
|
|
1
|
+
<!--
|
|
2
|
+
AuthProvider Component
|
|
3
|
+
|
|
4
|
+
Responsibilities (strictly limited):
|
|
5
|
+
- Wire auth instance into Svelte context
|
|
6
|
+
- Sync initial session from SSR (synchronously to avoid hydration mismatch)
|
|
7
|
+
|
|
8
|
+
It must NOT implement business logic.
|
|
9
|
+
-->
|
|
10
|
+
<script lang="ts">
|
|
11
|
+
import { onMount, onDestroy } from 'svelte';
|
|
12
|
+
import { setAuthContext } from '../utils/context.js';
|
|
13
|
+
import type { AuthrimClient } from '../types.js';
|
|
14
|
+
import type { Session, User } from '@authrim/core';
|
|
15
|
+
|
|
16
|
+
/** Authrim client instance */
|
|
17
|
+
export let auth: AuthrimClient;
|
|
18
|
+
|
|
19
|
+
/** Initial session from SSR (optional) */
|
|
20
|
+
export let initialSession: Session | null = null;
|
|
21
|
+
|
|
22
|
+
/** Initial user from SSR (optional) */
|
|
23
|
+
export let initialUser: User | null = null;
|
|
24
|
+
|
|
25
|
+
// Set auth context for child components
|
|
26
|
+
setAuthContext(auth);
|
|
27
|
+
|
|
28
|
+
// Sync initial session from SSR synchronously to avoid hydration mismatch
|
|
29
|
+
// This must happen before the first render
|
|
30
|
+
if (initialSession && initialUser) {
|
|
31
|
+
auth._syncFromSSR(initialSession, initialUser);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Optionally validate session in the background after hydration
|
|
35
|
+
onMount(() => {
|
|
36
|
+
if (initialSession && initialUser) {
|
|
37
|
+
// Session already synced above, optionally revalidate in background
|
|
38
|
+
// This ensures the session is still valid on the server
|
|
39
|
+
auth.session.get().catch((error) => {
|
|
40
|
+
console.warn('[Authrim] Failed to revalidate session:', error);
|
|
41
|
+
});
|
|
42
|
+
} else if (!initialSession && !initialUser) {
|
|
43
|
+
// No SSR session provided, check if there's a session in storage
|
|
44
|
+
auth.session.get().catch((error) => {
|
|
45
|
+
console.warn('[Authrim] Failed to fetch session:', error);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Cleanup on destroy
|
|
51
|
+
onDestroy(() => {
|
|
52
|
+
auth.destroy();
|
|
53
|
+
});
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<slot />
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
ProtectedRoute Component
|
|
3
|
-
|
|
4
|
-
Shows content only when user is authenticated.
|
|
5
|
-
Redirects or shows fallback when not authenticated.
|
|
6
|
-
-->
|
|
7
|
-
<script lang="ts">
|
|
8
|
-
import { onMount, tick } from 'svelte';
|
|
9
|
-
import { goto } from '$app/navigation';
|
|
10
|
-
import { getAuthContext } from '../utils/context.js';
|
|
11
|
-
|
|
12
|
-
/** Redirect URL for unauthenticated users (if not provided, shows fallback) */
|
|
13
|
-
export let redirectTo: string | undefined = undefined;
|
|
14
|
-
|
|
15
|
-
/** Include current path in redirect URL */
|
|
16
|
-
export let includeReturnPath: boolean = true;
|
|
17
|
-
|
|
18
|
-
/** Return path parameter name */
|
|
19
|
-
export let returnPathParam: string = 'redirectTo';
|
|
20
|
-
|
|
21
|
-
/** Custom class */
|
|
22
|
-
let className: string = '';
|
|
23
|
-
export { className as class };
|
|
24
|
-
|
|
25
|
-
const auth = getAuthContext();
|
|
26
|
-
const { isAuthenticated, loadingState } = auth.stores;
|
|
27
|
-
|
|
28
|
-
// State machine: 'loading' -> 'ready'
|
|
29
|
-
// Prevents race condition where redirect happens before session check completes
|
|
30
|
-
let state: 'loading' | 'ready' = 'loading';
|
|
31
|
-
|
|
32
|
-
onMount(async () => {
|
|
33
|
-
try {
|
|
34
|
-
// Check session on mount
|
|
35
|
-
await auth.session.get();
|
|
36
|
-
} catch (error) {
|
|
37
|
-
console.warn('[Authrim] Failed to check session:', error);
|
|
38
|
-
} finally {
|
|
39
|
-
// Wait for stores to update before changing state
|
|
40
|
-
await tick();
|
|
41
|
-
state = 'ready';
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// Only perform redirect when:
|
|
46
|
-
// 1. Component has finished initialization (state === 'ready')
|
|
47
|
-
// 2. User is not authenticated
|
|
48
|
-
// 3. No async operation is in progress (loadingState === 'idle')
|
|
49
|
-
// 4. A redirect URL is configured
|
|
50
|
-
$: if (state === 'ready' && !$isAuthenticated && $loadingState === 'idle' && redirectTo) {
|
|
51
|
-
const url = new URL(redirectTo, window.location.origin);
|
|
52
|
-
if (includeReturnPath) {
|
|
53
|
-
url.searchParams.set(returnPathParam, window.location.pathname + window.location.search);
|
|
54
|
-
}
|
|
55
|
-
goto(url.toString());
|
|
56
|
-
}
|
|
57
|
-
</script>
|
|
58
|
-
|
|
59
|
-
{#if state === 'loading' || $loadingState === 'initializing'}
|
|
60
|
-
<slot name="loading">
|
|
61
|
-
<div class={className} {...$$restProps}>Loading...</div>
|
|
62
|
-
</slot>
|
|
63
|
-
{:else if $isAuthenticated}
|
|
64
|
-
<slot />
|
|
65
|
-
{:else if !redirectTo}
|
|
66
|
-
<slot name="unauthenticated">
|
|
67
|
-
<div class={className} {...$$restProps}>
|
|
68
|
-
<p>You must be signed in to view this content.</p>
|
|
69
|
-
</div>
|
|
70
|
-
</slot>
|
|
71
|
-
{/if}
|
|
1
|
+
<!--
|
|
2
|
+
ProtectedRoute Component
|
|
3
|
+
|
|
4
|
+
Shows content only when user is authenticated.
|
|
5
|
+
Redirects or shows fallback when not authenticated.
|
|
6
|
+
-->
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import { onMount, tick } from 'svelte';
|
|
9
|
+
import { goto } from '$app/navigation';
|
|
10
|
+
import { getAuthContext } from '../utils/context.js';
|
|
11
|
+
|
|
12
|
+
/** Redirect URL for unauthenticated users (if not provided, shows fallback) */
|
|
13
|
+
export let redirectTo: string | undefined = undefined;
|
|
14
|
+
|
|
15
|
+
/** Include current path in redirect URL */
|
|
16
|
+
export let includeReturnPath: boolean = true;
|
|
17
|
+
|
|
18
|
+
/** Return path parameter name */
|
|
19
|
+
export let returnPathParam: string = 'redirectTo';
|
|
20
|
+
|
|
21
|
+
/** Custom class */
|
|
22
|
+
let className: string = '';
|
|
23
|
+
export { className as class };
|
|
24
|
+
|
|
25
|
+
const auth = getAuthContext();
|
|
26
|
+
const { isAuthenticated, loadingState } = auth.stores;
|
|
27
|
+
|
|
28
|
+
// State machine: 'loading' -> 'ready'
|
|
29
|
+
// Prevents race condition where redirect happens before session check completes
|
|
30
|
+
let state: 'loading' | 'ready' = 'loading';
|
|
31
|
+
|
|
32
|
+
onMount(async () => {
|
|
33
|
+
try {
|
|
34
|
+
// Check session on mount
|
|
35
|
+
await auth.session.get();
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.warn('[Authrim] Failed to check session:', error);
|
|
38
|
+
} finally {
|
|
39
|
+
// Wait for stores to update before changing state
|
|
40
|
+
await tick();
|
|
41
|
+
state = 'ready';
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Only perform redirect when:
|
|
46
|
+
// 1. Component has finished initialization (state === 'ready')
|
|
47
|
+
// 2. User is not authenticated
|
|
48
|
+
// 3. No async operation is in progress (loadingState === 'idle')
|
|
49
|
+
// 4. A redirect URL is configured
|
|
50
|
+
$: if (state === 'ready' && !$isAuthenticated && $loadingState === 'idle' && redirectTo) {
|
|
51
|
+
const url = new URL(redirectTo, window.location.origin);
|
|
52
|
+
if (includeReturnPath) {
|
|
53
|
+
url.searchParams.set(returnPathParam, window.location.pathname + window.location.search);
|
|
54
|
+
}
|
|
55
|
+
goto(url.toString());
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
{#if state === 'loading' || $loadingState === 'initializing'}
|
|
60
|
+
<slot name="loading">
|
|
61
|
+
<div class={className} {...$$restProps}>Loading...</div>
|
|
62
|
+
</slot>
|
|
63
|
+
{:else if $isAuthenticated}
|
|
64
|
+
<slot />
|
|
65
|
+
{:else if !redirectTo}
|
|
66
|
+
<slot name="unauthenticated">
|
|
67
|
+
<div class={className} {...$$restProps}>
|
|
68
|
+
<p>You must be signed in to view this content.</p>
|
|
69
|
+
</div>
|
|
70
|
+
</slot>
|
|
71
|
+
{/if}
|
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
SignInButton Component
|
|
3
|
-
|
|
4
|
-
A thin wrapper that delegates all behavior to props/events.
|
|
5
|
-
Responsibilities: Call auth API and dispatch events.
|
|
6
|
-
-->
|
|
7
|
-
<script lang="ts">
|
|
8
|
-
import { createEventDispatcher } from 'svelte';
|
|
9
|
-
import { getAuthContext } from '../utils/context.js';
|
|
10
|
-
import type { SocialProvider, Session, User, AuthLoadingState } from '../types.js';
|
|
11
|
-
import type { AuthError } from '../stores/auth.js';
|
|
12
|
-
|
|
13
|
-
/** Authentication method */
|
|
14
|
-
export let method: 'passkey' | 'social' = 'passkey';
|
|
15
|
-
|
|
16
|
-
/** Social provider (required when method is 'social') */
|
|
17
|
-
export let provider: SocialProvider | undefined = undefined;
|
|
18
|
-
|
|
19
|
-
/** Disabled state */
|
|
20
|
-
export let disabled: boolean = false;
|
|
21
|
-
|
|
22
|
-
/** Custom class */
|
|
23
|
-
let className: string = '';
|
|
24
|
-
export { className as class };
|
|
25
|
-
|
|
26
|
-
const auth = getAuthContext();
|
|
27
|
-
const dispatch = createEventDispatcher<{
|
|
28
|
-
success: { session: Session; user: User };
|
|
29
|
-
error: AuthError;
|
|
30
|
-
loading: AuthLoadingState;
|
|
31
|
-
}>();
|
|
32
|
-
|
|
33
|
-
let loading = false;
|
|
34
|
-
|
|
35
|
-
async function handleClick() {
|
|
36
|
-
if (disabled || loading) return;
|
|
37
|
-
|
|
38
|
-
loading = true;
|
|
39
|
-
dispatch('loading', 'authenticating');
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
let result;
|
|
43
|
-
|
|
44
|
-
if (method === 'passkey') {
|
|
45
|
-
result = await auth.passkey.login();
|
|
46
|
-
} else if (method === 'social' && provider) {
|
|
47
|
-
result = await auth.social.loginWithPopup(provider);
|
|
48
|
-
} else {
|
|
49
|
-
throw new Error('Invalid method or missing provider');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (result.data) {
|
|
53
|
-
dispatch('success', {
|
|
54
|
-
session: result.data.session,
|
|
55
|
-
user: result.data.user,
|
|
56
|
-
});
|
|
57
|
-
} else if (result.error) {
|
|
58
|
-
dispatch('error', {
|
|
59
|
-
code: result.error.code,
|
|
60
|
-
message: result.error.message,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
} catch (error) {
|
|
64
|
-
dispatch('error', {
|
|
65
|
-
code: 'UNKNOWN_ERROR',
|
|
66
|
-
message: error instanceof Error ? error.message : 'Unknown error',
|
|
67
|
-
});
|
|
68
|
-
} finally {
|
|
69
|
-
loading = false;
|
|
70
|
-
dispatch('loading', 'idle');
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
</script>
|
|
74
|
-
|
|
75
|
-
<button
|
|
76
|
-
type="button"
|
|
77
|
-
on:click={handleClick}
|
|
78
|
-
disabled={disabled || loading}
|
|
79
|
-
class={className}
|
|
80
|
-
{...$$restProps}
|
|
81
|
-
>
|
|
82
|
-
<slot>
|
|
83
|
-
{#if loading}
|
|
84
|
-
Signing in...
|
|
85
|
-
{:else if method === 'passkey'}
|
|
86
|
-
Sign in with Passkey
|
|
87
|
-
{:else if method === 'social' && provider}
|
|
88
|
-
Sign in with {provider.charAt(0).toUpperCase() + provider.slice(1)}
|
|
89
|
-
{:else}
|
|
90
|
-
Sign In
|
|
91
|
-
{/if}
|
|
92
|
-
</slot>
|
|
93
|
-
</button>
|
|
1
|
+
<!--
|
|
2
|
+
SignInButton Component
|
|
3
|
+
|
|
4
|
+
A thin wrapper that delegates all behavior to props/events.
|
|
5
|
+
Responsibilities: Call auth API and dispatch events.
|
|
6
|
+
-->
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import { createEventDispatcher } from 'svelte';
|
|
9
|
+
import { getAuthContext } from '../utils/context.js';
|
|
10
|
+
import type { SocialProvider, Session, User, AuthLoadingState } from '../types.js';
|
|
11
|
+
import type { AuthError } from '../stores/auth.js';
|
|
12
|
+
|
|
13
|
+
/** Authentication method */
|
|
14
|
+
export let method: 'passkey' | 'social' = 'passkey';
|
|
15
|
+
|
|
16
|
+
/** Social provider (required when method is 'social') */
|
|
17
|
+
export let provider: SocialProvider | undefined = undefined;
|
|
18
|
+
|
|
19
|
+
/** Disabled state */
|
|
20
|
+
export let disabled: boolean = false;
|
|
21
|
+
|
|
22
|
+
/** Custom class */
|
|
23
|
+
let className: string = '';
|
|
24
|
+
export { className as class };
|
|
25
|
+
|
|
26
|
+
const auth = getAuthContext();
|
|
27
|
+
const dispatch = createEventDispatcher<{
|
|
28
|
+
success: { session: Session; user: User };
|
|
29
|
+
error: AuthError;
|
|
30
|
+
loading: AuthLoadingState;
|
|
31
|
+
}>();
|
|
32
|
+
|
|
33
|
+
let loading = false;
|
|
34
|
+
|
|
35
|
+
async function handleClick() {
|
|
36
|
+
if (disabled || loading) return;
|
|
37
|
+
|
|
38
|
+
loading = true;
|
|
39
|
+
dispatch('loading', 'authenticating');
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
let result;
|
|
43
|
+
|
|
44
|
+
if (method === 'passkey') {
|
|
45
|
+
result = await auth.passkey.login();
|
|
46
|
+
} else if (method === 'social' && provider) {
|
|
47
|
+
result = await auth.social.loginWithPopup(provider);
|
|
48
|
+
} else {
|
|
49
|
+
throw new Error('Invalid method or missing provider');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (result.data) {
|
|
53
|
+
dispatch('success', {
|
|
54
|
+
session: result.data.session,
|
|
55
|
+
user: result.data.user,
|
|
56
|
+
});
|
|
57
|
+
} else if (result.error) {
|
|
58
|
+
dispatch('error', {
|
|
59
|
+
code: result.error.code,
|
|
60
|
+
message: result.error.message,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
dispatch('error', {
|
|
65
|
+
code: 'UNKNOWN_ERROR',
|
|
66
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
67
|
+
});
|
|
68
|
+
} finally {
|
|
69
|
+
loading = false;
|
|
70
|
+
dispatch('loading', 'idle');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
on:click={handleClick}
|
|
78
|
+
disabled={disabled || loading}
|
|
79
|
+
class={className}
|
|
80
|
+
{...$$restProps}
|
|
81
|
+
>
|
|
82
|
+
<slot>
|
|
83
|
+
{#if loading}
|
|
84
|
+
Signing in...
|
|
85
|
+
{:else if method === 'passkey'}
|
|
86
|
+
Sign in with Passkey
|
|
87
|
+
{:else if method === 'social' && provider}
|
|
88
|
+
Sign in with {provider.charAt(0).toUpperCase() + provider.slice(1)}
|
|
89
|
+
{:else}
|
|
90
|
+
Sign In
|
|
91
|
+
{/if}
|
|
92
|
+
</slot>
|
|
93
|
+
</button>
|
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
SignOutButton Component
|
|
3
|
-
|
|
4
|
-
A thin wrapper that delegates all behavior to props/events.
|
|
5
|
-
-->
|
|
6
|
-
<script lang="ts">
|
|
7
|
-
import { createEventDispatcher } from 'svelte';
|
|
8
|
-
import { getAuthContext } from '../utils/context.js';
|
|
9
|
-
import type { AuthLoadingState } from '../types.js';
|
|
10
|
-
import type { AuthError } from '../stores/auth.js';
|
|
11
|
-
|
|
12
|
-
/** Redirect URI after sign out */
|
|
13
|
-
export let redirectUri: string | undefined = undefined;
|
|
14
|
-
|
|
15
|
-
/** Revoke tokens on sign out */
|
|
16
|
-
export let revokeTokens: boolean = false;
|
|
17
|
-
|
|
18
|
-
/** Disabled state */
|
|
19
|
-
export let disabled: boolean = false;
|
|
20
|
-
|
|
21
|
-
/** Custom class */
|
|
22
|
-
let className: string = '';
|
|
23
|
-
export { className as class };
|
|
24
|
-
|
|
25
|
-
const auth = getAuthContext();
|
|
26
|
-
const dispatch = createEventDispatcher<{
|
|
27
|
-
success: void;
|
|
28
|
-
error: AuthError;
|
|
29
|
-
loading: AuthLoadingState;
|
|
30
|
-
}>();
|
|
31
|
-
|
|
32
|
-
let loading = false;
|
|
33
|
-
|
|
34
|
-
async function handleClick() {
|
|
35
|
-
if (disabled || loading) return;
|
|
36
|
-
|
|
37
|
-
loading = true;
|
|
38
|
-
dispatch('loading', 'signing_out');
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
await auth.signOut({
|
|
42
|
-
redirectUri,
|
|
43
|
-
revokeTokens,
|
|
44
|
-
});
|
|
45
|
-
dispatch('success');
|
|
46
|
-
} catch (error) {
|
|
47
|
-
dispatch('error', {
|
|
48
|
-
code: 'SIGN_OUT_ERROR',
|
|
49
|
-
message: error instanceof Error ? error.message : 'Sign out failed',
|
|
50
|
-
});
|
|
51
|
-
} finally {
|
|
52
|
-
loading = false;
|
|
53
|
-
dispatch('loading', 'idle');
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
</script>
|
|
57
|
-
|
|
58
|
-
<button
|
|
59
|
-
type="button"
|
|
60
|
-
on:click={handleClick}
|
|
61
|
-
disabled={disabled || loading}
|
|
62
|
-
class={className}
|
|
63
|
-
{...$$restProps}
|
|
64
|
-
>
|
|
65
|
-
<slot>
|
|
66
|
-
{#if loading}
|
|
67
|
-
Signing out...
|
|
68
|
-
{:else}
|
|
69
|
-
Sign Out
|
|
70
|
-
{/if}
|
|
71
|
-
</slot>
|
|
72
|
-
</button>
|
|
1
|
+
<!--
|
|
2
|
+
SignOutButton Component
|
|
3
|
+
|
|
4
|
+
A thin wrapper that delegates all behavior to props/events.
|
|
5
|
+
-->
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import { createEventDispatcher } from 'svelte';
|
|
8
|
+
import { getAuthContext } from '../utils/context.js';
|
|
9
|
+
import type { AuthLoadingState } from '../types.js';
|
|
10
|
+
import type { AuthError } from '../stores/auth.js';
|
|
11
|
+
|
|
12
|
+
/** Redirect URI after sign out */
|
|
13
|
+
export let redirectUri: string | undefined = undefined;
|
|
14
|
+
|
|
15
|
+
/** Revoke tokens on sign out */
|
|
16
|
+
export let revokeTokens: boolean = false;
|
|
17
|
+
|
|
18
|
+
/** Disabled state */
|
|
19
|
+
export let disabled: boolean = false;
|
|
20
|
+
|
|
21
|
+
/** Custom class */
|
|
22
|
+
let className: string = '';
|
|
23
|
+
export { className as class };
|
|
24
|
+
|
|
25
|
+
const auth = getAuthContext();
|
|
26
|
+
const dispatch = createEventDispatcher<{
|
|
27
|
+
success: void;
|
|
28
|
+
error: AuthError;
|
|
29
|
+
loading: AuthLoadingState;
|
|
30
|
+
}>();
|
|
31
|
+
|
|
32
|
+
let loading = false;
|
|
33
|
+
|
|
34
|
+
async function handleClick() {
|
|
35
|
+
if (disabled || loading) return;
|
|
36
|
+
|
|
37
|
+
loading = true;
|
|
38
|
+
dispatch('loading', 'signing_out');
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
await auth.signOut({
|
|
42
|
+
redirectUri,
|
|
43
|
+
revokeTokens,
|
|
44
|
+
});
|
|
45
|
+
dispatch('success');
|
|
46
|
+
} catch (error) {
|
|
47
|
+
dispatch('error', {
|
|
48
|
+
code: 'SIGN_OUT_ERROR',
|
|
49
|
+
message: error instanceof Error ? error.message : 'Sign out failed',
|
|
50
|
+
});
|
|
51
|
+
} finally {
|
|
52
|
+
loading = false;
|
|
53
|
+
dispatch('loading', 'idle');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
on:click={handleClick}
|
|
61
|
+
disabled={disabled || loading}
|
|
62
|
+
class={className}
|
|
63
|
+
{...$$restProps}
|
|
64
|
+
>
|
|
65
|
+
<slot>
|
|
66
|
+
{#if loading}
|
|
67
|
+
Signing out...
|
|
68
|
+
{:else}
|
|
69
|
+
Sign Out
|
|
70
|
+
{/if}
|
|
71
|
+
</slot>
|
|
72
|
+
</button>
|