@ascendkit/nextjs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/client/hooks.d.ts +109 -0
- package/dist/client/hooks.d.ts.map +1 -0
- package/dist/client/hooks.js +372 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +3 -0
- package/dist/client/provider.d.ts +66 -0
- package/dist/client/provider.d.ts.map +1 -0
- package/dist/client/provider.js +284 -0
- package/dist/client/use-analytics.d.ts +27 -0
- package/dist/client/use-analytics.d.ts.map +1 -0
- package/dist/client/use-analytics.js +133 -0
- package/dist/components/auth-card.d.ts +20 -0
- package/dist/components/auth-card.d.ts.map +1 -0
- package/dist/components/auth-card.js +128 -0
- package/dist/components/auth-modal.d.ts +9 -0
- package/dist/components/auth-modal.d.ts.map +1 -0
- package/dist/components/auth-modal.js +110 -0
- package/dist/components/branding-badge.d.ts +2 -0
- package/dist/components/branding-badge.d.ts.map +1 -0
- package/dist/components/branding-badge.js +9 -0
- package/dist/components/email-verification.d.ts +2 -0
- package/dist/components/email-verification.d.ts.map +1 -0
- package/dist/components/email-verification.js +48 -0
- package/dist/components/forgot-password.d.ts +6 -0
- package/dist/components/forgot-password.d.ts.map +1 -0
- package/dist/components/forgot-password.js +37 -0
- package/dist/components/index.d.ts +13 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +12 -0
- package/dist/components/login.d.ts +9 -0
- package/dist/components/login.d.ts.map +1 -0
- package/dist/components/login.js +48 -0
- package/dist/components/reset-password.d.ts +6 -0
- package/dist/components/reset-password.d.ts.map +1 -0
- package/dist/components/reset-password.js +47 -0
- package/dist/components/sign-in-button.d.ts +19 -0
- package/dist/components/sign-in-button.d.ts.map +1 -0
- package/dist/components/sign-in-button.js +27 -0
- package/dist/components/sign-up-button.d.ts +19 -0
- package/dist/components/sign-up-button.d.ts.map +1 -0
- package/dist/components/sign-up-button.js +27 -0
- package/dist/components/signup.d.ts +9 -0
- package/dist/components/signup.d.ts.map +1 -0
- package/dist/components/signup.js +60 -0
- package/dist/components/social-button.d.ts +8 -0
- package/dist/components/social-button.d.ts.map +1 -0
- package/dist/components/social-button.js +10 -0
- package/dist/components/user-button.d.ts +11 -0
- package/dist/components/user-button.d.ts.map +1 -0
- package/dist/components/user-button.js +14 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/server/access-token.d.ts +39 -0
- package/dist/server/access-token.d.ts.map +1 -0
- package/dist/server/access-token.js +74 -0
- package/dist/server/adapter.d.ts +6 -0
- package/dist/server/adapter.d.ts.map +1 -0
- package/dist/server/adapter.js +57 -0
- package/dist/server/analytics.d.ts +61 -0
- package/dist/server/analytics.d.ts.map +1 -0
- package/dist/server/analytics.js +117 -0
- package/dist/server/ascendkit-auth.d.ts +122 -0
- package/dist/server/ascendkit-auth.d.ts.map +1 -0
- package/dist/server/ascendkit-auth.js +146 -0
- package/dist/server/auth-runtime.d.ts +12 -0
- package/dist/server/auth-runtime.d.ts.map +1 -0
- package/dist/server/auth-runtime.js +123 -0
- package/dist/server/config-fetcher.d.ts +22 -0
- package/dist/server/config-fetcher.d.ts.map +1 -0
- package/dist/server/config-fetcher.js +26 -0
- package/dist/server/email-sender.d.ts +29 -0
- package/dist/server/email-sender.d.ts.map +1 -0
- package/dist/server/email-sender.js +58 -0
- package/dist/server/index.d.ts +10 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +7 -0
- package/dist/server/oauth-proxy-plugin.d.ts +10 -0
- package/dist/server/oauth-proxy-plugin.d.ts.map +1 -0
- package/dist/server/oauth-proxy-plugin.js +156 -0
- package/dist/server/social-providers.d.ts +12 -0
- package/dist/server/social-providers.d.ts.map +1 -0
- package/dist/server/social-providers.js +15 -0
- package/dist/server/webhooks.d.ts +43 -0
- package/dist/server/webhooks.d.ts.map +1 -0
- package/dist/server/webhooks.js +83 -0
- package/dist/shared/http-client.d.ts +17 -0
- package/dist/shared/http-client.d.ts.map +1 -0
- package/dist/shared/http-client.js +52 -0
- package/dist/shared/types.d.ts +49 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ascendkit.dev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { AscendKitSession, AscendKitUser } from "../shared/types";
|
|
2
|
+
/**
|
|
3
|
+
* Primary hook for accessing auth state in AscendKit.
|
|
4
|
+
*
|
|
5
|
+
* Returns the current user (with AscendKit fields like `waitlistStatus`),
|
|
6
|
+
* loading state, and signOut function. The user profile is fetched from
|
|
7
|
+
* the AscendKit backend on session load so it includes all custom fields.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ```tsx
|
|
11
|
+
* const { user, isLoaded, isAuthenticated, signOut } = useAscendKit();
|
|
12
|
+
* if (user?.waitlistStatus === "pending") { ... }
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function useAscendKit(): {
|
|
16
|
+
user: AscendKitUser | null;
|
|
17
|
+
isLoaded: boolean;
|
|
18
|
+
isAuthenticated: boolean;
|
|
19
|
+
signOut: () => Promise<{
|
|
20
|
+
data: {
|
|
21
|
+
success: boolean;
|
|
22
|
+
};
|
|
23
|
+
error: null;
|
|
24
|
+
} | {
|
|
25
|
+
data: null;
|
|
26
|
+
error: {
|
|
27
|
+
code?: string | undefined | undefined;
|
|
28
|
+
message?: string | undefined | undefined;
|
|
29
|
+
status: number;
|
|
30
|
+
statusText: string;
|
|
31
|
+
};
|
|
32
|
+
}>;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Hook for programmatic control of the global auth modal.
|
|
36
|
+
*
|
|
37
|
+
* Requires <AuthModal /> to be rendered inside <AscendKitProvider>.
|
|
38
|
+
*
|
|
39
|
+
* Usage:
|
|
40
|
+
* ```tsx
|
|
41
|
+
* const { open } = useAuthModal();
|
|
42
|
+
*
|
|
43
|
+
* <button onClick={() => open("sign-in")}>Sign in</button>
|
|
44
|
+
* <button onClick={() => open("sign-up")}>Sign up</button>
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function useAuthModal(): import("./provider").AuthModalState;
|
|
48
|
+
/**
|
|
49
|
+
* Hook for obtaining short-lived RS256 access tokens for backend API calls.
|
|
50
|
+
*
|
|
51
|
+
* Automatically fetches a new token when the current one is near expiry
|
|
52
|
+
* (within 5 minutes). Tokens are cached in memory and concurrent requests
|
|
53
|
+
* are deduplicated. The cache is keyed by the current session user ID, so
|
|
54
|
+
* it invalidates automatically on sign-out/sign-in.
|
|
55
|
+
*
|
|
56
|
+
* Requires a server-side route created with `createAccessTokenHandler`
|
|
57
|
+
* from `@ascendkit/nextjs/server`.
|
|
58
|
+
*
|
|
59
|
+
* Usage:
|
|
60
|
+
* ```tsx
|
|
61
|
+
* const { getAccessToken } = useAccessToken();
|
|
62
|
+
*
|
|
63
|
+
* async function callBackend() {
|
|
64
|
+
* const token = await getAccessToken();
|
|
65
|
+
* const res = await fetch("https://my-api.com/data", {
|
|
66
|
+
* headers: { Authorization: `Bearer ${token}` },
|
|
67
|
+
* });
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare function useAccessToken(path?: string): {
|
|
72
|
+
getAccessToken: () => Promise<string>;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Hook for listing and revoking active sessions for the current user.
|
|
76
|
+
*/
|
|
77
|
+
export declare function useSessions(): {
|
|
78
|
+
sessions: AscendKitSession[];
|
|
79
|
+
isLoaded: boolean;
|
|
80
|
+
isAuthenticated: boolean;
|
|
81
|
+
loading: boolean;
|
|
82
|
+
error: string | null;
|
|
83
|
+
refresh: () => Promise<void>;
|
|
84
|
+
revokeSession: (sessionId: string) => Promise<void>;
|
|
85
|
+
revokeAllSessions: () => Promise<void>;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* User profile hook with mutation helpers.
|
|
89
|
+
*/
|
|
90
|
+
export declare function useUser(): {
|
|
91
|
+
user: AscendKitUser | null;
|
|
92
|
+
isLoaded: boolean;
|
|
93
|
+
isAuthenticated: boolean;
|
|
94
|
+
loading: boolean;
|
|
95
|
+
error: string | null;
|
|
96
|
+
refresh: () => Promise<void>;
|
|
97
|
+
updateProfile: (data: {
|
|
98
|
+
name?: string;
|
|
99
|
+
image?: string;
|
|
100
|
+
}) => Promise<AscendKitUser | null>;
|
|
101
|
+
deleteAccount: () => Promise<{
|
|
102
|
+
ok: boolean;
|
|
103
|
+
}>;
|
|
104
|
+
};
|
|
105
|
+
export declare function verifyEmail(apiUrl: string, publicKey: string, token: string): Promise<{
|
|
106
|
+
ok: boolean;
|
|
107
|
+
userId?: string;
|
|
108
|
+
}>;
|
|
109
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/client/hooks.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEvE;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY;;;;;;;;;;;;;;;;;;EAyE3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,wCAG3B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAAC,IAAI,SAAsB;0BAeV,OAAO,CAAC,MAAM,CAAC;EA2D7D;AAED;;GAEG;AACH,wBAAgB,WAAW;;;;;;;+BA4C2B,MAAM;;EA8C3D;AAED;;GAEG;AACH,wBAAgB,OAAO;;;;;;;0BA4C0B;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;;;;EA0DjF;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAe3C"}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { useAscendKitContext } from "./provider";
|
|
4
|
+
/**
|
|
5
|
+
* Primary hook for accessing auth state in AscendKit.
|
|
6
|
+
*
|
|
7
|
+
* Returns the current user (with AscendKit fields like `waitlistStatus`),
|
|
8
|
+
* loading state, and signOut function. The user profile is fetched from
|
|
9
|
+
* the AscendKit backend on session load so it includes all custom fields.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const { user, isLoaded, isAuthenticated, signOut } = useAscendKit();
|
|
14
|
+
* if (user?.waitlistStatus === "pending") { ... }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function useAscendKit() {
|
|
18
|
+
const { authClient, apiUrl, publicKey } = useAscendKitContext();
|
|
19
|
+
const { data: session, isPending } = authClient.useSession();
|
|
20
|
+
const [profile, setProfile] = useState(null);
|
|
21
|
+
const [profileLoading, setProfileLoading] = useState(false);
|
|
22
|
+
const fetchedForRef = useRef(null);
|
|
23
|
+
const retryTimerRef = useRef(null);
|
|
24
|
+
const sessionUserId = session?.user?.id ?? null;
|
|
25
|
+
const sessionToken = session?.session?.token;
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!sessionUserId || !sessionToken) {
|
|
28
|
+
setProfile(null);
|
|
29
|
+
fetchedForRef.current = null;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Skip if we already successfully fetched for this user
|
|
33
|
+
if (fetchedForRef.current === sessionUserId)
|
|
34
|
+
return;
|
|
35
|
+
let cancelled = false;
|
|
36
|
+
setProfileLoading(true);
|
|
37
|
+
async function fetchProfile(attempt) {
|
|
38
|
+
if (cancelled)
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch(`${apiUrl}/api/users/me`, {
|
|
42
|
+
headers: {
|
|
43
|
+
"X-AscendKit-Public-Key": publicKey,
|
|
44
|
+
"Authorization": `Bearer ${sessionToken}`,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
const json = await res.json();
|
|
48
|
+
if (!cancelled && json.data) {
|
|
49
|
+
setProfile(json.data);
|
|
50
|
+
fetchedForRef.current = sessionUserId;
|
|
51
|
+
setProfileLoading(false);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Network error — fall through to retry
|
|
57
|
+
}
|
|
58
|
+
// Retry once after 3s on transient failure
|
|
59
|
+
if (!cancelled && attempt < 1) {
|
|
60
|
+
retryTimerRef.current = setTimeout(() => fetchProfile(attempt + 1), 3000);
|
|
61
|
+
}
|
|
62
|
+
else if (!cancelled) {
|
|
63
|
+
setProfileLoading(false);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
fetchProfile(0);
|
|
67
|
+
return () => {
|
|
68
|
+
cancelled = true;
|
|
69
|
+
if (retryTimerRef.current)
|
|
70
|
+
clearTimeout(retryTimerRef.current);
|
|
71
|
+
setProfileLoading(false);
|
|
72
|
+
};
|
|
73
|
+
}, [sessionUserId, sessionToken, apiUrl, publicKey]);
|
|
74
|
+
const isLoaded = !isPending && !profileLoading;
|
|
75
|
+
return {
|
|
76
|
+
user: profile,
|
|
77
|
+
isLoaded,
|
|
78
|
+
isAuthenticated: !!profile,
|
|
79
|
+
signOut: () => {
|
|
80
|
+
setProfile(null);
|
|
81
|
+
fetchedForRef.current = null;
|
|
82
|
+
return authClient.signOut();
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Hook for programmatic control of the global auth modal.
|
|
88
|
+
*
|
|
89
|
+
* Requires <AuthModal /> to be rendered inside <AscendKitProvider>.
|
|
90
|
+
*
|
|
91
|
+
* Usage:
|
|
92
|
+
* ```tsx
|
|
93
|
+
* const { open } = useAuthModal();
|
|
94
|
+
*
|
|
95
|
+
* <button onClick={() => open("sign-in")}>Sign in</button>
|
|
96
|
+
* <button onClick={() => open("sign-up")}>Sign up</button>
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function useAuthModal() {
|
|
100
|
+
const { modal } = useAscendKitContext();
|
|
101
|
+
return modal;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Hook for obtaining short-lived RS256 access tokens for backend API calls.
|
|
105
|
+
*
|
|
106
|
+
* Automatically fetches a new token when the current one is near expiry
|
|
107
|
+
* (within 5 minutes). Tokens are cached in memory and concurrent requests
|
|
108
|
+
* are deduplicated. The cache is keyed by the current session user ID, so
|
|
109
|
+
* it invalidates automatically on sign-out/sign-in.
|
|
110
|
+
*
|
|
111
|
+
* Requires a server-side route created with `createAccessTokenHandler`
|
|
112
|
+
* from `@ascendkit/nextjs/server`.
|
|
113
|
+
*
|
|
114
|
+
* Usage:
|
|
115
|
+
* ```tsx
|
|
116
|
+
* const { getAccessToken } = useAccessToken();
|
|
117
|
+
*
|
|
118
|
+
* async function callBackend() {
|
|
119
|
+
* const token = await getAccessToken();
|
|
120
|
+
* const res = await fetch("https://my-api.com/data", {
|
|
121
|
+
* headers: { Authorization: `Bearer ${token}` },
|
|
122
|
+
* });
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export function useAccessToken(path = "/api/access-token") {
|
|
127
|
+
const { authClient } = useAscendKitContext();
|
|
128
|
+
const { data: session } = authClient.useSession();
|
|
129
|
+
const userId = session?.user?.id ?? null;
|
|
130
|
+
const tokenRef = useRef(null);
|
|
131
|
+
const fetchingRef = useRef(null);
|
|
132
|
+
const getAccessToken = useCallback(async () => {
|
|
133
|
+
if (!userId) {
|
|
134
|
+
throw new Error("No authenticated session");
|
|
135
|
+
}
|
|
136
|
+
// Return cached token if still valid and belongs to the current user
|
|
137
|
+
const cached = tokenRef.current;
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
if (cached &&
|
|
140
|
+
cached.userId === userId &&
|
|
141
|
+
cached.expiresAt - now > 5 * 60 * 1000) {
|
|
142
|
+
return cached.token;
|
|
143
|
+
}
|
|
144
|
+
// Deduplicate concurrent requests for the same user
|
|
145
|
+
const inflight = fetchingRef.current;
|
|
146
|
+
if (inflight && inflight.userId === userId) {
|
|
147
|
+
return inflight.promise;
|
|
148
|
+
}
|
|
149
|
+
const currentUserId = userId;
|
|
150
|
+
const fetchPromise = (async () => {
|
|
151
|
+
try {
|
|
152
|
+
const res = await fetch(path);
|
|
153
|
+
if (!res.ok) {
|
|
154
|
+
const body = await res.json().catch(() => ({}));
|
|
155
|
+
const detail = body.error ? `: ${body.error}` : "";
|
|
156
|
+
throw new Error(`Failed to get access token: ${res.status}${detail}`);
|
|
157
|
+
}
|
|
158
|
+
const json = await res.json();
|
|
159
|
+
const { accessToken, expiresIn } = json.data;
|
|
160
|
+
// Only cache if the session user hasn't changed during the fetch
|
|
161
|
+
if (currentUserId === userId) {
|
|
162
|
+
tokenRef.current = {
|
|
163
|
+
token: accessToken,
|
|
164
|
+
expiresAt: Date.now() + expiresIn * 1000,
|
|
165
|
+
userId: currentUserId,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return accessToken;
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
// Only clear if this is still our in-flight request
|
|
172
|
+
if (fetchingRef.current?.userId === currentUserId) {
|
|
173
|
+
fetchingRef.current = null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
})();
|
|
177
|
+
fetchingRef.current = { promise: fetchPromise, userId: currentUserId };
|
|
178
|
+
return fetchPromise;
|
|
179
|
+
}, [path, userId]);
|
|
180
|
+
return { getAccessToken };
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Hook for listing and revoking active sessions for the current user.
|
|
184
|
+
*/
|
|
185
|
+
export function useSessions() {
|
|
186
|
+
const { authClient, apiUrl, publicKey } = useAscendKitContext();
|
|
187
|
+
const { data: session, isPending } = authClient.useSession();
|
|
188
|
+
const sessionToken = session?.session?.token;
|
|
189
|
+
const [sessions, setSessions] = useState([]);
|
|
190
|
+
const [loading, setLoading] = useState(false);
|
|
191
|
+
const [error, setError] = useState(null);
|
|
192
|
+
const refresh = useCallback(async () => {
|
|
193
|
+
if (!sessionToken) {
|
|
194
|
+
setSessions([]);
|
|
195
|
+
setError(null);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
setLoading(true);
|
|
199
|
+
setError(null);
|
|
200
|
+
try {
|
|
201
|
+
const res = await fetch(`${apiUrl}/api/auth/sessions`, {
|
|
202
|
+
headers: {
|
|
203
|
+
"X-AscendKit-Public-Key": publicKey,
|
|
204
|
+
"Authorization": `Bearer ${sessionToken}`,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
const json = await res.json().catch(() => ({}));
|
|
208
|
+
if (!res.ok) {
|
|
209
|
+
throw new Error(json.detail || json.error || "Failed to load sessions");
|
|
210
|
+
}
|
|
211
|
+
setSessions(Array.isArray(json.data) ? json.data : []);
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
setError(err instanceof Error ? err.message : "Failed to load sessions");
|
|
215
|
+
setSessions([]);
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
setLoading(false);
|
|
219
|
+
}
|
|
220
|
+
}, [apiUrl, publicKey, sessionToken]);
|
|
221
|
+
useEffect(() => {
|
|
222
|
+
void refresh();
|
|
223
|
+
}, [refresh]);
|
|
224
|
+
const revokeSession = useCallback(async (sessionId) => {
|
|
225
|
+
if (!sessionToken) {
|
|
226
|
+
throw new Error("No authenticated session");
|
|
227
|
+
}
|
|
228
|
+
const res = await fetch(`${apiUrl}/api/auth/sessions/${encodeURIComponent(sessionId)}`, {
|
|
229
|
+
method: "DELETE",
|
|
230
|
+
headers: {
|
|
231
|
+
"X-AscendKit-Public-Key": publicKey,
|
|
232
|
+
"Authorization": `Bearer ${sessionToken}`,
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
const json = await res.json().catch(() => ({}));
|
|
236
|
+
if (!res.ok) {
|
|
237
|
+
throw new Error(json.detail || json.error || "Failed to revoke session");
|
|
238
|
+
}
|
|
239
|
+
await refresh();
|
|
240
|
+
}, [apiUrl, publicKey, refresh, sessionToken]);
|
|
241
|
+
const revokeAllSessions = useCallback(async () => {
|
|
242
|
+
if (!sessionToken) {
|
|
243
|
+
throw new Error("No authenticated session");
|
|
244
|
+
}
|
|
245
|
+
const res = await fetch(`${apiUrl}/api/auth/sessions`, {
|
|
246
|
+
method: "DELETE",
|
|
247
|
+
headers: {
|
|
248
|
+
"X-AscendKit-Public-Key": publicKey,
|
|
249
|
+
"Authorization": `Bearer ${sessionToken}`,
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
const json = await res.json().catch(() => ({}));
|
|
253
|
+
if (!res.ok) {
|
|
254
|
+
throw new Error(json.detail || json.error || "Failed to revoke all sessions");
|
|
255
|
+
}
|
|
256
|
+
await refresh();
|
|
257
|
+
}, [apiUrl, publicKey, refresh, sessionToken]);
|
|
258
|
+
return {
|
|
259
|
+
sessions,
|
|
260
|
+
isLoaded: !isPending && !loading,
|
|
261
|
+
isAuthenticated: !!sessionToken,
|
|
262
|
+
loading,
|
|
263
|
+
error,
|
|
264
|
+
refresh,
|
|
265
|
+
revokeSession,
|
|
266
|
+
revokeAllSessions,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* User profile hook with mutation helpers.
|
|
271
|
+
*/
|
|
272
|
+
export function useUser() {
|
|
273
|
+
const { authClient, apiUrl, publicKey } = useAscendKitContext();
|
|
274
|
+
const { data: session, isPending } = authClient.useSession();
|
|
275
|
+
const sessionToken = session?.session?.token;
|
|
276
|
+
const [user, setUser] = useState(null);
|
|
277
|
+
const [loading, setLoading] = useState(false);
|
|
278
|
+
const [error, setError] = useState(null);
|
|
279
|
+
const refresh = useCallback(async () => {
|
|
280
|
+
if (!sessionToken) {
|
|
281
|
+
setUser(null);
|
|
282
|
+
setError(null);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
setLoading(true);
|
|
286
|
+
setError(null);
|
|
287
|
+
try {
|
|
288
|
+
const res = await fetch(`${apiUrl}/api/users/me`, {
|
|
289
|
+
headers: {
|
|
290
|
+
"X-AscendKit-Public-Key": publicKey,
|
|
291
|
+
"Authorization": `Bearer ${sessionToken}`,
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
const json = await res.json().catch(() => ({}));
|
|
295
|
+
if (!res.ok) {
|
|
296
|
+
throw new Error(json.detail || json.error || "Failed to load user profile");
|
|
297
|
+
}
|
|
298
|
+
setUser(json.data ?? null);
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
setError(err instanceof Error ? err.message : "Failed to load user profile");
|
|
302
|
+
setUser(null);
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
setLoading(false);
|
|
306
|
+
}
|
|
307
|
+
}, [apiUrl, publicKey, sessionToken]);
|
|
308
|
+
useEffect(() => {
|
|
309
|
+
void refresh();
|
|
310
|
+
}, [refresh]);
|
|
311
|
+
const updateProfile = useCallback(async (data) => {
|
|
312
|
+
if (!sessionToken) {
|
|
313
|
+
throw new Error("No authenticated session");
|
|
314
|
+
}
|
|
315
|
+
const res = await fetch(`${apiUrl}/api/users/me`, {
|
|
316
|
+
method: "PATCH",
|
|
317
|
+
headers: {
|
|
318
|
+
"Content-Type": "application/json",
|
|
319
|
+
"X-AscendKit-Public-Key": publicKey,
|
|
320
|
+
"Authorization": `Bearer ${sessionToken}`,
|
|
321
|
+
},
|
|
322
|
+
body: JSON.stringify(data),
|
|
323
|
+
});
|
|
324
|
+
const json = await res.json().catch(() => ({}));
|
|
325
|
+
if (!res.ok) {
|
|
326
|
+
throw new Error(json.detail || json.error || "Failed to update profile");
|
|
327
|
+
}
|
|
328
|
+
setUser(json.data ?? null);
|
|
329
|
+
return json.data ?? null;
|
|
330
|
+
}, [apiUrl, publicKey, sessionToken]);
|
|
331
|
+
const deleteAccount = useCallback(async () => {
|
|
332
|
+
if (!sessionToken) {
|
|
333
|
+
throw new Error("No authenticated session");
|
|
334
|
+
}
|
|
335
|
+
const res = await fetch(`${apiUrl}/api/users/me`, {
|
|
336
|
+
method: "DELETE",
|
|
337
|
+
headers: {
|
|
338
|
+
"X-AscendKit-Public-Key": publicKey,
|
|
339
|
+
"Authorization": `Bearer ${sessionToken}`,
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
const json = await res.json().catch(() => ({}));
|
|
343
|
+
if (!res.ok) {
|
|
344
|
+
throw new Error(json.detail || json.error || "Failed to delete account");
|
|
345
|
+
}
|
|
346
|
+
setUser(null);
|
|
347
|
+
await authClient.signOut();
|
|
348
|
+
return { ok: true };
|
|
349
|
+
}, [apiUrl, authClient, publicKey, sessionToken]);
|
|
350
|
+
return {
|
|
351
|
+
user,
|
|
352
|
+
isLoaded: !isPending && !loading,
|
|
353
|
+
isAuthenticated: !!sessionToken,
|
|
354
|
+
loading,
|
|
355
|
+
error,
|
|
356
|
+
refresh,
|
|
357
|
+
updateProfile,
|
|
358
|
+
deleteAccount,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
export async function verifyEmail(apiUrl, publicKey, token) {
|
|
362
|
+
const res = await fetch(`${apiUrl}/api/auth/verify-email`, {
|
|
363
|
+
method: "POST",
|
|
364
|
+
headers: { "Content-Type": "application/json" },
|
|
365
|
+
body: JSON.stringify({ publicKey, token }),
|
|
366
|
+
});
|
|
367
|
+
const json = await res.json().catch(() => ({}));
|
|
368
|
+
if (!res.ok || !json.data) {
|
|
369
|
+
throw new Error(json.detail || json.error || "Email verification failed");
|
|
370
|
+
}
|
|
371
|
+
return json.data;
|
|
372
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { AscendKitProvider, useAscendKitContext, type AuthNotification } from "./provider";
|
|
2
|
+
export { useAscendKit, useAuthModal, useAccessToken, useSessions, useUser, verifyEmail } from "./hooks";
|
|
3
|
+
export { useAnalytics } from "./use-analytics";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC3F,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACxG,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createAuthClient } from "better-auth/react";
|
|
2
|
+
import type { BrandingConfig } from "../shared/types";
|
|
3
|
+
export type AuthModalView = "sign-in" | "sign-up";
|
|
4
|
+
export interface AuthModalState {
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
view: AuthModalView;
|
|
7
|
+
open: (view?: AuthModalView) => void;
|
|
8
|
+
close: () => void;
|
|
9
|
+
}
|
|
10
|
+
type ToastVariant = "default" | "success" | "error" | "info" | "warning";
|
|
11
|
+
export interface AuthNotification {
|
|
12
|
+
variant: ToastVariant;
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
interface AscendKitContextValue {
|
|
16
|
+
publicKey: string;
|
|
17
|
+
apiUrl: string;
|
|
18
|
+
authClient: ReturnType<typeof createAuthClient>;
|
|
19
|
+
enabledProviders: string[];
|
|
20
|
+
refreshSettings: () => Promise<void>;
|
|
21
|
+
/** True once the settings fetch has completed (success or failure). */
|
|
22
|
+
settingsLoaded: boolean;
|
|
23
|
+
/** Non-null when the settings fetch failed or returned no usable providers. */
|
|
24
|
+
settingsError: string | null;
|
|
25
|
+
branding: BrandingConfig;
|
|
26
|
+
modal: AuthModalState;
|
|
27
|
+
/** Display name of the environment (e.g. "Acme Corp"). Used for contextual auth headings. */
|
|
28
|
+
environmentName: string;
|
|
29
|
+
/** In-modal notification set by auth form actions (signup, login errors). */
|
|
30
|
+
authNotification: AuthNotification | null;
|
|
31
|
+
clearAuthNotification: () => void;
|
|
32
|
+
}
|
|
33
|
+
export declare function useAscendKitContext(): AscendKitContextValue;
|
|
34
|
+
interface AscendKitProviderProps {
|
|
35
|
+
publicKey?: string;
|
|
36
|
+
apiUrl?: string;
|
|
37
|
+
/** Override auto-detected providers. Skips the settings fetch. */
|
|
38
|
+
enabledProviders?: string[];
|
|
39
|
+
/** Refresh auth settings when browser tab regains focus. Default false. */
|
|
40
|
+
onFocusRefresh?: boolean;
|
|
41
|
+
/** Base path for auth views (default: "/auth"). */
|
|
42
|
+
basePath?: string;
|
|
43
|
+
/** Custom path segments for auth views (e.g. { SIGN_IN: "login", SIGN_UP: "signup" }). */
|
|
44
|
+
viewPaths?: Record<string, string>;
|
|
45
|
+
children: React.ReactNode;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Wraps Better Auth UI's AuthUIProvider and provides AscendKit config context.
|
|
49
|
+
*
|
|
50
|
+
* Automatically fetches enabled providers and branding config on mount.
|
|
51
|
+
* Pass `enabledProviders` to skip the fetch and use static config.
|
|
52
|
+
*
|
|
53
|
+
* Usage (zero-config — reads from env vars automatically):
|
|
54
|
+
* ```tsx
|
|
55
|
+
* <AscendKitProvider>
|
|
56
|
+
* {children}
|
|
57
|
+
* </AscendKitProvider>
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* Environment variables (set by `ascendkit init` + `ascendkit set-env`):
|
|
61
|
+
* - NEXT_PUBLIC_ASCENDKIT_ENV_KEY — public key (client-side)
|
|
62
|
+
* - NEXT_PUBLIC_ASCENDKIT_API_URL — backend URL (default: https://api.ascendkit.com)
|
|
63
|
+
*/
|
|
64
|
+
export declare function AscendKitProvider({ publicKey, apiUrl, enabledProviders: overrideProviders, onFocusRefresh, basePath, viewPaths, children, }: AscendKitProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/client/provider.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,SAAS,CAAC;AAElD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;IACrC,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,KAAK,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAEzE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,qBAAqB;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;IAChD,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,uEAAuE;IACvE,cAAc,EAAE,OAAO,CAAC;IACxB,+EAA+E;IAC/E,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,cAAc,CAAC;IACtB,6FAA6F;IAC7F,eAAe,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,qBAAqB,EAAE,MAAM,IAAI,CAAC;CACnC;AA2BD,wBAAgB,mBAAmB,0BAMlC;AAED,UAAU,sBAAsB;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,2EAA2E;IAC3E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0FAA0F;IAC1F,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAcD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAA2D,EAC3D,MAAiF,EACjF,gBAAgB,EAAE,iBAAiB,EACnC,cAAsB,EACtB,QAAQ,EACR,SAAS,EACT,QAAQ,GACT,EAAE,sBAAsB,2CAqSxB"}
|