@rific/updater 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -6
- package/src/checkForUpdate.ts +10 -0
- package/src/getUpdateConfirmation.ts +17 -0
- package/src/globals.d.ts +1 -0
- package/src/index.ts +2 -0
- package/src/types.ts +4 -0
- package/src/useUpdater.ts +86 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rific/updater",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "OTA update hook for Expo apps — silent background fetch on foreground, manual check with confirmation dialog",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"expo",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"type": "commonjs",
|
|
24
24
|
"exports": {
|
|
25
25
|
".": {
|
|
26
|
-
"types": "./dist/index.d.ts",
|
|
27
26
|
"react-native": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
28
|
"import": "./dist/index.mjs",
|
|
29
29
|
"require": "./dist/index.js"
|
|
30
30
|
}
|
|
@@ -37,7 +37,10 @@
|
|
|
37
37
|
},
|
|
38
38
|
"files": [
|
|
39
39
|
"dist",
|
|
40
|
-
"scripts"
|
|
40
|
+
"scripts",
|
|
41
|
+
"src",
|
|
42
|
+
"!src/__tests__",
|
|
43
|
+
"!src/__mocks__"
|
|
41
44
|
],
|
|
42
45
|
"scripts": {
|
|
43
46
|
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
@@ -51,7 +54,7 @@
|
|
|
51
54
|
"test": "jest",
|
|
52
55
|
"test:watch": "jest --watchAll",
|
|
53
56
|
"typecheck": "tsc --noEmit",
|
|
54
|
-
"preversion": "npm run lint && npm test"
|
|
57
|
+
"preversion": "npm run lint && npm test && npm run build"
|
|
55
58
|
},
|
|
56
59
|
"devDependencies": {
|
|
57
60
|
"@testing-library/react": "^16.3.2",
|
|
@@ -77,8 +80,8 @@
|
|
|
77
80
|
},
|
|
78
81
|
"peerDependencies": {
|
|
79
82
|
"expo-updates": ">=0.25.0",
|
|
80
|
-
"react": ">=
|
|
81
|
-
"react-native": ">=0.
|
|
83
|
+
"react": ">=18.0.0",
|
|
84
|
+
"react-native": ">=0.76.0"
|
|
82
85
|
},
|
|
83
86
|
"publishConfig": {
|
|
84
87
|
"access": "public"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { checkForUpdateAsync, fetchUpdateAsync } from 'expo-updates'
|
|
2
|
+
|
|
3
|
+
import { UpdateManifest } from './types'
|
|
4
|
+
|
|
5
|
+
export const checkForUpdate = async (): Promise<UpdateManifest | null> => {
|
|
6
|
+
const { isAvailable } = await checkForUpdateAsync()
|
|
7
|
+
if (!isAvailable) return null
|
|
8
|
+
const { manifest } = await fetchUpdateAsync()
|
|
9
|
+
return manifest ? (manifest as unknown as UpdateManifest) : null
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Alert } from 'react-native'
|
|
2
|
+
|
|
3
|
+
import { UpdateManifest } from './types'
|
|
4
|
+
|
|
5
|
+
export const getUpdateConfirmation = (manifest: UpdateManifest): Promise<boolean> => {
|
|
6
|
+
const date = new Date(manifest.createdAt)
|
|
7
|
+
let info = `A new update was released on ${date.toLocaleDateString()} at ${date.toLocaleTimeString()}.`
|
|
8
|
+
if (manifest.metadata?.message) info += `\n\nMessage: ${manifest.metadata.message}.`
|
|
9
|
+
info += '\n\nRestart app to update.'
|
|
10
|
+
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
Alert.alert('Update available', info, [
|
|
13
|
+
{ text: 'Cancel', style: 'cancel', onPress: () => resolve(false) },
|
|
14
|
+
{ text: 'Restart', onPress: () => resolve(true) }
|
|
15
|
+
])
|
|
16
|
+
})
|
|
17
|
+
}
|
package/src/globals.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare const __DEV__: boolean
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { reloadAsync } from 'expo-updates'
|
|
2
|
+
import { useEffect, useRef, useState } from 'react'
|
|
3
|
+
import { Alert, AppState, AppStateStatus, Platform } from 'react-native'
|
|
4
|
+
|
|
5
|
+
import { checkForUpdate } from './checkForUpdate'
|
|
6
|
+
import { getUpdateConfirmation } from './getUpdateConfirmation'
|
|
7
|
+
import { UpdateManifest } from './types'
|
|
8
|
+
|
|
9
|
+
export interface UseUpdaterOptions {
|
|
10
|
+
autoCheck?: boolean
|
|
11
|
+
onConfirm?: (manifest: UpdateManifest) => Promise<boolean>
|
|
12
|
+
onError?: (message: string) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UseUpdaterReturn {
|
|
16
|
+
check: () => Promise<void>
|
|
17
|
+
checking: boolean
|
|
18
|
+
updateReady: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const isUnsupported = () => __DEV__ || Platform.OS === 'web'
|
|
22
|
+
|
|
23
|
+
export const useUpdater = (options: UseUpdaterOptions = {}): UseUpdaterReturn => {
|
|
24
|
+
const { autoCheck = true, onConfirm, onError } = options
|
|
25
|
+
const [checking, setChecking] = useState(false)
|
|
26
|
+
const [updateReady, setUpdateReady] = useState(false)
|
|
27
|
+
const checkingRef = useRef(false)
|
|
28
|
+
const appState = useRef(AppState.currentState)
|
|
29
|
+
const stagedManifest = useRef<UpdateManifest | null>(null)
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!autoCheck || isUnsupported()) return
|
|
33
|
+
|
|
34
|
+
const subscription = AppState.addEventListener('change', (nextState: AppStateStatus) => {
|
|
35
|
+
if (/inactive|background/.test(appState.current) && nextState === 'active') {
|
|
36
|
+
checkForUpdate()
|
|
37
|
+
.then((manifest) => {
|
|
38
|
+
if (manifest) {
|
|
39
|
+
stagedManifest.current = manifest
|
|
40
|
+
setUpdateReady(true)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
.catch(() => {})
|
|
44
|
+
}
|
|
45
|
+
appState.current = nextState
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return () => subscription.remove()
|
|
49
|
+
}, [autoCheck])
|
|
50
|
+
|
|
51
|
+
const check = async (): Promise<void> => {
|
|
52
|
+
if (__DEV__) {
|
|
53
|
+
Alert.alert('Updates unavailable', 'Update checks are disabled in development mode.')
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
if (Platform.OS === 'web') {
|
|
57
|
+
Alert.alert('Updates unavailable', 'Update checks are not supported on web.')
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
if (checkingRef.current) return
|
|
61
|
+
checkingRef.current = true
|
|
62
|
+
setChecking(true)
|
|
63
|
+
try {
|
|
64
|
+
const manifest = stagedManifest.current ?? (await checkForUpdate())
|
|
65
|
+
if (!manifest) {
|
|
66
|
+
Alert.alert('No update', 'You are on the most recent version.')
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
const confirmFn = onConfirm ?? getUpdateConfirmation
|
|
70
|
+
const confirmed = await confirmFn(manifest)
|
|
71
|
+
if (!confirmed) return
|
|
72
|
+
await reloadAsync()
|
|
73
|
+
} catch (err) {
|
|
74
|
+
const message = err instanceof Error ? err.message : 'Could not check for updates.'
|
|
75
|
+
if (onError) onError(message)
|
|
76
|
+
else Alert.alert('Update error', message)
|
|
77
|
+
} finally {
|
|
78
|
+
stagedManifest.current = null
|
|
79
|
+
setUpdateReady(false)
|
|
80
|
+
checkingRef.current = false
|
|
81
|
+
setChecking(false)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { check, checking, updateReady }
|
|
86
|
+
}
|