UNPKG

@hot-updater/react-native

Version:

React Native OTA solution for self-hosted

255 lines (238 loc) 8.21 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HotUpdaterConstants = void 0; Object.defineProperty(exports, "HotUpdaterErrorCode", { enumerable: true, get: function () { return _error.HotUpdaterErrorCode; } }); exports.getMinBundleId = exports.getFingerprintHash = exports.getCrashHistory = exports.getChannel = exports.getBundleId = exports.getBaseURL = exports.getAppVersion = exports.clearCrashHistory = exports.addListener = void 0; Object.defineProperty(exports, "isHotUpdaterError", { enumerable: true, get: function () { return _error.isHotUpdaterError; } }); exports.reload = exports.notifyAppReady = void 0; exports.updateBundle = updateBundle; var _reactNative = require("react-native"); var _error = require("./error.js"); var _NativeHotUpdater = _interopRequireDefault(require("./specs/NativeHotUpdater.js")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const NIL_UUID = "00000000-0000-0000-0000-000000000000"; const HotUpdaterConstants = exports.HotUpdaterConstants = { HOT_UPDATER_BUNDLE_ID: __HOT_UPDATER_BUNDLE_ID || NIL_UUID }; const addListener = (eventName, listener) => { const eventEmitter = new _reactNative.NativeEventEmitter(_NativeHotUpdater.default); const subscription = eventEmitter.addListener(eventName, listener); return () => { subscription.remove(); }; }; exports.addListener = addListener; // In-flight update deduplication by bundleId (session-scoped). const inflightUpdates = new Map(); // Tracks the last successfully installed bundleId for this session. let lastInstalledBundleId = null; /** * Downloads files and applies them to the app. * * @param {UpdateParams} params - Parameters object required for bundle update * @returns {Promise<boolean>} Resolves with true if download was successful * @throws {Error} Rejects with error.code from HotUpdaterErrorCode enum and error.message */ /** * @deprecated Use updateBundle(params: UpdateBundleParamsWithStatus) instead */ async function updateBundle(paramsOrBundleId, fileUrl) { const updateBundleId = typeof paramsOrBundleId === "string" ? paramsOrBundleId : paramsOrBundleId.bundleId; const status = typeof paramsOrBundleId === "string" ? "UPDATE" : paramsOrBundleId.status; // If we have already installed this bundle in this session, skip re-download. if (status === "UPDATE" && lastInstalledBundleId === updateBundleId) { return true; } const currentBundleId = getBundleId(); // updateBundleId <= currentBundleId if (status === "UPDATE" && updateBundleId.localeCompare(currentBundleId) <= 0) { throw new Error("Update bundle id is the same as the current bundle id. Preventing infinite update loop."); } // In-flight guard: return the same promise if the same bundle is already updating. const existing = inflightUpdates.get(updateBundleId); if (existing) return existing; const targetFileUrl = typeof paramsOrBundleId === "string" ? fileUrl ?? null : paramsOrBundleId.fileUrl; const targetFileHash = typeof paramsOrBundleId === "string" ? undefined : paramsOrBundleId.fileHash; const promise = (async () => { try { const ok = await _NativeHotUpdater.default.updateBundle({ bundleId: updateBundleId, fileUrl: targetFileUrl, fileHash: targetFileHash ?? null }); if (ok) { lastInstalledBundleId = updateBundleId; } return ok; } finally { inflightUpdates.delete(updateBundleId); } })(); inflightUpdates.set(updateBundleId, promise); return promise; } /** * Fetches the current app version. */ const getAppVersion = () => { const constants = _NativeHotUpdater.default.getConstants(); return constants?.APP_VERSION ?? null; }; /** * Reloads the app. */ exports.getAppVersion = getAppVersion; const reload = async () => { await _NativeHotUpdater.default.reload(); }; /** * Fetches the minimum bundle id, which represents the initial bundle of the app * since it is created at build time. * * @returns {string} Resolves with the minimum bundle id or null if not available. */ exports.reload = reload; const getMinBundleId = () => { const constants = _NativeHotUpdater.default.getConstants(); return constants.MIN_BUNDLE_ID; }; /** * Fetches the current bundle version id. * * @async * @returns {string} Resolves with the current version id or null if not available. */ exports.getMinBundleId = getMinBundleId; const getBundleId = () => { return HotUpdaterConstants.HOT_UPDATER_BUNDLE_ID === NIL_UUID ? getMinBundleId() : HotUpdaterConstants.HOT_UPDATER_BUNDLE_ID; }; /** * Fetches the channel for the app. * * @returns {string} Resolves with the channel or null if not available. */ exports.getBundleId = getBundleId; const getChannel = () => { const constants = _NativeHotUpdater.default.getConstants(); return constants.CHANNEL; }; /** * Fetches the fingerprint for the app. * * @returns {string | null} Resolves with the fingerprint hash */ exports.getChannel = getChannel; const getFingerprintHash = () => { const constants = _NativeHotUpdater.default.getConstants(); return constants.FINGERPRINT_HASH; }; /** * Result returned by notifyAppReady() */ exports.getFingerprintHash = getFingerprintHash; /** * Notifies the native side that the app has successfully started with the current bundle. * If the bundle matches the staging bundle, it promotes to stable. * * This function is called automatically when the module loads. * * @returns {NotifyAppReadyResult} Bundle state information * - `status: "PROMOTED"` - Staging bundle was promoted to stable (ACTIVE event) * - `status: "RECOVERED"` - App recovered from crash, rollback occurred (ROLLBACK event) * - `status: "STABLE"` - No changes, already stable * - `crashedBundleId` - Present only when status is "RECOVERED" * * @example * ```ts * const result = HotUpdater.notifyAppReady(); * * switch (result.status) { * case "PROMOTED": * // Send ACTIVE analytics event * analytics.track('bundle_active', { bundleId: HotUpdater.getBundleId() }); * break; * case "RECOVERED": * // Send ROLLBACK analytics event * analytics.track('bundle_rollback', { crashedBundleId: result.crashedBundleId }); * break; * case "STABLE": * // No special action needed * break; * } * ``` */ const notifyAppReady = () => { const bundleId = getBundleId(); const result = _NativeHotUpdater.default.notifyAppReady({ bundleId }); // Oldarch returns JSON string, newarch returns array if (typeof result === "string") { try { return JSON.parse(result); } catch { return { status: "STABLE" }; } } return result; }; /** * Gets the list of bundle IDs that have been marked as crashed. * These bundles will be rejected if attempted to install again. * * @returns {string[]} Array of crashed bundle IDs */ exports.notifyAppReady = notifyAppReady; const getCrashHistory = () => { const result = _NativeHotUpdater.default.getCrashHistory(); // Oldarch returns JSON string, newarch returns array if (typeof result === "string") { try { return JSON.parse(result); } catch { return []; } } return result; }; /** * Clears the crashed bundle history, allowing previously crashed bundles * to be installed again. * * @returns {boolean} true if clearing was successful */ exports.getCrashHistory = getCrashHistory; const clearCrashHistory = () => { return _NativeHotUpdater.default.clearCrashHistory(); }; /** * Gets the base URL for the current active bundle directory. * Returns the file:// URL to the bundle directory without trailing slash. * This is used for Expo DOM components to construct full asset paths. * * @returns {string | null} Base URL string (e.g., "file:///data/.../bundle-store/abc123") or null if not available */ exports.clearCrashHistory = clearCrashHistory; const getBaseURL = () => { const result = _NativeHotUpdater.default.getBaseURL(); if (typeof result === "string" && result !== "") { return result; } return null; }; exports.getBaseURL = getBaseURL; //# sourceMappingURL=native.js.map