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() {
|
export async function registerUser() {
|
||||||
const { data } = await network.apolloClient.mutate({
|
const { data } = await network.apolloClient.mutate({
|
||||||
mutation: REGISTER_USER_MUTATION,
|
mutation: REGISTER_USER_MUTATION,
|
||||||
|
context: {
|
||||||
|
skipAuth: true, // Skip adding Authorization header
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const authToken = data.addOneAuthInitToken.authTokenJwt;
|
const authToken = data.addOneAuthInitToken.authTokenJwt;
|
||||||
return { authToken };
|
return { authToken };
|
||||||
|
@ -27,6 +30,9 @@ export async function loginUserToken({ authToken }) {
|
||||||
phoneModel: Device.modelName,
|
phoneModel: Device.modelName,
|
||||||
deviceUuid,
|
deviceUuid,
|
||||||
},
|
},
|
||||||
|
context: {
|
||||||
|
skipAuth: true, // Skip adding Authorization header
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const userToken = data.doAuthLoginToken.userBearerJwt;
|
const userToken = data.doAuthLoginToken.userBearerJwt;
|
||||||
return { userToken };
|
return { userToken };
|
||||||
|
|
40
src/env.js
40
src/env.js
|
@ -1,4 +1,8 @@
|
||||||
import { Platform } from "react-native";
|
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
|
// Logging configuration
|
||||||
const LOG_SCOPES = process.env.APP_LOG_SCOPES;
|
const LOG_SCOPES = process.env.APP_LOG_SCOPES;
|
||||||
|
@ -85,16 +89,50 @@ const stagingMap = {
|
||||||
IS_STAGING: true,
|
IS_STAGING: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setStaging = (enabled) => {
|
export const setStaging = async (enabled) => {
|
||||||
for (const key of Object.keys(env)) {
|
for (const key of Object.keys(env)) {
|
||||||
if (stagingMap[key] !== undefined) {
|
if (stagingMap[key] !== undefined) {
|
||||||
env[key] = enabled ? stagingMap[key] : envMap[key];
|
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 };
|
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;
|
export default env;
|
||||||
|
|
||||||
// +1
|
// +1
|
||||||
|
|
|
@ -64,7 +64,31 @@ export default async function trackLocation() {
|
||||||
feature: "tracking",
|
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) {
|
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", {
|
locationLogger.info("Handling auth token update", {
|
||||||
hasToken: !!userToken,
|
hasToken: !!userToken,
|
||||||
});
|
});
|
||||||
|
@ -144,13 +168,29 @@ export default async function trackLocation() {
|
||||||
method: response?.method,
|
method: response?.method,
|
||||||
isSync: response?.isSync,
|
isSync: response?.isSync,
|
||||||
});
|
});
|
||||||
|
|
||||||
const statusCode = response?.status;
|
const statusCode = response?.status;
|
||||||
|
const now = Date.now();
|
||||||
|
const timeSinceLastReload = now - lastAuthReloadTime;
|
||||||
|
|
||||||
switch (statusCode) {
|
switch (statusCode) {
|
||||||
case 410:
|
case 410:
|
||||||
|
// Token expired, logout
|
||||||
|
locationLogger.info("Auth token expired (410), logging out");
|
||||||
authActions.logout();
|
authActions.logout();
|
||||||
break;
|
break;
|
||||||
case 401:
|
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");
|
locationLogger.info("Refreshing authentication token");
|
||||||
|
lastAuthReloadTime = now;
|
||||||
authActions.reload(); // should retriger sync in handleAuth via subscribeAuthState when done
|
authActions.reload(); // should retriger sync in handleAuth via subscribeAuthState when done
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,17 @@ export default function createAuthLink({ store }) {
|
||||||
const { getAuthState } = store;
|
const { getAuthState } = store;
|
||||||
|
|
||||||
const authLink = new ApolloLink((operation, forward) => {
|
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 { userToken } = getAuthState();
|
||||||
const headers = operation.getContext().hasOwnProperty("headers")
|
if (userToken) {
|
||||||
? operation.getContext().headers
|
|
||||||
: {};
|
|
||||||
if (userToken && headers["X-Hasura-Role"] !== "anonymous") {
|
|
||||||
setBearerHeader(headers, userToken);
|
setBearerHeader(headers, userToken);
|
||||||
} else {
|
|
||||||
headers["X-Hasura-Role"] = "anonymous";
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// authLinkLogger.debug("Request headers", { headers });
|
// authLinkLogger.debug("Request headers", { headers });
|
||||||
operation.setContext({ headers });
|
operation.setContext({ headers });
|
||||||
return forward(operation);
|
return forward(operation);
|
||||||
|
|
|
@ -95,9 +95,9 @@ export default function Developer() {
|
||||||
<Switch
|
<Switch
|
||||||
value={isStaging}
|
value={isStaging}
|
||||||
onValueChange={async (value) => {
|
onValueChange={async (value) => {
|
||||||
setStaging(value);
|
setIsStaging(value); // Update UI immediately
|
||||||
setIsStaging(value);
|
await setStaging(value); // Persist the change
|
||||||
await reset();
|
await reset(); // Reset auth state
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -132,14 +132,43 @@ export default createAtom(({ get, merge, getActions }) => {
|
||||||
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
authLogger.info("Reloading auth state");
|
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()) {
|
if (isLoading()) {
|
||||||
await loadingPromise;
|
await loadingPromise;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set reloading state
|
||||||
|
merge({ isReloading: true, lastReloadTime: now });
|
||||||
|
|
||||||
|
try {
|
||||||
startLoading();
|
startLoading();
|
||||||
await secureStore.deleteItemAsync("userToken");
|
await secureStore.deleteItemAsync("userToken");
|
||||||
await init();
|
await init();
|
||||||
return true;
|
return true;
|
||||||
|
} finally {
|
||||||
|
// Clear reloading state even if there was an error
|
||||||
|
merge({ isReloading: false });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onReload = async () => {
|
const onReload = async () => {
|
||||||
|
@ -247,6 +276,8 @@ export default createAtom(({ get, merge, getActions }) => {
|
||||||
onReload: false,
|
onReload: false,
|
||||||
onReloadAuthToken: null,
|
onReloadAuthToken: null,
|
||||||
userOffMode: false,
|
userOffMode: false,
|
||||||
|
isReloading: false,
|
||||||
|
lastReloadTime: 0,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
init,
|
init,
|
||||||
|
|
Loading…
Add table
Reference in a new issue