@pinooxhq/auth 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.
@@ -0,0 +1,862 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
6
+ // src/react/useAuth.ts
7
+
8
+ // src/core/logger.ts
9
+ var defaultSink = (event, enabled) => {
10
+ if (!enabled && event.level === "debug") {
11
+ return;
12
+ }
13
+ const prefix = `[pinoox-auth:${event.code}]`;
14
+ const payload = event.context ? [prefix, event.message, event.context] : [prefix, event.message];
15
+ switch (event.level) {
16
+ case "error":
17
+ console.error(...payload);
18
+ break;
19
+ case "warn":
20
+ console.warn(...payload);
21
+ break;
22
+ case "info":
23
+ console.info(...payload);
24
+ break;
25
+ default:
26
+ console.debug(...payload);
27
+ }
28
+ };
29
+ function createLogger(debug = false, sink) {
30
+ let customSink = null;
31
+ const emit = (event) => {
32
+ if (customSink) {
33
+ customSink(event);
34
+ return;
35
+ }
36
+ defaultSink(event, debug);
37
+ };
38
+ return {
39
+ emit,
40
+ debug: (code, message, context) => emit({ level: "debug", code, message, context }),
41
+ info: (code, message, context) => emit({ level: "info", code, message, context }),
42
+ warn: (code, message, context) => emit({ level: "warn", code, message, context }),
43
+ error: (code, message, context) => emit({ level: "error", code, message, context }),
44
+ setSink: (next) => {
45
+ customSink = next;
46
+ }
47
+ };
48
+ }
49
+
50
+ // src/core/returnPath.ts
51
+ var DEFAULT_FALLBACK = "/";
52
+ var DEFAULT_BLOCKED = ["/account"];
53
+ function isSafeReturnPath(value, blockedPrefixes = DEFAULT_BLOCKED) {
54
+ if (typeof value !== "string") {
55
+ return false;
56
+ }
57
+ const trimmed = value.trim();
58
+ if (!trimmed.startsWith("/") || trimmed.startsWith("//")) {
59
+ return false;
60
+ }
61
+ if (trimmed.includes("://") || trimmed.includes("\\")) {
62
+ return false;
63
+ }
64
+ for (const prefix of blockedPrefixes) {
65
+ if (!prefix) {
66
+ continue;
67
+ }
68
+ if (trimmed === prefix || trimmed.startsWith(`${prefix}/`)) {
69
+ return false;
70
+ }
71
+ }
72
+ return true;
73
+ }
74
+ function resolveReturnPath(queryOrPath = void 0, fallback = DEFAULT_FALLBACK, blockedPrefixes = DEFAULT_BLOCKED) {
75
+ let candidate = queryOrPath;
76
+ if (queryOrPath && typeof queryOrPath === "object" && !Array.isArray(queryOrPath)) {
77
+ candidate = queryOrPath.redirect;
78
+ }
79
+ if (candidate === void 0 && typeof window !== "undefined") {
80
+ candidate = new URLSearchParams(window.location.search).get("redirect");
81
+ }
82
+ if (isSafeReturnPath(candidate, blockedPrefixes)) {
83
+ return candidate.trim();
84
+ }
85
+ return fallback;
86
+ }
87
+ function resolveSiteOrigin(bootUrl, explicit) {
88
+ const candidates = [explicit, bootUrl?.SITE];
89
+ for (const value of candidates) {
90
+ if (typeof value !== "string" || !value) {
91
+ continue;
92
+ }
93
+ try {
94
+ return new URL(value).origin;
95
+ } catch {
96
+ const cleaned = value.replace(/\/$/, "");
97
+ if (/^https?:\/\//i.test(cleaned)) {
98
+ return cleaned;
99
+ }
100
+ }
101
+ }
102
+ return null;
103
+ }
104
+ function toAbsoluteReturnUrl(path, siteOrigin) {
105
+ if (typeof path !== "string" || !path.startsWith("/")) {
106
+ return path;
107
+ }
108
+ if (!siteOrigin || typeof window === "undefined") {
109
+ return path;
110
+ }
111
+ if (window.location.origin === siteOrigin) {
112
+ return path;
113
+ }
114
+ return `${siteOrigin.replace(/\/$/, "")}${path}`;
115
+ }
116
+ function redirectToReturn(queryOrPath, options = {}) {
117
+ if (typeof window === "undefined") {
118
+ return;
119
+ }
120
+ const path = resolveReturnPath(queryOrPath, options.fallback, options.blockedPrefixes);
121
+ window.location.href = toAbsoluteReturnUrl(path, options.siteOrigin);
122
+ }
123
+
124
+ // src/core/resolveConfig.ts
125
+ function readBoot() {
126
+ if (typeof globalThis === "undefined") {
127
+ return {};
128
+ }
129
+ const g = globalThis;
130
+ if (g.__PINOOX__ && typeof g.__PINOOX__ === "object") {
131
+ return g.__PINOOX__;
132
+ }
133
+ if (g.PINOOX?.URL) {
134
+ return { url: g.PINOOX.URL };
135
+ }
136
+ return {};
137
+ }
138
+ function normalizeMode(value) {
139
+ const mode = String(value ?? "jwt").toLowerCase();
140
+ if (mode === "cookie" || mode === "session" || mode === "jwt") {
141
+ return mode;
142
+ }
143
+ return "jwt";
144
+ }
145
+ function normalizeBaseUrl(value) {
146
+ if (typeof value !== "string" || !value.trim()) {
147
+ return null;
148
+ }
149
+ let base = value.trim().replace(/\/$/, "");
150
+ if (!/^https?:\/\//i.test(base) && !base.startsWith("/")) {
151
+ base = `/${base}`;
152
+ }
153
+ return base;
154
+ }
155
+ function joinUrl(base, path) {
156
+ if (!path) {
157
+ return base;
158
+ }
159
+ if (/^https?:\/\//i.test(path)) {
160
+ return path;
161
+ }
162
+ if (path.startsWith("/")) {
163
+ return path;
164
+ }
165
+ const normalizedBase = base.replace(/\/$/, "");
166
+ return `${normalizedBase}/${path.replace(/^\//, "")}`;
167
+ }
168
+ function resolveEndpoint(path, fallback, baseUrl) {
169
+ const value = typeof path === "string" && path.trim() !== "" ? path.trim() : fallback;
170
+ if (/^https?:\/\//i.test(value) || value.startsWith("/")) {
171
+ return value;
172
+ }
173
+ if (baseUrl) {
174
+ return joinUrl(baseUrl, value);
175
+ }
176
+ return value;
177
+ }
178
+ function inferStrategy(bootAuth, explicit) {
179
+ if (explicit) {
180
+ return normalizeStrategy(explicit) ?? explicit;
181
+ }
182
+ const fromBoot = normalizeStrategy(bootAuth?.strategy);
183
+ if (fromBoot) {
184
+ return fromBoot;
185
+ }
186
+ if (bootAuth?.source) {
187
+ return "remote";
188
+ }
189
+ return "local";
190
+ }
191
+ function normalizeStrategy(value) {
192
+ if (value === "local" || value === "provider") {
193
+ return "local";
194
+ }
195
+ if (value === "remote" || value === "consumer" || value === "external") {
196
+ return "remote";
197
+ }
198
+ return null;
199
+ }
200
+ function defaultEndpoints(apiBase, strategy, baseUrl) {
201
+ if (strategy === "remote") {
202
+ if (baseUrl) {
203
+ return {
204
+ login: "auth/login",
205
+ logout: "auth/logout",
206
+ me: "auth/get"
207
+ };
208
+ }
209
+ return {
210
+ login: "/account/api/v1/auth/login",
211
+ logout: "/account/api/v1/auth/logout",
212
+ me: "/account/api/v1/auth/get"
213
+ };
214
+ }
215
+ return {
216
+ login: joinUrl(apiBase, "auth/login"),
217
+ logout: joinUrl(apiBase, "auth/logout"),
218
+ me: joinUrl(apiBase, "auth/get")
219
+ };
220
+ }
221
+ function resolveConfig(options = {}, logger) {
222
+ const boot = options.boot ?? readBoot();
223
+ const bootAuth = boot.auth ?? {};
224
+ const apiBase = (options.apiBase ?? boot.url?.API ?? "").replace(/\/$/, "");
225
+ const strategy = inferStrategy(bootAuth, options.strategy);
226
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? bootAuth.baseUrl ?? null);
227
+ const defaults = defaultEndpoints(apiBase || "/", strategy, baseUrl);
228
+ const bootEndpoints = bootAuth.endpoints ?? {};
229
+ const endpoints = {
230
+ login: resolveEndpoint(options.endpoints?.login ?? bootEndpoints.login, defaults.login, baseUrl),
231
+ logout: resolveEndpoint(options.endpoints?.logout ?? bootEndpoints.logout, defaults.logout, baseUrl),
232
+ me: resolveEndpoint(options.endpoints?.me ?? bootEndpoints.me, defaults.me, baseUrl)
233
+ };
234
+ const key = bootAuth.key || options.key || "";
235
+ const mode = normalizeMode(bootAuth.mode || options.mode);
236
+ const debug = options.debug ?? (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && !!undefined?.DEV);
237
+ if (!boot.auth && !options.key && !options.mode) {
238
+ logger?.warn("config.missing", "No __PINOOX__.auth and no explicit mode/key overrides", {
239
+ hasBoot: Object.keys(boot).length > 0
240
+ });
241
+ }
242
+ if (!key) {
243
+ logger?.warn("config.missing", "auth.key is empty \u2014 token storage will fail until key is provided", {
244
+ bootAuth
245
+ });
246
+ }
247
+ const loginUrl = options.loginUrl ?? bootAuth.loginUrl ?? (strategy === "remote" ? "/account/login" : null);
248
+ const appPath = typeof boot.url?.BASE === "string" && boot.url.BASE || typeof boot.url?.APP === "string" && boot.url.APP || (apiBase.match(/^(\/[^/]+)/)?.[1] ?? null);
249
+ const siteOrigin = resolveSiteOrigin(boot.url, options.siteOrigin);
250
+ const resolved = {
251
+ mode,
252
+ key,
253
+ provider: bootAuth.provider ?? null,
254
+ source: bootAuth.source ?? null,
255
+ strategy,
256
+ loginUrl,
257
+ baseUrl,
258
+ endpoints,
259
+ apiBase,
260
+ siteOrigin,
261
+ appPath,
262
+ debug: !!debug
263
+ };
264
+ logger?.debug("config.resolved", "Auth config resolved", {
265
+ mode: resolved.mode,
266
+ key: resolved.key,
267
+ provider: resolved.provider,
268
+ source: resolved.source,
269
+ strategy: resolved.strategy,
270
+ baseUrl: resolved.baseUrl,
271
+ endpoints: resolved.endpoints,
272
+ siteOrigin: resolved.siteOrigin,
273
+ appPath: resolved.appPath
274
+ });
275
+ return resolved;
276
+ }
277
+
278
+ // src/core/storage.ts
279
+ function readCookie(name) {
280
+ if (typeof document === "undefined") {
281
+ return null;
282
+ }
283
+ const match = document.cookie.match(new RegExp(`(?:^|; )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=([^;]*)`));
284
+ return match ? decodeURIComponent(match[1]) : null;
285
+ }
286
+ function writeCookie(name, value) {
287
+ if (typeof document === "undefined") {
288
+ return;
289
+ }
290
+ const maxAge = 60 * 60 * 24 * 90;
291
+ document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAge}; SameSite=Lax`;
292
+ }
293
+ function deleteCookie(name) {
294
+ if (typeof document === "undefined") {
295
+ return;
296
+ }
297
+ document.cookie = `${name}=; path=/; max-age=0; SameSite=Lax`;
298
+ }
299
+ function isDev() {
300
+ try {
301
+ return typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && !!undefined?.DEV;
302
+ } catch {
303
+ return false;
304
+ }
305
+ }
306
+ function createStorage(key, logger) {
307
+ const syncDevCookie = isDev();
308
+ return {
309
+ get: () => {
310
+ if (!key) {
311
+ return null;
312
+ }
313
+ try {
314
+ if (typeof localStorage !== "undefined") {
315
+ const fromStorage = localStorage.getItem(key)?.trim() || null;
316
+ if (fromStorage) {
317
+ if (syncDevCookie) {
318
+ writeCookie(key, fromStorage);
319
+ }
320
+ return fromStorage;
321
+ }
322
+ }
323
+ } catch (error) {
324
+ logger?.warn("token.storage", "Failed to read localStorage", { error: String(error) });
325
+ }
326
+ if (!syncDevCookie) {
327
+ return null;
328
+ }
329
+ const fromCookie = readCookie(key);
330
+ if (fromCookie) {
331
+ try {
332
+ localStorage.setItem(key, fromCookie);
333
+ } catch {
334
+ }
335
+ return fromCookie;
336
+ }
337
+ return null;
338
+ },
339
+ set: (token) => {
340
+ if (!key) {
341
+ logger?.warn("mismatch.key", "Cannot store token without auth.key");
342
+ return;
343
+ }
344
+ if (typeof localStorage === "undefined") {
345
+ return;
346
+ }
347
+ try {
348
+ if (token) {
349
+ const value = token.trim();
350
+ localStorage.setItem(key, value);
351
+ logger?.debug("token.stored", "Token stored", { key });
352
+ if (syncDevCookie) {
353
+ writeCookie(key, value);
354
+ }
355
+ } else {
356
+ localStorage.removeItem(key);
357
+ logger?.debug("token.cleared", "Token cleared", { key });
358
+ if (syncDevCookie) {
359
+ deleteCookie(key);
360
+ }
361
+ }
362
+ } catch (error) {
363
+ logger?.warn("token.storage", "Failed to write storage", { error: String(error) });
364
+ }
365
+ },
366
+ clear: () => {
367
+ if (!key || typeof localStorage === "undefined") {
368
+ return;
369
+ }
370
+ try {
371
+ localStorage.removeItem(key);
372
+ } catch {
373
+ }
374
+ if (syncDevCookie) {
375
+ deleteCookie(key);
376
+ }
377
+ logger?.debug("token.cleared", "Token cleared", { key });
378
+ }
379
+ };
380
+ }
381
+
382
+ // src/strategies/local.ts
383
+ function unwrapPayload(data) {
384
+ if (!data || typeof data !== "object") {
385
+ return {};
386
+ }
387
+ const record = data;
388
+ if (record.data && typeof record.data === "object") {
389
+ return record.data;
390
+ }
391
+ return record;
392
+ }
393
+ function extractTokenAndUser(payload) {
394
+ const data = unwrapPayload(payload);
395
+ const tokenCandidate = typeof data.token === "string" && data.token || typeof payload?.token === "string" && payload.token || null;
396
+ const userCandidate = (data.user && typeof data.user === "object" ? data.user : null) || (data.username || data.email || data.user_id ? data : null);
397
+ return {
398
+ token: tokenCandidate,
399
+ user: userCandidate
400
+ };
401
+ }
402
+ async function localLogin(config, http, storage, credentials, logger) {
403
+ const response = await http.request({
404
+ url: config.endpoints.login,
405
+ method: "POST",
406
+ body: credentials,
407
+ credentials: "include",
408
+ headers: {
409
+ "Content-Type": "application/json",
410
+ "X-Requested-With": "XMLHttpRequest"
411
+ }
412
+ });
413
+ if (!response.ok) {
414
+ logger?.error("session.unauthorized", "Login request failed", {
415
+ status: response.status,
416
+ url: config.endpoints.login
417
+ });
418
+ throw Object.assign(new Error("Login failed"), { status: response.status, data: response.data });
419
+ }
420
+ const { token, user } = extractTokenAndUser(response.data);
421
+ if (config.mode === "jwt" && !token) {
422
+ logger?.warn("mismatch.mode", "JWT mode expected token in login response but none found", {
423
+ mode: config.mode,
424
+ url: config.endpoints.login
425
+ });
426
+ }
427
+ if (token) {
428
+ storage.set(token);
429
+ }
430
+ logger?.info("session.ok", "Login succeeded", { hasToken: !!token, hasUser: !!user });
431
+ return { token, user, raw: response.data };
432
+ }
433
+
434
+ // src/strategies/remote.ts
435
+ function buildRemoteLoginUrl(config, returnPath, logger) {
436
+ const loginUrl = config.loginUrl ?? "/account/login";
437
+ const blocked = config.appPath ? [config.appPath, "/account"] : ["/account"];
438
+ const safeRedirect = resolveReturnPath(
439
+ returnPath ?? (typeof window !== "undefined" ? `${window.location.pathname}${window.location.search}` : "/"),
440
+ "/",
441
+ blocked
442
+ );
443
+ const url = `${loginUrl}?redirect=${encodeURIComponent(safeRedirect)}`;
444
+ logger?.info("redirect.login", "Redirecting to remote login", { url, returnPath: safeRedirect });
445
+ return url;
446
+ }
447
+ function redirectToRemoteLogin(config, returnPath, logger) {
448
+ if (typeof window === "undefined") {
449
+ return;
450
+ }
451
+ window.location.href = buildRemoteLoginUrl(config, returnPath, logger);
452
+ }
453
+
454
+ // src/http/types.ts
455
+ function createFetchProvider() {
456
+ return {
457
+ async request(input) {
458
+ const headers = {
459
+ Accept: "application/json",
460
+ ...input.headers
461
+ };
462
+ let body;
463
+ if (input.body !== void 0 && input.body !== null) {
464
+ if (typeof input.body === "string" || input.body instanceof FormData || input.body instanceof Blob) {
465
+ body = input.body;
466
+ } else {
467
+ headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
468
+ body = JSON.stringify(input.body);
469
+ }
470
+ }
471
+ const response = await fetch(input.url, {
472
+ method: input.method ?? "GET",
473
+ headers,
474
+ body,
475
+ credentials: input.credentials ?? "include"
476
+ });
477
+ const contentType = response.headers.get("content-type") ?? "";
478
+ let data;
479
+ if (contentType.includes("application/json")) {
480
+ data = await response.json();
481
+ } else {
482
+ data = await response.text();
483
+ }
484
+ return {
485
+ status: response.status,
486
+ data,
487
+ headers: response.headers,
488
+ ok: response.ok
489
+ };
490
+ }
491
+ };
492
+ }
493
+
494
+ // src/core/createAuth.ts
495
+ var defaultInstance = null;
496
+ function getAuth() {
497
+ if (!defaultInstance) {
498
+ throw new Error("@pinooxhq/auth: call createAuth() before getAuth()");
499
+ }
500
+ return defaultInstance;
501
+ }
502
+ function createAuth(options = {}) {
503
+ const logger = options.logger ?? createLogger(options.debug);
504
+ const config = resolveConfig(options, logger);
505
+ const storage = createStorage(config.key, logger);
506
+ let http = options.http ?? createFetchProvider();
507
+ let token = storage.get();
508
+ let user = null;
509
+ let authenticated = false;
510
+ const listeners = /* @__PURE__ */ new Map();
511
+ const emit = (event, payload) => {
512
+ const set = listeners.get(event);
513
+ if (!set) {
514
+ return;
515
+ }
516
+ for (const handler of set) {
517
+ try {
518
+ handler(payload);
519
+ } catch (error) {
520
+ logger.error("error", "Event handler failed", { event, error: String(error) });
521
+ }
522
+ }
523
+ };
524
+ const syncToken = (value) => {
525
+ token = value;
526
+ storage.set(value);
527
+ };
528
+ const getAuthHeader = () => {
529
+ if (config.mode !== "jwt" || !token) {
530
+ return null;
531
+ }
532
+ return token.toLowerCase().startsWith("bearer ") ? token : `Bearer ${token}`;
533
+ };
534
+ const getRequestAuth = () => ({
535
+ header: getAuthHeader(),
536
+ credentials: "include",
537
+ mode: config.mode,
538
+ key: config.key
539
+ });
540
+ const authorize = (headers = {}) => {
541
+ const auth = getRequestAuth();
542
+ const next = { ...headers };
543
+ if (auth.header) {
544
+ next.Authorization = auth.header;
545
+ }
546
+ logger.debug("request.auth", "Authorized request headers", {
547
+ mode: auth.mode,
548
+ hasHeader: !!auth.header
549
+ });
550
+ return { ...auth, headers: next };
551
+ };
552
+ const withAuthHeaders = (headers = {}) => {
553
+ return authorize(headers).headers;
554
+ };
555
+ const loginFromResponse = (payload) => {
556
+ const extracted = extractTokenAndUser(payload);
557
+ if (config.mode === "jwt" && !extracted.token) {
558
+ logger.warn("mismatch.mode", "JWT mode expected token in response but none found", {
559
+ mode: config.mode
560
+ });
561
+ emit("mismatch", { code: "mismatch.mode", payload });
562
+ }
563
+ if (extracted.token) {
564
+ syncToken(extracted.token);
565
+ }
566
+ if (extracted.user) {
567
+ user = extracted.user;
568
+ }
569
+ authenticated = !!(extracted.token || config.mode !== "jwt");
570
+ emit("login", { token: extracted.token, user: extracted.user });
571
+ return { ...extracted, raw: payload };
572
+ };
573
+ const me = async () => {
574
+ const current = storage.get();
575
+ token = current;
576
+ if (config.mode === "jwt" && !current) {
577
+ authenticated = false;
578
+ user = null;
579
+ logger.debug("session.unauthorized", "No token for me()", { mode: config.mode });
580
+ return null;
581
+ }
582
+ try {
583
+ const response = await http.request({
584
+ url: config.endpoints.me,
585
+ method: "GET",
586
+ credentials: "include",
587
+ headers: withAuthHeaders({
588
+ Accept: "application/json",
589
+ "X-Requested-With": "XMLHttpRequest"
590
+ })
591
+ });
592
+ if (response.status === 401) {
593
+ syncToken(null);
594
+ user = null;
595
+ authenticated = false;
596
+ logger.warn("session.unauthorized", "Session validation returned 401", {
597
+ url: config.endpoints.me
598
+ });
599
+ emit("unauthorized", { status: 401 });
600
+ return null;
601
+ }
602
+ if (!response.ok) {
603
+ logger.error("error", "me() request failed", { status: response.status });
604
+ emit("error", { status: response.status, data: response.data });
605
+ return null;
606
+ }
607
+ const extracted = extractTokenAndUser(response.data);
608
+ user = extracted.user ?? response.data;
609
+ authenticated = true;
610
+ logger.info("session.ok", "Session validated", { hasUser: !!user });
611
+ return user;
612
+ } catch (error) {
613
+ logger.error("error", "me() threw", { error: String(error) });
614
+ emit("error", error);
615
+ return null;
616
+ }
617
+ };
618
+ const login = async (credentials) => {
619
+ if (config.strategy === "remote") {
620
+ redirectToRemoteLogin(config, void 0, logger);
621
+ return;
622
+ }
623
+ if (!credentials?.password) {
624
+ throw new Error("@pinooxhq/auth: login() requires credentials for local strategy");
625
+ }
626
+ const result = await localLogin(config, http, storage, credentials, logger);
627
+ token = result.token ?? storage.get();
628
+ user = result.user;
629
+ authenticated = !!(result.token || config.mode !== "jwt");
630
+ emit("login", result);
631
+ return result;
632
+ };
633
+ const logout = async () => {
634
+ try {
635
+ await http.request({
636
+ url: config.endpoints.logout,
637
+ method: "GET",
638
+ credentials: "include",
639
+ headers: withAuthHeaders({
640
+ Accept: "application/json",
641
+ "X-Requested-With": "XMLHttpRequest"
642
+ })
643
+ });
644
+ } catch (error) {
645
+ logger.warn("error", "Remote logout failed; clearing local session anyway", {
646
+ error: String(error)
647
+ });
648
+ }
649
+ syncToken(null);
650
+ user = null;
651
+ authenticated = false;
652
+ emit("logout");
653
+ logger.info("token.cleared", "Logged out");
654
+ };
655
+ const instance = {
656
+ config,
657
+ logger,
658
+ get user() {
659
+ return user;
660
+ },
661
+ set user(value) {
662
+ user = value;
663
+ },
664
+ get token() {
665
+ return token ?? storage.get();
666
+ },
667
+ set token(value) {
668
+ syncToken(value);
669
+ },
670
+ get isAuthenticated() {
671
+ return authenticated && (!!token || config.mode !== "jwt");
672
+ },
673
+ set isAuthenticated(value) {
674
+ authenticated = value;
675
+ },
676
+ login,
677
+ logout,
678
+ me,
679
+ getToken: () => token ?? storage.get(),
680
+ setToken: (value) => {
681
+ syncToken(value);
682
+ },
683
+ clearToken: () => {
684
+ syncToken(null);
685
+ authenticated = false;
686
+ },
687
+ getAuthHeader,
688
+ authorize,
689
+ getRequestAuth,
690
+ setHttp: (client) => {
691
+ http = client;
692
+ },
693
+ loginFromResponse,
694
+ redirectToLogin: (returnPath) => {
695
+ if (config.strategy === "remote") {
696
+ redirectToRemoteLogin(config, returnPath, logger);
697
+ return;
698
+ }
699
+ const url = config.loginUrl ?? config.endpoints.login;
700
+ logger.info("redirect.login", "Redirecting to login", { url });
701
+ if (typeof window !== "undefined") {
702
+ window.location.href = url;
703
+ }
704
+ },
705
+ getReturnPath: (queryOrPath, fallback = "/") => {
706
+ const blocked = config.appPath ? [config.appPath] : ["/account"];
707
+ return resolveReturnPath(queryOrPath, fallback, blocked);
708
+ },
709
+ getReturnUrl: (queryOrPath, fallback = "/") => {
710
+ const blocked = config.appPath ? [config.appPath] : ["/account"];
711
+ const path = resolveReturnPath(queryOrPath, fallback, blocked);
712
+ return toAbsoluteReturnUrl(path, config.siteOrigin);
713
+ },
714
+ redirectBack: (queryOrPath, fallback = "/") => {
715
+ const blocked = config.appPath ? [config.appPath] : ["/account"];
716
+ logger.info("redirect.back", "Redirecting after auth", { queryOrPath, fallback });
717
+ redirectToReturn(queryOrPath, {
718
+ fallback,
719
+ siteOrigin: config.siteOrigin,
720
+ blockedPrefixes: blocked
721
+ });
722
+ },
723
+ getRedirectQuery: (queryOrPath, fallback = "/") => {
724
+ const blocked = config.appPath ? [config.appPath] : ["/account"];
725
+ return { redirect: resolveReturnPath(queryOrPath, fallback, blocked) };
726
+ },
727
+ notifyUnauthorized: (payload) => {
728
+ syncToken(null);
729
+ user = null;
730
+ authenticated = false;
731
+ logger.warn("session.unauthorized", "Unauthorized notified", {
732
+ payload: payload ? String(payload) : void 0
733
+ });
734
+ emit("unauthorized", payload ?? { status: 401 });
735
+ },
736
+ on: (event, handler) => {
737
+ if (!listeners.has(event)) {
738
+ listeners.set(event, /* @__PURE__ */ new Set());
739
+ }
740
+ listeners.get(event).add(handler);
741
+ return () => {
742
+ listeners.get(event)?.delete(handler);
743
+ };
744
+ },
745
+ off: (event, handler) => {
746
+ listeners.get(event)?.delete(handler);
747
+ }
748
+ };
749
+ defaultInstance = instance;
750
+ emit("config", config);
751
+ return instance;
752
+ }
753
+
754
+ // src/react/useAuth.ts
755
+ var AuthContext = react.createContext(null);
756
+ function resolveAuth(options) {
757
+ try {
758
+ return getAuth();
759
+ } catch {
760
+ return createAuth(options ?? {});
761
+ }
762
+ }
763
+ function readSnapshot(auth) {
764
+ const token = auth.getToken();
765
+ const user = auth.user;
766
+ return {
767
+ user,
768
+ token,
769
+ isAuthenticated: auth.isAuthenticated || !!token && !!user
770
+ };
771
+ }
772
+ function snapshotsEqual(a, b) {
773
+ return a.user === b.user && a.token === b.token && a.isAuthenticated === b.isAuthenticated;
774
+ }
775
+ function subscribeAuth(auth, onStoreChange) {
776
+ const offLogin = auth.on("login", onStoreChange);
777
+ const offLogout = auth.on("logout", onStoreChange);
778
+ const offUnauthorized = auth.on("unauthorized", onStoreChange);
779
+ return () => {
780
+ offLogin();
781
+ offLogout();
782
+ offUnauthorized();
783
+ };
784
+ }
785
+ function AuthProvider({ children, options, auth: authProp }) {
786
+ const auth = react.useMemo(() => authProp ?? resolveAuth(options), [authProp, options]);
787
+ return react.createElement(AuthContext.Provider, { value: auth }, children);
788
+ }
789
+ function useAuthInstance() {
790
+ const ctx = react.useContext(AuthContext);
791
+ if (ctx) {
792
+ return ctx;
793
+ }
794
+ return resolveAuth();
795
+ }
796
+ function useAuth() {
797
+ const auth = useAuthInstance();
798
+ const cache = react.useRef(readSnapshot(auth));
799
+ const snapshot = react.useSyncExternalStore(
800
+ (onStoreChange) => subscribeAuth(auth, onStoreChange),
801
+ () => {
802
+ const next = readSnapshot(auth);
803
+ if (!snapshotsEqual(cache.current, next)) {
804
+ cache.current = next;
805
+ }
806
+ return cache.current;
807
+ },
808
+ () => ({
809
+ user: null,
810
+ token: null,
811
+ isAuthenticated: false
812
+ })
813
+ );
814
+ const me = react.useCallback(async () => {
815
+ return auth.me();
816
+ }, [auth]);
817
+ const canAccess = react.useCallback(async (refresh = false) => {
818
+ const token = auth.getToken();
819
+ if (!refresh && auth.isAuthenticated) {
820
+ return true;
821
+ }
822
+ if (!token && auth.config.mode === "jwt") {
823
+ auth.isAuthenticated = false;
824
+ return false;
825
+ }
826
+ const profile = await auth.me();
827
+ return !!profile || auth.isAuthenticated;
828
+ }, [auth]);
829
+ const login = react.useCallback(async (credentials) => {
830
+ return auth.login(credentials);
831
+ }, [auth]);
832
+ const logout = react.useCallback(async () => {
833
+ await auth.logout();
834
+ }, [auth]);
835
+ return {
836
+ auth,
837
+ user: snapshot.user,
838
+ token: snapshot.token,
839
+ isAuthenticated: snapshot.isAuthenticated,
840
+ login,
841
+ logout,
842
+ me,
843
+ canAccess
844
+ };
845
+ }
846
+ function createReactAuth(options = {}) {
847
+ const auth = createAuth(options);
848
+ function BoundAuthProvider({ children }) {
849
+ return react.createElement(AuthProvider, { auth, children });
850
+ }
851
+ return {
852
+ auth,
853
+ AuthProvider: BoundAuthProvider,
854
+ useAuth
855
+ };
856
+ }
857
+
858
+ exports.AuthProvider = AuthProvider;
859
+ exports.createReactAuth = createReactAuth;
860
+ exports.useAuth = useAuth;
861
+ //# sourceMappingURL=index.cjs.map
862
+ //# sourceMappingURL=index.cjs.map