import { Inter_900Black } from '@expo-google-fonts/inter'; import Constants from 'expo-constants'; import { ExpoUpdatesManifest } from 'expo-manifests'; import { requireNativeModule } from 'expo-modules-core'; import { StatusBar } from 'expo-status-bar'; import * as Updates from 'expo-updates'; import { UpdatesLogEntry } from 'expo-updates'; import React from 'react'; import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; const ExpoUpdatesE2ETest = requireNativeModule('ExpoUpdatesE2ETest'); require('./includedAssets/test.png'); require('./includedAssets/lock-filled.svg'); // eslint-disable-next-line no-unused-expressions Inter_900Black; // keep the line below for replacement in generate-test-update-bundles // REPLACE_WITH_CRASHING_CODE function TestValue(props: { testID: string; value: string }) { return ( {props.testID}   {props.value || 'null'} ); } function TestButton(props: { testID: string; onPress: () => void }) { return ( [styles.button, pressed && styles.buttonPressed]} onPress={props.onPress}> {props.testID} ); } export default function App() { const [numAssetFiles, setNumAssetFiles] = React.useState(0); const [logs, setLogs] = React.useState([]); const [numActive, setNumActive] = React.useState(0); const [extraParamsString, setExtraParamsString] = React.useState(''); const [isRollback, setIsRollback] = React.useState(false); const [isReloading, setIsReloading] = React.useState(false); const [startTime, setStartTime] = React.useState(null); const [didCheckAndDownloadHappenInParallel, setDidCheckAndDownloadHappenInParallel] = React.useState(false); const [wasIsStartupProcedureRunningEverTrue, setWasIsStartupProcedureRunningEverTrue] = React.useState(false); const { isStartupProcedureRunning, currentlyRunning, availableUpdate, downloadedUpdate, isUpdateAvailable, isUpdatePending, checkError, isChecking, isDownloading, isRestarting, restartCount, downloadProgress, } = Updates.useUpdates(); React.useEffect(() => { setStartTime(Date.now()); }, []); React.useEffect(() => { if (isStartupProcedureRunning) { setWasIsStartupProcedureRunningEverTrue(true); } }, [isStartupProcedureRunning]); // Get rollback state with this, until useUpdates() supports rollbacks React.useEffect(() => { const handleAsync = async () => { setIsRollback(availableUpdate?.type === Updates.UpdateInfoType.ROLLBACK); }; if (isUpdateAvailable) { handleAsync(); } }, [isUpdateAvailable]); // Record if checking an downloading happen in parallel (they shouldn't) React.useEffect(() => { if (isChecking && isDownloading) { setDidCheckAndDownloadHappenInParallel(true); } }, [isChecking, isDownloading]); const runBlockAsync = (block: () => Promise) => async () => { setNumActive((n) => n + 1); try { await block(); } catch (e) { console.warn(e); } finally { setNumActive((n) => n - 1); } }; const handleSetExtraParams = runBlockAsync(async () => { await Updates.setExtraParamAsync('testsetnull', 'testvalue'); await Updates.setExtraParamAsync('testsetnull', null); await Updates.setExtraParamAsync('testparam', 'testvalue'); const params = await Updates.getExtraParamsAsync(); setExtraParamsString(JSON.stringify(params, null, 2)); }); const handleSetUpdateURLAndRequestHeadersOverride = runBlockAsync(async () => { Updates.setUpdateURLAndRequestHeadersOverride({ updateUrl: `${Constants.expoConfig?.updates?.url}-override`, requestHeaders: {}, }); }); const handleToggleUpdateRequestHeadersOverride = runBlockAsync(async () => { if (currentlyRunning.channel !== 'preview') { Updates.setUpdateRequestHeadersOverride({ 'expo-channel-name': 'preview', }); } else { Updates.setUpdateRequestHeadersOverride(null); } }); const handleReadAssetFiles = runBlockAsync(async () => { const numFiles = await ExpoUpdatesE2ETest.readInternalAssetsFolderAsync(); setNumAssetFiles(numFiles); }); const handleClearAssetFiles = runBlockAsync(async () => { await ExpoUpdatesE2ETest.clearInternalAssetsFolderAsync(); const numFiles = await ExpoUpdatesE2ETest.readInternalAssetsFolderAsync(); setNumAssetFiles(numFiles); }); const handleReadLogEntries = runBlockAsync(async () => { const logEntries = await Updates.readLogEntriesAsync(60000); setLogs(logEntries); const server_port = process.env.EXPO_PUBLIC_UPDATES_SERVER_PORT; try { await fetch(`http://localhost:${server_port}/upload-log-entries`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(logEntries), }); } catch (e) { console.log('Server does not support the log entry endpoints'); } }); const handleClearLogEntries = runBlockAsync(async () => { await Updates.clearLogEntriesAsync(); }); const handleReload = async () => { setIsReloading(true); // this is done after a timeout so that the button press finishes for detox setTimeout(async () => { try { await Updates.reloadAsync(); setIsReloading(false); } catch (e) { console.warn(e); } }, 2000); }; const handleCheckForUpdate = runBlockAsync(async () => { await Updates.checkForUpdateAsync(); }); const handleDownloadUpdate = runBlockAsync(async () => { await Updates.fetchUpdateAsync(); }); const handleCheckAndDownloadAtSameTime = runBlockAsync(async () => { await Promise.all([ Updates.checkForUpdateAsync(), Updates.fetchUpdateAsync(), Updates.checkForUpdateAsync(), Updates.fetchUpdateAsync(), Updates.checkForUpdateAsync(), Updates.fetchUpdateAsync(), Updates.checkForUpdateAsync(), ]); }); const logsToString = (logs: UpdatesLogEntry[]) => JSON.stringify( logs.map((log) => { return { message: log.message, time: new Date(log.timestamp).toISOString(), code: log.code, }; }) ); return ( Log messages {logsToString(logs)} Updates expoConfig {JSON.stringify((Updates.manifest as ExpoUpdatesManifest)?.extra?.expoClient || {})} Constants expoConfig {JSON.stringify(Constants.expoConfig)} {numActive > 0 ? : null} ); } const styles = StyleSheet.create({ container: { flex: 1, marginTop: 100, marginBottom: 100, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', }, button: { alignItems: 'center', justifyContent: 'center', margin: 5, paddingVertical: 2, paddingHorizontal: 10, borderRadius: 4, elevation: 3, backgroundColor: '#4630EB', }, buttonPressed: { backgroundColor: '#FFFFFF', }, buttonText: { color: 'white', fontSize: 10, }, labelText: { fontSize: 10, }, logEntriesContainer: { margin: 10, height: 20, paddingVertical: 5, paddingHorizontal: 10, width: '90%', minWidth: '90%', borderColor: '#4630EB', borderWidth: 1, borderRadius: 4, }, logEntriesText: { fontSize: 6, }, });