@fluid-app/rep-sdk 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -45,6 +45,45 @@ var HTTP_METHODS = {
45
45
  DELETE: "DELETE"
46
46
  };
47
47
 
48
+ // src/auth/auth-redirect.ts
49
+ var DEFAULT_AUTH_URL = "https://auth.fluid.app";
50
+ var AUTH_REDIRECT_TOKEN_KEY = "jwt";
51
+ var REDIRECT_TIMESTAMP_KEY = "__fluid_auth_redirect_ts";
52
+ var REDIRECT_COOLDOWN_S = 10;
53
+ function isRedirectLoop() {
54
+ try {
55
+ const ts = sessionStorage.getItem(REDIRECT_TIMESTAMP_KEY);
56
+ if (!ts) return false;
57
+ const elapsed = (Date.now() - Number(ts)) / 1e3;
58
+ return elapsed < REDIRECT_COOLDOWN_S;
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+ function markRedirect() {
64
+ try {
65
+ sessionStorage.setItem(REDIRECT_TIMESTAMP_KEY, String(Date.now()));
66
+ } catch {
67
+ }
68
+ }
69
+ function createDefaultAuthRedirect(authUrl) {
70
+ return () => {
71
+ if (isRedirectLoop()) {
72
+ console.warn(
73
+ "[FluidAuth] Auth redirect suppressed \u2014 possible redirect loop. Check that your auth server returns a token accepted by the API."
74
+ );
75
+ return;
76
+ }
77
+ markRedirect();
78
+ const base = authUrl ?? DEFAULT_AUTH_URL;
79
+ const currentUrl = encodeURIComponent(window.location.href);
80
+ window.location.href = `${base}/?redirect_url=${currentUrl}`;
81
+ };
82
+ }
83
+ function resolveAuthFailureHandler(onAuthFailure, authUrl) {
84
+ return onAuthFailure ?? createDefaultAuthRedirect(authUrl);
85
+ }
86
+
48
87
  // src/client/fluid-client.ts
49
88
  var ApiError = class _ApiError extends Error {
50
89
  status;
@@ -88,6 +127,7 @@ function extractErrorMessage(data, fallback) {
88
127
  }
89
128
  function createFluidClient(config) {
90
129
  const { baseUrl, getAuthToken, onAuthError, defaultHeaders = {} } = config;
130
+ const effectiveOnAuthError = onAuthError ?? createDefaultAuthRedirect();
91
131
  async function buildHeaders(customHeaders) {
92
132
  const headers = {
93
133
  "Content-Type": "application/json",
@@ -173,8 +213,9 @@ function createFluidClient(config) {
173
213
  null
174
214
  );
175
215
  }
176
- if (response.status === 401 && onAuthError) {
177
- onAuthError();
216
+ if (response.status === 401) {
217
+ effectiveOnAuthError();
218
+ return null;
178
219
  }
179
220
  if (!response.ok) {
180
221
  try {
@@ -335,6 +376,16 @@ function createFluidClient(config) {
335
376
  var ThemeContext = react.createContext(null);
336
377
  function applyThemeVariables(theme, container) {
337
378
  const target = container ?? document.documentElement;
379
+ const toRemove = [];
380
+ for (let i = 0; i < target.style.length; i++) {
381
+ const prop = target.style[i];
382
+ if (prop.startsWith("--fluid-")) {
383
+ toRemove.push(prop);
384
+ }
385
+ }
386
+ for (const prop of toRemove) {
387
+ target.style.removeProperty(prop);
388
+ }
338
389
  for (const [key, value] of Object.entries(theme.config)) {
339
390
  target.style.setProperty(`--fluid-${key}`, value);
340
391
  }
@@ -401,7 +452,11 @@ function FluidProvider({
401
452
  const configRef = react.useRef(config);
402
453
  configRef.current = config;
403
454
  const client = react.useMemo(
404
- () => createFluidClient(configRef.current),
455
+ () => createFluidClient({
456
+ ...configRef.current,
457
+ getAuthToken: () => configRef.current.getAuthToken?.() ?? null,
458
+ onAuthError: () => configRef.current.onAuthError?.()
459
+ }),
405
460
  // eslint-disable-next-line react-hooks/exhaustive-deps
406
461
  [config.baseUrl]
407
462
  );
@@ -454,10 +509,12 @@ var URL_PARAMS = {
454
509
  COMPANY_TOKEN: "fluidCompanyToken"
455
510
  };
456
511
 
457
- // src/auth/url-token.ts
512
+ // src/auth/browser-utils.ts
458
513
  function isBrowser() {
459
- return typeof window !== "undefined";
514
+ return typeof window !== "undefined" && typeof document !== "undefined";
460
515
  }
516
+
517
+ // src/auth/url-token.ts
461
518
  function extractTokenFromUrl(tokenKey = URL_PARAMS.USER_TOKEN) {
462
519
  if (!isBrowser()) {
463
520
  return null;
@@ -528,11 +585,8 @@ function extractAllTokensFromUrl(userTokenKey = URL_PARAMS.USER_TOKEN, companyTo
528
585
  }
529
586
 
530
587
  // src/auth/token-storage.ts
531
- function isBrowser2() {
532
- return typeof window !== "undefined" && typeof document !== "undefined";
533
- }
534
588
  function parseCookies() {
535
- if (!isBrowser2()) {
589
+ if (!isBrowser()) {
536
590
  return {};
537
591
  }
538
592
  const cookies = {};
@@ -549,7 +603,7 @@ function parseCookies() {
549
603
  return cookies;
550
604
  }
551
605
  function setCookie(name, value, options = {}) {
552
- if (!isBrowser2()) {
606
+ if (!isBrowser()) {
553
607
  return;
554
608
  }
555
609
  const {
@@ -568,13 +622,13 @@ function setCookie(name, value, options = {}) {
568
622
  document.cookie = cookieString;
569
623
  }
570
624
  function deleteCookie(name, path = "/") {
571
- if (!isBrowser2()) {
625
+ if (!isBrowser()) {
572
626
  return;
573
627
  }
574
628
  document.cookie = `${name}=; path=${path}; max-age=0`;
575
629
  }
576
630
  function getStoredToken(config) {
577
- if (!isBrowser2()) {
631
+ if (!isBrowser()) {
578
632
  return null;
579
633
  }
580
634
  const cookieKey = config?.cookieKey ?? STORAGE_KEYS.AUTH_COOKIE;
@@ -591,7 +645,7 @@ function getStoredToken(config) {
591
645
  }
592
646
  }
593
647
  function storeToken(token, config) {
594
- if (!isBrowser2()) {
648
+ if (!isBrowser()) {
595
649
  return;
596
650
  }
597
651
  const cookieKey = config?.cookieKey ?? STORAGE_KEYS.AUTH_COOKIE;
@@ -614,7 +668,7 @@ function storeToken(token, config) {
614
668
  }
615
669
  }
616
670
  function clearTokens(config) {
617
- if (!isBrowser2()) {
671
+ if (!isBrowser()) {
618
672
  return;
619
673
  }
620
674
  const cookieKey = config?.cookieKey ?? STORAGE_KEYS.AUTH_COOKIE;
@@ -631,16 +685,31 @@ function clearTokens(config) {
631
685
  function hasStoredToken(config) {
632
686
  return getStoredToken(config) !== null;
633
687
  }
688
+
689
+ // src/auth/types.ts
690
+ var USER_TYPES = {
691
+ admin: "admin",
692
+ rep: "rep",
693
+ root_admin: "root_admin",
694
+ customer: "customer"
695
+ };
696
+ function isUserType(value) {
697
+ return Object.values(USER_TYPES).includes(value);
698
+ }
699
+
700
+ // src/auth/token-utils.ts
634
701
  function extractPayloadFromJose(decoded) {
702
+ const rawUserType = decoded.user_type;
703
+ const rawOgUserType = decoded.og_user_type;
635
704
  return {
636
- id: decoded.id,
637
- email: decoded.email,
638
- full_name: decoded.full_name,
639
- user_type: decoded.user_type ?? "rep",
640
- og_user_type: decoded.og_user_type,
641
- company_id: decoded.company_id,
705
+ id: typeof decoded.id === "number" ? decoded.id : void 0,
706
+ email: typeof decoded.email === "string" ? decoded.email : void 0,
707
+ full_name: typeof decoded.full_name === "string" ? decoded.full_name : void 0,
708
+ user_type: typeof rawUserType === "string" && isUserType(rawUserType) ? rawUserType : "rep",
709
+ og_user_type: typeof rawOgUserType === "string" && isUserType(rawOgUserType) ? rawOgUserType : void 0,
710
+ company_id: typeof decoded.company_id === "number" ? decoded.company_id : void 0,
642
711
  exp: decoded.exp,
643
- auth_type: decoded.auth_type
712
+ auth_type: typeof decoded.auth_type === "string" ? decoded.auth_type : void 0
644
713
  };
645
714
  }
646
715
  function decodeToken(token) {
@@ -730,17 +799,6 @@ async function verifyToken(token, jwksUrl) {
730
799
  }
731
800
  }
732
801
 
733
- // src/auth/types.ts
734
- var USER_TYPES = {
735
- admin: "admin",
736
- rep: "rep",
737
- root_admin: "root_admin",
738
- customer: "customer"
739
- };
740
- function isUserType(value) {
741
- return Object.values(USER_TYPES).includes(value);
742
- }
743
-
744
802
  // src/auth/dev-utils.ts
745
803
  function isDevBypassActive(devBypass) {
746
804
  if (!devBypass) return false;
@@ -752,12 +810,14 @@ function isDevBypassActive(devBypass) {
752
810
  }
753
811
  function createDevUser() {
754
812
  return {
755
- id: 0,
813
+ id: 99999,
814
+ // Dev placeholder — avoids falsy 0
756
815
  email: "dev@localhost",
757
816
  full_name: "Dev User",
758
817
  user_type: USER_TYPES.rep,
759
818
  og_user_type: void 0,
760
- company_id: 0,
819
+ company_id: 99999,
820
+ // Dev placeholder — avoids falsy 0
761
821
  exp: void 0,
762
822
  // Never expires
763
823
  auth_type: "dev_bypass"
@@ -776,6 +836,11 @@ function FluidAuthProvider({
776
836
  const [error, setError] = react.useState(null);
777
837
  react.useEffect(() => {
778
838
  const initializeAuth = async () => {
839
+ const handleAuthFailure = () => {
840
+ const current = configRef.current;
841
+ const handler = resolveAuthFailureHandler(current?.onAuthFailure, current?.authUrl);
842
+ handler();
843
+ };
779
844
  try {
780
845
  if (isDevBypassActive(config?.devBypass)) {
781
846
  const envToken = undefined.VITE_DEV_TOKEN;
@@ -803,7 +868,11 @@ function FluidAuthProvider({
803
868
  }
804
869
  const tokenKey = config?.tokenKey ?? "fluidUserToken";
805
870
  let candidateToken = extractTokenFromUrl(tokenKey);
871
+ if (!candidateToken && tokenKey !== AUTH_REDIRECT_TOKEN_KEY) {
872
+ candidateToken = extractTokenFromUrl(AUTH_REDIRECT_TOKEN_KEY);
873
+ }
806
874
  cleanTokenFromUrl(tokenKey);
875
+ cleanTokenFromUrl(AUTH_REDIRECT_TOKEN_KEY);
807
876
  if (!candidateToken) {
808
877
  candidateToken = getStoredToken(config);
809
878
  }
@@ -816,7 +885,7 @@ function FluidAuthProvider({
816
885
  setToken(null);
817
886
  setUser(null);
818
887
  setError(new Error("JWT signature verification failed"));
819
- config?.onAuthFailure?.();
888
+ handleAuthFailure();
820
889
  return;
821
890
  }
822
891
  if (isTokenExpired(candidateToken, config?.gracePeriodMs)) {
@@ -824,7 +893,7 @@ function FluidAuthProvider({
824
893
  setToken(null);
825
894
  setUser(null);
826
895
  setError(new Error("Token has expired"));
827
- config?.onAuthFailure?.();
896
+ handleAuthFailure();
828
897
  return;
829
898
  }
830
899
  } else {
@@ -839,7 +908,7 @@ function FluidAuthProvider({
839
908
  setToken(null);
840
909
  setUser(null);
841
910
  setError(new Error(validation.error ?? "Invalid token"));
842
- config?.onAuthFailure?.();
911
+ handleAuthFailure();
843
912
  return;
844
913
  }
845
914
  }
@@ -851,14 +920,14 @@ function FluidAuthProvider({
851
920
  setToken(null);
852
921
  setUser(null);
853
922
  setError(new Error("No authentication token found"));
854
- config?.onAuthFailure?.();
923
+ handleAuthFailure();
855
924
  }
856
925
  } catch (err) {
857
926
  const error2 = err instanceof Error ? err : new Error("Authentication error");
858
927
  setError(error2);
859
928
  setToken(null);
860
929
  setUser(null);
861
- config?.onAuthFailure?.();
930
+ handleAuthFailure();
862
931
  } finally {
863
932
  setIsLoading(false);
864
933
  }
@@ -1894,7 +1963,19 @@ function AuthError({
1894
1963
  }
1895
1964
  );
1896
1965
  }
1966
+ var SPIN_STYLE_ID = "fluid-auth-loading-spin";
1967
+ function ensureSpinStyle() {
1968
+ if (typeof document === "undefined") return;
1969
+ if (document.getElementById(SPIN_STYLE_ID)) return;
1970
+ const style = document.createElement("style");
1971
+ style.id = SPIN_STYLE_ID;
1972
+ style.textContent = `@keyframes spin { to { transform: rotate(360deg); } }`;
1973
+ document.head.appendChild(style);
1974
+ }
1897
1975
  function AuthLoading() {
1976
+ react.useEffect(() => {
1977
+ ensureSpinStyle();
1978
+ }, []);
1898
1979
  return /* @__PURE__ */ jsxRuntime.jsxs(
1899
1980
  "div",
1900
1981
  {
@@ -1931,12 +2012,7 @@ function AuthLoading() {
1931
2012
  },
1932
2013
  children: "Authenticating..."
1933
2014
  }
1934
- ),
1935
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
1936
- @keyframes spin {
1937
- to { transform: rotate(360deg); }
1938
- }
1939
- ` })
2015
+ )
1940
2016
  ]
1941
2017
  }
1942
2018
  );
@@ -2353,6 +2429,7 @@ exports.BUILT_IN_THEMES = export_BUILT_IN_THEMES;
2353
2429
  exports.CORE_COLOR_KEYS = export_CORE_COLOR_KEYS;
2354
2430
  exports.CORE_PAGE_IDS = CORE_PAGE_IDS;
2355
2431
  exports.CURRENT_REP_QUERY_KEY = CURRENT_REP_QUERY_KEY;
2432
+ exports.DEFAULT_AUTH_URL = DEFAULT_AUTH_URL;
2356
2433
  exports.DEFAULT_CORE_COLORS = export_DEFAULT_CORE_COLORS;
2357
2434
  exports.FluidAuthProvider = FluidAuthProvider;
2358
2435
  exports.FluidProvider = FluidProvider;
@@ -2370,6 +2447,7 @@ exports.catppuccinMocha = export_catppuccinMocha;
2370
2447
  exports.clampChroma = export_clampChroma;
2371
2448
  exports.cleanTokenFromUrl = cleanTokenFromUrl;
2372
2449
  exports.clearTokens = clearTokens;
2450
+ exports.createDefaultAuthRedirect = createDefaultAuthRedirect;
2373
2451
  exports.createFluidClient = createFluidClient;
2374
2452
  exports.decodeToken = decodeToken;
2375
2453
  exports.defaultTheme = export_defaultTheme;