@fluixi/oauth2 1.0.0-alpha.8 → 1.0.0-alpha.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +1 -1
  2. package/dist/backchannel.cjs +462 -0
  3. package/dist/backchannel.d.ts +39 -0
  4. package/dist/backchannel.d.ts.map +1 -0
  5. package/dist/backchannel.js +97 -0
  6. package/dist/backchannel.mjs +307 -0
  7. package/dist/{browser-NYKDDB73.mjs → browser-LQSO5OS2.mjs} +2 -2
  8. package/dist/browser.cjs +6 -1
  9. package/dist/browser.mjs +6 -1
  10. package/dist/callback.cjs +8 -7
  11. package/dist/callback.js +1 -1
  12. package/dist/callback.mjs +3 -2
  13. package/dist/chunk-32562UUL.mjs +108 -0
  14. package/dist/{chunk-7R2PVIX6.mjs → chunk-P6SSCHQX.mjs} +1 -1
  15. package/dist/{chunk-QPL6B2YE.mjs → chunk-V27WQGTT.mjs} +1 -1
  16. package/dist/discovery.cjs +6 -1
  17. package/dist/discovery.d.ts.map +1 -1
  18. package/dist/discovery.js +6 -1
  19. package/dist/discovery.mjs +6 -1
  20. package/dist/dpop-2Q6R3KLB.mjs +22 -0
  21. package/dist/{dpop-BR4S6SCZ.mjs → dpop-4FGDUD4B.mjs} +4 -4
  22. package/dist/index.cjs +116 -8
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -0
  26. package/dist/index.mjs +119 -11
  27. package/dist/module.cjs +1 -1
  28. package/dist/module.mjs +1 -1
  29. package/dist/server.cjs +649 -137
  30. package/dist/server.d.ts +64 -4
  31. package/dist/server.d.ts.map +1 -1
  32. package/dist/server.js +376 -61
  33. package/dist/server.mjs +532 -139
  34. package/dist/service-client.cjs +7 -2
  35. package/dist/service-client.mjs +9 -4
  36. package/dist/service.cjs +112 -5
  37. package/dist/service.d.ts +0 -14
  38. package/dist/service.d.ts.map +1 -1
  39. package/dist/service.js +48 -5
  40. package/dist/service.mjs +112 -5
  41. package/dist/session-monitor.cjs +98 -0
  42. package/dist/session-monitor.d.ts +32 -0
  43. package/dist/session-monitor.d.ts.map +1 -0
  44. package/dist/session-monitor.js +104 -0
  45. package/dist/session-monitor.mjs +77 -0
  46. package/dist/tokens.cjs +1 -1
  47. package/dist/tokens.d.ts +2 -2
  48. package/dist/tokens.d.ts.map +1 -1
  49. package/dist/tokens.js +1 -1
  50. package/dist/tokens.mjs +1 -1
  51. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  52. package/dist/types.d.ts +8 -0
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/verify.cjs +7 -2
  55. package/dist/verify.mjs +12 -7
  56. package/package.json +17 -15
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! @fluixi/oauth2 v1.0.0-alpha.8 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/oauth2 v1.0.0-alpha.99 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
  "use strict";
3
3
  var __create = Object.create;
4
4
  var __defProp = Object.defineProperty;
@@ -51,11 +51,13 @@ __export(index_exports, {
51
51
  paramsFromUrl: () => paramsFromUrl,
52
52
  proofFor: () => proofFor,
53
53
  safeReturnTo: () => safeReturnTo,
54
- thumbprintOf: () => thumbprintOf
54
+ thumbprintOf: () => thumbprintOf,
55
+ watchProviderSession: () => watchProviderSession
55
56
  });
56
57
  module.exports = __toCommonJS(index_exports);
57
58
 
58
59
  // src/service.ts
60
+ var import_signal = require("@fluixi/reactive/signal");
59
61
  var import_session = require("@fluixi/session");
60
62
  var import_client = require("@fluixi/auth/client");
61
63
 
@@ -363,8 +365,12 @@ async function discover(issuer, fetcher = fetch) {
363
365
  try {
364
366
  response = await fetcher(documentUrl(issuer));
365
367
  } catch (cause) {
368
+ const why = cause instanceof Error ? cause.message : String(cause);
366
369
  throw new OAuthError(
367
- `Could not read the discovery document at ${documentUrl(issuer)}. In a browser this is usually the provider refusing a cross-origin read: give \`endpoints\` directly, or resolve them on a server. (${cause instanceof Error ? cause.message : String(cause)})`,
370
+ typeof window === "undefined" ? (
371
+ // On a server nothing blocks the read: the provider is down, or the address is wrong.
372
+ `Could not reach the provider at ${documentUrl(issuer)}: is it running, and is the issuer right? (${why})`
373
+ ) : `Could not read the discovery document at ${documentUrl(issuer)}. In a browser this is usually the provider refusing a cross-origin read: give \`endpoints\` directly, or resolve them on a server. (${why})`,
368
374
  "discovery_unreachable"
369
375
  );
370
376
  }
@@ -389,6 +395,7 @@ async function discover(issuer, fetcher = fetch) {
389
395
  jwks: doc.jwks_uri,
390
396
  introspection: doc.introspection_endpoint,
391
397
  revocation: doc.revocation_endpoint,
398
+ checkSession: doc.check_session_iframe,
392
399
  issuer: doc.issuer ?? issuer
393
400
  };
394
401
  })();
@@ -847,6 +854,81 @@ function createServerClient(config, fetcher = fetch) {
847
854
  };
848
855
  }
849
856
 
857
+ // src/session-monitor.ts
858
+ var SETTLE_MS = 15e3;
859
+ var GUARD_KEY = "fx-session-check";
860
+ function recentlyActed() {
861
+ try {
862
+ return Date.now() - Number(sessionStorage.getItem(GUARD_KEY) ?? 0) < SETTLE_MS;
863
+ } catch {
864
+ return false;
865
+ }
866
+ }
867
+ function markActed() {
868
+ try {
869
+ sessionStorage.setItem(GUARD_KEY, String(Date.now()));
870
+ } catch {
871
+ }
872
+ }
873
+ function watchProviderSession(options = {}) {
874
+ if (typeof window === "undefined" || typeof document === "undefined") return () => {
875
+ };
876
+ const base = (options.basePath ?? "/auth").replace(/\/$/, "");
877
+ const fetcher = options.fetcher ?? fetch;
878
+ const onChange = options.onChange ?? (() => {
879
+ const login = new URLSearchParams({ prompt: "none", returnTo: location.pathname + location.search });
880
+ location.assign(`${base}/login?${login}`);
881
+ });
882
+ let stopped = false;
883
+ const cleanups = [];
884
+ const stop = () => {
885
+ stopped = true;
886
+ for (const cleanup of cleanups.splice(0)) cleanup();
887
+ };
888
+ void (async () => {
889
+ const check = await fetcher(`${base}/session-check`, { credentials: "same-origin" }).then((response) => response.ok ? response.json() : null).catch(() => null);
890
+ if (stopped || !check?.frame || !check.state) return;
891
+ const origin = new URL(check.frame).origin;
892
+ const frame = document.createElement("iframe");
893
+ frame.src = check.frame;
894
+ frame.title = "session check";
895
+ frame.setAttribute("aria-hidden", "true");
896
+ frame.tabIndex = -1;
897
+ frame.style.cssText = "position:absolute;width:0;height:0;border:0;visibility:hidden";
898
+ const message = `${check.clientId} ${check.state}`;
899
+ const ask = () => {
900
+ if (!stopped && document.visibilityState === "visible") frame.contentWindow?.postMessage(message, origin);
901
+ };
902
+ let confirmed = false;
903
+ const answer = (event) => {
904
+ if (event.origin !== origin || event.source !== frame.contentWindow) return;
905
+ if (event.data === "unchanged") confirmed = true;
906
+ else if (event.data === "changed") {
907
+ stop();
908
+ if (!confirmed && recentlyActed()) return;
909
+ markActed();
910
+ onChange();
911
+ } else if (event.data === "error") {
912
+ stop();
913
+ }
914
+ };
915
+ window.addEventListener("message", answer);
916
+ frame.addEventListener("load", ask);
917
+ document.addEventListener("visibilitychange", ask);
918
+ window.addEventListener("focus", ask);
919
+ const timer = setInterval(ask, options.intervalMs ?? 3e3);
920
+ document.body.appendChild(frame);
921
+ cleanups.push(() => {
922
+ window.removeEventListener("message", answer);
923
+ document.removeEventListener("visibilitychange", ask);
924
+ window.removeEventListener("focus", ask);
925
+ clearInterval(timer);
926
+ frame.remove();
927
+ });
928
+ })();
929
+ return stop;
930
+ }
931
+
850
932
  // src/service.ts
851
933
  function paramsFromUrl(url) {
852
934
  const href = url ?? (typeof location === "undefined" ? null : location.href);
@@ -864,18 +946,28 @@ function clientFor(options) {
864
946
  return options.config.tokens === "server" ? createServerClient(options.config) : createBrowserClient(options.config);
865
947
  }
866
948
  function createAuthService(options) {
949
+ const tokens = options.config.tokens;
950
+ const scope = (0, import_signal.openReactiveScope)({ kind: "service", name: "AuthService", ...tokens ? { detail: `tokens: ${tokens}` } : {} });
951
+ const service = (0, import_signal.withReactiveScope)(scope, () => buildAuthService(options));
952
+ (0, import_signal.markReactiveScope)(service, scope);
953
+ return service;
954
+ }
955
+ function buildAuthService(options) {
867
956
  const client = clientFor(options);
868
957
  let latest = null;
958
+ const fetchUserFor = async (token) => {
959
+ if (options.fetchUser) return options.fetchUser(token);
960
+ return client.fetchUser(token === COOKIE_SESSION ? void 0 : token);
961
+ };
962
+ const resumesFromCookie = options.config.tokens === "server" && typeof window !== "undefined";
963
+ const [ready, setReady] = (0, import_signal.createSignal)(!resumesFromCookie);
869
964
  const session = (0, import_session.createSession)({
870
965
  /** The credential is the callback, so this is the code exchange. */
871
966
  async login(params) {
872
967
  latest = await client.handleCallback(params);
873
968
  return { token: latest.accessToken, expiresAt: latest.expiresAt };
874
969
  },
875
- async fetchUser(token) {
876
- if (options.fetchUser) return options.fetchUser(token);
877
- return client.fetchUser(token === COOKIE_SESSION ? void 0 : token);
878
- },
970
+ fetchUser: fetchUserFor,
879
971
  async refresh() {
880
972
  latest = await client.refresh();
881
973
  return { token: latest.accessToken, expiresAt: latest.expiresAt };
@@ -898,11 +990,27 @@ function createAuthService(options) {
898
990
  });
899
991
  const hooks = (0, import_client.createAuthHooks)({
900
992
  session,
993
+ ready,
901
994
  navigate: options.navigate,
902
995
  loginPath: options.loginPath ?? "/login",
903
996
  homePath: options.homePath ?? "/",
904
997
  forbiddenPath: options.forbiddenPath ?? "/403"
905
998
  });
999
+ if (resumesFromCookie && options.config.tokens === "server" && options.config.watchSession !== false) {
1000
+ watchProviderSession({ basePath: options.config.basePath });
1001
+ }
1002
+ const renderedSignedOut = hooks.store.seeded && hooks.store.value().user == null;
1003
+ if (resumesFromCookie && renderedSignedOut) setReady(true);
1004
+ else if (resumesFromCookie) {
1005
+ void (async () => {
1006
+ try {
1007
+ await session.adopt(COOKIE_SESSION, await fetchUserFor(COOKIE_SESSION));
1008
+ } catch {
1009
+ } finally {
1010
+ setReady(true);
1011
+ }
1012
+ })();
1013
+ }
906
1014
  return Object.assign(session, {
907
1015
  client,
908
1016
  protectedRoute: hooks.protectedRoute,
@@ -938,7 +1046,7 @@ function createAuthService(options) {
938
1046
  }
939
1047
 
940
1048
  // src/tokens.ts
941
- var import_di = require("@fluixi/start/di");
1049
+ var import_di = require("@fluixi/di");
942
1050
  var AuthServiceToken = (0, import_di.injectionToken)("AuthService");
943
1051
  var AuthClientToken = (0, import_di.injectionToken)("AuthClient");
944
1052
 
package/dist/index.d.ts CHANGED
@@ -93,6 +93,8 @@ export type { AuthService, AuthServiceOptions, AuthCredentials } from './service
93
93
  export { createBrowserClient } from './browser.js';
94
94
  export { createServerClient, COOKIE_SESSION } from './server-client.js';
95
95
  export { discover, clearDiscoveryCache } from './discovery.js';
96
+ export { watchProviderSession } from './session-monitor.js';
97
+ export type { SessionCheck, WatchOptions } from './session-monitor.js';
96
98
  export { createPkcePair, createVerifier, createChallenge, createStateValue, expiryFromJwt, } from './pkce.js';
97
99
  export { createDpopKey, proofFor, thumbprintOf, boundKeyOf } from './dpop.js';
98
100
  export type { DpopKey, ProofOptions } from './dpop.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFG;AACH,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAErF,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAExE,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EACL,cAAc,EACd,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9E,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,YAAY,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,cAAc,EACd,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,MAAM,GACP,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFG;AACH,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAErF,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAExE,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EACL,cAAc,EACd,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9E,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,YAAY,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,cAAc,EACd,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,MAAM,GACP,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -92,6 +92,7 @@ export { createAuthService, paramsFromUrl } from './service.js';
92
92
  export { createBrowserClient } from './browser.js';
93
93
  export { createServerClient, COOKIE_SESSION } from './server-client.js';
94
94
  export { discover, clearDiscoveryCache } from './discovery.js';
95
+ export { watchProviderSession } from './session-monitor.js';
95
96
  export { createPkcePair, createVerifier, createChallenge, createStateValue, expiryFromJwt, } from './pkce.js';
96
97
  export { createDpopKey, proofFor, thumbprintOf, boundKeyOf } from './dpop.js';
97
98
  export { safeReturnTo } from './return-to.js';
package/dist/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
- /*! @fluixi/oauth2 v1.0.0-alpha.8 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
- import "./chunk-7R2PVIX6.mjs";
1
+ /*! @fluixi/oauth2 v1.0.0-alpha.99 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+ import "./chunk-P6SSCHQX.mjs";
3
3
 
4
4
  // src/service.ts
5
+ import { createSignal, markReactiveScope, openReactiveScope, withReactiveScope } from "@fluixi/reactive/signal";
5
6
  import { createSession } from "@fluixi/session";
6
7
  import { createAuthHooks } from "@fluixi/auth/client";
7
8
 
@@ -111,7 +112,7 @@ function resolveMethod(auth) {
111
112
  }
112
113
  async function loadJose() {
113
114
  try {
114
- return await import("./browser-NYKDDB73.mjs");
115
+ return await import("./browser-LQSO5OS2.mjs");
115
116
  } catch {
116
117
  throw new OAuthError(
117
118
  "private_key_jwt needs `jose`. Install it: npm i jose. It is an optional peer, so a client using a secret does not carry it.",
@@ -151,7 +152,7 @@ async function applyClientAuth(auth, endpoint, headers, form) {
151
152
  // src/dpop.ts
152
153
  async function loadJose2() {
153
154
  try {
154
- return await import("./browser-NYKDDB73.mjs");
155
+ return await import("./browser-LQSO5OS2.mjs");
155
156
  } catch {
156
157
  throw new OAuthError(
157
158
  "DPoP needs `jose`. Install it: npm i jose. It is an optional peer, so a client not using DPoP does not carry it.",
@@ -309,8 +310,12 @@ async function discover(issuer, fetcher = fetch) {
309
310
  try {
310
311
  response = await fetcher(documentUrl(issuer));
311
312
  } catch (cause) {
313
+ const why = cause instanceof Error ? cause.message : String(cause);
312
314
  throw new OAuthError(
313
- `Could not read the discovery document at ${documentUrl(issuer)}. In a browser this is usually the provider refusing a cross-origin read: give \`endpoints\` directly, or resolve them on a server. (${cause instanceof Error ? cause.message : String(cause)})`,
315
+ typeof window === "undefined" ? (
316
+ // On a server nothing blocks the read: the provider is down, or the address is wrong.
317
+ `Could not reach the provider at ${documentUrl(issuer)}: is it running, and is the issuer right? (${why})`
318
+ ) : `Could not read the discovery document at ${documentUrl(issuer)}. In a browser this is usually the provider refusing a cross-origin read: give \`endpoints\` directly, or resolve them on a server. (${why})`,
314
319
  "discovery_unreachable"
315
320
  );
316
321
  }
@@ -335,6 +340,7 @@ async function discover(issuer, fetcher = fetch) {
335
340
  jwks: doc.jwks_uri,
336
341
  introspection: doc.introspection_endpoint,
337
342
  revocation: doc.revocation_endpoint,
343
+ checkSession: doc.check_session_iframe,
338
344
  issuer: doc.issuer ?? issuer
339
345
  };
340
346
  })();
@@ -793,6 +799,81 @@ function createServerClient(config, fetcher = fetch) {
793
799
  };
794
800
  }
795
801
 
802
+ // src/session-monitor.ts
803
+ var SETTLE_MS = 15e3;
804
+ var GUARD_KEY = "fx-session-check";
805
+ function recentlyActed() {
806
+ try {
807
+ return Date.now() - Number(sessionStorage.getItem(GUARD_KEY) ?? 0) < SETTLE_MS;
808
+ } catch {
809
+ return false;
810
+ }
811
+ }
812
+ function markActed() {
813
+ try {
814
+ sessionStorage.setItem(GUARD_KEY, String(Date.now()));
815
+ } catch {
816
+ }
817
+ }
818
+ function watchProviderSession(options = {}) {
819
+ if (typeof window === "undefined" || typeof document === "undefined") return () => {
820
+ };
821
+ const base = (options.basePath ?? "/auth").replace(/\/$/, "");
822
+ const fetcher = options.fetcher ?? fetch;
823
+ const onChange = options.onChange ?? (() => {
824
+ const login = new URLSearchParams({ prompt: "none", returnTo: location.pathname + location.search });
825
+ location.assign(`${base}/login?${login}`);
826
+ });
827
+ let stopped = false;
828
+ const cleanups = [];
829
+ const stop = () => {
830
+ stopped = true;
831
+ for (const cleanup of cleanups.splice(0)) cleanup();
832
+ };
833
+ void (async () => {
834
+ const check = await fetcher(`${base}/session-check`, { credentials: "same-origin" }).then((response) => response.ok ? response.json() : null).catch(() => null);
835
+ if (stopped || !check?.frame || !check.state) return;
836
+ const origin = new URL(check.frame).origin;
837
+ const frame = document.createElement("iframe");
838
+ frame.src = check.frame;
839
+ frame.title = "session check";
840
+ frame.setAttribute("aria-hidden", "true");
841
+ frame.tabIndex = -1;
842
+ frame.style.cssText = "position:absolute;width:0;height:0;border:0;visibility:hidden";
843
+ const message = `${check.clientId} ${check.state}`;
844
+ const ask = () => {
845
+ if (!stopped && document.visibilityState === "visible") frame.contentWindow?.postMessage(message, origin);
846
+ };
847
+ let confirmed = false;
848
+ const answer = (event) => {
849
+ if (event.origin !== origin || event.source !== frame.contentWindow) return;
850
+ if (event.data === "unchanged") confirmed = true;
851
+ else if (event.data === "changed") {
852
+ stop();
853
+ if (!confirmed && recentlyActed()) return;
854
+ markActed();
855
+ onChange();
856
+ } else if (event.data === "error") {
857
+ stop();
858
+ }
859
+ };
860
+ window.addEventListener("message", answer);
861
+ frame.addEventListener("load", ask);
862
+ document.addEventListener("visibilitychange", ask);
863
+ window.addEventListener("focus", ask);
864
+ const timer = setInterval(ask, options.intervalMs ?? 3e3);
865
+ document.body.appendChild(frame);
866
+ cleanups.push(() => {
867
+ window.removeEventListener("message", answer);
868
+ document.removeEventListener("visibilitychange", ask);
869
+ window.removeEventListener("focus", ask);
870
+ clearInterval(timer);
871
+ frame.remove();
872
+ });
873
+ })();
874
+ return stop;
875
+ }
876
+
796
877
  // src/service.ts
797
878
  function paramsFromUrl(url) {
798
879
  const href = url ?? (typeof location === "undefined" ? null : location.href);
@@ -810,18 +891,28 @@ function clientFor(options) {
810
891
  return options.config.tokens === "server" ? createServerClient(options.config) : createBrowserClient(options.config);
811
892
  }
812
893
  function createAuthService(options) {
894
+ const tokens = options.config.tokens;
895
+ const scope = openReactiveScope({ kind: "service", name: "AuthService", ...tokens ? { detail: `tokens: ${tokens}` } : {} });
896
+ const service = withReactiveScope(scope, () => buildAuthService(options));
897
+ markReactiveScope(service, scope);
898
+ return service;
899
+ }
900
+ function buildAuthService(options) {
813
901
  const client = clientFor(options);
814
902
  let latest = null;
903
+ const fetchUserFor = async (token) => {
904
+ if (options.fetchUser) return options.fetchUser(token);
905
+ return client.fetchUser(token === COOKIE_SESSION ? void 0 : token);
906
+ };
907
+ const resumesFromCookie = options.config.tokens === "server" && typeof window !== "undefined";
908
+ const [ready, setReady] = createSignal(!resumesFromCookie);
815
909
  const session = createSession({
816
910
  /** The credential is the callback, so this is the code exchange. */
817
911
  async login(params) {
818
912
  latest = await client.handleCallback(params);
819
913
  return { token: latest.accessToken, expiresAt: latest.expiresAt };
820
914
  },
821
- async fetchUser(token) {
822
- if (options.fetchUser) return options.fetchUser(token);
823
- return client.fetchUser(token === COOKIE_SESSION ? void 0 : token);
824
- },
915
+ fetchUser: fetchUserFor,
825
916
  async refresh() {
826
917
  latest = await client.refresh();
827
918
  return { token: latest.accessToken, expiresAt: latest.expiresAt };
@@ -844,11 +935,27 @@ function createAuthService(options) {
844
935
  });
845
936
  const hooks = createAuthHooks({
846
937
  session,
938
+ ready,
847
939
  navigate: options.navigate,
848
940
  loginPath: options.loginPath ?? "/login",
849
941
  homePath: options.homePath ?? "/",
850
942
  forbiddenPath: options.forbiddenPath ?? "/403"
851
943
  });
944
+ if (resumesFromCookie && options.config.tokens === "server" && options.config.watchSession !== false) {
945
+ watchProviderSession({ basePath: options.config.basePath });
946
+ }
947
+ const renderedSignedOut = hooks.store.seeded && hooks.store.value().user == null;
948
+ if (resumesFromCookie && renderedSignedOut) setReady(true);
949
+ else if (resumesFromCookie) {
950
+ void (async () => {
951
+ try {
952
+ await session.adopt(COOKIE_SESSION, await fetchUserFor(COOKIE_SESSION));
953
+ } catch {
954
+ } finally {
955
+ setReady(true);
956
+ }
957
+ })();
958
+ }
852
959
  return Object.assign(session, {
853
960
  client,
854
961
  protectedRoute: hooks.protectedRoute,
@@ -884,7 +991,7 @@ function createAuthService(options) {
884
991
  }
885
992
 
886
993
  // src/tokens.ts
887
- import { injectionToken } from "@fluixi/start/di";
994
+ import { injectionToken } from "@fluixi/di";
888
995
  var AuthServiceToken = injectionToken("AuthService");
889
996
  var AuthClientToken = injectionToken("AuthClient");
890
997
 
@@ -922,5 +1029,6 @@ export {
922
1029
  paramsFromUrl,
923
1030
  proofFor,
924
1031
  safeReturnTo,
925
- thumbprintOf
1032
+ thumbprintOf,
1033
+ watchProviderSession
926
1034
  };
package/dist/module.cjs CHANGED
@@ -26,7 +26,7 @@ module.exports = __toCommonJS(module_exports);
26
26
  var import_app = require("@fluixi/start/app");
27
27
 
28
28
  // src/tokens.ts
29
- var import_di = require("@fluixi/start/di");
29
+ var import_di = require("@fluixi/di");
30
30
  var AuthServiceToken = (0, import_di.injectionToken)("AuthService");
31
31
  var AuthClientToken = (0, import_di.injectionToken)("AuthClient");
32
32
 
package/dist/module.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { defineModule } from "@fluixi/start/app";
3
3
 
4
4
  // src/tokens.ts
5
- import { injectionToken } from "@fluixi/start/di";
5
+ import { injectionToken } from "@fluixi/di";
6
6
  var AuthServiceToken = injectionToken("AuthService");
7
7
  var AuthClientToken = injectionToken("AuthClient");
8
8