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