xref: /expo/packages/expo-updates/build/Updates.js (revision 57cc75f7)
1import { CodedError, NativeModulesProxy, UnavailabilityError } from 'expo-modules-core';
2import ExpoUpdates from './ExpoUpdates';
3/**
4 * The UUID that uniquely identifies the currently running update. The
5 * UUID is represented in its canonical string form (`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`) and
6 * will always use lowercase letters. This value is `null` when running in a local development environment or any other environment where `expo-updates` is disabled.
7 */
8export const updateId = ExpoUpdates.updateId && typeof ExpoUpdates.updateId === 'string'
9    ? ExpoUpdates.updateId.toLowerCase()
10    : null;
11/**
12 * The name of the release channel currently configured in this standalone or bare app when using
13 * classic updates. When using Expo Updates, the value of this field is always `"default"`.
14 */
15export const releaseChannel = ExpoUpdates.releaseChannel ?? 'default';
16/**
17 * The channel name of the current build, if configured for use with EAS Update. `null` otherwise.
18 *
19 * Expo Go and development builds are not set to a specific channel and can run any updates compatible with their native runtime. Therefore, this value will always be `null` when running an update on Expo Go or a development build.
20 */
21export const channel = ExpoUpdates.channel ?? null;
22/**
23 * The runtime version of the current build.
24 */
25export const runtimeVersion = ExpoUpdates.runtimeVersion ?? null;
26const _checkAutomaticallyMapNativeToJS = {
27    ALWAYS: 'ON_LOAD',
28    ERROR_RECOVERY_ONLY: 'ON_ERROR_RECOVERY',
29    NEVER: 'NEVER',
30    WIFI_ONLY: 'WIFI_ONLY',
31};
32/**
33 * Determines if and when expo-updates checks for and downloads updates automatically on startup.
34 */
35export const checkAutomatically = _checkAutomaticallyMapNativeToJS[ExpoUpdates.checkAutomatically] ?? null;
36// @docsMissing
37/**
38 * @hidden
39 */
40export const localAssets = ExpoUpdates.localAssets ?? {};
41/**
42 * `expo-updates` does its very best to always launch monotonically newer versions of your app so
43 * you don't need to worry about backwards compatibility when you put out an update. In very rare
44 * cases, it's possible that `expo-updates` may need to fall back to the update that's embedded in
45 * the app binary, even after newer updates have been downloaded and run (an "emergency launch").
46 * This boolean will be `true` if the app is launching under this fallback mechanism and `false`
47 * otherwise. If you are concerned about backwards compatibility of future updates to your app, you
48 * can use this constant to provide special behavior for this rare case.
49 */
50export const isEmergencyLaunch = ExpoUpdates.isEmergencyLaunch || false;
51/**
52 * This will be true if the currently running update is the one embedded in the build,
53 * and not one downloaded from the updates server.
54 */
55export const isEmbeddedLaunch = ExpoUpdates.isEmbeddedLaunch || false;
56// @docsMissing
57/**
58 * @hidden
59 */
60export const isUsingEmbeddedAssets = ExpoUpdates.isUsingEmbeddedAssets || false;
61/**
62 * If `expo-updates` is enabled, this is the
63 * [manifest](/versions/latest/sdk/constants/#manifest) (or
64 * [classic manifest](/versions/latest/sdk/constants/#appmanifest))
65 * object for the update that's currently running.
66 *
67 * In development mode, or any other environment in which `expo-updates` is disabled, this object is
68 * empty.
69 */
70export const manifest = (ExpoUpdates.manifestString ? JSON.parse(ExpoUpdates.manifestString) : ExpoUpdates.manifest) ??
71    {};
72/**
73 * If `expo-updates` is enabled, this is a `Date` object representing the creation time of the update that's currently running (whether it was embedded or downloaded at runtime).
74 *
75 * In development mode, or any other environment in which `expo-updates` is disabled, this value is
76 * null.
77 */
78export const createdAt = ExpoUpdates.commitTime
79    ? new Date(ExpoUpdates.commitTime)
80    : null;
81const isUsingDeveloperTool = !!manifest.developer?.tool;
82const isUsingExpoDevelopmentClient = NativeModulesProxy.ExponentConstants?.appOwnership === 'expo';
83const manualUpdatesInstructions = isUsingExpoDevelopmentClient
84    ? 'To test manual updates, publish your project using `expo publish` and open the published ' +
85        'version in this development client.'
86    : 'To test manual updates, make a release build with `npm run ios --configuration Release` or ' +
87        '`npm run android --variant Release`.';
88/**
89 * Instructs the app to reload using the most recently downloaded version. This is useful for
90 * triggering a newly downloaded update to launch without the user needing to manually restart the
91 * app.
92 *
93 * It is not recommended to place any meaningful logic after a call to `await
94 * Updates.reloadAsync()`. This is because the promise is resolved after verifying that the app can
95 * be reloaded, and immediately before posting an asynchronous task to the main thread to actually
96 * reload the app. It is unsafe to make any assumptions about whether any more JS code will be
97 * executed after the `Updates.reloadAsync` method call resolves, since that depends on the OS and
98 * the state of the native module and main threads.
99 *
100 * This method cannot be used in development mode, and the returned promise will be rejected if you
101 * try to do so.
102 *
103 * @return A promise that fulfills right before the reload instruction is sent to the JS runtime, or
104 * rejects if it cannot find a reference to the JS runtime. If the promise is rejected in production
105 * mode, it most likely means you have installed the module incorrectly. Double check you've
106 * followed the installation instructions. In particular, on iOS ensure that you set the `bridge`
107 * property on `EXUpdatesAppController` with a pointer to the `RCTBridge` you want to reload, and on
108 * Android ensure you either call `UpdatesController.initialize` with the instance of
109 * `ReactApplication` you want to reload, or call `UpdatesController.setReactNativeHost` with the
110 * proper instance of `ReactNativeHost`.
111 */
112export async function reloadAsync() {
113    if (!ExpoUpdates.reload) {
114        throw new UnavailabilityError('Updates', 'reloadAsync');
115    }
116    if (!ExpoUpdates?.nativeDebug && __DEV__ && !isUsingExpoDevelopmentClient) {
117        throw new CodedError('ERR_UPDATES_DISABLED', `You cannot use the Updates module in development mode in a production app. ${manualUpdatesInstructions}`);
118    }
119    await ExpoUpdates.reload();
120}
121/**
122 * Checks the server to see if a newly deployed update to your project is available. Does not
123 * actually download the update. This method cannot be used in development mode, and the returned
124 * promise will be rejected if you try to do so.
125 *
126 * Checking for an update uses a device's bandwidth and battery life like any network call.
127 * Additionally, updates served by Expo may be rate limited. A good rule of thumb to check for
128 * updates judiciously is to check when the user launches or foregrounds the app. Avoid polling for
129 * updates in a frequent loop.
130 *
131 * @return A promise that fulfills with an [`UpdateCheckResult`](#updatecheckresult) object.
132 *
133 * The promise rejects if the app is in development mode, or if there is an unexpected error or
134 * timeout communicating with the server.
135 */
136export async function checkForUpdateAsync() {
137    if (!ExpoUpdates.checkForUpdateAsync) {
138        throw new UnavailabilityError('Updates', 'checkForUpdateAsync');
139    }
140    if (!ExpoUpdates?.nativeDebug && (__DEV__ || isUsingDeveloperTool)) {
141        throw new CodedError('ERR_UPDATES_DISABLED', `You cannot check for updates in development mode. ${manualUpdatesInstructions}`);
142    }
143    const result = await ExpoUpdates.checkForUpdateAsync();
144    if (result.manifestString) {
145        result.manifest = JSON.parse(result.manifestString);
146        delete result.manifestString;
147    }
148    return result;
149}
150/**
151 * Retrieves the current extra params.
152 */
153export async function getExtraParamsAsync() {
154    if (!ExpoUpdates.getExtraParamsAsync) {
155        throw new UnavailabilityError('Updates', 'getExtraParamsAsync');
156    }
157    return await ExpoUpdates.getExtraParamsAsync();
158}
159/**
160 * Sets an extra param if value is non-null, otherwise unsets the param.
161 * Extra params are sent as an [Expo Structured Field Value Dictionary](https://docs.expo.dev/technical-specs/expo-sfv-0/)
162 * in the `Expo-Extra-Params` header of update requests. A compliant update server may use these params when selecting an update to serve.
163 */
164export async function setExtraParamAsync(key, value) {
165    if (!ExpoUpdates.setExtraParamAsync) {
166        throw new UnavailabilityError('Updates', 'setExtraParamAsync');
167    }
168    return await ExpoUpdates.setExtraParamAsync(key, value ?? null);
169}
170/**
171 * Retrieves the most recent expo-updates log entries.
172 *
173 * @param maxAge Sets the max age of retrieved log entries in milliseconds. Default to 3600000 ms (1 hour).
174 *
175 * @return A promise that fulfills with an array of [`UpdatesLogEntry`](#updateslogentry) objects;
176 *
177 * The promise rejects if there is an unexpected error in retrieving the logs.
178 */
179export async function readLogEntriesAsync(maxAge = 3600000) {
180    if (!ExpoUpdates.readLogEntriesAsync) {
181        throw new UnavailabilityError('Updates', 'readLogEntriesAsync');
182    }
183    return await ExpoUpdates.readLogEntriesAsync(maxAge);
184}
185/**
186 * Clears existing expo-updates log entries.
187 *
188 * > For now, this operation does nothing on the client.  Once log persistence has been
189 * > implemented, this operation will actually remove existing logs.
190 *
191 * @return A promise that fulfills if the clear operation was successful.
192 *
193 * The promise rejects if there is an unexpected error in clearing the logs.
194 *
195 */
196export async function clearLogEntriesAsync() {
197    if (!ExpoUpdates.clearLogEntriesAsync) {
198        throw new UnavailabilityError('Updates', 'clearLogEntriesAsync');
199    }
200    await ExpoUpdates.clearLogEntriesAsync();
201}
202/**
203 * Downloads the most recently deployed update to your project from server to the device's local
204 * storage. This method cannot be used in development mode, and the returned promise will be
205 * rejected if you try to do so.
206 *
207 > **Note:** [`reloadAsync()`](#updatesreloadasync) can be called after promise resolution to
208 * reload the app using the most recently downloaded version. Otherwise, the update will be applied
209 * on the next app cold start.
210 *
211 * @return A promise that fulfills with an [`UpdateFetchResult`](#updatefetchresult) object.
212 *
213 * The promise rejects if the app is in development mode, or if there is an unexpected error or
214 * timeout communicating with the server.
215 */
216export async function fetchUpdateAsync() {
217    if (!ExpoUpdates.fetchUpdateAsync) {
218        throw new UnavailabilityError('Updates', 'fetchUpdateAsync');
219    }
220    if (!ExpoUpdates?.nativeDebug && (__DEV__ || isUsingDeveloperTool)) {
221        throw new CodedError('ERR_UPDATES_DISABLED', `You cannot fetch updates in development mode. ${manualUpdatesInstructions}`);
222    }
223    const result = await ExpoUpdates.fetchUpdateAsync();
224    if (result.manifestString) {
225        result.manifest = JSON.parse(result.manifestString);
226        delete result.manifestString;
227    }
228    return result;
229}
230/**
231 * @hidden
232 */
233export function clearUpdateCacheExperimentalAsync(_sdkVersion) {
234    console.warn("This method is no longer necessary. `expo-updates` now automatically deletes your app's old bundle files!");
235}
236/**
237 * @hidden
238 */
239export function transformNativeStateMachineContext(originalNativeContext) {
240    const nativeContext = { ...originalNativeContext };
241    if (nativeContext.latestManifestString) {
242        nativeContext.latestManifest = JSON.parse(nativeContext.latestManifestString);
243        delete nativeContext.latestManifestString;
244    }
245    if (nativeContext.downloadedManifestString) {
246        nativeContext.downloadedManifest = JSON.parse(nativeContext.downloadedManifestString);
247        delete nativeContext.downloadedManifestString;
248    }
249    if (nativeContext.lastCheckForUpdateTimeString) {
250        nativeContext.lastCheckForUpdateTime = new Date(nativeContext.lastCheckForUpdateTimeString);
251        delete nativeContext.lastCheckForUpdateTimeString;
252    }
253    if (nativeContext.rollbackString) {
254        nativeContext.rollback = JSON.parse(nativeContext.rollbackString);
255        delete nativeContext.rollbackString;
256    }
257    return nativeContext;
258}
259/**
260 * @hidden
261 */
262export async function getNativeStateMachineContextAsync() {
263    // Return the current state machine context
264    if (!ExpoUpdates.getNativeStateMachineContextAsync) {
265        throw new UnavailabilityError('Updates', 'getNativeStateMachineContextAsync');
266    }
267    const nativeContext = await ExpoUpdates.getNativeStateMachineContextAsync();
268    return transformNativeStateMachineContext(nativeContext);
269}
270//# sourceMappingURL=Updates.js.map