talizen 0.2.13 → 0.2.15
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 +8 -0
- package/auth.d.ts +22 -0
- package/auth.js +146 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -120,6 +120,7 @@ import {
|
|
|
120
120
|
logout,
|
|
121
121
|
register,
|
|
122
122
|
updateProfile,
|
|
123
|
+
useAuth,
|
|
123
124
|
} from "talizen/auth";
|
|
124
125
|
|
|
125
126
|
await register({
|
|
@@ -140,6 +141,13 @@ const providers = await listAuthProviders();
|
|
|
140
141
|
console.log(providers.map((provider) => provider.key));
|
|
141
142
|
|
|
142
143
|
await loginWithOAuth("github", { redirectUrl: "/account" });
|
|
144
|
+
|
|
145
|
+
function AccountBadge() {
|
|
146
|
+
const { user, loading, logout } = useAuth();
|
|
147
|
+
if (loading) return <span>Loading...</span>;
|
|
148
|
+
if (!user) return <a href="/login">Sign in</a>;
|
|
149
|
+
return <button onClick={() => logout()}>{user.name ?? user.account ?? "Logout"}</button>;
|
|
150
|
+
}
|
|
143
151
|
```
|
|
144
152
|
|
|
145
153
|
### Invoke a custom function
|
package/auth.d.ts
CHANGED
|
@@ -49,11 +49,33 @@ 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
57
|
export declare function login(input: AuthPasswordInput, options?: TalizenRequestOptions): Promise<AuthUser>;
|
|
58
|
+
export interface UseAuthResult {
|
|
59
|
+
user: AuthUser | null;
|
|
60
|
+
loading: boolean;
|
|
61
|
+
error: unknown;
|
|
62
|
+
isAuthenticated: boolean;
|
|
63
|
+
isInitialized: boolean;
|
|
64
|
+
refresh(): Promise<AuthUser | null>;
|
|
65
|
+
login(input: AuthPasswordInput): Promise<AuthUser>;
|
|
66
|
+
login(account: string, password: string): Promise<AuthUser>;
|
|
67
|
+
register(input: AuthRegisterInput): Promise<AuthUser>;
|
|
68
|
+
register(account: string, password: string, name?: string): Promise<AuthUser>;
|
|
69
|
+
logout(): Promise<void>;
|
|
70
|
+
updateProfile(profile: AuthProfile): Promise<AuthUser>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Subscribe to the current auth user.
|
|
74
|
+
*
|
|
75
|
+
* The hook reloads `/auth/me` whenever `setTalizenConfig()` changes runtime
|
|
76
|
+
* config, which lets preview auth headers update the page without remounting.
|
|
77
|
+
*/
|
|
78
|
+
export declare function useAuth(options?: TalizenRequestOptions): UseAuthResult;
|
|
57
79
|
export declare function logout(options?: TalizenRequestOptions): Promise<void>;
|
|
58
80
|
export declare function currentUser(options?: TalizenRequestOptions): Promise<AuthUser | null>;
|
|
59
81
|
export declare function updateProfile(profile: AuthProfile, options?: TalizenRequestOptions): Promise<AuthUser>;
|
package/auth.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { getTalizenConfig, requestJson, subscribeTalizenConfig, TalizenHttpError, } from "./core.js";
|
|
2
3
|
export class TalizenAuthError extends Error {
|
|
3
4
|
constructor(message) {
|
|
4
5
|
super(message);
|
|
@@ -17,6 +18,150 @@ export async function login(input, options) {
|
|
|
17
18
|
body: JSON.stringify(input),
|
|
18
19
|
}, options);
|
|
19
20
|
}
|
|
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();
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Subscribe to the current auth user.
|
|
120
|
+
*
|
|
121
|
+
* The hook reloads `/auth/me` whenever `setTalizenConfig()` changes runtime
|
|
122
|
+
* config, which lets preview auth headers update the page without remounting.
|
|
123
|
+
*/
|
|
124
|
+
export function useAuth(options) {
|
|
125
|
+
const snapshot = React.useSyncExternalStore(subscribeAuth, () => authSnapshot, () => authSnapshot);
|
|
126
|
+
const optionsRef = React.useRef(options);
|
|
127
|
+
React.useEffect(() => {
|
|
128
|
+
optionsRef.current = options;
|
|
129
|
+
}, [options]);
|
|
130
|
+
const refresh = React.useCallback(async () => {
|
|
131
|
+
return refreshAuthState(optionsRef.current);
|
|
132
|
+
}, []);
|
|
133
|
+
React.useEffect(() => {
|
|
134
|
+
ensureAuthConfigSubscription();
|
|
135
|
+
void refresh().catch(() => { });
|
|
136
|
+
}, [refresh]);
|
|
137
|
+
const loginAndRefresh = React.useCallback(async (input, password) => {
|
|
138
|
+
const nextUser = await login(normalizeLoginInput(input, password), optionsRef.current);
|
|
139
|
+
setAuthUser(nextUser);
|
|
140
|
+
return nextUser;
|
|
141
|
+
}, []);
|
|
142
|
+
const registerAndRefresh = React.useCallback(async (input, password, name) => {
|
|
143
|
+
const nextUser = await register(normalizeRegisterInput(input, password, name), optionsRef.current);
|
|
144
|
+
setAuthUser(nextUser);
|
|
145
|
+
return nextUser;
|
|
146
|
+
}, []);
|
|
147
|
+
const logoutAndRefresh = React.useCallback(async () => {
|
|
148
|
+
await logout(optionsRef.current);
|
|
149
|
+
setAuthUser(null);
|
|
150
|
+
}, []);
|
|
151
|
+
const updateProfileAndRefresh = React.useCallback(async (profile) => {
|
|
152
|
+
const nextUser = await updateProfile(profile, optionsRef.current);
|
|
153
|
+
setAuthUser(nextUser);
|
|
154
|
+
return nextUser;
|
|
155
|
+
}, []);
|
|
156
|
+
return {
|
|
157
|
+
...snapshot,
|
|
158
|
+
refresh,
|
|
159
|
+
login: loginAndRefresh,
|
|
160
|
+
register: registerAndRefresh,
|
|
161
|
+
logout: logoutAndRefresh,
|
|
162
|
+
updateProfile: updateProfileAndRefresh,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
20
165
|
export async function logout(options) {
|
|
21
166
|
await requestJson("/auth/logout", {
|
|
22
167
|
method: "POST",
|