@take-out/native-hot-update 0.0.36
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/README.md +171 -0
- package/dist/cjs/createHotUpdater.cjs +183 -0
- package/dist/cjs/createHotUpdater.js +146 -0
- package/dist/cjs/createHotUpdater.js.map +6 -0
- package/dist/cjs/createHotUpdater.native.js +213 -0
- package/dist/cjs/createHotUpdater.native.js.map +1 -0
- package/dist/cjs/index.cjs +26 -0
- package/dist/cjs/index.js +21 -0
- package/dist/cjs/index.js.map +6 -0
- package/dist/cjs/index.native.js +29 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/mmkv.cjs +32 -0
- package/dist/cjs/mmkv.js +27 -0
- package/dist/cjs/mmkv.js.map +6 -0
- package/dist/cjs/mmkv.native.js +41 -0
- package/dist/cjs/mmkv.native.js.map +1 -0
- package/dist/cjs/types.cjs +16 -0
- package/dist/cjs/types.js +14 -0
- package/dist/cjs/types.js.map +6 -0
- package/dist/cjs/types.native.js +19 -0
- package/dist/cjs/types.native.js.map +1 -0
- package/dist/esm/createHotUpdater.js +129 -0
- package/dist/esm/createHotUpdater.js.map +6 -0
- package/dist/esm/createHotUpdater.mjs +149 -0
- package/dist/esm/createHotUpdater.mjs.map +1 -0
- package/dist/esm/createHotUpdater.native.js +176 -0
- package/dist/esm/createHotUpdater.native.js.map +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +6 -0
- package/dist/esm/index.mjs +3 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +3 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/mmkv.js +11 -0
- package/dist/esm/mmkv.js.map +6 -0
- package/dist/esm/mmkv.mjs +9 -0
- package/dist/esm/mmkv.mjs.map +1 -0
- package/dist/esm/mmkv.native.js +15 -0
- package/dist/esm/mmkv.native.js.map +1 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/types.js.map +6 -0
- package/dist/esm/types.mjs +2 -0
- package/dist/esm/types.mjs.map +1 -0
- package/dist/esm/types.native.js +2 -0
- package/dist/esm/types.native.js.map +1 -0
- package/package.json +63 -0
- package/src/createHotUpdater.ts +240 -0
- package/src/index.ts +2 -0
- package/src/mmkv.ts +20 -0
- package/src/types.ts +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# @take-out/native-hot-update
|
|
2
|
+
|
|
3
|
+
Minimal, headless React Native hot update library.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Simple instance-based API
|
|
8
|
+
- Pluggable storage (MMKV, AsyncStorage, custom)
|
|
9
|
+
- Timeout protection (5s + 20s hard limit)
|
|
10
|
+
- Pre-release testing workflow
|
|
11
|
+
- Works with Expo & React Native
|
|
12
|
+
- Zero UI - bring your own components
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun add @take-out/native-hot-update react-native-mmkv
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### 1. Create Hot Updater Instance
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// src/hotUpdater.ts
|
|
26
|
+
import { MMKV } from 'react-native-mmkv'
|
|
27
|
+
import { createHotUpdater } from '@take-out/native-hot-update'
|
|
28
|
+
import { createMMKVStorage } from '@take-out/native-hot-update/mmkv'
|
|
29
|
+
|
|
30
|
+
const mmkv = new MMKV({ id: 'hot-updater' })
|
|
31
|
+
|
|
32
|
+
const hotUpdaterInstance = createHotUpdater({
|
|
33
|
+
serverUrl: 'https://your-update-server.com',
|
|
34
|
+
storage: createMMKVStorage(mmkv),
|
|
35
|
+
updateStrategy: 'appVersion',
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// Export the hook directly for convenience
|
|
39
|
+
export const useOtaUpdater = hotUpdaterInstance.useOtaUpdater
|
|
40
|
+
export const hotUpdater = hotUpdaterInstance
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Use in Your App
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// app/_layout.tsx
|
|
47
|
+
import { useOtaUpdater } from './hotUpdater'
|
|
48
|
+
|
|
49
|
+
export default function RootLayout() {
|
|
50
|
+
const { userClearedForAccess, progress } = useOtaUpdater({
|
|
51
|
+
enabled: true,
|
|
52
|
+
onUpdateDownloaded: (info) => {
|
|
53
|
+
console.info('Update downloaded:', info.id)
|
|
54
|
+
},
|
|
55
|
+
onError: (error) => {
|
|
56
|
+
console.error('Update failed:', error)
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
if (!userClearedForAccess) {
|
|
61
|
+
return <SplashScreen progress={progress} />
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return <YourApp />
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. Display Version Info
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { hotUpdater } from './hotUpdater'
|
|
72
|
+
import * as Application from 'expo-application'
|
|
73
|
+
|
|
74
|
+
export function VersionDisplay() {
|
|
75
|
+
const otaId = hotUpdater.getShortOtaId()
|
|
76
|
+
const channel = hotUpdater.getChannel()
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Text>
|
|
80
|
+
v{Application.nativeApplicationVersion}
|
|
81
|
+
{otaId && ` (${otaId})`} • {channel}
|
|
82
|
+
</Text>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 4. Dev Tools
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { hotUpdater } from './hotUpdater'
|
|
91
|
+
import { Button, View, Text, Alert } from 'react-native'
|
|
92
|
+
|
|
93
|
+
export function DevUpdateTools() {
|
|
94
|
+
const handleCheckUpdate = async () => {
|
|
95
|
+
const update = await hotUpdater.checkForUpdate()
|
|
96
|
+
if (update) {
|
|
97
|
+
Alert.alert('Update available', `ID: ${update.id}`, [
|
|
98
|
+
{ text: 'Later' },
|
|
99
|
+
{ text: 'Reload', onPress: () => hotUpdater.reload() },
|
|
100
|
+
])
|
|
101
|
+
} else {
|
|
102
|
+
Alert.alert('No update available')
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const handleCheckPreRelease = async () => {
|
|
107
|
+
const update = await hotUpdater.checkForUpdate({
|
|
108
|
+
channel: 'production-pre',
|
|
109
|
+
isPreRelease: true,
|
|
110
|
+
})
|
|
111
|
+
if (update) {
|
|
112
|
+
Alert.alert('Pre-release available', `ID: ${update.id}`, [
|
|
113
|
+
{ text: 'Later' },
|
|
114
|
+
{ text: 'Reload', onPress: () => hotUpdater.reload() },
|
|
115
|
+
])
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<View>
|
|
121
|
+
<Text>Current: {hotUpdater.getAppliedOta() || 'Native build'}</Text>
|
|
122
|
+
<Text>Channel: {hotUpdater.getChannel()}</Text>
|
|
123
|
+
<Button title="Check for Update" onPress={handleCheckUpdate} />
|
|
124
|
+
<Button title="Check Pre-release" onPress={handleCheckPreRelease} />
|
|
125
|
+
<Button title="Reload App" onPress={() => hotUpdater.reload()} />
|
|
126
|
+
</View>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## API
|
|
132
|
+
|
|
133
|
+
### `createHotUpdater(config)`
|
|
134
|
+
|
|
135
|
+
Creates a hot updater instance.
|
|
136
|
+
|
|
137
|
+
**Config:**
|
|
138
|
+
|
|
139
|
+
- `serverUrl` (string): Your hot update server URL
|
|
140
|
+
- `storage` (HotUpdateStorage): Storage adapter for persisting state
|
|
141
|
+
- `updateStrategy` ('appVersion' | 'fingerprint'): Update strategy (default:
|
|
142
|
+
'appVersion')
|
|
143
|
+
|
|
144
|
+
**Returns:** `HotUpdaterInstance`
|
|
145
|
+
|
|
146
|
+
### `useOtaUpdater(options?)`
|
|
147
|
+
|
|
148
|
+
React hook for automatic update checking.
|
|
149
|
+
|
|
150
|
+
**Options:**
|
|
151
|
+
|
|
152
|
+
- `enabled` (boolean): Enable/disable update checking (default: true)
|
|
153
|
+
- `onUpdateDownloaded` (function): Callback when update is downloaded
|
|
154
|
+
- `onError` (function): Callback on error
|
|
155
|
+
|
|
156
|
+
**Returns:**
|
|
157
|
+
|
|
158
|
+
- `userClearedForAccess` (boolean): Whether user can access app
|
|
159
|
+
- `progress` (number): Download progress (0-100)
|
|
160
|
+
- `isUpdatePending` (boolean): Whether update will apply on restart
|
|
161
|
+
|
|
162
|
+
### Instance Methods
|
|
163
|
+
|
|
164
|
+
- `checkForUpdate(options?)` - Manually check for updates
|
|
165
|
+
- `getAppliedOta()` - Get current OTA bundle ID
|
|
166
|
+
- `getShortOtaId()` - Get short OTA ID
|
|
167
|
+
- `getIsUpdatePending()` - Check if update is pending
|
|
168
|
+
- `reload()` - Reload app
|
|
169
|
+
- `getBundleId()` - Get current bundle ID
|
|
170
|
+
- `getMinBundleId()` - Get minimum bundle ID
|
|
171
|
+
- `getChannel()` - Get current channel
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf,
|
|
6
|
+
__hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all) __defProp(target, name, {
|
|
9
|
+
get: all[name],
|
|
10
|
+
enumerable: !0
|
|
11
|
+
});
|
|
12
|
+
},
|
|
13
|
+
__copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from == "object" || typeof from == "function") for (let key of __getOwnPropNames(from)) !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, {
|
|
15
|
+
get: () => from[key],
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
26
|
+
value: mod,
|
|
27
|
+
enumerable: !0
|
|
28
|
+
}) : target, mod)),
|
|
29
|
+
__toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
|
|
30
|
+
value: !0
|
|
31
|
+
}), mod);
|
|
32
|
+
var createHotUpdater_exports = {};
|
|
33
|
+
__export(createHotUpdater_exports, {
|
|
34
|
+
createHotUpdater: () => createHotUpdater
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(createHotUpdater_exports);
|
|
37
|
+
var import_react_native = require("@hot-updater/react-native"),
|
|
38
|
+
Application = __toESM(require("expo-application"), 1),
|
|
39
|
+
import_react = require("react"),
|
|
40
|
+
import_react_native2 = require("react-native-web");
|
|
41
|
+
const INITIAL_OTA_ID = "00000000-0000-0000-0000-000000000000",
|
|
42
|
+
BUNDLE_ID_KEY_PREFIX = "hotUpdater.bundleId",
|
|
43
|
+
PRE_RELEASE_BUNDLE_ID_KEY = "hotUpdater.preReleaseBundleId";
|
|
44
|
+
function createHotUpdater(config) {
|
|
45
|
+
const {
|
|
46
|
+
serverUrl,
|
|
47
|
+
storage,
|
|
48
|
+
updateStrategy = "appVersion"
|
|
49
|
+
} = config;
|
|
50
|
+
let isUpdatePending = !1;
|
|
51
|
+
const getAppliedOta = () => {
|
|
52
|
+
const id = import_react_native.HotUpdater.getBundleId();
|
|
53
|
+
return id === INITIAL_OTA_ID || id === import_react_native.HotUpdater.getMinBundleId() ? null : id;
|
|
54
|
+
},
|
|
55
|
+
getShortOtaId = () => {
|
|
56
|
+
const fullId = getAppliedOta();
|
|
57
|
+
return fullId ? fullId.slice(24) : null;
|
|
58
|
+
},
|
|
59
|
+
getIsUpdatePending = () => isUpdatePending,
|
|
60
|
+
getPreReleaseBundleId = () => storage.get(PRE_RELEASE_BUNDLE_ID_KEY),
|
|
61
|
+
handleUpdateDownloaded = (id, options = {}) => {
|
|
62
|
+
const appVersion = Application.nativeApplicationVersion;
|
|
63
|
+
appVersion && storage.set(`${BUNDLE_ID_KEY_PREFIX}.${appVersion}`, id), options.isPreRelease ? storage.set(PRE_RELEASE_BUNDLE_ID_KEY, id) : storage.delete(PRE_RELEASE_BUNDLE_ID_KEY), isUpdatePending = !0;
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
useOtaUpdater: (options = {}) => {
|
|
67
|
+
const {
|
|
68
|
+
enabled = !0,
|
|
69
|
+
onUpdateDownloaded: onUpdateDownloadedCallback,
|
|
70
|
+
onError
|
|
71
|
+
} = options,
|
|
72
|
+
progress = (0, import_react_native.useHotUpdaterStore)(state => state.progress),
|
|
73
|
+
isUpdating = progress > 0.01,
|
|
74
|
+
[isUserClearedForAccess, setIsUserClearedForAccess] = (0, import_react.useState)(!1),
|
|
75
|
+
isUserClearedForAccessRef = (0, import_react.useRef)(isUserClearedForAccess);
|
|
76
|
+
return isUserClearedForAccessRef.current = isUserClearedForAccess, (0, import_react.useEffect)(() => {
|
|
77
|
+
if (!isUpdating) {
|
|
78
|
+
const timer = setTimeout(() => {
|
|
79
|
+
isUserClearedForAccessRef.current || setIsUserClearedForAccess(!0);
|
|
80
|
+
}, 5e3);
|
|
81
|
+
return () => clearTimeout(timer);
|
|
82
|
+
}
|
|
83
|
+
}, [isUpdating]), (0, import_react.useEffect)(() => {
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
isUserClearedForAccessRef.current || setIsUserClearedForAccess(!0);
|
|
86
|
+
}, 2e4);
|
|
87
|
+
return () => clearTimeout(timer);
|
|
88
|
+
}, []), (0, import_react.useEffect)(() => {
|
|
89
|
+
let shouldContinue = !0;
|
|
90
|
+
return (async () => {
|
|
91
|
+
try {
|
|
92
|
+
if (!enabled) {
|
|
93
|
+
setIsUserClearedForAccess(!0);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!shouldContinue) return;
|
|
97
|
+
const updateInfo = await import_react_native.HotUpdater.checkForUpdate({
|
|
98
|
+
source: (0, import_react_native.getUpdateSource)(serverUrl, {
|
|
99
|
+
updateStrategy
|
|
100
|
+
})
|
|
101
|
+
});
|
|
102
|
+
if (!updateInfo) {
|
|
103
|
+
setIsUserClearedForAccess(!0);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (!shouldContinue) return;
|
|
107
|
+
if (updateInfo.status === "ROLLBACK") {
|
|
108
|
+
const preReleaseBundleId = getPreReleaseBundleId(),
|
|
109
|
+
currentBundleId = import_react_native.HotUpdater.getBundleId();
|
|
110
|
+
if (preReleaseBundleId && preReleaseBundleId === currentBundleId && currentBundleId > updateInfo.id) {
|
|
111
|
+
import_react_native2.Alert.alert("Update Skipped", "Skipped rollback because you are using a newer pre-release bundle."), setIsUserClearedForAccess(!0);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (updateInfo.shouldForceUpdate || setIsUserClearedForAccess(!0), !shouldContinue) return;
|
|
116
|
+
await updateInfo.updateBundle(), handleUpdateDownloaded(updateInfo.id);
|
|
117
|
+
const info = {
|
|
118
|
+
id: updateInfo.id,
|
|
119
|
+
isCriticalUpdate: updateInfo.shouldForceUpdate,
|
|
120
|
+
fileUrl: updateInfo.fileUrl,
|
|
121
|
+
message: updateInfo.message
|
|
122
|
+
};
|
|
123
|
+
if (onUpdateDownloadedCallback?.(info), !shouldContinue) return;
|
|
124
|
+
if (updateInfo.shouldForceUpdate) {
|
|
125
|
+
if (!isUserClearedForAccessRef.current) {
|
|
126
|
+
import_react_native.HotUpdater.reload();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
import_react_native2.Alert.alert("Update Downloaded", "An important update has been downloaded. Reload now to apply it?", [{
|
|
130
|
+
text: "Later",
|
|
131
|
+
style: "cancel"
|
|
132
|
+
}, {
|
|
133
|
+
text: "Reload Now",
|
|
134
|
+
onPress: () => import_react_native.HotUpdater.reload()
|
|
135
|
+
}]);
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
onError?.(error), setIsUserClearedForAccess(!0);
|
|
139
|
+
} finally {
|
|
140
|
+
isUserClearedForAccessRef.current || setIsUserClearedForAccess(!0);
|
|
141
|
+
}
|
|
142
|
+
})(), () => {
|
|
143
|
+
shouldContinue = !1;
|
|
144
|
+
};
|
|
145
|
+
}, [enabled, onUpdateDownloadedCallback, onError]), {
|
|
146
|
+
userClearedForAccess: isUserClearedForAccess,
|
|
147
|
+
progress,
|
|
148
|
+
isUpdatePending: getIsUpdatePending()
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
checkForUpdate: async (options = {}) => {
|
|
152
|
+
const {
|
|
153
|
+
channel,
|
|
154
|
+
isPreRelease = !1
|
|
155
|
+
} = options,
|
|
156
|
+
updateSource = (0, import_react_native.getUpdateSource)(serverUrl, {
|
|
157
|
+
updateStrategy
|
|
158
|
+
}),
|
|
159
|
+
finalSource = channel ? params => updateSource({
|
|
160
|
+
...params,
|
|
161
|
+
channel
|
|
162
|
+
}) : updateSource,
|
|
163
|
+
updateInfo = await import_react_native.HotUpdater.checkForUpdate({
|
|
164
|
+
source: finalSource
|
|
165
|
+
});
|
|
166
|
+
return updateInfo ? (await updateInfo.updateBundle(), handleUpdateDownloaded(updateInfo.id, {
|
|
167
|
+
isPreRelease
|
|
168
|
+
}), {
|
|
169
|
+
id: updateInfo.id,
|
|
170
|
+
isCriticalUpdate: updateInfo.shouldForceUpdate,
|
|
171
|
+
fileUrl: updateInfo.fileUrl,
|
|
172
|
+
message: updateInfo.message
|
|
173
|
+
}) : null;
|
|
174
|
+
},
|
|
175
|
+
getAppliedOta,
|
|
176
|
+
getShortOtaId,
|
|
177
|
+
getIsUpdatePending,
|
|
178
|
+
reload: () => import_react_native.HotUpdater.reload(),
|
|
179
|
+
getBundleId: () => import_react_native.HotUpdater.getBundleId(),
|
|
180
|
+
getMinBundleId: () => import_react_native.HotUpdater.getMinBundleId(),
|
|
181
|
+
getChannel: () => import_react_native.HotUpdater.getChannel()
|
|
182
|
+
};
|
|
183
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf, __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: !0 });
|
|
9
|
+
}, __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from == "object" || typeof from == "function")
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
!__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
return to;
|
|
14
|
+
};
|
|
15
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
16
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
17
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
18
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
19
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
20
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target,
|
|
21
|
+
mod
|
|
22
|
+
)), __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: !0 }), mod);
|
|
23
|
+
var createHotUpdater_exports = {};
|
|
24
|
+
__export(createHotUpdater_exports, {
|
|
25
|
+
createHotUpdater: () => createHotUpdater
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(createHotUpdater_exports);
|
|
28
|
+
var import_react_native = require("@hot-updater/react-native"), Application = __toESM(require("expo-application"), 1), import_react = require("react"), import_react_native2 = require("react-native-web");
|
|
29
|
+
const INITIAL_OTA_ID = "00000000-0000-0000-0000-000000000000", BUNDLE_ID_KEY_PREFIX = "hotUpdater.bundleId", PRE_RELEASE_BUNDLE_ID_KEY = "hotUpdater.preReleaseBundleId";
|
|
30
|
+
function createHotUpdater(config) {
|
|
31
|
+
const { serverUrl, storage, updateStrategy = "appVersion" } = config;
|
|
32
|
+
let isUpdatePending = !1;
|
|
33
|
+
const getAppliedOta = () => {
|
|
34
|
+
const id = import_react_native.HotUpdater.getBundleId();
|
|
35
|
+
return id === INITIAL_OTA_ID || id === import_react_native.HotUpdater.getMinBundleId() ? null : id;
|
|
36
|
+
}, getShortOtaId = () => {
|
|
37
|
+
const fullId = getAppliedOta();
|
|
38
|
+
return fullId ? fullId.slice(24) : null;
|
|
39
|
+
}, getIsUpdatePending = () => isUpdatePending, getPreReleaseBundleId = () => storage.get(PRE_RELEASE_BUNDLE_ID_KEY), handleUpdateDownloaded = (id, options = {}) => {
|
|
40
|
+
const appVersion = Application.nativeApplicationVersion;
|
|
41
|
+
appVersion && storage.set(`${BUNDLE_ID_KEY_PREFIX}.${appVersion}`, id), options.isPreRelease ? storage.set(PRE_RELEASE_BUNDLE_ID_KEY, id) : storage.delete(PRE_RELEASE_BUNDLE_ID_KEY), isUpdatePending = !0;
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
useOtaUpdater: (options = {}) => {
|
|
45
|
+
const {
|
|
46
|
+
enabled = !0,
|
|
47
|
+
onUpdateDownloaded: onUpdateDownloadedCallback,
|
|
48
|
+
onError
|
|
49
|
+
} = options, progress = (0, import_react_native.useHotUpdaterStore)((state) => state.progress), isUpdating = progress > 0.01, [isUserClearedForAccess, setIsUserClearedForAccess] = (0, import_react.useState)(!1), isUserClearedForAccessRef = (0, import_react.useRef)(isUserClearedForAccess);
|
|
50
|
+
return isUserClearedForAccessRef.current = isUserClearedForAccess, (0, import_react.useEffect)(() => {
|
|
51
|
+
if (!isUpdating) {
|
|
52
|
+
const timer = setTimeout(() => {
|
|
53
|
+
isUserClearedForAccessRef.current || setIsUserClearedForAccess(!0);
|
|
54
|
+
}, 5e3);
|
|
55
|
+
return () => clearTimeout(timer);
|
|
56
|
+
}
|
|
57
|
+
}, [isUpdating]), (0, import_react.useEffect)(() => {
|
|
58
|
+
const timer = setTimeout(() => {
|
|
59
|
+
isUserClearedForAccessRef.current || setIsUserClearedForAccess(!0);
|
|
60
|
+
}, 2e4);
|
|
61
|
+
return () => clearTimeout(timer);
|
|
62
|
+
}, []), (0, import_react.useEffect)(() => {
|
|
63
|
+
let shouldContinue = !0;
|
|
64
|
+
return (async () => {
|
|
65
|
+
try {
|
|
66
|
+
if (!enabled) {
|
|
67
|
+
setIsUserClearedForAccess(!0);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!shouldContinue) return;
|
|
71
|
+
const updateInfo = await import_react_native.HotUpdater.checkForUpdate({
|
|
72
|
+
source: (0, import_react_native.getUpdateSource)(serverUrl, { updateStrategy })
|
|
73
|
+
});
|
|
74
|
+
if (!updateInfo) {
|
|
75
|
+
setIsUserClearedForAccess(!0);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (!shouldContinue) return;
|
|
79
|
+
if (updateInfo.status === "ROLLBACK") {
|
|
80
|
+
const preReleaseBundleId = getPreReleaseBundleId(), currentBundleId = import_react_native.HotUpdater.getBundleId();
|
|
81
|
+
if (preReleaseBundleId && preReleaseBundleId === currentBundleId && currentBundleId > updateInfo.id) {
|
|
82
|
+
import_react_native2.Alert.alert(
|
|
83
|
+
"Update Skipped",
|
|
84
|
+
"Skipped rollback because you are using a newer pre-release bundle."
|
|
85
|
+
), setIsUserClearedForAccess(!0);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (updateInfo.shouldForceUpdate || setIsUserClearedForAccess(!0), !shouldContinue) return;
|
|
90
|
+
await updateInfo.updateBundle(), handleUpdateDownloaded(updateInfo.id);
|
|
91
|
+
const info = {
|
|
92
|
+
id: updateInfo.id,
|
|
93
|
+
isCriticalUpdate: updateInfo.shouldForceUpdate,
|
|
94
|
+
fileUrl: updateInfo.fileUrl,
|
|
95
|
+
message: updateInfo.message
|
|
96
|
+
};
|
|
97
|
+
if (onUpdateDownloadedCallback?.(info), !shouldContinue) return;
|
|
98
|
+
if (updateInfo.shouldForceUpdate) {
|
|
99
|
+
if (!isUserClearedForAccessRef.current) {
|
|
100
|
+
import_react_native.HotUpdater.reload();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
import_react_native2.Alert.alert(
|
|
104
|
+
"Update Downloaded",
|
|
105
|
+
"An important update has been downloaded. Reload now to apply it?",
|
|
106
|
+
[
|
|
107
|
+
{ text: "Later", style: "cancel" },
|
|
108
|
+
{ text: "Reload Now", onPress: () => import_react_native.HotUpdater.reload() }
|
|
109
|
+
]
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
onError?.(error), setIsUserClearedForAccess(!0);
|
|
114
|
+
} finally {
|
|
115
|
+
isUserClearedForAccessRef.current || setIsUserClearedForAccess(!0);
|
|
116
|
+
}
|
|
117
|
+
})(), () => {
|
|
118
|
+
shouldContinue = !1;
|
|
119
|
+
};
|
|
120
|
+
}, [enabled, onUpdateDownloadedCallback, onError]), {
|
|
121
|
+
userClearedForAccess: isUserClearedForAccess,
|
|
122
|
+
progress,
|
|
123
|
+
isUpdatePending: getIsUpdatePending()
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
checkForUpdate: async (options = {}) => {
|
|
127
|
+
const { channel, isPreRelease = !1 } = options, updateSource = (0, import_react_native.getUpdateSource)(serverUrl, { updateStrategy }), finalSource = channel ? (params) => updateSource({ ...params, channel }) : updateSource, updateInfo = await import_react_native.HotUpdater.checkForUpdate({
|
|
128
|
+
source: finalSource
|
|
129
|
+
});
|
|
130
|
+
return updateInfo ? (await updateInfo.updateBundle(), handleUpdateDownloaded(updateInfo.id, { isPreRelease }), {
|
|
131
|
+
id: updateInfo.id,
|
|
132
|
+
isCriticalUpdate: updateInfo.shouldForceUpdate,
|
|
133
|
+
fileUrl: updateInfo.fileUrl,
|
|
134
|
+
message: updateInfo.message
|
|
135
|
+
}) : null;
|
|
136
|
+
},
|
|
137
|
+
getAppliedOta,
|
|
138
|
+
getShortOtaId,
|
|
139
|
+
getIsUpdatePending,
|
|
140
|
+
reload: () => import_react_native.HotUpdater.reload(),
|
|
141
|
+
getBundleId: () => import_react_native.HotUpdater.getBundleId(),
|
|
142
|
+
getMinBundleId: () => import_react_native.HotUpdater.getMinBundleId(),
|
|
143
|
+
getChannel: () => import_react_native.HotUpdater.getChannel()
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=createHotUpdater.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/createHotUpdater.ts"],
|
|
4
|
+
"mappings": "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",
|
|
5
|
+
"names": ["import_react_native"]
|
|
6
|
+
}
|