talizen 0.2.14 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -7
- package/auth.d.ts +5 -2
- package/auth.js +113 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -115,9 +115,7 @@ When a `File` object appears in the payload, `submitForm()` will:
|
|
|
115
115
|
import {
|
|
116
116
|
currentUser,
|
|
117
117
|
listAuthProviders,
|
|
118
|
-
login,
|
|
119
118
|
loginWithOAuth,
|
|
120
|
-
logout,
|
|
121
119
|
register,
|
|
122
120
|
updateProfile,
|
|
123
121
|
useAuth,
|
|
@@ -129,13 +127,11 @@ await register({
|
|
|
129
127
|
name: "Alice",
|
|
130
128
|
email: "hi@talizen.com",
|
|
131
129
|
});
|
|
132
|
-
await login({ account: "alice", password: "secret" });
|
|
133
130
|
|
|
134
131
|
const user = await currentUser();
|
|
135
132
|
await updateProfile({
|
|
136
133
|
address: "No. 1 Example Road",
|
|
137
134
|
});
|
|
138
|
-
await logout();
|
|
139
135
|
|
|
140
136
|
const providers = await listAuthProviders();
|
|
141
137
|
console.log(providers.map((provider) => provider.key));
|
|
@@ -143,9 +139,9 @@ console.log(providers.map((provider) => provider.key));
|
|
|
143
139
|
await loginWithOAuth("github", { redirectUrl: "/account" });
|
|
144
140
|
|
|
145
141
|
function AccountBadge() {
|
|
146
|
-
const { user, loading, logout } = useAuth();
|
|
142
|
+
const { user, loading, login, logout } = useAuth();
|
|
147
143
|
if (loading) return <span>Loading...</span>;
|
|
148
|
-
if (!user) return <
|
|
144
|
+
if (!user) return <button onClick={() => login("alice", "secret")}>Sign in</button>;
|
|
149
145
|
return <button onClick={() => logout()}>{user.name ?? user.account ?? "Logout"}</button>;
|
|
150
146
|
}
|
|
151
147
|
```
|
|
@@ -231,7 +227,7 @@ Func HTTP responses use the HTTP status code rather than a top-level `ok` field.
|
|
|
231
227
|
## Package Layout
|
|
232
228
|
|
|
233
229
|
- `talizen/core`: shared runtime config, request helpers, and base data types.
|
|
234
|
-
- `talizen/auth`: project user register,
|
|
230
|
+
- `talizen/auth`: project user register, current user helpers, and the React `useAuth()` state hook for login/logout flows.
|
|
235
231
|
- `talizen/cms`: CMS content types and content query APIs.
|
|
236
232
|
- `talizen/form`: form submission helpers and related types.
|
|
237
233
|
- `talizen/func`: custom function invocation helpers such as `invoke`.
|
package/auth.d.ts
CHANGED
|
@@ -49,18 +49,22 @@ export interface AuthProviderListResponse {
|
|
|
49
49
|
export interface AuthOAuthLoginURLOptions extends TalizenRequestOptions {
|
|
50
50
|
redirectUrl?: string;
|
|
51
51
|
}
|
|
52
|
+
export type User = AuthUser;
|
|
52
53
|
export declare class TalizenAuthError extends Error {
|
|
53
54
|
constructor(message: string);
|
|
54
55
|
}
|
|
55
56
|
export declare function register(input: AuthRegisterInput, options?: TalizenRequestOptions): Promise<AuthUser>;
|
|
56
|
-
export declare function login(input: AuthPasswordInput, options?: TalizenRequestOptions): Promise<AuthUser>;
|
|
57
57
|
export interface UseAuthResult {
|
|
58
58
|
user: AuthUser | null;
|
|
59
59
|
loading: boolean;
|
|
60
60
|
error: unknown;
|
|
61
|
+
isAuthenticated: boolean;
|
|
62
|
+
isInitialized: boolean;
|
|
61
63
|
refresh(): Promise<AuthUser | null>;
|
|
62
64
|
login(input: AuthPasswordInput): Promise<AuthUser>;
|
|
65
|
+
login(account: string, password: string): Promise<AuthUser>;
|
|
63
66
|
register(input: AuthRegisterInput): Promise<AuthUser>;
|
|
67
|
+
register(account: string, password: string, name?: string): Promise<AuthUser>;
|
|
64
68
|
logout(): Promise<void>;
|
|
65
69
|
updateProfile(profile: AuthProfile): Promise<AuthUser>;
|
|
66
70
|
}
|
|
@@ -71,7 +75,6 @@ export interface UseAuthResult {
|
|
|
71
75
|
* config, which lets preview auth headers update the page without remounting.
|
|
72
76
|
*/
|
|
73
77
|
export declare function useAuth(options?: TalizenRequestOptions): UseAuthResult;
|
|
74
|
-
export declare function logout(options?: TalizenRequestOptions): Promise<void>;
|
|
75
78
|
export declare function currentUser(options?: TalizenRequestOptions): Promise<AuthUser | null>;
|
|
76
79
|
export declare function updateProfile(profile: AuthProfile, options?: TalizenRequestOptions): Promise<AuthUser>;
|
|
77
80
|
export declare function requireUser(options?: TalizenRequestOptions): Promise<AuthUser>;
|
package/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { getTalizenConfig, requestJson, subscribeTalizenConfig, } from "./core.js";
|
|
2
|
+
import { getTalizenConfig, requestJson, subscribeTalizenConfig, TalizenHttpError, } from "./core.js";
|
|
3
3
|
export class TalizenAuthError extends Error {
|
|
4
4
|
constructor(message) {
|
|
5
5
|
super(message);
|
|
@@ -12,14 +12,108 @@ export async function register(input, options) {
|
|
|
12
12
|
body: JSON.stringify(input),
|
|
13
13
|
}, options);
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
async function loginRequest(input, options) {
|
|
16
16
|
return requestJson("/auth/login", {
|
|
17
17
|
method: "POST",
|
|
18
18
|
body: JSON.stringify(input),
|
|
19
19
|
}, options);
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const authListeners = new Set();
|
|
22
|
+
let currentUserState = null;
|
|
23
|
+
let authErrorState = null;
|
|
24
|
+
let isAuthInitialized = false;
|
|
25
|
+
let isAuthLoading = false;
|
|
26
|
+
let authSnapshot = readAuthSnapshot();
|
|
27
|
+
let authRequestId = 0;
|
|
28
|
+
let isAuthConfigSubscriptionStarted = false;
|
|
29
|
+
function readAuthSnapshot() {
|
|
30
|
+
return {
|
|
31
|
+
user: currentUserState,
|
|
32
|
+
loading: isAuthLoading,
|
|
33
|
+
error: authErrorState,
|
|
34
|
+
isAuthenticated: !!currentUserState,
|
|
35
|
+
isInitialized: isAuthInitialized,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function emitAuthChange() {
|
|
39
|
+
authSnapshot = readAuthSnapshot();
|
|
40
|
+
authListeners.forEach(listener => listener());
|
|
41
|
+
}
|
|
42
|
+
function subscribeAuth(listener) {
|
|
43
|
+
authListeners.add(listener);
|
|
44
|
+
return () => {
|
|
45
|
+
authListeners.delete(listener);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function ensureAuthConfigSubscription() {
|
|
49
|
+
if (isAuthConfigSubscriptionStarted)
|
|
50
|
+
return;
|
|
51
|
+
isAuthConfigSubscriptionStarted = true;
|
|
52
|
+
subscribeTalizenConfig(() => {
|
|
53
|
+
void refreshAuthState().catch(() => { });
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function normalizeLoginInput(input, password) {
|
|
57
|
+
if (typeof input === "string") {
|
|
58
|
+
return {
|
|
59
|
+
account: input,
|
|
60
|
+
password: password ?? "",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return input;
|
|
64
|
+
}
|
|
65
|
+
function normalizeRegisterInput(input, password, name) {
|
|
66
|
+
if (typeof input === "string") {
|
|
67
|
+
return {
|
|
68
|
+
account: input,
|
|
69
|
+
email: input,
|
|
70
|
+
password: password ?? "",
|
|
71
|
+
name,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return input;
|
|
75
|
+
}
|
|
76
|
+
function isUnauthenticatedError(error) {
|
|
77
|
+
return error instanceof TalizenHttpError && error.status === 401;
|
|
78
|
+
}
|
|
79
|
+
async function refreshAuthState(options) {
|
|
80
|
+
const requestId = authRequestId + 1;
|
|
81
|
+
authRequestId = requestId;
|
|
82
|
+
isAuthLoading = true;
|
|
83
|
+
authErrorState = null;
|
|
84
|
+
emitAuthChange();
|
|
85
|
+
try {
|
|
86
|
+
const user = await currentUser(options);
|
|
87
|
+
if (authRequestId === requestId) {
|
|
88
|
+
currentUserState = user;
|
|
89
|
+
authErrorState = null;
|
|
90
|
+
isAuthInitialized = true;
|
|
91
|
+
isAuthLoading = false;
|
|
92
|
+
emitAuthChange();
|
|
93
|
+
}
|
|
94
|
+
return user;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (authRequestId === requestId) {
|
|
98
|
+
currentUserState = null;
|
|
99
|
+
authErrorState = isUnauthenticatedError(error) ? null : error;
|
|
100
|
+
isAuthInitialized = true;
|
|
101
|
+
isAuthLoading = false;
|
|
102
|
+
emitAuthChange();
|
|
103
|
+
}
|
|
104
|
+
if (isUnauthenticatedError(error)) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function setAuthUser(user) {
|
|
111
|
+
authRequestId += 1;
|
|
112
|
+
currentUserState = user;
|
|
113
|
+
authErrorState = null;
|
|
114
|
+
isAuthInitialized = true;
|
|
115
|
+
isAuthLoading = false;
|
|
116
|
+
emitAuthChange();
|
|
23
117
|
}
|
|
24
118
|
/**
|
|
25
119
|
* Subscribe to the current auth user.
|
|
@@ -28,63 +122,39 @@ function readAuthConfigSnapshot() {
|
|
|
28
122
|
* config, which lets preview auth headers update the page without remounting.
|
|
29
123
|
*/
|
|
30
124
|
export function useAuth(options) {
|
|
31
|
-
const
|
|
125
|
+
const snapshot = React.useSyncExternalStore(subscribeAuth, () => authSnapshot, () => authSnapshot);
|
|
32
126
|
const optionsRef = React.useRef(options);
|
|
33
|
-
const requestIdRef = React.useRef(0);
|
|
34
|
-
const [state, setState] = React.useState({
|
|
35
|
-
user: null,
|
|
36
|
-
loading: true,
|
|
37
|
-
error: null,
|
|
38
|
-
});
|
|
39
127
|
React.useEffect(() => {
|
|
40
128
|
optionsRef.current = options;
|
|
41
129
|
}, [options]);
|
|
42
130
|
const refresh = React.useCallback(async () => {
|
|
43
|
-
|
|
44
|
-
requestIdRef.current = requestId;
|
|
45
|
-
setState(current => ({ ...current, loading: true, error: null }));
|
|
46
|
-
try {
|
|
47
|
-
const nextUser = await currentUser(optionsRef.current);
|
|
48
|
-
if (requestIdRef.current === requestId) {
|
|
49
|
-
setState({ user: nextUser, loading: false, error: null });
|
|
50
|
-
}
|
|
51
|
-
return nextUser;
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
if (requestIdRef.current === requestId) {
|
|
55
|
-
setState(current => ({ ...current, loading: false, error }));
|
|
56
|
-
}
|
|
57
|
-
throw error;
|
|
58
|
-
}
|
|
131
|
+
return refreshAuthState(optionsRef.current);
|
|
59
132
|
}, []);
|
|
60
133
|
React.useEffect(() => {
|
|
134
|
+
ensureAuthConfigSubscription();
|
|
61
135
|
void refresh().catch(() => { });
|
|
62
|
-
}, [
|
|
63
|
-
const loginAndRefresh = React.useCallback(async (input) => {
|
|
64
|
-
const nextUser = await
|
|
65
|
-
|
|
66
|
-
setState({ user: nextUser, loading: false, error: null });
|
|
136
|
+
}, [refresh]);
|
|
137
|
+
const loginAndRefresh = React.useCallback(async (input, password) => {
|
|
138
|
+
const nextUser = await loginRequest(normalizeLoginInput(input, password), optionsRef.current);
|
|
139
|
+
setAuthUser(nextUser);
|
|
67
140
|
return nextUser;
|
|
68
141
|
}, []);
|
|
69
|
-
const registerAndRefresh = React.useCallback(async (input) => {
|
|
70
|
-
const nextUser = await register(input, optionsRef.current);
|
|
71
|
-
|
|
72
|
-
setState({ user: nextUser, loading: false, error: null });
|
|
142
|
+
const registerAndRefresh = React.useCallback(async (input, password, name) => {
|
|
143
|
+
const nextUser = await register(normalizeRegisterInput(input, password, name), optionsRef.current);
|
|
144
|
+
setAuthUser(nextUser);
|
|
73
145
|
return nextUser;
|
|
74
146
|
}, []);
|
|
75
147
|
const logoutAndRefresh = React.useCallback(async () => {
|
|
76
|
-
await
|
|
77
|
-
|
|
78
|
-
setState({ user: null, loading: false, error: null });
|
|
148
|
+
await logoutRequest(optionsRef.current);
|
|
149
|
+
setAuthUser(null);
|
|
79
150
|
}, []);
|
|
80
151
|
const updateProfileAndRefresh = React.useCallback(async (profile) => {
|
|
81
152
|
const nextUser = await updateProfile(profile, optionsRef.current);
|
|
82
|
-
|
|
83
|
-
setState({ user: nextUser, loading: false, error: null });
|
|
153
|
+
setAuthUser(nextUser);
|
|
84
154
|
return nextUser;
|
|
85
155
|
}, []);
|
|
86
156
|
return {
|
|
87
|
-
...
|
|
157
|
+
...snapshot,
|
|
88
158
|
refresh,
|
|
89
159
|
login: loginAndRefresh,
|
|
90
160
|
register: registerAndRefresh,
|
|
@@ -92,7 +162,7 @@ export function useAuth(options) {
|
|
|
92
162
|
updateProfile: updateProfileAndRefresh,
|
|
93
163
|
};
|
|
94
164
|
}
|
|
95
|
-
|
|
165
|
+
async function logoutRequest(options) {
|
|
96
166
|
await requestJson("/auth/logout", {
|
|
97
167
|
method: "POST",
|
|
98
168
|
body: JSON.stringify({}),
|