@basictech/react 0.7.0-beta.4 → 0.7.0-beta.6
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 +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +179 -103
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +179 -103
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/AuthContext.tsx +233 -120
- package/src/sync/index.ts +4 -4
- package/src/utils/storage.ts +1 -0
package/changelog.md
CHANGED
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -167,15 +167,15 @@ var BasicSync = class extends import_dexie2.Dexie {
|
|
|
167
167
|
this.version(2).stores({});
|
|
168
168
|
this.Collection.prototype.get = this.Collection.prototype.toArray;
|
|
169
169
|
}
|
|
170
|
-
async connect({ access_token }) {
|
|
171
|
-
const WS_URL =
|
|
170
|
+
async connect({ access_token, ws_url }) {
|
|
171
|
+
const WS_URL = ws_url || "wss://pds.basic.id/ws";
|
|
172
172
|
log("Connecting to", WS_URL);
|
|
173
173
|
await this.updateSyncNodes();
|
|
174
174
|
log("Starting connection...");
|
|
175
175
|
return this.syncable.connect("websocket", WS_URL, { authToken: access_token, schema: this.basic_schema });
|
|
176
176
|
}
|
|
177
|
-
async disconnect() {
|
|
178
|
-
const WS_URL =
|
|
177
|
+
async disconnect({ ws_url } = {}) {
|
|
178
|
+
const WS_URL = ws_url || "wss://pds.basic.id/ws";
|
|
179
179
|
return this.syncable.disconnect(WS_URL);
|
|
180
180
|
}
|
|
181
181
|
async updateSyncNodes() {
|
|
@@ -279,7 +279,7 @@ var BasicSync = class extends import_dexie2.Dexie {
|
|
|
279
279
|
};
|
|
280
280
|
|
|
281
281
|
// package.json
|
|
282
|
-
var version = "0.7.0-beta.
|
|
282
|
+
var version = "0.7.0-beta.4";
|
|
283
283
|
|
|
284
284
|
// src/updater/versionUpdater.ts
|
|
285
285
|
var VersionUpdater = class {
|
|
@@ -423,6 +423,7 @@ var STORAGE_KEYS = {
|
|
|
423
423
|
USER_INFO: "basic_user_info",
|
|
424
424
|
AUTH_STATE: "basic_auth_state",
|
|
425
425
|
REDIRECT_URI: "basic_redirect_uri",
|
|
426
|
+
SERVER_URL: "basic_server_url",
|
|
426
427
|
DEBUG: "basic_debug"
|
|
427
428
|
};
|
|
428
429
|
function getCookie(name) {
|
|
@@ -621,8 +622,9 @@ async function validateAndCheckSchema(schema) {
|
|
|
621
622
|
// src/AuthContext.tsx
|
|
622
623
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
623
624
|
var DEFAULT_AUTH_CONFIG = {
|
|
624
|
-
scopes: "profile
|
|
625
|
-
server_url: "https://api.basic.tech"
|
|
625
|
+
scopes: "profile,email,app:admin",
|
|
626
|
+
server_url: "https://api.basic.tech",
|
|
627
|
+
ws_url: "wss://pds.basic.id/ws"
|
|
626
628
|
};
|
|
627
629
|
var BasicContext = (0, import_react.createContext)({
|
|
628
630
|
unicorn: "\u{1F984}",
|
|
@@ -661,9 +663,11 @@ function BasicProvider({
|
|
|
661
663
|
const storageAdapter = storage || new LocalStorageAdapter();
|
|
662
664
|
const authConfig = {
|
|
663
665
|
scopes: auth?.scopes || DEFAULT_AUTH_CONFIG.scopes,
|
|
664
|
-
server_url: auth?.server_url || DEFAULT_AUTH_CONFIG.server_url
|
|
666
|
+
server_url: auth?.server_url || DEFAULT_AUTH_CONFIG.server_url,
|
|
667
|
+
ws_url: auth?.ws_url || DEFAULT_AUTH_CONFIG.ws_url
|
|
665
668
|
};
|
|
666
669
|
const scopesString = Array.isArray(authConfig.scopes) ? authConfig.scopes.join(" ") : authConfig.scopes;
|
|
670
|
+
const refreshPromiseRef = (0, import_react.useRef)(null);
|
|
667
671
|
const isDevMode = () => isDevelopment(debug);
|
|
668
672
|
const cleanOAuthParams = () => cleanOAuthParamsFromUrl();
|
|
669
673
|
(0, import_react.useEffect)(() => {
|
|
@@ -751,7 +755,10 @@ function BasicProvider({
|
|
|
751
755
|
return;
|
|
752
756
|
}
|
|
753
757
|
log("connecting to db...");
|
|
754
|
-
syncRef.current?.connect({
|
|
758
|
+
syncRef.current?.connect({
|
|
759
|
+
access_token: tok,
|
|
760
|
+
ws_url: authConfig.ws_url
|
|
761
|
+
}).catch((e) => {
|
|
755
762
|
log("error connecting to db", e);
|
|
756
763
|
});
|
|
757
764
|
}
|
|
@@ -761,6 +768,17 @@ function BasicProvider({
|
|
|
761
768
|
(0, import_react.useEffect)(() => {
|
|
762
769
|
const initializeAuth = async () => {
|
|
763
770
|
await storageAdapter.set(STORAGE_KEYS.DEBUG, debug ? "true" : "false");
|
|
771
|
+
const storedServerUrl = await storageAdapter.get(STORAGE_KEYS.SERVER_URL);
|
|
772
|
+
if (storedServerUrl && storedServerUrl !== authConfig.server_url) {
|
|
773
|
+
log("Server URL changed, clearing stored tokens");
|
|
774
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
775
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
776
|
+
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
777
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
778
|
+
clearCookie("basic_token");
|
|
779
|
+
clearCookie("basic_access_token");
|
|
780
|
+
}
|
|
781
|
+
await storageAdapter.set(STORAGE_KEYS.SERVER_URL, authConfig.server_url);
|
|
764
782
|
try {
|
|
765
783
|
const versionUpdater = createVersionUpdater(storageAdapter, version, getMigrations());
|
|
766
784
|
const updateResult = await versionUpdater.checkAndUpdate();
|
|
@@ -831,16 +849,21 @@ function BasicProvider({
|
|
|
831
849
|
(0, import_react.useEffect)(() => {
|
|
832
850
|
async function fetchUser(acc_token) {
|
|
833
851
|
console.info("fetching user");
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
852
|
+
try {
|
|
853
|
+
const response = await fetch(`${authConfig.server_url}/auth/userInfo`, {
|
|
854
|
+
method: "GET",
|
|
855
|
+
headers: {
|
|
856
|
+
"Authorization": `Bearer ${acc_token}`
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
if (!response.ok) {
|
|
860
|
+
throw new Error(`Failed to fetch user info: ${response.status}`);
|
|
861
|
+
}
|
|
862
|
+
const user2 = await response.json();
|
|
863
|
+
if (user2.error) {
|
|
864
|
+
log("error fetching user", user2.error);
|
|
865
|
+
throw new Error(`User info error: ${user2.error}`);
|
|
838
866
|
}
|
|
839
|
-
}).then((response) => response.json()).catch((error2) => log("Error:", error2));
|
|
840
|
-
if (user2.error) {
|
|
841
|
-
log("error fetching user", user2.error);
|
|
842
|
-
return;
|
|
843
|
-
} else {
|
|
844
867
|
if (token?.refresh_token) {
|
|
845
868
|
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token.refresh_token);
|
|
846
869
|
}
|
|
@@ -851,6 +874,9 @@ function BasicProvider({
|
|
|
851
874
|
setUser(user2);
|
|
852
875
|
setIsSignedIn(true);
|
|
853
876
|
setIsAuthReady(true);
|
|
877
|
+
} catch (error2) {
|
|
878
|
+
log("Failed to fetch user info:", error2);
|
|
879
|
+
setIsAuthReady(true);
|
|
854
880
|
}
|
|
855
881
|
}
|
|
856
882
|
async function checkToken() {
|
|
@@ -860,7 +886,8 @@ function BasicProvider({
|
|
|
860
886
|
return;
|
|
861
887
|
}
|
|
862
888
|
const decoded = (0, import_jwt_decode.jwtDecode)(token?.access_token);
|
|
863
|
-
const
|
|
889
|
+
const expirationBuffer = 5;
|
|
890
|
+
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3 + expirationBuffer;
|
|
864
891
|
if (isExpired) {
|
|
865
892
|
log("token is expired - refreshing ...");
|
|
866
893
|
try {
|
|
@@ -919,7 +946,9 @@ function BasicProvider({
|
|
|
919
946
|
}
|
|
920
947
|
const signInLink = await getSignInLink();
|
|
921
948
|
log("Generated sign-in link:", signInLink);
|
|
922
|
-
|
|
949
|
+
try {
|
|
950
|
+
new URL(signInLink);
|
|
951
|
+
} catch {
|
|
923
952
|
log("Error: Invalid sign-in link generated");
|
|
924
953
|
throw new Error("Failed to generate valid sign-in URL");
|
|
925
954
|
}
|
|
@@ -977,6 +1006,7 @@ function BasicProvider({
|
|
|
977
1006
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
978
1007
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
979
1008
|
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1009
|
+
await storageAdapter.remove(STORAGE_KEYS.SERVER_URL);
|
|
980
1010
|
if (syncRef.current) {
|
|
981
1011
|
(async () => {
|
|
982
1012
|
try {
|
|
@@ -996,6 +1026,18 @@ function BasicProvider({
|
|
|
996
1026
|
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
997
1027
|
if (refreshToken) {
|
|
998
1028
|
log("No token in memory, attempting to refresh from storage");
|
|
1029
|
+
if (refreshPromiseRef.current) {
|
|
1030
|
+
log("Token refresh already in progress, waiting...");
|
|
1031
|
+
try {
|
|
1032
|
+
const newToken = await refreshPromiseRef.current;
|
|
1033
|
+
if (newToken?.access_token) {
|
|
1034
|
+
return newToken.access_token;
|
|
1035
|
+
}
|
|
1036
|
+
} catch (error2) {
|
|
1037
|
+
log("In-flight refresh failed:", error2);
|
|
1038
|
+
throw error2;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
999
1041
|
try {
|
|
1000
1042
|
const newToken = await fetchToken(refreshToken, true);
|
|
1001
1043
|
if (newToken?.access_token) {
|
|
@@ -1018,9 +1060,24 @@ function BasicProvider({
|
|
|
1018
1060
|
throw new Error("no token found");
|
|
1019
1061
|
}
|
|
1020
1062
|
const decoded = (0, import_jwt_decode.jwtDecode)(token?.access_token);
|
|
1021
|
-
const
|
|
1063
|
+
const expirationBuffer = 5;
|
|
1064
|
+
const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3 + expirationBuffer;
|
|
1022
1065
|
if (isExpired) {
|
|
1023
1066
|
log("token is expired - refreshing ...");
|
|
1067
|
+
if (refreshPromiseRef.current) {
|
|
1068
|
+
log("Token refresh already in progress, waiting...");
|
|
1069
|
+
try {
|
|
1070
|
+
const newToken = await refreshPromiseRef.current;
|
|
1071
|
+
return newToken?.access_token || "";
|
|
1072
|
+
} catch (error2) {
|
|
1073
|
+
log("In-flight refresh failed:", error2);
|
|
1074
|
+
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
1075
|
+
log("Network issue - using expired token until network is restored");
|
|
1076
|
+
return token.access_token;
|
|
1077
|
+
}
|
|
1078
|
+
throw error2;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1024
1081
|
const refreshToken = token?.refresh_token || await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1025
1082
|
if (refreshToken) {
|
|
1026
1083
|
try {
|
|
@@ -1041,98 +1098,117 @@ function BasicProvider({
|
|
|
1041
1098
|
return token?.access_token || "";
|
|
1042
1099
|
};
|
|
1043
1100
|
const fetchToken = async (codeOrRefreshToken, isRefreshToken = false) => {
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
let requestBody;
|
|
1051
|
-
if (isRefreshToken) {
|
|
1052
|
-
requestBody = {
|
|
1053
|
-
grant_type: "refresh_token",
|
|
1054
|
-
refresh_token: codeOrRefreshToken
|
|
1055
|
-
};
|
|
1056
|
-
if (project_id) {
|
|
1057
|
-
requestBody.client_id = project_id;
|
|
1058
|
-
}
|
|
1059
|
-
} else {
|
|
1060
|
-
requestBody = {
|
|
1061
|
-
grant_type: "authorization_code",
|
|
1062
|
-
code: codeOrRefreshToken
|
|
1063
|
-
};
|
|
1064
|
-
const storedRedirectUri = await storageAdapter.get(STORAGE_KEYS.REDIRECT_URI);
|
|
1065
|
-
if (storedRedirectUri) {
|
|
1066
|
-
requestBody.redirect_uri = storedRedirectUri;
|
|
1067
|
-
log("Including redirect_uri in token exchange:", storedRedirectUri);
|
|
1068
|
-
} else {
|
|
1069
|
-
log("Warning: No redirect_uri found in storage for token exchange");
|
|
1070
|
-
}
|
|
1071
|
-
if (project_id) {
|
|
1072
|
-
requestBody.client_id = project_id;
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
log("Token exchange request body:", { ...requestBody, refresh_token: isRefreshToken ? "[REDACTED]" : void 0, code: !isRefreshToken ? "[REDACTED]" : void 0 });
|
|
1076
|
-
const token2 = await fetch(`${authConfig.server_url}/auth/token`, {
|
|
1077
|
-
method: "POST",
|
|
1078
|
-
headers: {
|
|
1079
|
-
"Content-Type": "application/json"
|
|
1080
|
-
},
|
|
1081
|
-
body: JSON.stringify(requestBody)
|
|
1082
|
-
}).then((response) => response.json()).catch((error2) => {
|
|
1083
|
-
log("Network error fetching token:", error2);
|
|
1101
|
+
if (isRefreshToken && refreshPromiseRef.current) {
|
|
1102
|
+
log("Reusing in-flight refresh token request");
|
|
1103
|
+
return refreshPromiseRef.current;
|
|
1104
|
+
}
|
|
1105
|
+
const refreshPromise = (async () => {
|
|
1106
|
+
try {
|
|
1084
1107
|
if (!isOnline) {
|
|
1108
|
+
log("Network is offline, marking refresh as pending");
|
|
1085
1109
|
setPendingRefresh(true);
|
|
1086
1110
|
throw new Error("Network offline - refresh will be retried when online");
|
|
1087
1111
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1112
|
+
let requestBody;
|
|
1113
|
+
if (isRefreshToken) {
|
|
1114
|
+
requestBody = {
|
|
1115
|
+
grant_type: "refresh_token",
|
|
1116
|
+
refresh_token: codeOrRefreshToken
|
|
1117
|
+
};
|
|
1118
|
+
if (project_id) {
|
|
1119
|
+
requestBody.client_id = project_id;
|
|
1120
|
+
}
|
|
1121
|
+
} else {
|
|
1122
|
+
requestBody = {
|
|
1123
|
+
grant_type: "authorization_code",
|
|
1124
|
+
code: codeOrRefreshToken
|
|
1125
|
+
};
|
|
1126
|
+
const storedRedirectUri = await storageAdapter.get(STORAGE_KEYS.REDIRECT_URI);
|
|
1127
|
+
if (storedRedirectUri) {
|
|
1128
|
+
requestBody.redirect_uri = storedRedirectUri;
|
|
1129
|
+
log("Including redirect_uri in token exchange:", storedRedirectUri);
|
|
1130
|
+
} else {
|
|
1131
|
+
log("Warning: No redirect_uri found in storage for token exchange");
|
|
1132
|
+
}
|
|
1133
|
+
if (project_id) {
|
|
1134
|
+
requestBody.client_id = project_id;
|
|
1135
|
+
}
|
|
1095
1136
|
}
|
|
1096
|
-
|
|
1097
|
-
await
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1137
|
+
log("Token exchange request body:", { ...requestBody, refresh_token: isRefreshToken ? "[REDACTED]" : void 0, code: !isRefreshToken ? "[REDACTED]" : void 0 });
|
|
1138
|
+
const token2 = await fetch(`${authConfig.server_url}/auth/token`, {
|
|
1139
|
+
method: "POST",
|
|
1140
|
+
headers: {
|
|
1141
|
+
"Content-Type": "application/json"
|
|
1142
|
+
},
|
|
1143
|
+
body: JSON.stringify(requestBody)
|
|
1144
|
+
}).then((response) => response.json()).catch((error2) => {
|
|
1145
|
+
log("Network error fetching token:", error2);
|
|
1146
|
+
if (!isOnline) {
|
|
1147
|
+
setPendingRefresh(true);
|
|
1148
|
+
throw new Error("Network offline - refresh will be retried when online");
|
|
1149
|
+
}
|
|
1150
|
+
throw new Error("Network error during token refresh");
|
|
1151
|
+
});
|
|
1152
|
+
if (token2.error) {
|
|
1153
|
+
log("error fetching token", token2.error);
|
|
1154
|
+
if (token2.error.includes("network") || token2.error.includes("timeout")) {
|
|
1155
|
+
setPendingRefresh(true);
|
|
1156
|
+
throw new Error("Network issue - refresh will be retried when online");
|
|
1157
|
+
}
|
|
1158
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1159
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1160
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1161
|
+
await storageAdapter.remove(STORAGE_KEYS.SERVER_URL);
|
|
1162
|
+
clearCookie("basic_token");
|
|
1163
|
+
clearCookie("basic_access_token");
|
|
1164
|
+
setUser({});
|
|
1165
|
+
setIsSignedIn(false);
|
|
1166
|
+
setToken(null);
|
|
1167
|
+
setIsAuthReady(true);
|
|
1168
|
+
throw new Error(`Token refresh failed: ${token2.error}`);
|
|
1169
|
+
} else {
|
|
1170
|
+
setToken(token2);
|
|
1171
|
+
setPendingRefresh(false);
|
|
1172
|
+
if (token2.refresh_token) {
|
|
1173
|
+
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token2.refresh_token);
|
|
1174
|
+
log("Updated refresh token in storage");
|
|
1175
|
+
}
|
|
1176
|
+
if (!isRefreshToken) {
|
|
1177
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1178
|
+
log("Cleaned up redirect_uri from storage after successful exchange");
|
|
1179
|
+
}
|
|
1180
|
+
setCookie("basic_access_token", token2.access_token, { httpOnly: false });
|
|
1181
|
+
setCookie("basic_token", JSON.stringify(token2));
|
|
1182
|
+
log("Updated access token and full token in cookies");
|
|
1112
1183
|
}
|
|
1113
|
-
|
|
1184
|
+
return token2;
|
|
1185
|
+
} catch (error2) {
|
|
1186
|
+
log("Token refresh error:", error2);
|
|
1187
|
+
if (!error2.message.includes("offline") && !error2.message.includes("Network")) {
|
|
1188
|
+
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1189
|
+
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1114
1190
|
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
|
|
1115
|
-
|
|
1191
|
+
await storageAdapter.remove(STORAGE_KEYS.SERVER_URL);
|
|
1192
|
+
clearCookie("basic_token");
|
|
1193
|
+
clearCookie("basic_access_token");
|
|
1194
|
+
setUser({});
|
|
1195
|
+
setIsSignedIn(false);
|
|
1196
|
+
setToken(null);
|
|
1197
|
+
setIsAuthReady(true);
|
|
1116
1198
|
}
|
|
1117
|
-
|
|
1118
|
-
log("Updated access token in cookie");
|
|
1199
|
+
throw error2;
|
|
1119
1200
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
setUser({});
|
|
1130
|
-
setIsSignedIn(false);
|
|
1131
|
-
setToken(null);
|
|
1132
|
-
setIsAuthReady(true);
|
|
1133
|
-
}
|
|
1134
|
-
throw error2;
|
|
1201
|
+
})();
|
|
1202
|
+
if (isRefreshToken) {
|
|
1203
|
+
refreshPromiseRef.current = refreshPromise;
|
|
1204
|
+
refreshPromise.finally(() => {
|
|
1205
|
+
if (refreshPromiseRef.current === refreshPromise) {
|
|
1206
|
+
refreshPromiseRef.current = null;
|
|
1207
|
+
log("Cleared refresh promise reference");
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1135
1210
|
}
|
|
1211
|
+
return refreshPromise;
|
|
1136
1212
|
};
|
|
1137
1213
|
const noDb = {
|
|
1138
1214
|
collection: () => {
|