@fluoce/auth-react 0.1.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/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # @fluoce/auth-react
2
+
3
+ React SDK for Fluoce Auth. Wraps the exchange / me / refresh flow, stores
4
+ tokens in cookies, and gives you `AuthGuard` + `useFluoceAuth()`.
5
+
6
+ ## Setup
7
+
8
+ ```tsx
9
+ // App.tsx
10
+ import { FluoceAuthProvider } from '@fluoce/auth-react';
11
+
12
+ export function App() {
13
+ return (
14
+ <FluoceAuthProvider
15
+ authUrl="https://auth.fluoce.com"
16
+ apiUrl="https://api.fluoce.com" // your auth backend
17
+ appUrl="https://myapp.com/auth" // this app's callback url (the `ref`)
18
+ >
19
+ <Routes />
20
+ </FluoceAuthProvider>
21
+ );
22
+ }
23
+ ```
24
+
25
+ `appUrl` must be a route in your app that stays mounted under `FluoceAuthProvider`
26
+ (e.g. `/auth`) — auth.fluoce.com redirects back to `${appUrl}?code=...`, and the
27
+ provider picks the `code` param up automatically wherever it appears, exchanges
28
+ it, sets cookies, and strips it from the URL.
29
+
30
+ ## Protecting a route
31
+
32
+ ```tsx
33
+ import { AuthGuard } from '@fluoce/auth-react';
34
+
35
+ function DashboardPage() {
36
+ return (
37
+ <AuthGuard fallback={<Spinner />}>
38
+ <Dashboard />
39
+ </AuthGuard>
40
+ );
41
+ }
42
+ ```
43
+
44
+ If there's no valid session, `AuthGuard` redirects to
45
+ `${authUrl}/auth?ref=${appUrl}` for you — nothing else to wire up.
46
+
47
+ ## Reading the user / calling other endpoints
48
+
49
+ ```tsx
50
+ import { useFluoceAuth } from '@fluoce/auth-react';
51
+
52
+ function Profile() {
53
+ const { user, loading, logout, authFetch } = useFluoceAuth();
54
+
55
+ if (loading) return <Spinner />;
56
+
57
+ const updateName = (name: string) =>
58
+ authFetch('/me', {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json' },
61
+ body: JSON.stringify({ name }),
62
+ });
63
+
64
+ return (
65
+ <div>
66
+ <p>{user?.name ?? user?.email}</p>
67
+ <button onClick={() => logout()}>Log out</button>
68
+ </div>
69
+ );
70
+ }
71
+ ```
72
+
73
+ `authFetch` is the same authenticated-fetch helper the SDK uses internally for
74
+ `/me` — it auto-refreshes on 401 and redirects to login if the refresh also
75
+ fails. Use it for any endpoint, including ones not wrapped yet
76
+ (`/me/add/email`, `/me/add/phone`, etc.) until they get first-class methods.
77
+
78
+ ## Cookies
79
+
80
+ Access/refresh tokens are stored as regular (non-httpOnly — this is
81
+ client-side JS) cookies:
82
+
83
+ - `fluoce_access_token`
84
+ - `fluoce_refresh_token`
85
+
86
+ Override names/expiry via provider props: `accessTokenCookie`,
87
+ `refreshTokenCookie`, `accessTokenExpiryDays`, `refreshTokenExpiryDays`.
88
+
89
+ ## Not yet wrapped
90
+
91
+ `/email`, `/phone`, `/google`, `/github`, `/me/add/email`, `/me/add/phone`,
92
+ `/logout/all` (client method exists, no dedicated hook) — call these directly
93
+ via `authFetch` (or plain `fetch` for the pre-login ones like `/email`,
94
+ `/phone`, `/google`, `/github`) until added.
package/dist/index.cjs ADDED
@@ -0,0 +1,300 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AuthGuard: () => AuthGuard,
24
+ FluoceApiClient: () => FluoceApiClient,
25
+ FluoceAuthProvider: () => FluoceAuthProvider,
26
+ useFluoceAuth: () => useFluoceAuth
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/FluoceAuthProvider.tsx
31
+ var import_react2 = require("react");
32
+
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
+ }
44
+
45
+ // src/cookies.ts
46
+ function setCookie(name, value, days) {
47
+ const expires = new Date(Date.now() + days * 864e5).toUTCString();
48
+ document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`;
49
+ }
50
+ function getCookie(name) {
51
+ const escaped = name.replace(/([.$?*|{}()[\]\\/+^])/g, "\\$1");
52
+ const match = document.cookie.match(new RegExp("(?:^|; )" + escaped + "=([^;]*)"));
53
+ return match ? decodeURIComponent(match[1]) : null;
54
+ }
55
+ function deleteCookie(name) {
56
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
57
+ }
58
+
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 = () => {
65
+ };
66
+ this.config = {
67
+ accessTokenCookie: "fluoce_access_token",
68
+ refreshTokenCookie: "fluoce_refresh_token",
69
+ accessTokenExpiryDays: 1,
70
+ refreshTokenExpiryDays: 30,
71
+ ...config
72
+ };
73
+ }
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
90
+ );
91
+ }
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");
113
+ }
114
+ async updateMe(payload) {
115
+ return this.authFetch("/me", {
116
+ method: "POST",
117
+ headers: { "Content-Type": "application/json" },
118
+ body: JSON.stringify(payload)
119
+ });
120
+ }
121
+ 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`, {
128
+ method: "POST",
129
+ headers: {
130
+ "Content-Type": "application/json",
131
+ Authorization: `Bearer ${refreshToken}`
132
+ }
133
+ });
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;
146
+ }
147
+ })();
148
+ return this.refreshPromise;
149
+ }
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
+ }
162
+ }
163
+ );
164
+ } catch {
165
+ }
166
+ }
167
+ this.clearTokens();
168
+ }
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
+ }
181
+ });
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
+ }
195
+ }
196
+ if (!res.ok) throw new Error(`request failed: ${res.status}`);
197
+ return res.json();
198
+ }
199
+ };
200
+
201
+ // src/FluoceAuthProvider.tsx
202
+ var import_jsx_runtime = require("react/jsx-runtime");
203
+ function FluoceAuthProvider({
204
+ 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 () => {
218
+ try {
219
+ const me = await client.getMe();
220
+ setUser(me);
221
+ setError(null);
222
+ } catch (e) {
223
+ setUser(null);
224
+ setError(e);
225
+ }
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);
256
+ }
257
+ }
258
+ init();
259
+ return () => {
260
+ cancelled = true;
261
+ };
262
+ }, []);
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
+ }
277
+
278
+ // src/AuthGuard.tsx
279
+ var import_react3 = require("react");
280
+ 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) {
289
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
290
+ }
291
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
292
+ }
293
+ // Annotate the CommonJS export names for ESM import in node:
294
+ 0 && (module.exports = {
295
+ AuthGuard,
296
+ FluoceApiClient,
297
+ FluoceAuthProvider,
298
+ useFluoceAuth
299
+ });
300
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/FluoceAuthProvider.tsx","../src/AuthContext.tsx","../src/cookies.ts","../src/apiClient.ts","../src/AuthGuard.tsx"],"sourcesContent":["export { FluoceAuthProvider } from './FluoceAuthProvider';\nexport type { FluoceAuthProviderProps } from './FluoceAuthProvider';\nexport { AuthGuard } from './AuthGuard';\nexport type { AuthGuardProps } from './AuthGuard';\nexport { useFluoceAuth } from './AuthContext';\nexport { FluoceApiClient } from './apiClient';\nexport type {\n FluoceUser,\n TokenPair,\n FluoceAuthConfig,\n FluoceAuthContextValue,\n} from './types';\n","import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { FluoceAuthContext } from \"./AuthContext\";\nimport { FluoceApiClient } from \"./apiClient\";\nimport type { FluoceAuthConfig, FluoceUser } from \"./types\";\n\nexport interface FluoceAuthProviderProps extends FluoceAuthConfig {\n children: ReactNode;\n}\n\nexport function FluoceAuthProvider({\n children,\n ...config\n}: FluoceAuthProviderProps) {\n const clientRef = useRef<FluoceApiClient>();\n if (!clientRef.current) clientRef.current = new FluoceApiClient(config);\n const client = clientRef.current;\n\n const [user, setUser] = useState<FluoceUser | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const redirectToLogin = useCallback(() => {\n window.location.href = client.buildLoginUrl();\n }, [client]);\n\n // keep the client's failure hook pointed at the latest redirect fn\n client.onAuthFailure = redirectToLogin;\n\n const refreshUser = useCallback(async () => {\n try {\n const me = await client.getMe();\n setUser(me);\n setError(null);\n } catch (e) {\n setUser(null);\n setError(e as Error);\n }\n }, [client]);\n\n const logout = useCallback(\n async (allDevices = false) => {\n await client.logout(allDevices);\n setUser(null);\n redirectToLogin();\n },\n [client, redirectToLogin],\n );\n\n useEffect(() => {\n let cancelled = false;\n\n async function init() {\n const params = new URLSearchParams(window.location.search);\n const code = params.get(\"code\");\n\n try {\n if (code) {\n await client.exchangeCode(code);\n params.delete(\"code\");\n const query = params.toString();\n const cleanUrl =\n window.location.pathname +\n (query ? `?${query}` : \"\") +\n window.location.hash;\n window.history.replaceState({}, \"\", cleanUrl);\n }\n\n if (client.getAccessToken() || client.getRefreshToken()) {\n const me = await client.getMe();\n if (!cancelled) setUser(me);\n }\n } catch (e) {\n if (!cancelled) setError(e as Error);\n } finally {\n if (!cancelled) setLoading(false);\n }\n }\n\n init();\n return () => {\n cancelled = true;\n };\n // run once on mount only\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // const authFetch = useCallback(\n // <T,>(path: string, init?: RequestInit) => client.authFetch<T>(path, init),\n // [client],\n // );\n\n const value = useMemo(\n () => ({\n user,\n loading,\n isAuthenticated: !!user,\n error,\n refreshUser,\n logout,\n redirectToLogin,\n }),\n [user, loading, error, refreshUser, logout, redirectToLogin],\n );\n\n return (\n <FluoceAuthContext.Provider value={value}>\n {children}\n </FluoceAuthContext.Provider>\n );\n}\n","import { createContext, useContext } from \"react\";\nimport type { FluoceAuthContextValue } from \"./types\";\n\nexport const FluoceAuthContext = createContext<FluoceAuthContextValue | null>(\n null,\n);\n\nexport function useFluoceAuth(): FluoceAuthContextValue {\n const ctx = useContext(FluoceAuthContext);\n if (!ctx)\n throw new Error(\"useFluoceAuth must be used within <FluoceAuthProvider>\");\n return ctx;\n}\n","export function setCookie(name: string, value: string, days: number): void {\n const expires = new Date(Date.now() + days * 864e5).toUTCString();\n document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`;\n}\n\nexport function getCookie(name: string): string | null {\n const escaped = name.replace(/([.$?*|{}()[\\]\\\\/+^])/g, '\\\\$1');\n const match = document.cookie.match(new RegExp('(?:^|; )' + escaped + '=([^;]*)'));\n return match ? decodeURIComponent(match[1]) : null;\n}\n\nexport function deleteCookie(name: string): void {\n document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;\n}\n","import { getCookie, setCookie, deleteCookie } from \"./cookies\";\nimport type { FluoceAuthConfig, FluoceUser, TokenPair } from \"./types\";\n\ntype ResolvedConfig = Required<FluoceAuthConfig>;\n\nexport class FluoceApiClient {\n private config: ResolvedConfig;\n private refreshPromise: Promise<TokenPair | null> | null = null;\n\n /** set by the Provider — called once refresh fails and there's no session left */\n onAuthFailure: () => void = () => {};\n\n constructor(config: FluoceAuthConfig) {\n this.config = {\n accessTokenCookie: \"fluoce_access_token\",\n refreshTokenCookie: \"fluoce_refresh_token\",\n accessTokenExpiryDays: 1,\n refreshTokenExpiryDays: 30,\n ...config,\n };\n }\n\n getAccessToken(): string | null {\n return getCookie(this.config.accessTokenCookie);\n }\n\n getRefreshToken(): string | null {\n return getCookie(this.config.refreshTokenCookie);\n }\n\n setTokens(tokens: TokenPair): void {\n setCookie(\n this.config.accessTokenCookie,\n tokens.accessToken,\n this.config.accessTokenExpiryDays,\n );\n setCookie(\n this.config.refreshTokenCookie,\n tokens.refreshToken,\n this.config.refreshTokenExpiryDays,\n );\n }\n\n clearTokens(): void {\n deleteCookie(this.config.accessTokenCookie);\n deleteCookie(this.config.refreshTokenCookie);\n }\n\n buildLoginUrl(): string {\n const ref = encodeURIComponent(this.config.appUrl);\n return `${this.config.authUrl}/auth?ref=${ref}`;\n }\n\n async exchangeCode(code: string): Promise<TokenPair> {\n const res = await fetch(`${this.config.apiUrl}/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ code }),\n });\n if (!res.ok) throw new Error(`exchange failed: ${res.status}`);\n const data = (await res.json()) as TokenPair;\n this.setTokens(data);\n return data;\n }\n\n async getMe(): Promise<FluoceUser> {\n return this.authFetch<FluoceUser>(\"/me\");\n }\n\n async updateMe(payload: {\n name?: string;\n photo?: string;\n }): Promise<FluoceUser> {\n return this.authFetch<FluoceUser>(\"/me\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n });\n }\n\n async refresh(): Promise<TokenPair | null> {\n // dedupe concurrent refresh calls triggered by parallel 401s\n if (this.refreshPromise) return this.refreshPromise;\n\n const refreshToken = this.getRefreshToken();\n if (!refreshToken) return null;\n\n this.refreshPromise = (async () => {\n try {\n const res = await fetch(`${this.config.apiUrl}/refresh`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${refreshToken}`,\n },\n });\n if (!res.ok) {\n this.clearTokens();\n return null;\n }\n const data = (await res.json()) as TokenPair;\n this.setTokens(data);\n return data;\n } catch {\n this.clearTokens();\n return null;\n } finally {\n this.refreshPromise = null;\n }\n })();\n\n return this.refreshPromise;\n }\n\n async logout(allDevices = false): Promise<void> {\n const refreshToken = this.getRefreshToken();\n if (refreshToken) {\n try {\n await fetch(\n `${this.config.apiUrl}${allDevices ? \"/logout/all\" : \"/logout\"}`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${refreshToken}`,\n },\n },\n );\n } catch {\n // best-effort — clear local session regardless\n }\n }\n this.clearTokens();\n }\n\n /**\n * Authenticated fetch. Attaches access token, retries once through /refresh\n * on 401, and calls onAuthFailure (-> redirect to auth frontend) if that\n * fails too. Use this for /me, /me updates, and any endpoint you add later.\n */\n async authFetch<T = unknown>(\n path: string,\n init: RequestInit = {},\n ): Promise<T> {\n const doFetch = (token: string | null) =>\n fetch(`${this.config.apiUrl}${path}`, {\n ...init,\n headers: {\n ...(init.headers || {}),\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n },\n });\n\n let res = await doFetch(this.getAccessToken());\n\n if (res.status === 401) {\n const refreshed = await this.refresh();\n if (!refreshed) {\n this.onAuthFailure();\n throw new Error(\"unauthenticated\");\n }\n res = await doFetch(refreshed.accessToken);\n if (res.status === 401) {\n this.clearTokens();\n this.onAuthFailure();\n throw new Error(\"unauthenticated\");\n }\n }\n\n if (!res.ok) throw new Error(`request failed: ${res.status}`);\n\n return res.json() as Promise<T>;\n }\n}\n","import { useEffect, type ReactNode } from \"react\";\nimport { useFluoceAuth } from \"./AuthContext\";\n\nexport interface AuthGuardProps {\n children: ReactNode;\n /** shown while loading or right before the redirect fires */\n fallback?: ReactNode;\n}\n\nexport function AuthGuard({ children, fallback = null }: AuthGuardProps) {\n const { loading, isAuthenticated, redirectToLogin } = useFluoceAuth();\n\n useEffect(() => {\n if (!loading && !isAuthenticated) {\n redirectToLogin();\n }\n }, [loading, isAuthenticated, redirectToLogin]);\n\n if (loading || !isAuthenticated) {\n return <>{fallback}</>;\n }\n\n return <>{children}</>;\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime"]}
@@ -0,0 +1,82 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface FluoceUser {
5
+ id: string;
6
+ name?: string;
7
+ email?: string;
8
+ phone?: string;
9
+ photo?: string;
10
+ [key: string]: unknown;
11
+ }
12
+ interface TokenPair {
13
+ accessToken: string;
14
+ refreshToken: string;
15
+ }
16
+ interface FluoceAuthConfig {
17
+ /** Auth frontend base url, e.g. "https://auth.fluoce.com" */
18
+ authUrl: string;
19
+ /** Auth backend api base url, e.g. "http://localhost:3001" */
20
+ apiUrl: string;
21
+ /**
22
+ * This app's own callback url, sent as `ref` and where auth.fluoce.com
23
+ * redirects back with `?code=`. e.g. "https://myapp.com/auth"
24
+ */
25
+ appUrl: string;
26
+ accessTokenCookie?: string;
27
+ refreshTokenCookie?: string;
28
+ accessTokenExpiryDays?: number;
29
+ refreshTokenExpiryDays?: number;
30
+ }
31
+ interface FluoceAuthContextValue {
32
+ user: FluoceUser | null;
33
+ loading: boolean;
34
+ isAuthenticated: boolean;
35
+ error: Error | null;
36
+ refreshUser: () => Promise<void>;
37
+ logout: (allDevices?: boolean) => Promise<void>;
38
+ redirectToLogin: () => void;
39
+ }
40
+
41
+ interface FluoceAuthProviderProps extends FluoceAuthConfig {
42
+ children: ReactNode;
43
+ }
44
+ declare function FluoceAuthProvider({ children, ...config }: FluoceAuthProviderProps): react.JSX.Element;
45
+
46
+ interface AuthGuardProps {
47
+ children: ReactNode;
48
+ /** shown while loading or right before the redirect fires */
49
+ fallback?: ReactNode;
50
+ }
51
+ declare function AuthGuard({ children, fallback }: AuthGuardProps): react.JSX.Element;
52
+
53
+ declare function useFluoceAuth(): FluoceAuthContextValue;
54
+
55
+ declare class FluoceApiClient {
56
+ private config;
57
+ private refreshPromise;
58
+ /** set by the Provider — called once refresh fails and there's no session left */
59
+ onAuthFailure: () => void;
60
+ constructor(config: FluoceAuthConfig);
61
+ getAccessToken(): string | null;
62
+ getRefreshToken(): string | null;
63
+ setTokens(tokens: TokenPair): void;
64
+ clearTokens(): void;
65
+ buildLoginUrl(): string;
66
+ exchangeCode(code: string): Promise<TokenPair>;
67
+ getMe(): Promise<FluoceUser>;
68
+ updateMe(payload: {
69
+ name?: string;
70
+ photo?: string;
71
+ }): Promise<FluoceUser>;
72
+ refresh(): Promise<TokenPair | null>;
73
+ logout(allDevices?: boolean): Promise<void>;
74
+ /**
75
+ * Authenticated fetch. Attaches access token, retries once through /refresh
76
+ * on 401, and calls onAuthFailure (-> redirect to auth frontend) if that
77
+ * fails too. Use this for /me, /me updates, and any endpoint you add later.
78
+ */
79
+ authFetch<T = unknown>(path: string, init?: RequestInit): Promise<T>;
80
+ }
81
+
82
+ export { AuthGuard, type AuthGuardProps, FluoceApiClient, type FluoceAuthConfig, type FluoceAuthContextValue, FluoceAuthProvider, type FluoceAuthProviderProps, type FluoceUser, type TokenPair, useFluoceAuth };
@@ -0,0 +1,82 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface FluoceUser {
5
+ id: string;
6
+ name?: string;
7
+ email?: string;
8
+ phone?: string;
9
+ photo?: string;
10
+ [key: string]: unknown;
11
+ }
12
+ interface TokenPair {
13
+ accessToken: string;
14
+ refreshToken: string;
15
+ }
16
+ interface FluoceAuthConfig {
17
+ /** Auth frontend base url, e.g. "https://auth.fluoce.com" */
18
+ authUrl: string;
19
+ /** Auth backend api base url, e.g. "http://localhost:3001" */
20
+ apiUrl: string;
21
+ /**
22
+ * This app's own callback url, sent as `ref` and where auth.fluoce.com
23
+ * redirects back with `?code=`. e.g. "https://myapp.com/auth"
24
+ */
25
+ appUrl: string;
26
+ accessTokenCookie?: string;
27
+ refreshTokenCookie?: string;
28
+ accessTokenExpiryDays?: number;
29
+ refreshTokenExpiryDays?: number;
30
+ }
31
+ interface FluoceAuthContextValue {
32
+ user: FluoceUser | null;
33
+ loading: boolean;
34
+ isAuthenticated: boolean;
35
+ error: Error | null;
36
+ refreshUser: () => Promise<void>;
37
+ logout: (allDevices?: boolean) => Promise<void>;
38
+ redirectToLogin: () => void;
39
+ }
40
+
41
+ interface FluoceAuthProviderProps extends FluoceAuthConfig {
42
+ children: ReactNode;
43
+ }
44
+ declare function FluoceAuthProvider({ children, ...config }: FluoceAuthProviderProps): react.JSX.Element;
45
+
46
+ interface AuthGuardProps {
47
+ children: ReactNode;
48
+ /** shown while loading or right before the redirect fires */
49
+ fallback?: ReactNode;
50
+ }
51
+ declare function AuthGuard({ children, fallback }: AuthGuardProps): react.JSX.Element;
52
+
53
+ declare function useFluoceAuth(): FluoceAuthContextValue;
54
+
55
+ declare class FluoceApiClient {
56
+ private config;
57
+ private refreshPromise;
58
+ /** set by the Provider — called once refresh fails and there's no session left */
59
+ onAuthFailure: () => void;
60
+ constructor(config: FluoceAuthConfig);
61
+ getAccessToken(): string | null;
62
+ getRefreshToken(): string | null;
63
+ setTokens(tokens: TokenPair): void;
64
+ clearTokens(): void;
65
+ buildLoginUrl(): string;
66
+ exchangeCode(code: string): Promise<TokenPair>;
67
+ getMe(): Promise<FluoceUser>;
68
+ updateMe(payload: {
69
+ name?: string;
70
+ photo?: string;
71
+ }): Promise<FluoceUser>;
72
+ refresh(): Promise<TokenPair | null>;
73
+ logout(allDevices?: boolean): Promise<void>;
74
+ /**
75
+ * Authenticated fetch. Attaches access token, retries once through /refresh
76
+ * on 401, and calls onAuthFailure (-> redirect to auth frontend) if that
77
+ * fails too. Use this for /me, /me updates, and any endpoint you add later.
78
+ */
79
+ authFetch<T = unknown>(path: string, init?: RequestInit): Promise<T>;
80
+ }
81
+
82
+ export { AuthGuard, type AuthGuardProps, FluoceApiClient, type FluoceAuthConfig, type FluoceAuthContextValue, FluoceAuthProvider, type FluoceAuthProviderProps, type FluoceUser, type TokenPair, useFluoceAuth };
package/dist/index.js ADDED
@@ -0,0 +1,276 @@
1
+ // src/FluoceAuthProvider.tsx
2
+ import {
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState
8
+ } from "react";
9
+
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
+ }
21
+
22
+ // src/cookies.ts
23
+ function setCookie(name, value, days) {
24
+ const expires = new Date(Date.now() + days * 864e5).toUTCString();
25
+ document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`;
26
+ }
27
+ function getCookie(name) {
28
+ const escaped = name.replace(/([.$?*|{}()[\]\\/+^])/g, "\\$1");
29
+ const match = document.cookie.match(new RegExp("(?:^|; )" + escaped + "=([^;]*)"));
30
+ return match ? decodeURIComponent(match[1]) : null;
31
+ }
32
+ function deleteCookie(name) {
33
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
34
+ }
35
+
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 = () => {
42
+ };
43
+ this.config = {
44
+ accessTokenCookie: "fluoce_access_token",
45
+ refreshTokenCookie: "fluoce_refresh_token",
46
+ accessTokenExpiryDays: 1,
47
+ refreshTokenExpiryDays: 30,
48
+ ...config
49
+ };
50
+ }
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
67
+ );
68
+ }
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");
90
+ }
91
+ async updateMe(payload) {
92
+ return this.authFetch("/me", {
93
+ method: "POST",
94
+ headers: { "Content-Type": "application/json" },
95
+ body: JSON.stringify(payload)
96
+ });
97
+ }
98
+ 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`, {
105
+ method: "POST",
106
+ headers: {
107
+ "Content-Type": "application/json",
108
+ Authorization: `Bearer ${refreshToken}`
109
+ }
110
+ });
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;
123
+ }
124
+ })();
125
+ return this.refreshPromise;
126
+ }
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
+ }
139
+ }
140
+ );
141
+ } catch {
142
+ }
143
+ }
144
+ this.clearTokens();
145
+ }
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
+ }
158
+ });
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
+ }
172
+ }
173
+ if (!res.ok) throw new Error(`request failed: ${res.status}`);
174
+ return res.json();
175
+ }
176
+ };
177
+
178
+ // src/FluoceAuthProvider.tsx
179
+ import { jsx } from "react/jsx-runtime";
180
+ function FluoceAuthProvider({
181
+ 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 () => {
195
+ try {
196
+ const me = await client.getMe();
197
+ setUser(me);
198
+ setError(null);
199
+ } catch (e) {
200
+ setUser(null);
201
+ setError(e);
202
+ }
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);
233
+ }
234
+ }
235
+ init();
236
+ return () => {
237
+ cancelled = true;
238
+ };
239
+ }, []);
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
+ }
254
+
255
+ // src/AuthGuard.tsx
256
+ import { useEffect as useEffect2 } from "react";
257
+ 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) {
266
+ return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
267
+ }
268
+ return /* @__PURE__ */ jsx2(Fragment, { children });
269
+ }
270
+ export {
271
+ AuthGuard,
272
+ FluoceApiClient,
273
+ FluoceAuthProvider,
274
+ useFluoceAuth
275
+ };
276
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/FluoceAuthProvider.tsx","../src/AuthContext.tsx","../src/cookies.ts","../src/apiClient.ts","../src/AuthGuard.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { FluoceAuthContext } from \"./AuthContext\";\nimport { FluoceApiClient } from \"./apiClient\";\nimport type { FluoceAuthConfig, FluoceUser } from \"./types\";\n\nexport interface FluoceAuthProviderProps extends FluoceAuthConfig {\n children: ReactNode;\n}\n\nexport function FluoceAuthProvider({\n children,\n ...config\n}: FluoceAuthProviderProps) {\n const clientRef = useRef<FluoceApiClient>();\n if (!clientRef.current) clientRef.current = new FluoceApiClient(config);\n const client = clientRef.current;\n\n const [user, setUser] = useState<FluoceUser | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const redirectToLogin = useCallback(() => {\n window.location.href = client.buildLoginUrl();\n }, [client]);\n\n // keep the client's failure hook pointed at the latest redirect fn\n client.onAuthFailure = redirectToLogin;\n\n const refreshUser = useCallback(async () => {\n try {\n const me = await client.getMe();\n setUser(me);\n setError(null);\n } catch (e) {\n setUser(null);\n setError(e as Error);\n }\n }, [client]);\n\n const logout = useCallback(\n async (allDevices = false) => {\n await client.logout(allDevices);\n setUser(null);\n redirectToLogin();\n },\n [client, redirectToLogin],\n );\n\n useEffect(() => {\n let cancelled = false;\n\n async function init() {\n const params = new URLSearchParams(window.location.search);\n const code = params.get(\"code\");\n\n try {\n if (code) {\n await client.exchangeCode(code);\n params.delete(\"code\");\n const query = params.toString();\n const cleanUrl =\n window.location.pathname +\n (query ? `?${query}` : \"\") +\n window.location.hash;\n window.history.replaceState({}, \"\", cleanUrl);\n }\n\n if (client.getAccessToken() || client.getRefreshToken()) {\n const me = await client.getMe();\n if (!cancelled) setUser(me);\n }\n } catch (e) {\n if (!cancelled) setError(e as Error);\n } finally {\n if (!cancelled) setLoading(false);\n }\n }\n\n init();\n return () => {\n cancelled = true;\n };\n // run once on mount only\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // const authFetch = useCallback(\n // <T,>(path: string, init?: RequestInit) => client.authFetch<T>(path, init),\n // [client],\n // );\n\n const value = useMemo(\n () => ({\n user,\n loading,\n isAuthenticated: !!user,\n error,\n refreshUser,\n logout,\n redirectToLogin,\n }),\n [user, loading, error, refreshUser, logout, redirectToLogin],\n );\n\n return (\n <FluoceAuthContext.Provider value={value}>\n {children}\n </FluoceAuthContext.Provider>\n );\n}\n","import { createContext, useContext } from \"react\";\nimport type { FluoceAuthContextValue } from \"./types\";\n\nexport const FluoceAuthContext = createContext<FluoceAuthContextValue | null>(\n null,\n);\n\nexport function useFluoceAuth(): FluoceAuthContextValue {\n const ctx = useContext(FluoceAuthContext);\n if (!ctx)\n throw new Error(\"useFluoceAuth must be used within <FluoceAuthProvider>\");\n return ctx;\n}\n","export function setCookie(name: string, value: string, days: number): void {\n const expires = new Date(Date.now() + days * 864e5).toUTCString();\n document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`;\n}\n\nexport function getCookie(name: string): string | null {\n const escaped = name.replace(/([.$?*|{}()[\\]\\\\/+^])/g, '\\\\$1');\n const match = document.cookie.match(new RegExp('(?:^|; )' + escaped + '=([^;]*)'));\n return match ? decodeURIComponent(match[1]) : null;\n}\n\nexport function deleteCookie(name: string): void {\n document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;\n}\n","import { getCookie, setCookie, deleteCookie } from \"./cookies\";\nimport type { FluoceAuthConfig, FluoceUser, TokenPair } from \"./types\";\n\ntype ResolvedConfig = Required<FluoceAuthConfig>;\n\nexport class FluoceApiClient {\n private config: ResolvedConfig;\n private refreshPromise: Promise<TokenPair | null> | null = null;\n\n /** set by the Provider — called once refresh fails and there's no session left */\n onAuthFailure: () => void = () => {};\n\n constructor(config: FluoceAuthConfig) {\n this.config = {\n accessTokenCookie: \"fluoce_access_token\",\n refreshTokenCookie: \"fluoce_refresh_token\",\n accessTokenExpiryDays: 1,\n refreshTokenExpiryDays: 30,\n ...config,\n };\n }\n\n getAccessToken(): string | null {\n return getCookie(this.config.accessTokenCookie);\n }\n\n getRefreshToken(): string | null {\n return getCookie(this.config.refreshTokenCookie);\n }\n\n setTokens(tokens: TokenPair): void {\n setCookie(\n this.config.accessTokenCookie,\n tokens.accessToken,\n this.config.accessTokenExpiryDays,\n );\n setCookie(\n this.config.refreshTokenCookie,\n tokens.refreshToken,\n this.config.refreshTokenExpiryDays,\n );\n }\n\n clearTokens(): void {\n deleteCookie(this.config.accessTokenCookie);\n deleteCookie(this.config.refreshTokenCookie);\n }\n\n buildLoginUrl(): string {\n const ref = encodeURIComponent(this.config.appUrl);\n return `${this.config.authUrl}/auth?ref=${ref}`;\n }\n\n async exchangeCode(code: string): Promise<TokenPair> {\n const res = await fetch(`${this.config.apiUrl}/exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ code }),\n });\n if (!res.ok) throw new Error(`exchange failed: ${res.status}`);\n const data = (await res.json()) as TokenPair;\n this.setTokens(data);\n return data;\n }\n\n async getMe(): Promise<FluoceUser> {\n return this.authFetch<FluoceUser>(\"/me\");\n }\n\n async updateMe(payload: {\n name?: string;\n photo?: string;\n }): Promise<FluoceUser> {\n return this.authFetch<FluoceUser>(\"/me\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n });\n }\n\n async refresh(): Promise<TokenPair | null> {\n // dedupe concurrent refresh calls triggered by parallel 401s\n if (this.refreshPromise) return this.refreshPromise;\n\n const refreshToken = this.getRefreshToken();\n if (!refreshToken) return null;\n\n this.refreshPromise = (async () => {\n try {\n const res = await fetch(`${this.config.apiUrl}/refresh`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${refreshToken}`,\n },\n });\n if (!res.ok) {\n this.clearTokens();\n return null;\n }\n const data = (await res.json()) as TokenPair;\n this.setTokens(data);\n return data;\n } catch {\n this.clearTokens();\n return null;\n } finally {\n this.refreshPromise = null;\n }\n })();\n\n return this.refreshPromise;\n }\n\n async logout(allDevices = false): Promise<void> {\n const refreshToken = this.getRefreshToken();\n if (refreshToken) {\n try {\n await fetch(\n `${this.config.apiUrl}${allDevices ? \"/logout/all\" : \"/logout\"}`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${refreshToken}`,\n },\n },\n );\n } catch {\n // best-effort — clear local session regardless\n }\n }\n this.clearTokens();\n }\n\n /**\n * Authenticated fetch. Attaches access token, retries once through /refresh\n * on 401, and calls onAuthFailure (-> redirect to auth frontend) if that\n * fails too. Use this for /me, /me updates, and any endpoint you add later.\n */\n async authFetch<T = unknown>(\n path: string,\n init: RequestInit = {},\n ): Promise<T> {\n const doFetch = (token: string | null) =>\n fetch(`${this.config.apiUrl}${path}`, {\n ...init,\n headers: {\n ...(init.headers || {}),\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n },\n });\n\n let res = await doFetch(this.getAccessToken());\n\n if (res.status === 401) {\n const refreshed = await this.refresh();\n if (!refreshed) {\n this.onAuthFailure();\n throw new Error(\"unauthenticated\");\n }\n res = await doFetch(refreshed.accessToken);\n if (res.status === 401) {\n this.clearTokens();\n this.onAuthFailure();\n throw new Error(\"unauthenticated\");\n }\n }\n\n if (!res.ok) throw new Error(`request failed: ${res.status}`);\n\n return res.json() as Promise<T>;\n }\n}\n","import { useEffect, type ReactNode } from \"react\";\nimport { useFluoceAuth } from \"./AuthContext\";\n\nexport interface AuthGuardProps {\n children: ReactNode;\n /** shown while loading or right before the redirect fires */\n fallback?: ReactNode;\n}\n\nexport function AuthGuard({ children, fallback = null }: AuthGuardProps) {\n const { loading, isAuthenticated, redirectToLogin } = useFluoceAuth();\n\n useEffect(() => {\n if (!loading && !isAuthenticated) {\n redirectToLogin();\n }\n }, [loading, isAuthenticated, redirectToLogin]);\n\n if (loading || !isAuthenticated) {\n return <>{fallback}</>;\n }\n\n return <>{children}</>;\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;;;ACPP,SAAS,eAAe,kBAAkB;AAGnC,IAAM,oBAAoB;AAAA,EAC/B;AACF;AAEO,SAAS,gBAAwC;AACtD,QAAM,MAAM,WAAW,iBAAiB;AACxC,MAAI,CAAC;AACH,UAAM,IAAI,MAAM,wDAAwD;AAC1E,SAAO;AACT;;;ACZO,SAAS,UAAU,MAAc,OAAe,MAAoB;AACzE,QAAM,UAAU,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,EAAE,YAAY;AAChE,WAAS,SAAS,GAAG,IAAI,IAAI,mBAAmB,KAAK,CAAC,aAAa,OAAO;AAC5E;AAEO,SAAS,UAAU,MAA6B;AACrD,QAAM,UAAU,KAAK,QAAQ,0BAA0B,MAAM;AAC7D,QAAM,QAAQ,SAAS,OAAO,MAAM,IAAI,OAAO,aAAa,UAAU,UAAU,CAAC;AACjF,SAAO,QAAQ,mBAAmB,MAAM,CAAC,CAAC,IAAI;AAChD;AAEO,SAAS,aAAa,MAAoB;AAC/C,WAAS,SAAS,GAAG,IAAI;AAC3B;;;ACRO,IAAM,kBAAN,MAAsB;AAAA,EAO3B,YAAY,QAA0B;AALtC,SAAQ,iBAAmD;AAG3D;AAAA,yBAA4B,MAAM;AAAA,IAAC;AAGjC,SAAK,SAAS;AAAA,MACZ,mBAAmB;AAAA,MACnB,oBAAoB;AAAA,MACpB,uBAAuB;AAAA,MACvB,wBAAwB;AAAA,MACxB,GAAG;AAAA,IACL;AAAA,EACF;AAAA,EAEA,iBAAgC;AAC9B,WAAO,UAAU,KAAK,OAAO,iBAAiB;AAAA,EAChD;AAAA,EAEA,kBAAiC;AAC/B,WAAO,UAAU,KAAK,OAAO,kBAAkB;AAAA,EACjD;AAAA,EAEA,UAAU,QAAyB;AACjC;AAAA,MACE,KAAK,OAAO;AAAA,MACZ,OAAO;AAAA,MACP,KAAK,OAAO;AAAA,IACd;AACA;AAAA,MACE,KAAK,OAAO;AAAA,MACZ,OAAO;AAAA,MACP,KAAK,OAAO;AAAA,IACd;AAAA,EACF;AAAA,EAEA,cAAoB;AAClB,iBAAa,KAAK,OAAO,iBAAiB;AAC1C,iBAAa,KAAK,OAAO,kBAAkB;AAAA,EAC7C;AAAA,EAEA,gBAAwB;AACtB,UAAM,MAAM,mBAAmB,KAAK,OAAO,MAAM;AACjD,WAAO,GAAG,KAAK,OAAO,OAAO,aAAa,GAAG;AAAA,EAC/C;AAAA,EAEA,MAAM,aAAa,MAAkC;AACnD,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,MAAM,aAAa;AAAA,MACxD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC;AAAA,IAC/B,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,oBAAoB,IAAI,MAAM,EAAE;AAC7D,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,SAAK,UAAU,IAAI;AACnB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAA6B;AACjC,WAAO,KAAK,UAAsB,KAAK;AAAA,EACzC;AAAA,EAEA,MAAM,SAAS,SAGS;AACtB,WAAO,KAAK,UAAsB,OAAO;AAAA,MACvC,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,IAC9B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAqC;AAEzC,QAAI,KAAK,eAAgB,QAAO,KAAK;AAErC,UAAM,eAAe,KAAK,gBAAgB;AAC1C,QAAI,CAAC,aAAc,QAAO;AAE1B,SAAK,kBAAkB,YAAY;AACjC,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,MAAM,YAAY;AAAA,UACvD,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,eAAe,UAAU,YAAY;AAAA,UACvC;AAAA,QACF,CAAC;AACD,YAAI,CAAC,IAAI,IAAI;AACX,eAAK,YAAY;AACjB,iBAAO;AAAA,QACT;AACA,cAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,aAAK,UAAU,IAAI;AACnB,eAAO;AAAA,MACT,QAAQ;AACN,aAAK,YAAY;AACjB,eAAO;AAAA,MACT,UAAE;AACA,aAAK,iBAAiB;AAAA,MACxB;AAAA,IACF,GAAG;AAEH,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,OAAO,aAAa,OAAsB;AAC9C,UAAM,eAAe,KAAK,gBAAgB;AAC1C,QAAI,cAAc;AAChB,UAAI;AACF,cAAM;AAAA,UACJ,GAAG,KAAK,OAAO,MAAM,GAAG,aAAa,gBAAgB,SAAS;AAAA,UAC9D;AAAA,YACE,QAAQ;AAAA,YACR,SAAS;AAAA,cACP,gBAAgB;AAAA,cAChB,eAAe,UAAU,YAAY;AAAA,YACvC;AAAA,UACF;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AACA,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UACJ,MACA,OAAoB,CAAC,GACT;AACZ,UAAM,UAAU,CAAC,UACf,MAAM,GAAG,KAAK,OAAO,MAAM,GAAG,IAAI,IAAI;AAAA,MACpC,GAAG;AAAA,MACH,SAAS;AAAA,QACP,GAAI,KAAK,WAAW,CAAC;AAAA,QACrB,GAAI,QAAQ,EAAE,eAAe,UAAU,KAAK,GAAG,IAAI,CAAC;AAAA,MACtD;AAAA,IACF,CAAC;AAEH,QAAI,MAAM,MAAM,QAAQ,KAAK,eAAe,CAAC;AAE7C,QAAI,IAAI,WAAW,KAAK;AACtB,YAAM,YAAY,MAAM,KAAK,QAAQ;AACrC,UAAI,CAAC,WAAW;AACd,aAAK,cAAc;AACnB,cAAM,IAAI,MAAM,iBAAiB;AAAA,MACnC;AACA,YAAM,MAAM,QAAQ,UAAU,WAAW;AACzC,UAAI,IAAI,WAAW,KAAK;AACtB,aAAK,YAAY;AACjB,aAAK,cAAc;AACnB,cAAM,IAAI,MAAM,iBAAiB;AAAA,MACnC;AAAA,IACF;AAEA,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,mBAAmB,IAAI,MAAM,EAAE;AAE5D,WAAO,IAAI,KAAK;AAAA,EAClB;AACF;;;AH7DI;AAhGG,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA,GAAG;AACL,GAA4B;AAC1B,QAAM,YAAY,OAAwB;AAC1C,MAAI,CAAC,UAAU,QAAS,WAAU,UAAU,IAAI,gBAAgB,MAAM;AACtE,QAAM,SAAS,UAAU;AAEzB,QAAM,CAAC,MAAM,OAAO,IAAI,SAA4B,IAAI;AACxD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,QAAM,kBAAkB,YAAY,MAAM;AACxC,WAAO,SAAS,OAAO,OAAO,cAAc;AAAA,EAC9C,GAAG,CAAC,MAAM,CAAC;AAGX,SAAO,gBAAgB;AAEvB,QAAM,cAAc,YAAY,YAAY;AAC1C,QAAI;AACF,YAAM,KAAK,MAAM,OAAO,MAAM;AAC9B,cAAQ,EAAE;AACV,eAAS,IAAI;AAAA,IACf,SAAS,GAAG;AACV,cAAQ,IAAI;AACZ,eAAS,CAAU;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,SAAS;AAAA,IACb,OAAO,aAAa,UAAU;AAC5B,YAAM,OAAO,OAAO,UAAU;AAC9B,cAAQ,IAAI;AACZ,sBAAgB;AAAA,IAClB;AAAA,IACA,CAAC,QAAQ,eAAe;AAAA,EAC1B;AAEA,YAAU,MAAM;AACd,QAAI,YAAY;AAEhB,mBAAe,OAAO;AACpB,YAAM,SAAS,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACzD,YAAM,OAAO,OAAO,IAAI,MAAM;AAE9B,UAAI;AACF,YAAI,MAAM;AACR,gBAAM,OAAO,aAAa,IAAI;AAC9B,iBAAO,OAAO,MAAM;AACpB,gBAAM,QAAQ,OAAO,SAAS;AAC9B,gBAAM,WACJ,OAAO,SAAS,YACf,QAAQ,IAAI,KAAK,KAAK,MACvB,OAAO,SAAS;AAClB,iBAAO,QAAQ,aAAa,CAAC,GAAG,IAAI,QAAQ;AAAA,QAC9C;AAEA,YAAI,OAAO,eAAe,KAAK,OAAO,gBAAgB,GAAG;AACvD,gBAAM,KAAK,MAAM,OAAO,MAAM;AAC9B,cAAI,CAAC,UAAW,SAAQ,EAAE;AAAA,QAC5B;AAAA,MACF,SAAS,GAAG;AACV,YAAI,CAAC,UAAW,UAAS,CAAU;AAAA,MACrC,UAAE;AACA,YAAI,CAAC,UAAW,YAAW,KAAK;AAAA,MAClC;AAAA,IACF;AAEA,SAAK;AACL,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EAGF,GAAG,CAAC,CAAC;AAOL,QAAM,QAAQ;AAAA,IACZ,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,iBAAiB,CAAC,CAAC;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,MAAM,SAAS,OAAO,aAAa,QAAQ,eAAe;AAAA,EAC7D;AAEA,SACE,oBAAC,kBAAkB,UAAlB,EAA2B,OACzB,UACH;AAEJ;;;AIpHA,SAAS,aAAAA,kBAAiC;AAmB/B,0BAAAC,YAAA;AAVJ,SAAS,UAAU,EAAE,UAAU,WAAW,KAAK,GAAmB;AACvE,QAAM,EAAE,SAAS,iBAAiB,gBAAgB,IAAI,cAAc;AAEpE,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,WAAW,CAAC,iBAAiB;AAChC,sBAAgB;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,SAAS,iBAAiB,eAAe,CAAC;AAE9C,MAAI,WAAW,CAAC,iBAAiB;AAC/B,WAAO,gBAAAD,KAAA,YAAG,oBAAS;AAAA,EACrB;AAEA,SAAO,gBAAAA,KAAA,YAAG,UAAS;AACrB;","names":["useEffect","jsx","useEffect"]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@fluoce/auth-react",
3
+ "version": "0.1.0",
4
+ "description": "React SDK for Fluoce Auth",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.mjs",
17
+ "require": "./dist/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "sideEffects": false,
24
+ "scripts": {
25
+ "build": "tsup src/index.ts --format esm,cjs --dts",
26
+ "dev": "tsup src/index.ts --watch --format esm,cjs --dts",
27
+ "prepublishOnly": "pnpm build"
28
+ },
29
+ "peerDependencies": {
30
+ "react": ">=18",
31
+ "react-dom": ">=18"
32
+ },
33
+ "devDependencies": {
34
+ "@types/react": "^18.3.31",
35
+ "tsup": "^8.5.1",
36
+ "typescript": "^5.9.3"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/fluoce/auth"
41
+ },
42
+ "homepage": "https://github.com/fluoce/auth",
43
+ "bugs": {
44
+ "url": "https://github.com/fluoce/auth/issues"
45
+ },
46
+ "keywords": [
47
+ "react",
48
+ "auth",
49
+ "authentication",
50
+ "oauth",
51
+ "sdk",
52
+ "fluoce"
53
+ ],
54
+ "engines": {
55
+ "node": ">=18"
56
+ }
57
+ }