fix: staging persistence + disconnect and geo auth reload loop
This commit is contained in:
parent
91c374756b
commit
40b27bff29
6 changed files with 133 additions and 16 deletions
|
@ -13,6 +13,9 @@ import { getDeviceUuid } from "./deviceUuid";
|
|||
export async function registerUser() {
|
||||
const { data } = await network.apolloClient.mutate({
|
||||
mutation: REGISTER_USER_MUTATION,
|
||||
context: {
|
||||
skipAuth: true, // Skip adding Authorization header
|
||||
},
|
||||
});
|
||||
const authToken = data.addOneAuthInitToken.authTokenJwt;
|
||||
return { authToken };
|
||||
|
@ -27,6 +30,9 @@ export async function loginUserToken({ authToken }) {
|
|||
phoneModel: Device.modelName,
|
||||
deviceUuid,
|
||||
},
|
||||
context: {
|
||||
skipAuth: true, // Skip adding Authorization header
|
||||
},
|
||||
});
|
||||
const userToken = data.doAuthLoginToken.userBearerJwt;
|
||||
return { userToken };
|
||||
|
|
40
src/env.js
40
src/env.js
|
@ -1,4 +1,8 @@
|
|||
import { Platform } from "react-native";
|
||||
import { secureStore } from "~/lib/secureStore";
|
||||
|
||||
// Key for storing staging setting in secureStore
|
||||
const STAGING_SETTING_KEY = "env.isStaging";
|
||||
|
||||
// Logging configuration
|
||||
const LOG_SCOPES = process.env.APP_LOG_SCOPES;
|
||||
|
@ -85,16 +89,50 @@ const stagingMap = {
|
|||
IS_STAGING: true,
|
||||
};
|
||||
|
||||
export const setStaging = (enabled) => {
|
||||
export const setStaging = async (enabled) => {
|
||||
for (const key of Object.keys(env)) {
|
||||
if (stagingMap[key] !== undefined) {
|
||||
env[key] = enabled ? stagingMap[key] : envMap[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Persist the staging setting
|
||||
await secureStore.setItemAsync(STAGING_SETTING_KEY, String(enabled));
|
||||
};
|
||||
|
||||
// Initialize with default values
|
||||
const env = { ...envMap };
|
||||
|
||||
// Load the staging setting from secureStore
|
||||
export const initializeEnv = async () => {
|
||||
try {
|
||||
const storedStaging = await secureStore.getItemAsync(STAGING_SETTING_KEY);
|
||||
if (storedStaging !== null) {
|
||||
const isStaging = storedStaging === "true";
|
||||
if (isStaging) {
|
||||
// Apply staging settings without persisting again
|
||||
for (const key of Object.keys(env)) {
|
||||
if (stagingMap[key] !== undefined) {
|
||||
env[key] = stagingMap[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load staging setting from secureStore:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize environment settings
|
||||
// We use an IIFE to handle the async initialization
|
||||
(async () => {
|
||||
try {
|
||||
await initializeEnv();
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize environment settings:", error);
|
||||
}
|
||||
})();
|
||||
|
||||
export default env;
|
||||
|
||||
// +1
|
||||
|
|
|
@ -64,7 +64,31 @@ export default async function trackLocation() {
|
|||
feature: "tracking",
|
||||
});
|
||||
|
||||
// Track the last time we handled auth changes to prevent rapid successive calls
|
||||
let lastAuthHandleTime = 0;
|
||||
const AUTH_HANDLE_COOLDOWN = 3000; // 3 seconds cooldown
|
||||
|
||||
// Track the last time we triggered an auth reload to prevent rapid successive calls
|
||||
let lastAuthReloadTime = 0;
|
||||
const AUTH_RELOAD_COOLDOWN = 5000; // 5 seconds cooldown
|
||||
|
||||
async function handleAuth(userToken) {
|
||||
// Implement debouncing for auth state changes
|
||||
const now = Date.now();
|
||||
const timeSinceLastHandle = now - lastAuthHandleTime;
|
||||
|
||||
if (timeSinceLastHandle < AUTH_HANDLE_COOLDOWN) {
|
||||
locationLogger.info(
|
||||
"Auth state change handled too recently, debouncing",
|
||||
{
|
||||
timeSinceLastHandle,
|
||||
cooldown: AUTH_HANDLE_COOLDOWN,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
lastAuthHandleTime = now;
|
||||
locationLogger.info("Handling auth token update", {
|
||||
hasToken: !!userToken,
|
||||
});
|
||||
|
@ -144,13 +168,29 @@ export default async function trackLocation() {
|
|||
method: response?.method,
|
||||
isSync: response?.isSync,
|
||||
});
|
||||
|
||||
const statusCode = response?.status;
|
||||
const now = Date.now();
|
||||
const timeSinceLastReload = now - lastAuthReloadTime;
|
||||
|
||||
switch (statusCode) {
|
||||
case 410:
|
||||
// Token expired, logout
|
||||
locationLogger.info("Auth token expired (410), logging out");
|
||||
authActions.logout();
|
||||
break;
|
||||
case 401:
|
||||
// Unauthorized, check cooldown before triggering reload
|
||||
if (timeSinceLastReload < AUTH_RELOAD_COOLDOWN) {
|
||||
locationLogger.info("Auth reload requested too soon, skipping", {
|
||||
timeSinceLastReload,
|
||||
cooldown: AUTH_RELOAD_COOLDOWN,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
locationLogger.info("Refreshing authentication token");
|
||||
lastAuthReloadTime = now;
|
||||
authActions.reload(); // should retriger sync in handleAuth via subscribeAuthState when done
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -13,15 +13,17 @@ export default function createAuthLink({ store }) {
|
|||
const { getAuthState } = store;
|
||||
|
||||
const authLink = new ApolloLink((operation, forward) => {
|
||||
const context = operation.getContext();
|
||||
const headers = context.hasOwnProperty("headers") ? context.headers : {};
|
||||
|
||||
// Skip adding auth header if skipAuth flag is set
|
||||
if (!context.skipAuth) {
|
||||
const { userToken } = getAuthState();
|
||||
const headers = operation.getContext().hasOwnProperty("headers")
|
||||
? operation.getContext().headers
|
||||
: {};
|
||||
if (userToken && headers["X-Hasura-Role"] !== "anonymous") {
|
||||
if (userToken) {
|
||||
setBearerHeader(headers, userToken);
|
||||
} else {
|
||||
headers["X-Hasura-Role"] = "anonymous";
|
||||
}
|
||||
}
|
||||
|
||||
// authLinkLogger.debug("Request headers", { headers });
|
||||
operation.setContext({ headers });
|
||||
return forward(operation);
|
||||
|
|
|
@ -95,9 +95,9 @@ export default function Developer() {
|
|||
<Switch
|
||||
value={isStaging}
|
||||
onValueChange={async (value) => {
|
||||
setStaging(value);
|
||||
setIsStaging(value);
|
||||
await reset();
|
||||
setIsStaging(value); // Update UI immediately
|
||||
await setStaging(value); // Persist the change
|
||||
await reset(); // Reset auth state
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
@ -132,14 +132,43 @@ export default createAtom(({ get, merge, getActions }) => {
|
|||
|
||||
const reload = async () => {
|
||||
authLogger.info("Reloading auth state");
|
||||
|
||||
// Check if we're already reloading or in a loading state
|
||||
const { isReloading, lastReloadTime } = get();
|
||||
const now = Date.now();
|
||||
const timeSinceLastReload = now - lastReloadTime;
|
||||
const RELOAD_COOLDOWN = 2000; // 2 seconds cooldown
|
||||
|
||||
if (isReloading) {
|
||||
authLogger.info("Auth reload already in progress, skipping");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (timeSinceLastReload < RELOAD_COOLDOWN) {
|
||||
authLogger.info("Auth reload requested too soon, skipping", {
|
||||
timeSinceLastReload,
|
||||
cooldown: RELOAD_COOLDOWN,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isLoading()) {
|
||||
await loadingPromise;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set reloading state
|
||||
merge({ isReloading: true, lastReloadTime: now });
|
||||
|
||||
try {
|
||||
startLoading();
|
||||
await secureStore.deleteItemAsync("userToken");
|
||||
await init();
|
||||
return true;
|
||||
} finally {
|
||||
// Clear reloading state even if there was an error
|
||||
merge({ isReloading: false });
|
||||
}
|
||||
};
|
||||
|
||||
const onReload = async () => {
|
||||
|
@ -247,6 +276,8 @@ export default createAtom(({ get, merge, getActions }) => {
|
|||
onReload: false,
|
||||
onReloadAuthToken: null,
|
||||
userOffMode: false,
|
||||
isReloading: false,
|
||||
lastReloadTime: 0,
|
||||
},
|
||||
actions: {
|
||||
init,
|
||||
|
|
Loading…
Add table
Reference in a new issue