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.
Files changed (4) hide show
  1. package/README.md +3 -7
  2. package/auth.d.ts +5 -2
  3. package/auth.js +113 -43
  4. 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 <a href="/login">Sign in</a>;
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, login, logout, and current user helpers.
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
- export async function login(input, options) {
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
- function readAuthConfigSnapshot() {
22
- return getTalizenConfig();
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 configSnapshot = React.useSyncExternalStore(subscribeTalizenConfig, readAuthConfigSnapshot, readAuthConfigSnapshot);
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
- const requestId = requestIdRef.current + 1;
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
- }, [configSnapshot, refresh]);
63
- const loginAndRefresh = React.useCallback(async (input) => {
64
- const nextUser = await login(input, optionsRef.current);
65
- requestIdRef.current += 1;
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
- requestIdRef.current += 1;
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 logout(optionsRef.current);
77
- requestIdRef.current += 1;
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
- requestIdRef.current += 1;
83
- setState({ user: nextUser, loading: false, error: null });
153
+ setAuthUser(nextUser);
84
154
  return nextUser;
85
155
  }, []);
86
156
  return {
87
- ...state,
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
- export async function logout(options) {
165
+ async function logoutRequest(options) {
96
166
  await requestJson("/auth/logout", {
97
167
  method: "POST",
98
168
  body: JSON.stringify({}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "talizen",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "description": "Talizen frontend SDK types for cms, form and core.",
5
5
  "type": "module",
6
6
  "license": "MIT",