@basictech/react 0.6.0 → 0.7.0-beta.1
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/changelog.md +12 -0
- package/dist/index.d.mts +14 -4
- package/dist/index.d.ts +14 -4
- package/dist/index.js +329 -76
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +329 -76
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/readme.md +127 -1
- package/src/AuthContext.tsx +424 -120
- package/src/index.ts +2 -2
package/changelog.md
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -2,6 +2,11 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
export { useLiveQuery as useQuery } from 'dexie-react-hooks';
|
|
4
4
|
|
|
5
|
+
interface BasicStorage {
|
|
6
|
+
get(key: string): Promise<string | null>;
|
|
7
|
+
set(key: string, value: string): Promise<void>;
|
|
8
|
+
remove(key: string): Promise<void>;
|
|
9
|
+
}
|
|
5
10
|
declare enum DBStatus {
|
|
6
11
|
LOADING = "LOADING",
|
|
7
12
|
OFFLINE = "OFFLINE",
|
|
@@ -19,21 +24,26 @@ type User = {
|
|
|
19
24
|
};
|
|
20
25
|
fullName?: string;
|
|
21
26
|
};
|
|
22
|
-
declare function BasicProvider({ children, project_id, schema, debug }: {
|
|
27
|
+
declare function BasicProvider({ children, project_id, schema, debug, storage }: {
|
|
23
28
|
children: React.ReactNode;
|
|
24
29
|
project_id?: string;
|
|
25
30
|
schema?: any;
|
|
26
31
|
debug?: boolean;
|
|
32
|
+
storage?: BasicStorage;
|
|
27
33
|
}): react_jsx_runtime.JSX.Element;
|
|
28
34
|
declare function useBasic(): {
|
|
29
35
|
unicorn: string;
|
|
30
36
|
isAuthReady: boolean;
|
|
31
37
|
isSignedIn: boolean;
|
|
32
38
|
user: User | null;
|
|
33
|
-
signout: () => void
|
|
34
|
-
signin: () => void
|
|
39
|
+
signout: () => Promise<void>;
|
|
40
|
+
signin: () => Promise<void>;
|
|
41
|
+
signinWithCode: (code: string, state?: string) => Promise<{
|
|
42
|
+
success: boolean;
|
|
43
|
+
error?: string;
|
|
44
|
+
}>;
|
|
35
45
|
getToken: () => Promise<string>;
|
|
36
|
-
getSignInLink: () => string
|
|
46
|
+
getSignInLink: (redirectUri?: string) => Promise<string>;
|
|
37
47
|
db: any;
|
|
38
48
|
dbStatus: DBStatus;
|
|
39
49
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
export { useLiveQuery as useQuery } from 'dexie-react-hooks';
|
|
4
4
|
|
|
5
|
+
interface BasicStorage {
|
|
6
|
+
get(key: string): Promise<string | null>;
|
|
7
|
+
set(key: string, value: string): Promise<void>;
|
|
8
|
+
remove(key: string): Promise<void>;
|
|
9
|
+
}
|
|
5
10
|
declare enum DBStatus {
|
|
6
11
|
LOADING = "LOADING",
|
|
7
12
|
OFFLINE = "OFFLINE",
|
|
@@ -19,21 +24,26 @@ type User = {
|
|
|
19
24
|
};
|
|
20
25
|
fullName?: string;
|
|
21
26
|
};
|
|
22
|
-
declare function BasicProvider({ children, project_id, schema, debug }: {
|
|
27
|
+
declare function BasicProvider({ children, project_id, schema, debug, storage }: {
|
|
23
28
|
children: React.ReactNode;
|
|
24
29
|
project_id?: string;
|
|
25
30
|
schema?: any;
|
|
26
31
|
debug?: boolean;
|
|
32
|
+
storage?: BasicStorage;
|
|
27
33
|
}): react_jsx_runtime.JSX.Element;
|
|
28
34
|
declare function useBasic(): {
|
|
29
35
|
unicorn: string;
|
|
30
36
|
isAuthReady: boolean;
|
|
31
37
|
isSignedIn: boolean;
|
|
32
38
|
user: User | null;
|
|
33
|
-
signout: () => void
|
|
34
|
-
signin: () => void
|
|
39
|
+
signout: () => Promise<void>;
|
|
40
|
+
signin: () => Promise<void>;
|
|
41
|
+
signinWithCode: (code: string, state?: string) => Promise<{
|
|
42
|
+
success: boolean;
|
|
43
|
+
error?: string;
|
|
44
|
+
}>;
|
|
35
45
|
getToken: () => Promise<string>;
|
|
36
|
-
getSignInLink: () => string
|
|
46
|
+
getSignInLink: (redirectUri?: string) => Promise<string>;
|
|
37
47
|
db: any;
|
|
38
48
|
dbStatus: DBStatus;
|
|
39
49
|
};
|
package/dist/index.js
CHANGED
|
@@ -446,22 +446,33 @@ async function deleteRecord({ projectId, accountId, tableName, id, token }) {
|
|
|
446
446
|
var import_schema2 = require("@basictech/schema");
|
|
447
447
|
|
|
448
448
|
// package.json
|
|
449
|
-
var version = "0.6.0
|
|
449
|
+
var version = "0.6.0";
|
|
450
450
|
|
|
451
451
|
// src/AuthContext.tsx
|
|
452
452
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
453
|
+
var LocalStorageAdapter = class {
|
|
454
|
+
async get(key) {
|
|
455
|
+
return localStorage.getItem(key);
|
|
456
|
+
}
|
|
457
|
+
async set(key, value) {
|
|
458
|
+
localStorage.setItem(key, value);
|
|
459
|
+
}
|
|
460
|
+
async remove(key) {
|
|
461
|
+
localStorage.removeItem(key);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
453
464
|
var BasicContext = (0, import_react.createContext)({
|
|
454
465
|
unicorn: "\u{1F984}",
|
|
455
466
|
isAuthReady: false,
|
|
456
467
|
isSignedIn: false,
|
|
457
468
|
user: null,
|
|
458
|
-
signout: () =>
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
},
|
|
469
|
+
signout: () => Promise.resolve(),
|
|
470
|
+
signin: () => Promise.resolve(),
|
|
471
|
+
signinWithCode: () => new Promise(() => {
|
|
472
|
+
}),
|
|
462
473
|
getToken: () => new Promise(() => {
|
|
463
474
|
}),
|
|
464
|
-
getSignInLink: () => "",
|
|
475
|
+
getSignInLink: () => Promise.resolve(""),
|
|
465
476
|
db: {},
|
|
466
477
|
dbStatus: "LOADING" /* LOADING */
|
|
467
478
|
});
|
|
@@ -578,7 +589,13 @@ run "npm install @basictech/react@${latestVersion}" to update`);
|
|
|
578
589
|
};
|
|
579
590
|
}
|
|
580
591
|
}
|
|
581
|
-
function BasicProvider({
|
|
592
|
+
function BasicProvider({
|
|
593
|
+
children,
|
|
594
|
+
project_id,
|
|
595
|
+
schema,
|
|
596
|
+
debug = false,
|
|
597
|
+
storage
|
|
598
|
+
}) {
|
|
582
599
|
const [isAuthReady, setIsAuthReady] = (0, import_react.useState)(false);
|
|
583
600
|
const [isSignedIn, setIsSignedIn] = (0, import_react.useState)(false);
|
|
584
601
|
const [token, setToken] = (0, import_react.useState)(null);
|
|
@@ -587,7 +604,56 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
587
604
|
const [isReady, setIsReady] = (0, import_react.useState)(false);
|
|
588
605
|
const [dbStatus, setDbStatus] = (0, import_react.useState)("OFFLINE" /* OFFLINE */);
|
|
589
606
|
const [error, setError] = (0, import_react.useState)(null);
|
|
607
|
+
const [isOnline, setIsOnline] = (0, import_react.useState)(navigator.onLine);
|
|
608
|
+
const [pendingRefresh, setPendingRefresh] = (0, import_react.useState)(false);
|
|
590
609
|
const syncRef = (0, import_react.useRef)(null);
|
|
610
|
+
const storageAdapter = storage || new LocalStorageAdapter();
|
|
611
|
+
const STORAGE_KEYS = {
|
|
612
|
+
REFRESH_TOKEN: "basic_refresh_token",
|
|
613
|
+
USER_INFO: "basic_user_info",
|
|
614
|
+
AUTH_STATE: "basic_auth_state",
|
|
615
|
+
DEBUG: "basic_debug"
|
|
616
|
+
};
|
|
617
|
+
const isDevelopment = () => {
|
|
618
|
+
return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname.includes("localhost") || window.location.hostname.includes("127.0.0.1") || window.location.hostname.includes(".local") || process.env.NODE_ENV === "development" || debug === true;
|
|
619
|
+
};
|
|
620
|
+
const cleanOAuthParamsFromUrl = () => {
|
|
621
|
+
if (window.location.search.includes("code") || window.location.search.includes("state")) {
|
|
622
|
+
const url = new URL(window.location.href);
|
|
623
|
+
url.searchParams.delete("code");
|
|
624
|
+
url.searchParams.delete("state");
|
|
625
|
+
window.history.pushState({}, document.title, url.pathname + url.search);
|
|
626
|
+
log("Cleaned OAuth parameters from URL");
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
(0, import_react.useEffect)(() => {
|
|
630
|
+
const handleOnline = () => {
|
|
631
|
+
log("Network came back online");
|
|
632
|
+
setIsOnline(true);
|
|
633
|
+
if (pendingRefresh) {
|
|
634
|
+
log("Retrying pending token refresh");
|
|
635
|
+
setPendingRefresh(false);
|
|
636
|
+
if (token) {
|
|
637
|
+
const refreshToken = token.refresh_token || localStorage.getItem("basic_refresh_token");
|
|
638
|
+
if (refreshToken) {
|
|
639
|
+
fetchToken(refreshToken).catch((error2) => {
|
|
640
|
+
log("Retry refresh failed:", error2);
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
const handleOffline = () => {
|
|
647
|
+
log("Network went offline");
|
|
648
|
+
setIsOnline(false);
|
|
649
|
+
};
|
|
650
|
+
window.addEventListener("online", handleOnline);
|
|
651
|
+
window.addEventListener("offline", handleOffline);
|
|
652
|
+
return () => {
|
|
653
|
+
window.removeEventListener("online", handleOnline);
|
|
654
|
+
window.removeEventListener("offline", handleOffline);
|
|
655
|
+
};
|
|
656
|
+
}, [pendingRefresh, token]);
|
|
591
657
|
(0, import_react.useEffect)(() => {
|
|
592
658
|
function initDb(options) {
|
|
593
659
|
if (!syncRef.current) {
|
|
@@ -653,32 +719,68 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
653
719
|
connectToDb();
|
|
654
720
|
}
|
|
655
721
|
}, [isSignedIn, shouldConnect]);
|
|
722
|
+
const connectToDb = async () => {
|
|
723
|
+
const tok = await getToken();
|
|
724
|
+
if (!tok) {
|
|
725
|
+
log("no token found");
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
log("connecting to db...");
|
|
729
|
+
syncRef.current.connect({ access_token: tok }).catch((e) => {
|
|
730
|
+
log("error connecting to db", e);
|
|
731
|
+
});
|
|
732
|
+
};
|
|
656
733
|
(0, import_react.useEffect)(() => {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
if (cookie_token !== "") {
|
|
674
|
-
setToken(JSON.parse(cookie_token));
|
|
734
|
+
const initializeAuth = async () => {
|
|
735
|
+
await storageAdapter.set(STORAGE_KEYS.DEBUG, debug ? "true" : "false");
|
|
736
|
+
try {
|
|
737
|
+
if (window.location.search.includes("code")) {
|
|
738
|
+
let code = window.location?.search?.split("code=")[1].split("&")[0];
|
|
739
|
+
const state = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
|
|
740
|
+
if (!state || state !== window.location.search.split("state=")[1].split("&")[0]) {
|
|
741
|
+
log("error: auth state does not match");
|
|
742
|
+
setIsAuthReady(true);
|
|
743
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
744
|
+
cleanOAuthParamsFromUrl();
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
748
|
+
cleanOAuthParamsFromUrl();
|
|
749
|
+
fetchToken(code);
|
|
675
750
|
} else {
|
|
676
|
-
|
|
751
|
+
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
752
|
+
if (refreshToken) {
|
|
753
|
+
log("Found refresh token in storage, attempting to refresh access token");
|
|
754
|
+
fetchToken(refreshToken);
|
|
755
|
+
} else {
|
|
756
|
+
let cookie_token = getCookie("basic_token");
|
|
757
|
+
if (cookie_token !== "") {
|
|
758
|
+
const tokenData = JSON.parse(cookie_token);
|
|
759
|
+
setToken(tokenData);
|
|
760
|
+
if (tokenData.refresh_token) {
|
|
761
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, tokenData.refresh_token);
|
|
762
|
+
}
|
|
763
|
+
} else {
|
|
764
|
+
const cachedUserInfo = await storageAdapter.get(STORAGE_KEYS.USER_INFO);
|
|
765
|
+
if (cachedUserInfo) {
|
|
766
|
+
try {
|
|
767
|
+
const userData = JSON.parse(cachedUserInfo);
|
|
768
|
+
setUser(userData);
|
|
769
|
+
setIsSignedIn(true);
|
|
770
|
+
log("Loaded cached user info for offline mode");
|
|
771
|
+
} catch (error2) {
|
|
772
|
+
log("Error parsing cached user info:", error2);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
setIsAuthReady(true);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
677
778
|
}
|
|
779
|
+
} catch (e) {
|
|
780
|
+
log("error getting token", e);
|
|
678
781
|
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
}
|
|
782
|
+
};
|
|
783
|
+
initializeAuth();
|
|
682
784
|
}, []);
|
|
683
785
|
(0, import_react.useEffect)(() => {
|
|
684
786
|
async function fetchUser(acc_token) {
|
|
@@ -693,10 +795,13 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
693
795
|
log("error fetching user", user2.error);
|
|
694
796
|
return;
|
|
695
797
|
} else {
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
window.history.pushState({}, document.title, "/");
|
|
798
|
+
if (token?.refresh_token) {
|
|
799
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token.refresh_token);
|
|
699
800
|
}
|
|
801
|
+
await storageAdapter.set(STORAGE_KEYS.USER_INFO, JSON.stringify(user2));
|
|
802
|
+
log("Cached user info in storage");
|
|
803
|
+
document.cookie = `basic_access_token=${token.access_token}; Secure; SameSite=Strict; HttpOnly=false`;
|
|
804
|
+
document.cookie = `basic_token=${JSON.stringify(token)}; Secure; SameSite=Strict`;
|
|
700
805
|
setUser(user2);
|
|
701
806
|
setIsSignedIn(true);
|
|
702
807
|
setIsAuthReady(true);
|
|
@@ -712,8 +817,18 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
712
817
|
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
|
|
713
818
|
if (isExpired) {
|
|
714
819
|
log("token is expired - refreshing ...");
|
|
715
|
-
|
|
716
|
-
|
|
820
|
+
try {
|
|
821
|
+
const newToken = await fetchToken(token?.refresh_token);
|
|
822
|
+
fetchUser(newToken.access_token);
|
|
823
|
+
} catch (error2) {
|
|
824
|
+
log("Failed to refresh token in checkToken:", error2);
|
|
825
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
826
|
+
log("Network issue - continuing with expired token until online");
|
|
827
|
+
fetchUser(token.access_token);
|
|
828
|
+
} else {
|
|
829
|
+
setIsAuthReady(true);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
717
832
|
} else {
|
|
718
833
|
fetchUser(token.access_token);
|
|
719
834
|
}
|
|
@@ -722,41 +837,97 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
722
837
|
checkToken();
|
|
723
838
|
}
|
|
724
839
|
}, [token]);
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
840
|
+
const getSignInLink = async (redirectUri) => {
|
|
841
|
+
try {
|
|
842
|
+
log("getting sign in link...");
|
|
843
|
+
if (!project_id) {
|
|
844
|
+
throw new Error("Project ID is required to generate sign-in link");
|
|
845
|
+
}
|
|
846
|
+
const randomState = Math.random().toString(36).substring(6);
|
|
847
|
+
await storageAdapter.set(STORAGE_KEYS.AUTH_STATE, randomState);
|
|
848
|
+
const redirectUrl = redirectUri || window.location.href;
|
|
849
|
+
if (!redirectUrl || !redirectUrl.startsWith("http://") && !redirectUrl.startsWith("https://")) {
|
|
850
|
+
throw new Error("Invalid redirect URI provided");
|
|
851
|
+
}
|
|
852
|
+
let baseUrl2 = "https://api.basic.tech/auth/authorize";
|
|
853
|
+
baseUrl2 += `?client_id=${project_id}`;
|
|
854
|
+
baseUrl2 += `&redirect_uri=${encodeURIComponent(redirectUrl)}`;
|
|
855
|
+
baseUrl2 += `&response_type=code`;
|
|
856
|
+
baseUrl2 += `&scope=profile`;
|
|
857
|
+
baseUrl2 += `&state=${randomState}`;
|
|
858
|
+
log("Generated sign-in link successfully");
|
|
859
|
+
return baseUrl2;
|
|
860
|
+
} catch (error2) {
|
|
861
|
+
log("Error generating sign-in link:", error2);
|
|
862
|
+
throw error2;
|
|
730
863
|
}
|
|
731
|
-
log("connecting to db...");
|
|
732
|
-
syncRef.current.connect({ access_token: tok }).catch((e) => {
|
|
733
|
-
log("error connecting to db", e);
|
|
734
|
-
});
|
|
735
864
|
};
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
865
|
+
const signin = async () => {
|
|
866
|
+
try {
|
|
867
|
+
log("signing in...");
|
|
868
|
+
if (!project_id) {
|
|
869
|
+
log("Error: project_id is required for sign-in");
|
|
870
|
+
throw new Error("Project ID is required for authentication");
|
|
871
|
+
}
|
|
872
|
+
const signInLink = await getSignInLink();
|
|
873
|
+
log("Generated sign-in link:", signInLink);
|
|
874
|
+
if (!signInLink || !signInLink.startsWith("https://")) {
|
|
875
|
+
log("Error: Invalid sign-in link generated");
|
|
876
|
+
throw new Error("Failed to generate valid sign-in URL");
|
|
877
|
+
}
|
|
878
|
+
window.location.href = signInLink;
|
|
879
|
+
} catch (error2) {
|
|
880
|
+
log("Error during sign-in:", error2);
|
|
881
|
+
if (isDevelopment()) {
|
|
882
|
+
setError({
|
|
883
|
+
code: "signin_error",
|
|
884
|
+
title: "Sign-in Failed",
|
|
885
|
+
message: error2.message || "An error occurred during sign-in. Please try again."
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
throw error2;
|
|
889
|
+
}
|
|
747
890
|
};
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
891
|
+
const signinWithCode = async (code, state) => {
|
|
892
|
+
try {
|
|
893
|
+
log("signinWithCode called with code:", code);
|
|
894
|
+
if (!code || typeof code !== "string") {
|
|
895
|
+
return { success: false, error: "Invalid authorization code" };
|
|
896
|
+
}
|
|
897
|
+
if (state) {
|
|
898
|
+
const storedState = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
|
|
899
|
+
if (storedState && storedState !== state) {
|
|
900
|
+
log("State parameter mismatch:", { provided: state, stored: storedState });
|
|
901
|
+
return { success: false, error: "State parameter mismatch" };
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
905
|
+
cleanOAuthParamsFromUrl();
|
|
906
|
+
const token2 = await fetchToken(code);
|
|
907
|
+
if (token2) {
|
|
908
|
+
log("signinWithCode successful");
|
|
909
|
+
return { success: true };
|
|
910
|
+
} else {
|
|
911
|
+
return { success: false, error: "Failed to exchange code for token" };
|
|
912
|
+
}
|
|
913
|
+
} catch (error2) {
|
|
914
|
+
log("signinWithCode error:", error2);
|
|
915
|
+
return {
|
|
916
|
+
success: false,
|
|
917
|
+
error: error2.message || "Authentication failed"
|
|
918
|
+
};
|
|
919
|
+
}
|
|
752
920
|
};
|
|
753
|
-
const signout = () => {
|
|
921
|
+
const signout = async () => {
|
|
754
922
|
log("signing out!");
|
|
755
923
|
setUser({});
|
|
756
924
|
setIsSignedIn(false);
|
|
757
925
|
setToken(null);
|
|
758
926
|
document.cookie = `basic_token=; Secure; SameSite=Strict`;
|
|
759
|
-
|
|
927
|
+
document.cookie = `basic_access_token=; Secure; SameSite=Strict`;
|
|
928
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
929
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
930
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
760
931
|
if (syncRef.current) {
|
|
761
932
|
(async () => {
|
|
762
933
|
try {
|
|
@@ -773,6 +944,27 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
773
944
|
const getToken = async () => {
|
|
774
945
|
log("getting token...");
|
|
775
946
|
if (!token) {
|
|
947
|
+
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
948
|
+
if (refreshToken) {
|
|
949
|
+
log("No token in memory, attempting to refresh from storage");
|
|
950
|
+
try {
|
|
951
|
+
const newToken = await fetchToken(refreshToken);
|
|
952
|
+
if (newToken?.access_token) {
|
|
953
|
+
return newToken.access_token;
|
|
954
|
+
}
|
|
955
|
+
} catch (error2) {
|
|
956
|
+
log("Failed to refresh token from storage:", error2);
|
|
957
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
958
|
+
log("Network issue - continuing with potentially expired token");
|
|
959
|
+
const lastToken = localStorage.getItem("basic_access_token");
|
|
960
|
+
if (lastToken) {
|
|
961
|
+
return lastToken;
|
|
962
|
+
}
|
|
963
|
+
throw new Error("Network offline - authentication will be retried when online");
|
|
964
|
+
}
|
|
965
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
966
|
+
}
|
|
967
|
+
}
|
|
776
968
|
log("no token found");
|
|
777
969
|
throw new Error("no token found");
|
|
778
970
|
}
|
|
@@ -780,8 +972,22 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
780
972
|
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
|
|
781
973
|
if (isExpired) {
|
|
782
974
|
log("token is expired - refreshing ...");
|
|
783
|
-
const
|
|
784
|
-
|
|
975
|
+
const refreshToken = token?.refresh_token || await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
976
|
+
if (refreshToken) {
|
|
977
|
+
try {
|
|
978
|
+
const newToken = await fetchToken(refreshToken);
|
|
979
|
+
return newToken?.access_token || "";
|
|
980
|
+
} catch (error2) {
|
|
981
|
+
log("Failed to refresh expired token:", error2);
|
|
982
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
983
|
+
log("Network issue - using expired token until network is restored");
|
|
984
|
+
return token.access_token;
|
|
985
|
+
}
|
|
986
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
987
|
+
}
|
|
988
|
+
} else {
|
|
989
|
+
throw new Error("no refresh token available");
|
|
990
|
+
}
|
|
785
991
|
}
|
|
786
992
|
return token?.access_token || "";
|
|
787
993
|
};
|
|
@@ -800,20 +1006,66 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
800
1006
|
return cookieValue;
|
|
801
1007
|
}
|
|
802
1008
|
const fetchToken = async (code) => {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1009
|
+
try {
|
|
1010
|
+
if (!isOnline) {
|
|
1011
|
+
log("Network is offline, marking refresh as pending");
|
|
1012
|
+
setPendingRefresh(true);
|
|
1013
|
+
throw new Error("Network offline - refresh will be retried when online");
|
|
1014
|
+
}
|
|
1015
|
+
const token2 = await fetch("https://api.basic.tech/auth/token", {
|
|
1016
|
+
method: "POST",
|
|
1017
|
+
headers: {
|
|
1018
|
+
"Content-Type": "application/json"
|
|
1019
|
+
},
|
|
1020
|
+
body: JSON.stringify({ code })
|
|
1021
|
+
}).then((response) => response.json()).catch((error2) => {
|
|
1022
|
+
log("Network error fetching token:", error2);
|
|
1023
|
+
if (!isOnline) {
|
|
1024
|
+
setPendingRefresh(true);
|
|
1025
|
+
throw new Error("Network offline - refresh will be retried when online");
|
|
1026
|
+
}
|
|
1027
|
+
throw new Error("Network error during token refresh");
|
|
1028
|
+
});
|
|
1029
|
+
if (token2.error) {
|
|
1030
|
+
log("error fetching token", token2.error);
|
|
1031
|
+
if (token2.error.includes("network") || token2.error.includes("timeout")) {
|
|
1032
|
+
setPendingRefresh(true);
|
|
1033
|
+
throw new Error("Network issue - refresh will be retried when online");
|
|
1034
|
+
}
|
|
1035
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1036
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1037
|
+
document.cookie = `basic_token=; Secure; SameSite=Strict`;
|
|
1038
|
+
document.cookie = `basic_access_token=; Secure; SameSite=Strict`;
|
|
1039
|
+
setUser({});
|
|
1040
|
+
setIsSignedIn(false);
|
|
1041
|
+
setToken(null);
|
|
1042
|
+
setIsAuthReady(true);
|
|
1043
|
+
throw new Error(`Token refresh failed: ${token2.error}`);
|
|
1044
|
+
} else {
|
|
1045
|
+
setToken(token2);
|
|
1046
|
+
setPendingRefresh(false);
|
|
1047
|
+
if (token2.refresh_token) {
|
|
1048
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token2.refresh_token);
|
|
1049
|
+
log("Updated refresh token in storage");
|
|
1050
|
+
}
|
|
1051
|
+
document.cookie = `basic_access_token=${token2.access_token}; Secure; SameSite=Strict; HttpOnly=false`;
|
|
1052
|
+
log("Updated access token in cookie");
|
|
1053
|
+
}
|
|
1054
|
+
return token2;
|
|
1055
|
+
} catch (error2) {
|
|
1056
|
+
log("Token refresh error:", error2);
|
|
1057
|
+
if (!error2.message.includes("offline") && !error2.message.includes("Network")) {
|
|
1058
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1059
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1060
|
+
document.cookie = `basic_token=; Secure; SameSite=Strict`;
|
|
1061
|
+
document.cookie = `basic_access_token=; Secure; SameSite=Strict`;
|
|
1062
|
+
setUser({});
|
|
1063
|
+
setIsSignedIn(false);
|
|
1064
|
+
setToken(null);
|
|
1065
|
+
setIsAuthReady(true);
|
|
1066
|
+
}
|
|
1067
|
+
throw error2;
|
|
815
1068
|
}
|
|
816
|
-
return token2;
|
|
817
1069
|
};
|
|
818
1070
|
const db_ = (tableName) => {
|
|
819
1071
|
const checkSignIn = () => {
|
|
@@ -856,12 +1108,13 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
|
|
|
856
1108
|
user,
|
|
857
1109
|
signout,
|
|
858
1110
|
signin,
|
|
1111
|
+
signinWithCode,
|
|
859
1112
|
getToken,
|
|
860
1113
|
getSignInLink,
|
|
861
1114
|
db: syncRef.current ? syncRef.current : noDb,
|
|
862
1115
|
dbStatus
|
|
863
1116
|
}, children: [
|
|
864
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorDisplay, { error }),
|
|
1117
|
+
error && isDevelopment() && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorDisplay, { error }),
|
|
865
1118
|
isReady && children
|
|
866
1119
|
] });
|
|
867
1120
|
}
|