@basictech/react 0.7.0-beta.4 → 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/changelog.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # 1.3.4
2
2
 
3
+ ## 0.7.0-beta.5
4
+
5
+ ### Patch Changes
6
+
7
+ - config server url & token patch
8
+
3
9
  ## 0.7.0-beta.4
4
10
 
5
11
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -16,6 +16,7 @@ declare class LocalStorageAdapter implements BasicStorage {
16
16
  type AuthConfig = {
17
17
  scopes?: string | string[];
18
18
  server_url?: string;
19
+ ws_url?: string;
19
20
  };
20
21
  type BasicProviderProps = {
21
22
  children: React.ReactNode;
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ declare class LocalStorageAdapter implements BasicStorage {
16
16
  type AuthConfig = {
17
17
  scopes?: string | string[];
18
18
  server_url?: string;
19
+ ws_url?: string;
19
20
  };
20
21
  type BasicProviderProps = {
21
22
  children: React.ReactNode;
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 = `wss://pds.basic.id/ws`;
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 = `wss://pds.basic.id/ws`;
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.3";
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 email app:admin",
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({ access_token: tok }).catch((e) => {
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
- const user2 = await fetch(`${authConfig.server_url}/auth/userInfo`, {
835
- method: "GET",
836
- headers: {
837
- "Authorization": `Bearer ${acc_token}`
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 isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
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
- if (!signInLink || !signInLink.startsWith("https://")) {
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 isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
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
- try {
1045
- if (!isOnline) {
1046
- log("Network is offline, marking refresh as pending");
1047
- setPendingRefresh(true);
1048
- throw new Error("Network offline - refresh will be retried when online");
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
- throw new Error("Network error during token refresh");
1089
- });
1090
- if (token2.error) {
1091
- log("error fetching token", token2.error);
1092
- if (token2.error.includes("network") || token2.error.includes("timeout")) {
1093
- setPendingRefresh(true);
1094
- throw new Error("Network issue - refresh will be retried when online");
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
- await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
1097
- await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
1098
- await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
1099
- clearCookie("basic_token");
1100
- clearCookie("basic_access_token");
1101
- setUser({});
1102
- setIsSignedIn(false);
1103
- setToken(null);
1104
- setIsAuthReady(true);
1105
- throw new Error(`Token refresh failed: ${token2.error}`);
1106
- } else {
1107
- setToken(token2);
1108
- setPendingRefresh(false);
1109
- if (token2.refresh_token) {
1110
- await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token2.refresh_token);
1111
- log("Updated refresh token in storage");
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
- if (!isRefreshToken) {
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
- log("Cleaned up redirect_uri from storage after successful exchange");
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
- setCookie("basic_access_token", token2.access_token, { httpOnly: false });
1118
- log("Updated access token in cookie");
1199
+ throw error2;
1119
1200
  }
1120
- return token2;
1121
- } catch (error2) {
1122
- log("Token refresh error:", error2);
1123
- if (!error2.message.includes("offline") && !error2.message.includes("Network")) {
1124
- await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
1125
- await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
1126
- await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI);
1127
- clearCookie("basic_token");
1128
- clearCookie("basic_access_token");
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: () => {