@fluoce/auth-react 0.1.1 → 0.2.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/dist/index.js CHANGED
@@ -1,276 +1,371 @@
1
- // src/FluoceAuthProvider.tsx
1
+ // src/provider/auth_provider.tsx
2
2
  import {
3
- useCallback,
4
- useEffect,
5
- useMemo,
6
- useRef,
7
- useState
3
+ useMemo as use_memo,
4
+ useState as use_state,
5
+ useEffect as use_effect
8
6
  } from "react";
9
7
 
10
- // src/AuthContext.tsx
11
- import { createContext, useContext } from "react";
12
- var FluoceAuthContext = createContext(
13
- null
14
- );
15
- function useFluoceAuth() {
16
- const ctx = useContext(FluoceAuthContext);
17
- if (!ctx)
18
- throw new Error("useFluoceAuth must be used within <FluoceAuthProvider>");
19
- return ctx;
20
- }
8
+ // src/const/api_endpoint.ts
9
+ var base_url = "https://auth-service.fluoce.com";
10
+ var api_endpoint = {
11
+ me: "/me",
12
+ exchange: "/exchange",
13
+ refresh: "/refresh",
14
+ logout: "/logout"
15
+ };
16
+
17
+ // src/const/route.ts
18
+ var route = {
19
+ fluoce_auth: (ref) => `https://auth.fluoce.com/auth?ref=${ref}`
20
+ };
21
21
 
22
- // src/cookies.ts
23
- function setCookie(name, value, days) {
22
+ // src/func/func_cookies.ts
23
+ function func_set_cookie(name, value, days) {
24
24
  const expires = new Date(Date.now() + days * 864e5).toUTCString();
25
25
  document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`;
26
26
  }
27
- function getCookie(name) {
27
+ function func_get_cookie(name) {
28
28
  const escaped = name.replace(/([.$?*|{}()[\]\\/+^])/g, "\\$1");
29
- const match = document.cookie.match(new RegExp("(?:^|; )" + escaped + "=([^;]*)"));
29
+ const match = document.cookie.match(
30
+ new RegExp("(?:^|; )" + escaped + "=([^;]*)")
31
+ );
30
32
  return match ? decodeURIComponent(match[1]) : null;
31
33
  }
32
- function deleteCookie(name) {
33
- document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
34
+ function func_delete_cookie(name) {
35
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax`;
36
+ }
37
+
38
+ // src/func/func_try_catch.ts
39
+ async function func_try_catch({
40
+ fn,
41
+ onError
42
+ }) {
43
+ try {
44
+ return await fn();
45
+ } catch (error) {
46
+ if (onError) {
47
+ onError(error);
48
+ return null;
49
+ }
50
+ if (error instanceof Error) {
51
+ throw error;
52
+ } else {
53
+ throw new Error(String(error));
54
+ }
55
+ }
34
56
  }
35
57
 
36
- // src/apiClient.ts
37
- var FluoceApiClient = class {
38
- constructor(config) {
39
- this.refreshPromise = null;
40
- /** set by the Provider — called once refresh fails and there's no session left */
41
- this.onAuthFailure = () => {
58
+ // src/api/api_service.ts
59
+ var Api_service = class {
60
+ constructor({
61
+ app_url,
62
+ on_error,
63
+ redirect
64
+ }, http = fetch) {
65
+ this.http = http;
66
+ this.base_url = base_url;
67
+ this.api_endpoint = api_endpoint;
68
+ this.redirect = {
69
+ type: "fluoce"
42
70
  };
43
- this.config = {
44
- accessTokenCookie: "fluoce_access_token",
45
- refreshTokenCookie: "fluoce_refresh_token",
46
- accessTokenExpiryDays: 1,
47
- refreshTokenExpiryDays: 30,
48
- ...config
71
+ this.redirect_at_fluoce_auth = (error) => {
72
+ console.warn(error);
73
+ window.location.replace(route.fluoce_auth(this.app_url));
49
74
  };
75
+ this.handle_redirect = (error) => {
76
+ var _a;
77
+ switch ((_a = this.redirect) == null ? void 0 : _a.type) {
78
+ case "fluoce":
79
+ this.redirect_at_fluoce_auth(error);
80
+ break;
81
+ case "custom":
82
+ this.redirect.redirect(error);
83
+ break;
84
+ case "none":
85
+ break;
86
+ }
87
+ };
88
+ this.handle_error = (error) => {
89
+ if (this.on_error) {
90
+ this.on_error(error);
91
+ return;
92
+ }
93
+ this.handle_redirect(error);
94
+ };
95
+ this.get_access_token = () => {
96
+ return func_get_cookie("accessToken");
97
+ };
98
+ this.get_refresh_token = () => {
99
+ return func_get_cookie("refreshToken");
100
+ };
101
+ this.delete_cookie = () => {
102
+ func_delete_cookie("accessToken");
103
+ func_delete_cookie("refreshToken");
104
+ };
105
+ this.app_url = app_url;
106
+ this.on_error = on_error;
107
+ if (redirect) {
108
+ this.redirect = redirect;
109
+ }
50
110
  }
51
- getAccessToken() {
52
- return getCookie(this.config.accessTokenCookie);
53
- }
54
- getRefreshToken() {
55
- return getCookie(this.config.refreshTokenCookie);
56
- }
57
- setTokens(tokens) {
58
- setCookie(
59
- this.config.accessTokenCookie,
60
- tokens.accessToken,
61
- this.config.accessTokenExpiryDays
62
- );
63
- setCookie(
64
- this.config.refreshTokenCookie,
65
- tokens.refreshToken,
66
- this.config.refreshTokenExpiryDays
111
+ async me({ accessToken }) {
112
+ const data = await func_try_catch(
113
+ {
114
+ fn: async () => {
115
+ const res = await this.http(this.base_url + this.api_endpoint.me, {
116
+ method: "GET",
117
+ headers: {
118
+ "Content-Type": "application/json",
119
+ Authorization: `Bearer ${accessToken ? accessToken : this.get_access_token()}`
120
+ }
121
+ });
122
+ return await res.json();
123
+ },
124
+ onError: (error) => {
125
+ this.handle_error(error);
126
+ }
127
+ }
67
128
  );
129
+ if (data == null ? void 0 : data.success) {
130
+ return data;
131
+ } else {
132
+ return null;
133
+ }
68
134
  }
69
- clearTokens() {
70
- deleteCookie(this.config.accessTokenCookie);
71
- deleteCookie(this.config.refreshTokenCookie);
72
- }
73
- buildLoginUrl() {
74
- const ref = encodeURIComponent(this.config.appUrl);
75
- return `${this.config.authUrl}/auth?ref=${ref}`;
76
- }
77
- async exchangeCode(code) {
78
- const res = await fetch(`${this.config.apiUrl}/exchange`, {
79
- method: "POST",
80
- headers: { "Content-Type": "application/json" },
81
- body: JSON.stringify({ code })
82
- });
83
- if (!res.ok) throw new Error(`exchange failed: ${res.status}`);
84
- const data = await res.json();
85
- this.setTokens(data);
86
- return data;
87
- }
88
- async getMe() {
89
- return this.authFetch("/me");
135
+ async safe_me({ accessToken }) {
136
+ const data = await func_try_catch(
137
+ {
138
+ fn: async () => {
139
+ const res = await this.http(this.base_url + this.api_endpoint.me, {
140
+ method: "GET",
141
+ headers: {
142
+ "Content-Type": "application/json",
143
+ Authorization: `Bearer ${accessToken ? accessToken : this.get_access_token()}`
144
+ }
145
+ });
146
+ return await res.json();
147
+ }
148
+ }
149
+ );
150
+ if (data == null ? void 0 : data.success) {
151
+ return data;
152
+ } else {
153
+ return null;
154
+ }
90
155
  }
91
- async updateMe(payload) {
92
- return this.authFetch("/me", {
93
- method: "POST",
94
- headers: { "Content-Type": "application/json" },
95
- body: JSON.stringify(payload)
156
+ async exchange({ code }) {
157
+ var _a, _b;
158
+ const data = await func_try_catch({
159
+ fn: async () => {
160
+ const res = await this.http(
161
+ this.base_url + this.api_endpoint.exchange,
162
+ {
163
+ method: "POST",
164
+ headers: {
165
+ "Content-Type": "application/json"
166
+ },
167
+ body: JSON.stringify({ code })
168
+ }
169
+ );
170
+ return await res.json();
171
+ },
172
+ onError: () => {
173
+ this.handle_error();
174
+ }
96
175
  });
176
+ const refreshToken = (_a = data == null ? void 0 : data.data) == null ? void 0 : _a.refreshToken;
177
+ const accessToken = (_b = data == null ? void 0 : data.data) == null ? void 0 : _b.accessToken;
178
+ if (refreshToken && accessToken) {
179
+ func_set_cookie("refreshToken", refreshToken, 59);
180
+ func_set_cookie("accessToken", accessToken, 14 / (24 * 60));
181
+ }
182
+ if (data == null ? void 0 : data.success) {
183
+ return data;
184
+ } else {
185
+ return null;
186
+ }
97
187
  }
98
188
  async refresh() {
99
- if (this.refreshPromise) return this.refreshPromise;
100
- const refreshToken = this.getRefreshToken();
101
- if (!refreshToken) return null;
102
- this.refreshPromise = (async () => {
103
- try {
104
- const res = await fetch(`${this.config.apiUrl}/refresh`, {
189
+ var _a, _b;
190
+ const data = await func_try_catch({
191
+ fn: async () => {
192
+ const res = await this.http(this.base_url + this.api_endpoint.refresh, {
105
193
  method: "POST",
106
194
  headers: {
107
195
  "Content-Type": "application/json",
108
- Authorization: `Bearer ${refreshToken}`
196
+ Authorization: `Bearer ${this.get_refresh_token()}`
109
197
  }
110
198
  });
111
- if (!res.ok) {
112
- this.clearTokens();
113
- return null;
114
- }
115
- const data = await res.json();
116
- this.setTokens(data);
117
- return data;
118
- } catch {
119
- this.clearTokens();
120
- return null;
121
- } finally {
122
- this.refreshPromise = null;
199
+ return await res.json();
200
+ },
201
+ onError: () => {
202
+ this.handle_error();
123
203
  }
124
- })();
125
- return this.refreshPromise;
204
+ });
205
+ const refreshToken = (_a = data == null ? void 0 : data.data) == null ? void 0 : _a.refreshToken;
206
+ const accessToken = (_b = data == null ? void 0 : data.data) == null ? void 0 : _b.accessToken;
207
+ if (refreshToken && accessToken) {
208
+ document.cookie = `refreshToken=${encodeURIComponent(refreshToken)}; path=/;`;
209
+ document.cookie = `accessToken=${encodeURIComponent(accessToken)}; path=/;`;
210
+ }
211
+ if (data == null ? void 0 : data.success) {
212
+ return data;
213
+ } else {
214
+ return null;
215
+ }
126
216
  }
127
- async logout(allDevices = false) {
128
- const refreshToken = this.getRefreshToken();
129
- if (refreshToken) {
130
- try {
131
- await fetch(
132
- `${this.config.apiUrl}${allDevices ? "/logout/all" : "/logout"}`,
133
- {
134
- method: "POST",
135
- headers: {
136
- "Content-Type": "application/json",
137
- Authorization: `Bearer ${refreshToken}`
138
- }
217
+ async logout() {
218
+ this.delete_cookie();
219
+ await func_try_catch({
220
+ fn: async () => {
221
+ const res = await this.http(this.base_url + this.api_endpoint.logout, {
222
+ method: "POST",
223
+ headers: {
224
+ "Content-Type": "application/json",
225
+ Authorization: `Bearer ${this.get_refresh_token()}`
139
226
  }
140
- );
141
- } catch {
227
+ });
228
+ return await res.json();
229
+ },
230
+ onError: () => {
231
+ this.handle_error();
142
232
  }
143
- }
144
- this.clearTokens();
233
+ });
145
234
  }
146
- /**
147
- * Authenticated fetch. Attaches access token, retries once through /refresh
148
- * on 401, and calls onAuthFailure (-> redirect to auth frontend) if that
149
- * fails too. Use this for /me, /me updates, and any endpoint you add later.
150
- */
151
- async authFetch(path, init = {}) {
152
- const doFetch = (token) => fetch(`${this.config.apiUrl}${path}`, {
153
- ...init,
154
- headers: {
155
- ...init.headers || {},
156
- ...token ? { Authorization: `Bearer ${token}` } : {}
157
- }
235
+ async auth_flow({ code }) {
236
+ var _a;
237
+ const exchange_data = await this.exchange({
238
+ code
158
239
  });
159
- let res = await doFetch(this.getAccessToken());
160
- if (res.status === 401) {
161
- const refreshed = await this.refresh();
162
- if (!refreshed) {
163
- this.onAuthFailure();
164
- throw new Error("unauthenticated");
165
- }
166
- res = await doFetch(refreshed.accessToken);
167
- if (res.status === 401) {
168
- this.clearTokens();
169
- this.onAuthFailure();
170
- throw new Error("unauthenticated");
171
- }
240
+ if (exchange_data == null ? void 0 : exchange_data.success) {
241
+ const me_data = await this.me({
242
+ accessToken: (_a = exchange_data == null ? void 0 : exchange_data.data) == null ? void 0 : _a.accessToken
243
+ });
244
+ return me_data;
245
+ } else {
246
+ return null;
172
247
  }
173
- if (!res.ok) throw new Error(`request failed: ${res.status}`);
174
- return res.json();
175
248
  }
176
249
  };
177
250
 
178
- // src/FluoceAuthProvider.tsx
251
+ // src/context/auth_context.tsx
252
+ import { createContext as create_context } from "react";
253
+ var auth_context = create_context(null);
254
+
255
+ // src/provider/auth_provider.tsx
179
256
  import { jsx } from "react/jsx-runtime";
180
- function FluoceAuthProvider({
257
+ var auth_provider = ({
181
258
  children,
182
- ...config
183
- }) {
184
- const clientRef = useRef();
185
- if (!clientRef.current) clientRef.current = new FluoceApiClient(config);
186
- const client = clientRef.current;
187
- const [user, setUser] = useState(null);
188
- const [loading, setLoading] = useState(true);
189
- const [error, setError] = useState(null);
190
- const redirectToLogin = useCallback(() => {
191
- window.location.href = client.buildLoginUrl();
192
- }, [client]);
193
- client.onAuthFailure = redirectToLogin;
194
- const refreshUser = useCallback(async () => {
259
+ app_url,
260
+ on_error,
261
+ redirect
262
+ }) => {
263
+ const auth_api = use_memo(() => {
264
+ return new Api_service({
265
+ app_url,
266
+ on_error,
267
+ redirect
268
+ });
269
+ }, [app_url, on_error, redirect]);
270
+ const [user, set_user] = use_state(null);
271
+ const [safe_loading, set_safe_loading] = use_state(false);
272
+ const [auth_flow_loading, set_auth_flow_loading] = use_state(false);
273
+ const refresh = async () => {
274
+ var _a;
275
+ const response = await auth_api.refresh();
276
+ return (_a = response == null ? void 0 : response.success) != null ? _a : false;
277
+ };
278
+ const logout = async () => {
279
+ await auth_api.logout();
280
+ set_user(null);
281
+ return true;
282
+ };
283
+ const auth_flow = async ({ code }) => {
284
+ set_auth_flow_loading(true);
195
285
  try {
196
- const me = await client.getMe();
197
- setUser(me);
198
- setError(null);
199
- } catch (e) {
200
- setUser(null);
201
- setError(e);
286
+ const data = await auth_api.auth_flow({
287
+ code
288
+ });
289
+ if ((data == null ? void 0 : data.success) && (data == null ? void 0 : data.data)) {
290
+ set_user(data == null ? void 0 : data.data);
291
+ return true;
292
+ } else {
293
+ return false;
294
+ }
295
+ } catch (error) {
296
+ return false;
297
+ } finally {
298
+ set_auth_flow_loading(false);
202
299
  }
203
- }, [client]);
204
- const logout = useCallback(
205
- async (allDevices = false) => {
206
- await client.logout(allDevices);
207
- setUser(null);
208
- redirectToLogin();
209
- },
210
- [client, redirectToLogin]
211
- );
212
- useEffect(() => {
213
- let cancelled = false;
214
- async function init() {
215
- const params = new URLSearchParams(window.location.search);
216
- const code = params.get("code");
217
- try {
218
- if (code) {
219
- await client.exchangeCode(code);
220
- params.delete("code");
221
- const query = params.toString();
222
- const cleanUrl = window.location.pathname + (query ? `?${query}` : "") + window.location.hash;
223
- window.history.replaceState({}, "", cleanUrl);
224
- }
225
- if (client.getAccessToken() || client.getRefreshToken()) {
226
- const me = await client.getMe();
227
- if (!cancelled) setUser(me);
228
- }
229
- } catch (e) {
230
- if (!cancelled) setError(e);
231
- } finally {
232
- if (!cancelled) setLoading(false);
300
+ };
301
+ const get_safe_user = async () => {
302
+ set_safe_loading(true);
303
+ try {
304
+ if (user) {
305
+ return user;
306
+ }
307
+ const data = await auth_api.safe_me({});
308
+ if ((data == null ? void 0 : data.success) && (data == null ? void 0 : data.data)) {
309
+ set_user(data == null ? void 0 : data.data);
310
+ return data.data;
311
+ } else {
312
+ return null;
233
313
  }
314
+ } catch (error) {
315
+ return null;
316
+ } finally {
317
+ set_safe_loading(false);
234
318
  }
235
- init();
236
- return () => {
237
- cancelled = true;
238
- };
319
+ };
320
+ use_effect(() => {
321
+ void get_safe_user();
239
322
  }, []);
240
- const value = useMemo(
241
- () => ({
242
- user,
243
- loading,
244
- isAuthenticated: !!user,
245
- error,
246
- refreshUser,
247
- logout,
248
- redirectToLogin
249
- }),
250
- [user, loading, error, refreshUser, logout, redirectToLogin]
251
- );
252
- return /* @__PURE__ */ jsx(FluoceAuthContext.Provider, { value, children });
253
- }
323
+ const value = {
324
+ user,
325
+ safe_loading,
326
+ auth_flow_loading,
327
+ refresh,
328
+ logout,
329
+ auth_flow,
330
+ get_safe_user,
331
+ get access_token() {
332
+ return auth_api.get_access_token();
333
+ },
334
+ get refresh_token() {
335
+ return auth_api.get_refresh_token();
336
+ }
337
+ };
338
+ return /* @__PURE__ */ jsx(auth_context.Provider, { value, children });
339
+ };
340
+
341
+ // src/hook/use_auth.ts
342
+ import { useContext as use_context } from "react";
343
+ var use_auth = () => {
344
+ const context = use_context(auth_context);
345
+ if (!context) {
346
+ throw new Error("use_auth must be used within an auth_provider");
347
+ }
348
+ return context;
349
+ };
254
350
 
255
- // src/AuthGuard.tsx
256
- import { useEffect as useEffect2 } from "react";
351
+ // src/guard/auth_guard.tsx
257
352
  import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
258
- function AuthGuard({ children, fallback = null }) {
259
- const { loading, isAuthenticated, redirectToLogin } = useFluoceAuth();
260
- useEffect2(() => {
261
- if (!loading && !isAuthenticated) {
262
- redirectToLogin();
263
- }
264
- }, [loading, isAuthenticated, redirectToLogin]);
265
- if (loading || !isAuthenticated) {
353
+ var auth_guard = ({
354
+ children,
355
+ fallback = null
356
+ }) => {
357
+ const { user, safe_loading } = use_auth();
358
+ if (safe_loading && !user) {
266
359
  return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
267
360
  }
361
+ if (!user && !safe_loading) {
362
+ return null;
363
+ }
268
364
  return /* @__PURE__ */ jsx2(Fragment, { children });
269
- }
365
+ };
270
366
  export {
271
- AuthGuard,
272
- FluoceApiClient,
273
- FluoceAuthProvider,
274
- useFluoceAuth
367
+ auth_guard as FluoceAuthGuard,
368
+ auth_provider as FluoceAuthProvider,
369
+ use_auth as useFluoceAuth
275
370
  };
276
371
  //# sourceMappingURL=index.js.map