-
Notifications
You must be signed in to change notification settings - Fork 4
/
UpdateMonitor.tsx
143 lines (122 loc) · 4.69 KB
/
UpdateMonitor.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
133
134
135
136
137
138
139
140
141
142
143
import { useUpdates, checkForUpdateAsync, fetchUpdateAsync, reloadAsync } from "expo-updates"
import React, { useEffect, useState } from "react"
import { StyleProp, View, ViewStyle } from "react-native"
import { ExpoDemoCard } from "./ExpoDemoCard"
import {
availableUpdateTitle,
availableUpdateDescription,
errorDescription,
isAvailableUpdateCritical,
useAppState,
usePersistentDate,
useInterval,
} from "../utils/updates"
import { dateDifferenceInMilliSeconds } from "../utils/updates/dateUtils"
// Wrap async expo-updates functions (errors are surfaced in useUpdates() hook so can be ignored)
const checkForUpdate: () => Promise<string | undefined> = async () => {
try {
const result = await checkForUpdateAsync()
return result.reason
} catch (_error) {}
}
const downloadUpdate = () => fetchUpdateAsync().catch((_error) => {})
const runUpdate = () => reloadAsync().catch((_error) => {})
const defaultUpdateCheckInterval = 3600000 // 1 hour
const defaultCheckOnForeground = false
const defaultAutoLaunchCritical = false
const defaultAlwaysVisible = false
const defaultButtonsAlwaysVisible = false
export interface UpdateMonitorProps {
/**
* An optional style override useful for padding & margin.
*/
style?: StyleProp<ViewStyle>
updateCheckInterval?: number
checkOnForeground?: boolean
autoLaunchCritical?: boolean
alwaysVisible?: boolean
buttonsAlwaysVisible?: boolean
}
export const UpdateMonitor: (props: UpdateMonitorProps) => JSX.Element = ({
style,
updateCheckInterval = defaultUpdateCheckInterval,
checkOnForeground = defaultCheckOnForeground,
autoLaunchCritical = defaultAutoLaunchCritical,
alwaysVisible = defaultAlwaysVisible,
buttonsAlwaysVisible = defaultButtonsAlwaysVisible,
}) => {
const updatesSystem = useUpdates()
const { isUpdateAvailable, isUpdatePending, lastCheckForUpdateTimeSinceRestart } = updatesSystem
const isUpdateCritical = isAvailableUpdateCritical(updatesSystem)
const lastCheckForUpdateTime = usePersistentDate(lastCheckForUpdateTimeSinceRestart)
const monitorInterval = updateCheckInterval
const [noUpdateReason, setNoUpdateReason] = useState<string | undefined>(undefined)
const needsUpdateCheck = () =>
dateDifferenceInMilliSeconds(new Date(), lastCheckForUpdateTime) > monitorInterval
// Check if needed when app becomes active
const appStateHandler = (activating: boolean) => {
if (activating) {
checkForUpdate()
}
}
const appState = useAppState(checkOnForeground ? appStateHandler : undefined)
// This effect runs periodically to see if an update check is needed
// The effect interval should be smaller than monitorInterval
useInterval(() => {
if (appState === "active" && needsUpdateCheck()) {
checkForUpdate().then((reason) => setNoUpdateReason(reason))
}
}, monitorInterval / 4)
// If update is critical, download it
useEffect(() => {
if (isUpdateCritical && !isUpdatePending && autoLaunchCritical) {
downloadUpdate()
}
}, [isUpdateCritical, isUpdatePending, autoLaunchCritical])
// Run the downloaded update (after delay) if download completes successfully and it is critical
useEffect(() => {
if (isUpdatePending && isUpdateCritical && autoLaunchCritical) {
setTimeout(() => runUpdate(), 2000)
}
}, [isUpdateCritical, isUpdatePending, autoLaunchCritical])
// Button press handlers
const handleDownloadButtonPress = () => downloadUpdate()
const handleRunButtonPress = () => setTimeout(() => runUpdate(), 2000)
// Text content
const title = `${availableUpdateTitle(updatesSystem)}`
const description = `${availableUpdateDescription(updatesSystem)} ${errorDescription(
updatesSystem,
)} ${isUpdateAvailable ? "" : noUpdateReason ?? ""}`
// Actions: only show actions that make sense based on the current state
const actions: { label: string; onPress: () => void }[] = []
if (isUpdateAvailable || buttonsAlwaysVisible) {
actions.push({
label: "Download",
onPress: () => {
handleDownloadButtonPress()
},
})
}
if (isUpdatePending || buttonsAlwaysVisible) {
actions.push({
label: "Launch",
onPress: () => {
handleRunButtonPress()
},
})
}
// Expo colors: green = no update needed, blue = update available, red = critical update available
const variant = isUpdateCritical ? "danger" : isUpdateAvailable ? "info" : "success"
const styles = style ? [style, $container] : $container
return (
<View style={styles}>
{isUpdateAvailable || alwaysVisible ? (
<ExpoDemoCard variant={variant} title={title} description={description} actions={actions} />
) : null}
</View>
)
}
const $container: ViewStyle = {
justifyContent: "center",
alignItems: "center",
}