@basictech/react 0.7.0-beta.3 → 0.7.0-beta.5
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/.turbo/turbo-build.log +10 -10
- package/changelog.md +12 -0
- package/dist/index.d.mts +20 -8
- package/dist/index.d.ts +20 -8
- package/dist/index.js +196 -81
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +196 -81
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/AuthContext.tsx +229 -127
- package/src/index.ts +3 -7
- package/src/sync/index.ts +4 -4
- package/src/utils/storage.ts +1 -0
package/dist/index.mjs
CHANGED
|
@@ -139,15 +139,15 @@ var BasicSync = class extends Dexie2 {
|
|
|
139
139
|
this.version(2).stores({});
|
|
140
140
|
this.Collection.prototype.get = this.Collection.prototype.toArray;
|
|
141
141
|
}
|
|
142
|
-
async connect({ access_token }) {
|
|
143
|
-
const WS_URL =
|
|
142
|
+
async connect({ access_token, ws_url }) {
|
|
143
|
+
const WS_URL = ws_url || "wss://pds.basic.id/ws";
|
|
144
144
|
log("Connecting to", WS_URL);
|
|
145
145
|
await this.updateSyncNodes();
|
|
146
146
|
log("Starting connection...");
|
|
147
147
|
return this.syncable.connect("websocket", WS_URL, { authToken: access_token, schema: this.basic_schema });
|
|
148
148
|
}
|
|
149
|
-
async disconnect() {
|
|
150
|
-
const WS_URL =
|
|
149
|
+
async disconnect({ ws_url } = {}) {
|
|
150
|
+
const WS_URL = ws_url || "wss://pds.basic.id/ws";
|
|
151
151
|
return this.syncable.disconnect(WS_URL);
|
|
152
152
|
}
|
|
153
153
|
async updateSyncNodes() {
|
|
@@ -251,7 +251,7 @@ var BasicSync = class extends Dexie2 {
|
|
|
251
251
|
};
|
|
252
252
|
|
|
253
253
|
// package.json
|
|
254
|
-
var version = "0.7.0-beta.
|
|
254
|
+
var version = "0.7.0-beta.4";
|
|
255
255
|
|
|
256
256
|
// src/updater/versionUpdater.ts
|
|
257
257
|
var VersionUpdater = class {
|
|
@@ -394,6 +394,8 @@ var STORAGE_KEYS = {
|
|
|
394
394
|
REFRESH_TOKEN: "basic_refresh_token",
|
|
395
395
|
USER_INFO: "basic_user_info",
|
|
396
396
|
AUTH_STATE: "basic_auth_state",
|
|
397
|
+
REDIRECT_URI: "basic_redirect_uri",
|
|
398
|
+
SERVER_URL: "basic_server_url",
|
|
397
399
|
DEBUG: "basic_debug"
|
|
398
400
|
};
|
|
399
401
|
function getCookie(name) {
|
|
@@ -591,6 +593,11 @@ async function validateAndCheckSchema(schema) {
|
|
|
591
593
|
|
|
592
594
|
// src/AuthContext.tsx
|
|
593
595
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
596
|
+
var DEFAULT_AUTH_CONFIG = {
|
|
597
|
+
scopes: "profile,email,app:admin",
|
|
598
|
+
server_url: "https://api.basic.tech",
|
|
599
|
+
ws_url: "wss://pds.basic.id/ws"
|
|
600
|
+
};
|
|
594
601
|
var BasicContext = createContext({
|
|
595
602
|
unicorn: "\u{1F984}",
|
|
596
603
|
isAuthReady: false,
|
|
@@ -611,7 +618,8 @@ function BasicProvider({
|
|
|
611
618
|
project_id,
|
|
612
619
|
schema,
|
|
613
620
|
debug = false,
|
|
614
|
-
storage
|
|
621
|
+
storage,
|
|
622
|
+
auth
|
|
615
623
|
}) {
|
|
616
624
|
const [isAuthReady, setIsAuthReady] = useState(false);
|
|
617
625
|
const [isSignedIn, setIsSignedIn] = useState(false);
|
|
@@ -625,6 +633,13 @@ function BasicProvider({
|
|
|
625
633
|
const [pendingRefresh, setPendingRefresh] = useState(false);
|
|
626
634
|
const syncRef = useRef(null);
|
|
627
635
|
const storageAdapter = storage || new LocalStorageAdapter();
|
|
636
|
+
const authConfig = {
|
|
637
|
+
scopes: auth?.scopes || DEFAULT_AUTH_CONFIG.scopes,
|
|
638
|
+
server_url: auth?.server_url || DEFAULT_AUTH_CONFIG.server_url,
|
|
639
|
+
ws_url: auth?.ws_url || DEFAULT_AUTH_CONFIG.ws_url
|
|
640
|
+
};
|
|
641
|
+
const scopesString = Array.isArray(authConfig.scopes) ? authConfig.scopes.join(" ") : authConfig.scopes;
|
|
642
|
+
const refreshPromiseRef = useRef(null);
|
|
628
643
|
const isDevMode = () => isDevelopment(debug);
|
|
629
644
|
const cleanOAuthParams = () => cleanOAuthParamsFromUrl();
|
|
630
645
|
useEffect(() => {
|
|
@@ -712,7 +727,10 @@ function BasicProvider({
|
|
|
712
727
|
return;
|
|
713
728
|
}
|
|
714
729
|
log("connecting to db...");
|
|
715
|
-
syncRef.current?.connect({
|
|
730
|
+
syncRef.current?.connect({
|
|
731
|
+
access_token: tok,
|
|
732
|
+
ws_url: authConfig.ws_url
|
|
733
|
+
}).catch((e) => {
|
|
716
734
|
log("error connecting to db", e);
|
|
717
735
|
});
|
|
718
736
|
}
|
|
@@ -722,6 +740,17 @@ function BasicProvider({
|
|
|
722
740
|
useEffect(() => {
|
|
723
741
|
const initializeAuth = async () => {
|
|
724
742
|
await storageAdapter.set(STORAGE_KEYS.DEBUG, debug ? "true" : "false");
|
|
743
|
+
const storedServerUrl = await storageAdapter.get(STORAGE_KEYS.SERVER_URL);
|
|
744
|
+
if (storedServerUrl && storedServerUrl !== authConfig.server_url) {
|
|
745
|
+
log("Server URL changed, clearing stored tokens");
|
|
746
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
747
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
748
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
749
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
750
|
+
clearCookie("basic_token");
|
|
751
|
+
clearCookie("basic_access_token");
|
|
752
|
+
}
|
|
753
|
+
await storageAdapter.set(STORAGE_KEYS.SERVER_URL, authConfig.server_url);
|
|
725
754
|
try {
|
|
726
755
|
const versionUpdater = createVersionUpdater(storageAdapter, version, getMigrations());
|
|
727
756
|
const updateResult = await versionUpdater.checkAndUpdate();
|
|
@@ -792,16 +821,21 @@ function BasicProvider({
|
|
|
792
821
|
useEffect(() => {
|
|
793
822
|
async function fetchUser(acc_token) {
|
|
794
823
|
console.info("fetching user");
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
824
|
+
try {
|
|
825
|
+
const response = await fetch(`${authConfig.server_url}/auth/userInfo`, {
|
|
826
|
+
method: "GET",
|
|
827
|
+
headers: {
|
|
828
|
+
"Authorization": `Bearer ${acc_token}`
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
if (!response.ok) {
|
|
832
|
+
throw new Error(`Failed to fetch user info: ${response.status}`);
|
|
833
|
+
}
|
|
834
|
+
const user2 = await response.json();
|
|
835
|
+
if (user2.error) {
|
|
836
|
+
log("error fetching user", user2.error);
|
|
837
|
+
throw new Error(`User info error: ${user2.error}`);
|
|
799
838
|
}
|
|
800
|
-
}).then((response) => response.json()).catch((error2) => log("Error:", error2));
|
|
801
|
-
if (user2.error) {
|
|
802
|
-
log("error fetching user", user2.error);
|
|
803
|
-
return;
|
|
804
|
-
} else {
|
|
805
839
|
if (token?.refresh_token) {
|
|
806
840
|
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token.refresh_token);
|
|
807
841
|
}
|
|
@@ -812,6 +846,9 @@ function BasicProvider({
|
|
|
812
846
|
setUser(user2);
|
|
813
847
|
setIsSignedIn(true);
|
|
814
848
|
setIsAuthReady(true);
|
|
849
|
+
} catch (error2) {
|
|
850
|
+
log("Failed to fetch user info:", error2);
|
|
851
|
+
setIsAuthReady(true);
|
|
815
852
|
}
|
|
816
853
|
}
|
|
817
854
|
async function checkToken() {
|
|
@@ -821,7 +858,8 @@ function BasicProvider({
|
|
|
821
858
|
return;
|
|
822
859
|
}
|
|
823
860
|
const decoded = jwtDecode(token?.access_token);
|
|
824
|
-
const
|
|
861
|
+
const expirationBuffer = 5;
|
|
862
|
+
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3 + expirationBuffer;
|
|
825
863
|
if (isExpired) {
|
|
826
864
|
log("token is expired - refreshing ...");
|
|
827
865
|
try {
|
|
@@ -856,13 +894,15 @@ function BasicProvider({
|
|
|
856
894
|
if (!redirectUrl || !redirectUrl.startsWith("http://") && !redirectUrl.startsWith("https://")) {
|
|
857
895
|
throw new Error("Invalid redirect URI provided");
|
|
858
896
|
}
|
|
859
|
-
|
|
897
|
+
await storageAdapter.set(STORAGE_KEYS.REDIRECT_URI, redirectUrl);
|
|
898
|
+
log("Stored redirect_uri for token exchange:", redirectUrl);
|
|
899
|
+
let baseUrl = `${authConfig.server_url}/auth/authorize`;
|
|
860
900
|
baseUrl += `?client_id=${project_id}`;
|
|
861
901
|
baseUrl += `&redirect_uri=${encodeURIComponent(redirectUrl)}`;
|
|
862
902
|
baseUrl += `&response_type=code`;
|
|
863
|
-
baseUrl += `&scope
|
|
903
|
+
baseUrl += `&scope=${encodeURIComponent(scopesString)}`;
|
|
864
904
|
baseUrl += `&state=${randomState}`;
|
|
865
|
-
log("Generated sign-in link successfully");
|
|
905
|
+
log("Generated sign-in link successfully with scopes:", scopesString);
|
|
866
906
|
return baseUrl;
|
|
867
907
|
} catch (error2) {
|
|
868
908
|
log("Error generating sign-in link:", error2);
|
|
@@ -878,7 +918,9 @@ function BasicProvider({
|
|
|
878
918
|
}
|
|
879
919
|
const signInLink = await getSignInLink();
|
|
880
920
|
log("Generated sign-in link:", signInLink);
|
|
881
|
-
|
|
921
|
+
try {
|
|
922
|
+
new URL(signInLink);
|
|
923
|
+
} catch {
|
|
882
924
|
log("Error: Invalid sign-in link generated");
|
|
883
925
|
throw new Error("Failed to generate valid sign-in URL");
|
|
884
926
|
}
|
|
@@ -935,6 +977,8 @@ function BasicProvider({
|
|
|
935
977
|
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
936
978
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
937
979
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
980
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
981
|
+
await storageAdapter.remove(STORAGE_KEYS.SERVER_URL);
|
|
938
982
|
if (syncRef.current) {
|
|
939
983
|
(async () => {
|
|
940
984
|
try {
|
|
@@ -954,6 +998,18 @@ function BasicProvider({
|
|
|
954
998
|
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
955
999
|
if (refreshToken) {
|
|
956
1000
|
log("No token in memory, attempting to refresh from storage");
|
|
1001
|
+
if (refreshPromiseRef.current) {
|
|
1002
|
+
log("Token refresh already in progress, waiting...");
|
|
1003
|
+
try {
|
|
1004
|
+
const newToken = await refreshPromiseRef.current;
|
|
1005
|
+
if (newToken?.access_token) {
|
|
1006
|
+
return newToken.access_token;
|
|
1007
|
+
}
|
|
1008
|
+
} catch (error2) {
|
|
1009
|
+
log("In-flight refresh failed:", error2);
|
|
1010
|
+
throw error2;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
957
1013
|
try {
|
|
958
1014
|
const newToken = await fetchToken(refreshToken, true);
|
|
959
1015
|
if (newToken?.access_token) {
|
|
@@ -976,9 +1032,24 @@ function BasicProvider({
|
|
|
976
1032
|
throw new Error("no token found");
|
|
977
1033
|
}
|
|
978
1034
|
const decoded = jwtDecode(token?.access_token);
|
|
979
|
-
const
|
|
1035
|
+
const expirationBuffer = 5;
|
|
1036
|
+
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3 + expirationBuffer;
|
|
980
1037
|
if (isExpired) {
|
|
981
1038
|
log("token is expired - refreshing ...");
|
|
1039
|
+
if (refreshPromiseRef.current) {
|
|
1040
|
+
log("Token refresh already in progress, waiting...");
|
|
1041
|
+
try {
|
|
1042
|
+
const newToken = await refreshPromiseRef.current;
|
|
1043
|
+
return newToken?.access_token || "";
|
|
1044
|
+
} catch (error2) {
|
|
1045
|
+
log("In-flight refresh failed:", error2);
|
|
1046
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
1047
|
+
log("Network issue - using expired token until network is restored");
|
|
1048
|
+
return token.access_token;
|
|
1049
|
+
}
|
|
1050
|
+
throw error2;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
982
1053
|
const refreshToken = token?.refresh_token || await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
983
1054
|
if (refreshToken) {
|
|
984
1055
|
try {
|
|
@@ -999,73 +1070,117 @@ function BasicProvider({
|
|
|
999
1070
|
return token?.access_token || "";
|
|
1000
1071
|
};
|
|
1001
1072
|
const fetchToken = async (codeOrRefreshToken, isRefreshToken = false) => {
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
const requestBody = isRefreshToken ? {
|
|
1009
|
-
grant_type: "refresh_token",
|
|
1010
|
-
refresh_token: codeOrRefreshToken
|
|
1011
|
-
} : {
|
|
1012
|
-
grant_type: "authorization_code",
|
|
1013
|
-
code: codeOrRefreshToken
|
|
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(requestBody)
|
|
1021
|
-
}).then((response) => response.json()).catch((error2) => {
|
|
1022
|
-
log("Network error fetching token:", error2);
|
|
1073
|
+
if (isRefreshToken && refreshPromiseRef.current) {
|
|
1074
|
+
log("Reusing in-flight refresh token request");
|
|
1075
|
+
return refreshPromiseRef.current;
|
|
1076
|
+
}
|
|
1077
|
+
const refreshPromise = (async () => {
|
|
1078
|
+
try {
|
|
1023
1079
|
if (!isOnline) {
|
|
1080
|
+
log("Network is offline, marking refresh as pending");
|
|
1024
1081
|
setPendingRefresh(true);
|
|
1025
1082
|
throw new Error("Network offline - refresh will be retried when online");
|
|
1026
1083
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1084
|
+
let requestBody;
|
|
1085
|
+
if (isRefreshToken) {
|
|
1086
|
+
requestBody = {
|
|
1087
|
+
grant_type: "refresh_token",
|
|
1088
|
+
refresh_token: codeOrRefreshToken
|
|
1089
|
+
};
|
|
1090
|
+
if (project_id) {
|
|
1091
|
+
requestBody.client_id = project_id;
|
|
1092
|
+
}
|
|
1093
|
+
} else {
|
|
1094
|
+
requestBody = {
|
|
1095
|
+
grant_type: "authorization_code",
|
|
1096
|
+
code: codeOrRefreshToken
|
|
1097
|
+
};
|
|
1098
|
+
const storedRedirectUri = await storageAdapter.get(STORAGE_KEYS.REDIRECT_URI);
|
|
1099
|
+
if (storedRedirectUri) {
|
|
1100
|
+
requestBody.redirect_uri = storedRedirectUri;
|
|
1101
|
+
log("Including redirect_uri in token exchange:", storedRedirectUri);
|
|
1102
|
+
} else {
|
|
1103
|
+
log("Warning: No redirect_uri found in storage for token exchange");
|
|
1104
|
+
}
|
|
1105
|
+
if (project_id) {
|
|
1106
|
+
requestBody.client_id = project_id;
|
|
1107
|
+
}
|
|
1034
1108
|
}
|
|
1035
|
-
|
|
1036
|
-
await
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1109
|
+
log("Token exchange request body:", { ...requestBody, refresh_token: isRefreshToken ? "[REDACTED]" : void 0, code: !isRefreshToken ? "[REDACTED]" : void 0 });
|
|
1110
|
+
const token2 = await fetch(`${authConfig.server_url}/auth/token`, {
|
|
1111
|
+
method: "POST",
|
|
1112
|
+
headers: {
|
|
1113
|
+
"Content-Type": "application/json"
|
|
1114
|
+
},
|
|
1115
|
+
body: JSON.stringify(requestBody)
|
|
1116
|
+
}).then((response) => response.json()).catch((error2) => {
|
|
1117
|
+
log("Network error fetching token:", error2);
|
|
1118
|
+
if (!isOnline) {
|
|
1119
|
+
setPendingRefresh(true);
|
|
1120
|
+
throw new Error("Network offline - refresh will be retried when online");
|
|
1121
|
+
}
|
|
1122
|
+
throw new Error("Network error during token refresh");
|
|
1123
|
+
});
|
|
1124
|
+
if (token2.error) {
|
|
1125
|
+
log("error fetching token", token2.error);
|
|
1126
|
+
if (token2.error.includes("network") || token2.error.includes("timeout")) {
|
|
1127
|
+
setPendingRefresh(true);
|
|
1128
|
+
throw new Error("Network issue - refresh will be retried when online");
|
|
1129
|
+
}
|
|
1130
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1131
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1132
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1133
|
+
await storageAdapter.remove(STORAGE_KEYS.SERVER_URL);
|
|
1134
|
+
clearCookie("basic_token");
|
|
1135
|
+
clearCookie("basic_access_token");
|
|
1136
|
+
setUser({});
|
|
1137
|
+
setIsSignedIn(false);
|
|
1138
|
+
setToken(null);
|
|
1139
|
+
setIsAuthReady(true);
|
|
1140
|
+
throw new Error(`Token refresh failed: ${token2.error}`);
|
|
1141
|
+
} else {
|
|
1142
|
+
setToken(token2);
|
|
1143
|
+
setPendingRefresh(false);
|
|
1144
|
+
if (token2.refresh_token) {
|
|
1145
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token2.refresh_token);
|
|
1146
|
+
log("Updated refresh token in storage");
|
|
1147
|
+
}
|
|
1148
|
+
if (!isRefreshToken) {
|
|
1149
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1150
|
+
log("Cleaned up redirect_uri from storage after successful exchange");
|
|
1151
|
+
}
|
|
1152
|
+
setCookie("basic_access_token", token2.access_token, { httpOnly: false });
|
|
1153
|
+
setCookie("basic_token", JSON.stringify(token2));
|
|
1154
|
+
log("Updated access token and full token in cookies");
|
|
1050
1155
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1156
|
+
return token2;
|
|
1157
|
+
} catch (error2) {
|
|
1158
|
+
log("Token refresh error:", error2);
|
|
1159
|
+
if (!error2.message.includes("offline") && !error2.message.includes("Network")) {
|
|
1160
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1161
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1162
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1163
|
+
await storageAdapter.remove(STORAGE_KEYS.SERVER_URL);
|
|
1164
|
+
clearCookie("basic_token");
|
|
1165
|
+
clearCookie("basic_access_token");
|
|
1166
|
+
setUser({});
|
|
1167
|
+
setIsSignedIn(false);
|
|
1168
|
+
setToken(null);
|
|
1169
|
+
setIsAuthReady(true);
|
|
1170
|
+
}
|
|
1171
|
+
throw error2;
|
|
1066
1172
|
}
|
|
1067
|
-
|
|
1173
|
+
})();
|
|
1174
|
+
if (isRefreshToken) {
|
|
1175
|
+
refreshPromiseRef.current = refreshPromise;
|
|
1176
|
+
refreshPromise.finally(() => {
|
|
1177
|
+
if (refreshPromiseRef.current === refreshPromise) {
|
|
1178
|
+
refreshPromiseRef.current = null;
|
|
1179
|
+
log("Cleared refresh promise reference");
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1068
1182
|
}
|
|
1183
|
+
return refreshPromise;
|
|
1069
1184
|
};
|
|
1070
1185
|
const noDb = {
|
|
1071
1186
|
collection: () => {
|