forked from PapillonApp/Papillon
-
Notifications
You must be signed in to change notification settings - Fork 1
/
App.tsx
132 lines (109 loc) · 4.96 KB
/
App.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import Router from "@/router";
import { useFonts } from "expo-font";
import * as SplashScreen from "expo-splash-screen";
import { LogBox, AppState, AppStateStatus } from "react-native";
import React, { useEffect, useState, useRef, useCallback } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { useAccounts, useCurrentAccount } from "@/stores/account";
import {AccountService, PrimaryAccount} from "@/stores/account/types";
import { log } from "@/utils/logger/logger";
import { expoGoWrapper } from "@/utils/native/expoGoAlert";
import { atobPolyfill, btoaPolyfill } from "js-base64";
SplashScreen.preventAutoHideAsync();
const DEFAULT_BACKGROUND_TIME = 15 * 60 * 1000; // 15 minutes
const BACKGROUND_LIMITS: Partial<Record<AccountService, number>> = {
[AccountService.EcoleDirecte]: 15 * 60 * 1000, // 15 minutes
[AccountService.Pronote]: 5 * 60 * 1000, // 5 minutes
[AccountService.Skolengo]: 12 * 60 * 60 * 1000, // 12 heures
};
export default function App () {
const [appState, setAppState] = useState<AppStateStatus>(AppState.currentState);
const backgroundStartTime = useRef<number | null>(null);
const switchTo = useCurrentAccount((store) => store.switchTo);
const accounts: PrimaryAccount[] = useAccounts((store) => store.accounts)
.filter(account => !account.isExternal) as PrimaryAccount[];
const [fontsLoaded, fontError] = useFonts({
light: require("./assets/fonts/FixelText-Light.ttf"),
regular: require("./assets/fonts/FixelText-Regular.ttf"),
medium: require("./assets/fonts/FixelText-Medium.ttf"),
semibold: require("./assets/fonts/FixelText-SemiBold.ttf"),
bold: require("./assets/fonts/FixelText-Bold.ttf"),
});
const getBackgroundTimeLimit = useCallback((service: AccountService): number => {
return BACKGROUND_LIMITS[service] ?? DEFAULT_BACKGROUND_TIME;
}, []);
const handleBackgroundState = useCallback(async () => {
try {
if (!backgroundStartTime.current) return;
const timeInBackground = Date.now() - backgroundStartTime.current;
await AsyncStorage.setItem("@background_timestamp", Date.now().toString());
for (const account of accounts) {
const timeLimit = getBackgroundTimeLimit(account.service);
const timeInBackgroundSeconds = Math.floor(timeInBackground / 1000);
const serviceName = AccountService[account.service];
log(`Checking account ${account.studentName.first} ${account.studentName.last}:`, "RefreshToken");
log(`Time in background: ${timeInBackgroundSeconds}s`, "RefreshToken");
log(`Time limit: ${timeLimit / 1000}s`, "RefreshToken");
log(`Account type: ${serviceName}`, "RefreshToken");
log(`Using ${BACKGROUND_LIMITS[account.service] ? "specific" : "default"} time limit`, "RefreshToken");
if (timeInBackground >= timeLimit) {
log(`⚠️ Refreshing account ${account.studentName.first} ${account.studentName.last} after ${timeInBackgroundSeconds}s in background`, "RefreshToken");
// Prevent React state updates during render
setTimeout(() => {
switchTo(account).catch((error) => {
log(`Error during switchTo: ${error}`, "RefreshToken");
});
}, 0);
// Wait before processing next account
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
} catch (error) {
log(`Error handling background state: ${error}`, "RefreshToken");
}
}, [accounts, switchTo, getBackgroundTimeLimit]);
useEffect(() => {
const subscription = AppState.addEventListener("change", async (nextAppState: AppStateStatus) => {
if (appState === nextAppState) return;
if (nextAppState === "active") {
log("🔄 App is active", "AppState");
await handleBackgroundState();
backgroundStartTime.current = null;
} else if (nextAppState.match(/inactive|background/)) {
log("App in background", "AppState");
backgroundStartTime.current = Date.now();
}
setAppState(nextAppState);
});
return () => subscription.remove();
}, [appState, handleBackgroundState]);
useEffect(() => {
LogBox.ignoreLogs([
"[react-native-gesture-handler]",
"VirtualizedLists should never be nested",
"TNodeChildrenRenderer: Support for defaultProps",
"Service not implemented",
"Linking found multiple possible"
]);
expoGoWrapper(async () => {
const { registerBackgroundTasks } = await import("@/background/BackgroundTasks");
registerBackgroundTasks();
});
}, []);
const applyGlobalPolyfills = useCallback(() => {
const encoding = require("text-encoding");
Object.assign(global, {
TextDecoder: encoding.TextDecoder,
TextEncoder: encoding.TextEncoder,
atob: atobPolyfill,
btoa: btoaPolyfill
});
}, []);
useEffect(() => {
applyGlobalPolyfills();
}, [applyGlobalPolyfills]);
if (!fontsLoaded && !fontError) {
return null;
}
return <Router />;
}