UNPKG

@ledgerhq/live-common

Version:
842 lines • 36.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.useDisclaimerRaw = exports.useRecentlyUsed = exports.useCacheBustedLiveApps = exports.useLocalLiveApp = exports.useCategories = exports.ExchangeType = exports.useWalletAPIServer = exports.useConfig = exports.usePermission = exports.useGetAccountIds = exports.useManifestCurrencies = exports.useWalletAPICurrencies = exports.useWalletAPIAccounts = exports.safeGetRefValue = void 0; const react_1 = require("react"); const semver_1 = __importDefault(require("semver")); const date_fns_1 = require("date-fns"); const react_2 = require("@ledgerhq/wallet-api-server/lib/react"); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const converters_1 = require("./converters"); const helpers_1 = require("./helpers"); const account_1 = require("../account"); const currencies_1 = require("../currencies"); const logic_1 = require("./logic"); const bridge_1 = require("../bridge"); const live_env_1 = require("@ledgerhq/live-env"); const openTransportAsSubject_1 = __importDefault(require("../hw/openTransportAsSubject")); const errors_1 = require("@ledgerhq/errors"); const constants_1 = require("./constants"); function safeGetRefValue(ref) { if (!ref.current) { throw new Error("Ref objects doesn't have a current value"); } return ref.current; } exports.safeGetRefValue = safeGetRefValue; function useWalletAPIAccounts(walletState, accounts) { return (0, react_1.useMemo)(() => { return accounts.map(account => { const parentAccount = (0, account_1.getParentAccount)(account, accounts); return (0, converters_1.accountToWalletAPIAccount)(walletState, account, parentAccount); }); }, [walletState, accounts]); } exports.useWalletAPIAccounts = useWalletAPIAccounts; function useWalletAPICurrencies() { return (0, react_1.useMemo)(() => { return (0, currencies_1.listCurrencies)(true).reduce((filtered, currency) => { if ((0, helpers_1.isWalletAPISupportedCurrency)(currency)) { filtered.push((0, converters_1.currencyToWalletAPICurrency)(currency)); } return filtered; }, []); }, []); } exports.useWalletAPICurrencies = useWalletAPICurrencies; function useManifestCurrencies(manifest) { return (0, react_1.useMemo)(() => { return (manifest.dapp?.networks.map(network => { return (0, currencies_1.getCryptoCurrencyById)(network.currency); }) ?? []); }, [manifest.dapp?.networks]); } exports.useManifestCurrencies = useManifestCurrencies; function useGetAccountIds(accounts$) { const [accounts, setAccounts] = (0, react_1.useState)([]); (0, react_1.useEffect)(() => { if (!accounts$) { return undefined; } const subscription = accounts$.subscribe(walletAccounts => { setAccounts(walletAccounts); }); return () => { subscription.unsubscribe(); }; }, [accounts$]); return (0, react_1.useMemo)(() => { if (!accounts$) { return undefined; } return accounts.reduce((accountIds, account) => { accountIds.set((0, converters_1.getAccountIdFromWalletAccountId)(account.id), true); return accountIds; }, new Map()); }, [accounts, accounts$]); } exports.useGetAccountIds = useGetAccountIds; function usePermission(manifest) { return (0, react_1.useMemo)(() => ({ currencyIds: manifest.currencies === "*" ? ["**"] : manifest.currencies, methodIds: manifest.permissions, // TODO remove when using the correct manifest type }), [manifest]); } exports.usePermission = usePermission; function useTransport(postMessage) { return (0, react_1.useMemo)(() => { return { onMessage: undefined, send: postMessage, }; }, [postMessage]); } function useConfig({ appId, userId, tracking, wallet, mevProtected, }) { return (0, react_1.useMemo)(() => ({ appId, userId, tracking, wallet, mevProtected, }), [appId, tracking, userId, wallet]); } exports.useConfig = useConfig; function useDeviceTransport({ manifest, tracking }) { const ref = (0, react_1.useRef)(); const subscribe = (0, react_1.useCallback)((deviceId) => { ref.current = (0, openTransportAsSubject_1.default)({ deviceId }); ref.current.subscribe({ complete: () => { ref.current = undefined; }, }); }, []); const close = (0, react_1.useCallback)(() => { ref.current?.complete(); }, []); const exchange = (0, react_1.useCallback)(({ apduHex }) => { const subject$ = ref.current; return new Promise((resolve, reject) => { if (!subject$) { reject(new Error("No device transport")); return; } subject$.pipe((0, operators_1.first)(e => e.type === "device-response" || e.type === "error")).subscribe({ next: e => { if (e.type === "device-response") { tracking.deviceExchangeSuccess(manifest); resolve(e.data); return; } if (e.type === "error") { tracking.deviceExchangeFail(manifest); reject(e.error || new Error("deviceExchange: unknown error")); } }, error: error => { tracking.deviceExchangeFail(manifest); reject(error); }, }); subject$.next({ type: "input-frame", apduHex }); }); }, [manifest, tracking]); (0, react_1.useEffect)(() => { return () => { ref.current?.complete(); }; }, []); return (0, react_1.useMemo)(() => ({ ref, subscribe, close, exchange }), [close, exchange, subscribe]); } function useWalletAPIServer({ walletState, manifest, accounts, tracking, config, webviewHook, uiHook: { "account.request": uiAccountRequest, "account.receive": uiAccountReceive, "message.sign": uiMessageSign, "storage.get": uiStorageGet, "storage.set": uiStorageSet, "transaction.sign": uiTxSign, "transaction.signRaw": uiTxSignRaw, "transaction.broadcast": uiTxBroadcast, "device.transport": uiDeviceTransport, "device.select": uiDeviceSelect, "exchange.start": uiExchangeStart, "exchange.complete": uiExchangeComplete, }, customHandlers, }) { const permission = usePermission(manifest); const transport = useTransport(webviewHook.postMessage); const [widgetLoaded, setWidgetLoaded] = (0, react_1.useState)(false); const walletAPIAccounts = useWalletAPIAccounts(walletState, accounts); const walletAPICurrencies = useWalletAPICurrencies(); const { server, onMessage } = (0, react_2.useWalletAPIServer)({ transport, config, accounts: walletAPIAccounts, currencies: walletAPICurrencies, permission, customHandlers, }); (0, react_1.useEffect)(() => { tracking.load(manifest); }, [tracking, manifest]); (0, react_1.useEffect)(() => { if (!uiAccountRequest) return; server.setHandler("account.request", async ({ accounts$, currencies$, drawerConfiguration, areCurrenciesFiltered, useCase }) => { tracking.requestAccountRequested(manifest); const currencies = await (0, rxjs_1.firstValueFrom)(currencies$); return new Promise((resolve, reject) => { // handle no curencies selected case const currencyList = currencies.reduce((prev, { id }) => { const currency = (0, currencies_1.findCryptoCurrencyById)(id) || (0, currencies_1.findTokenById)(id); if (currency) { prev.push(currency); } return prev; }, []); let done = false; uiAccountRequest({ accounts$, currencies: currencyList, drawerConfiguration, areCurrenciesFiltered, useCase, onSuccess: (account, parentAccount) => { if (done) return; done = true; tracking.requestAccountSuccess(manifest); resolve((0, converters_1.accountToWalletAPIAccount)(walletState, account, parentAccount)); }, onCancel: () => { if (done) return; done = true; tracking.requestAccountFail(manifest); reject(new Error("Canceled by user")); }, }); }); }); }, [walletState, manifest, server, tracking, uiAccountRequest]); (0, react_1.useEffect)(() => { if (!uiAccountReceive) return; server.setHandler("account.receive", ({ account, tokenCurrency }) => (0, logic_1.receiveOnAccountLogic)(walletState, { manifest, accounts, tracking }, account.id, (account, parentAccount, accountAddress) => new Promise((resolve, reject) => { let done = false; return uiAccountReceive({ account, parentAccount, accountAddress, onSuccess: accountAddress => { if (done) return; done = true; tracking.receiveSuccess(manifest); resolve(accountAddress); }, onCancel: () => { if (done) return; done = true; tracking.receiveFail(manifest); reject(new Error("User cancelled")); }, onError: error => { if (done) return; done = true; tracking.receiveFail(manifest); reject(error); }, }); }), tokenCurrency)); }, [walletState, accounts, manifest, server, tracking, uiAccountReceive]); (0, react_1.useEffect)(() => { if (!uiMessageSign) return; server.setHandler("message.sign", ({ account, message, options }) => (0, logic_1.signMessageLogic)({ manifest, accounts, tracking }, account.id, message.toString("hex"), (account, message) => new Promise((resolve, reject) => { let done = false; return uiMessageSign({ account, message, options, onSuccess: signature => { if (done) return; done = true; tracking.signMessageSuccess(manifest); resolve(signature.startsWith("0x") ? Buffer.from(signature.replace("0x", ""), "hex") : Buffer.from(signature)); }, onCancel: () => { if (done) return; done = true; tracking.signMessageFail(manifest); reject(new errors_1.UserRefusedOnDevice()); }, onError: error => { if (done) return; done = true; tracking.signMessageFail(manifest); reject(error); }, }); }))); }, [accounts, manifest, server, tracking, uiMessageSign]); (0, react_1.useEffect)(() => { if (!uiStorageGet) return; server.setHandler("storage.get", uiStorageGet); }, [server, uiStorageGet]); (0, react_1.useEffect)(() => { if (!uiStorageSet) return; server.setHandler("storage.set", uiStorageSet); }, [server, uiStorageSet]); (0, react_1.useEffect)(() => { if (!uiTxSign) return; server.setHandler("transaction.sign", async ({ account, tokenCurrency, transaction, options }) => { const signedOperation = await (0, logic_1.signTransactionLogic)({ manifest, accounts, tracking }, account.id, transaction, (account, parentAccount, signFlowInfos) => new Promise((resolve, reject) => { let done = false; return uiTxSign({ account, parentAccount, signFlowInfos, options, onSuccess: signedOperation => { if (done) return; done = true; tracking.signTransactionSuccess(manifest); resolve(signedOperation); }, onError: error => { if (done) return; done = true; tracking.signTransactionFail(manifest); reject(error); }, }); }), tokenCurrency); return account.currency === "solana" ? Buffer.from(signedOperation.signature, "hex") : Buffer.from(signedOperation.signature); }); }, [accounts, manifest, server, tracking, uiTxSign]); (0, react_1.useEffect)(() => { if (!uiTxSignRaw) return; server.setHandler("transaction.signRaw", async ({ account, transaction, broadcast, options }) => { const signedOperation = await (0, logic_1.signRawTransactionLogic)({ manifest, accounts, tracking }, account.id, transaction, (account, parentAccount, tx) => new Promise((resolve, reject) => { let done = false; return uiTxSignRaw({ account, parentAccount, transaction: tx, options, onSuccess: signedOperation => { if (done) return; done = true; tracking.signRawTransactionSuccess(manifest); resolve(signedOperation); }, onError: error => { if (done) return; done = true; tracking.signRawTransactionFail(manifest); reject(error); }, }); })); let hash; if (broadcast) { hash = await (0, logic_1.broadcastTransactionLogic)({ manifest, accounts, tracking }, account.id, signedOperation, async (account, parentAccount, signedOperation) => { const bridge = (0, bridge_1.getAccountBridge)(account, parentAccount); const mainAccount = (0, account_1.getMainAccount)(account, parentAccount); let optimisticOperation = signedOperation.operation; if (!(0, live_env_1.getEnv)("DISABLE_TRANSACTION_BROADCAST")) { try { optimisticOperation = await bridge.broadcast({ account: mainAccount, signedOperation, broadcastConfig: { mevProtected: !!config.mevProtected }, }); tracking.broadcastSuccess(manifest); } catch (error) { tracking.broadcastFail(manifest); throw error; } } uiTxBroadcast && uiTxBroadcast(account, parentAccount, mainAccount, optimisticOperation); return optimisticOperation.hash; }); } return { signedTransactionHex: signedOperation.signature, transactionHash: hash, }; }); }, [accounts, config.mevProtected, manifest, server, tracking, uiTxBroadcast, uiTxSignRaw]); (0, react_1.useEffect)(() => { if (!uiTxSign) return; server.setHandler("transaction.signAndBroadcast", async ({ account, tokenCurrency, transaction, options }) => { const signedTransaction = await (0, logic_1.signTransactionLogic)({ manifest, accounts, tracking }, account.id, transaction, (account, parentAccount, signFlowInfos) => new Promise((resolve, reject) => { let done = false; return uiTxSign({ account, parentAccount, signFlowInfos, options, onSuccess: signedOperation => { if (done) return; done = true; tracking.signTransactionSuccess(manifest); resolve(signedOperation); }, onError: error => { if (done) return; done = true; tracking.signTransactionFail(manifest); reject(error); }, }); }), tokenCurrency); return (0, logic_1.broadcastTransactionLogic)({ manifest, accounts, tracking }, account.id, signedTransaction, async (account, parentAccount, signedOperation) => { const bridge = (0, bridge_1.getAccountBridge)(account, parentAccount); const mainAccount = (0, account_1.getMainAccount)(account, parentAccount); let optimisticOperation = signedOperation.operation; if (!(0, live_env_1.getEnv)("DISABLE_TRANSACTION_BROADCAST")) { try { optimisticOperation = await bridge.broadcast({ account: mainAccount, signedOperation, broadcastConfig: { mevProtected: !!config.mevProtected }, }); tracking.broadcastSuccess(manifest); } catch (error) { tracking.broadcastFail(manifest); throw error; } } uiTxBroadcast && uiTxBroadcast(account, parentAccount, mainAccount, optimisticOperation); return optimisticOperation.hash; }, tokenCurrency); }); }, [accounts, config.mevProtected, manifest, server, tracking, uiTxBroadcast, uiTxSign]); const onLoad = (0, react_1.useCallback)(() => { tracking.loadSuccess(manifest); setWidgetLoaded(true); }, [manifest, tracking]); const onReload = (0, react_1.useCallback)(() => { tracking.reload(manifest); setWidgetLoaded(false); webviewHook.reload(); }, [manifest, tracking, webviewHook]); const onLoadError = (0, react_1.useCallback)(() => { tracking.loadFail(manifest); }, [manifest, tracking]); const device = useDeviceTransport({ manifest, tracking }); (0, react_1.useEffect)(() => { if (!uiDeviceTransport) return; server.setHandler("device.transport", ({ appName, appVersionRange, devices }) => new Promise((resolve, reject) => { if (device.ref.current) { return reject(new Error("Device already opened")); } tracking.deviceTransportRequested(manifest); let done = false; return uiDeviceTransport({ appName, onSuccess: ({ device: deviceParam, appAndVersion }) => { if (done) return; done = true; tracking.deviceTransportSuccess(manifest); if (!deviceParam) { reject(new Error("No device")); return; } if (devices && !devices.includes(deviceParam.modelId)) { reject(new Error("Device not in the devices list")); return; } if (appVersionRange && appAndVersion && semver_1.default.satisfies(appAndVersion.version, appVersionRange)) { reject(new Error("App version doesn't satisfies the range")); return; } // TODO handle appFirmwareRange & seeded params device.subscribe(deviceParam.deviceId); resolve("1"); }, onCancel: () => { if (done) return; done = true; tracking.deviceTransportFail(manifest); reject(new Error("User cancelled")); }, }); })); }, [device, manifest, server, tracking, uiDeviceTransport]); (0, react_1.useEffect)(() => { if (!uiDeviceSelect) return; server.setHandler("device.select", ({ appName, appVersionRange, devices }) => new Promise((resolve, reject) => { if (device.ref.current) { return reject(new Error("Device already opened")); } tracking.deviceSelectRequested(manifest); let done = false; return uiDeviceSelect({ appName, onSuccess: ({ device: deviceParam, appAndVersion }) => { if (done) return; done = true; tracking.deviceSelectSuccess(manifest); if (!deviceParam) { reject(new Error("No device")); return; } if (devices && !devices.includes(deviceParam.modelId)) { reject(new Error("Device not in the devices list")); return; } if (appVersionRange && appAndVersion && semver_1.default.satisfies(appAndVersion.version, appVersionRange)) { reject(new Error("App version doesn't satisfies the range")); return; } resolve(deviceParam.deviceId); }, onCancel: () => { if (done) return; done = true; tracking.deviceSelectFail(manifest); reject(new Error("User cancelled")); }, }); })); }, [device.ref, manifest, server, tracking, uiDeviceSelect]); (0, react_1.useEffect)(() => { server.setHandler("device.open", params => { if (device.ref.current) { return Promise.reject(new Error("Device already opened")); } tracking.deviceOpenRequested(manifest); device.subscribe(params.deviceId); return "1"; }); }, [device, manifest, server, tracking]); (0, react_1.useEffect)(() => { server.setHandler("device.exchange", params => { if (!device.ref.current) { return Promise.reject(new Error("No device opened")); } tracking.deviceExchangeRequested(manifest); return device.exchange(params); }); }, [device, manifest, server, tracking]); (0, react_1.useEffect)(() => { server.setHandler("device.close", ({ transportId }) => { if (!device.ref.current) { return Promise.reject(new Error("No device opened")); } tracking.deviceCloseRequested(manifest); device.close(); tracking.deviceCloseSuccess(manifest); return Promise.resolve(transportId); }); }, [device, manifest, server, tracking]); (0, react_1.useEffect)(() => { server.setHandler("bitcoin.getAddress", ({ accountId, derivationPath }) => { return (0, logic_1.bitcoinFamilyAccountGetAddressLogic)({ manifest, accounts, tracking }, accountId, derivationPath); }); }, [accounts, manifest, server, tracking]); (0, react_1.useEffect)(() => { server.setHandler("bitcoin.getPublicKey", ({ accountId, derivationPath }) => { return (0, logic_1.bitcoinFamilyAccountGetPublicKeyLogic)({ manifest, accounts, tracking }, accountId, derivationPath); }); }, [accounts, manifest, server, tracking]); (0, react_1.useEffect)(() => { server.setHandler("bitcoin.getXPub", ({ accountId }) => { return (0, logic_1.bitcoinFamilyAccountGetXPubLogic)({ manifest, accounts, tracking }, accountId); }); }, [accounts, manifest, server, tracking]); (0, react_1.useEffect)(() => { if (!uiExchangeStart) { return; } server.setHandler("exchange.start", ({ exchangeType }) => { return (0, logic_1.startExchangeLogic)({ manifest, accounts, tracking }, exchangeType, exchangeType => new Promise((resolve, reject) => { let done = false; return uiExchangeStart({ exchangeType, onSuccess: (nonce) => { if (done) return; done = true; tracking.startExchangeSuccess(manifest); resolve(nonce); }, onCancel: error => { if (done) return; done = true; tracking.completeExchangeFail(manifest); reject(error); }, }); })); }); }, [uiExchangeStart, accounts, manifest, server, tracking]); (0, react_1.useEffect)(() => { if (!uiExchangeComplete) { return; } server.setHandler("exchange.complete", params => { // retrofit of the exchange params to fit the old platform spec const request = { provider: params.provider, fromAccountId: params.fromAccount.id, toAccountId: params.exchangeType === "SWAP" ? params.toAccount.id : undefined, transaction: params.transaction, binaryPayload: params.binaryPayload.toString("hex"), signature: params.signature.toString("hex"), feesStrategy: params.feeStrategy, exchangeType: ExchangeType[params.exchangeType], swapId: params.exchangeType === "SWAP" ? params.swapId : undefined, rate: params.exchangeType === "SWAP" ? params.rate : undefined, tokenCurrency: params.exchangeType !== "SELL" ? params.tokenCurrency : undefined, }; return (0, logic_1.completeExchangeLogic)({ manifest, accounts, tracking }, request, request => new Promise((resolve, reject) => { let done = false; return uiExchangeComplete({ exchangeParams: request, onSuccess: (hash) => { if (done) return; done = true; tracking.completeExchangeSuccess(manifest); resolve(hash); }, onCancel: error => { if (done) return; done = true; tracking.completeExchangeFail(manifest); reject(error); }, }); })); }); }, [uiExchangeComplete, accounts, manifest, server, tracking]); return { widgetLoaded, onMessage, onLoad, onReload, onLoadError, server, }; } exports.useWalletAPIServer = useWalletAPIServer; var ExchangeType; (function (ExchangeType) { ExchangeType[ExchangeType["SWAP"] = 0] = "SWAP"; ExchangeType[ExchangeType["SELL"] = 1] = "SELL"; ExchangeType[ExchangeType["FUND"] = 2] = "FUND"; ExchangeType[ExchangeType["SWAP_NG"] = 3] = "SWAP_NG"; ExchangeType[ExchangeType["SELL_NG"] = 4] = "SELL_NG"; ExchangeType[ExchangeType["FUND_NG"] = 5] = "FUND_NG"; })(ExchangeType || (exports.ExchangeType = ExchangeType = {})); function useCategories(manifests, initialCategory) { const [selected, setSelected] = (0, react_1.useState)(initialCategory || constants_1.DISCOVER_INITIAL_CATEGORY); const reset = (0, react_1.useCallback)(() => { setSelected(constants_1.DISCOVER_INITIAL_CATEGORY); }, []); const manifestsByCategories = (0, react_1.useMemo)(() => { const res = manifests.reduce((res, manifest) => { manifest.categories.forEach(category => { const list = res.has(category) ? [...res.get(category), manifest] : [manifest]; res.set(category, list); }); return res; }, new Map().set("all", manifests)); return res; }, [manifests]); const categories = (0, react_1.useMemo)(() => [...manifestsByCategories.keys()], [manifestsByCategories]); return (0, react_1.useMemo)(() => ({ categories, manifestsByCategories, selected, setSelected, reset, }), [categories, manifestsByCategories, selected, reset]); } exports.useCategories = useCategories; function useLocalLiveApp([LocalLiveAppDb, setState]) { (0, react_1.useEffect)(() => { if (LocalLiveAppDb === undefined) { setState(discoverDB => { return { ...discoverDB, localLiveApp: constants_1.INITIAL_PLATFORM_STATE.localLiveApp }; }); } }, [LocalLiveAppDb, setState]); const addLocalManifest = (0, react_1.useCallback)((newLocalManifest) => { setState(discoverDB => { const newLocalLiveAppList = discoverDB.localLiveApp?.filter(manifest => manifest.id !== newLocalManifest.id); newLocalLiveAppList.push(newLocalManifest); return { ...discoverDB, localLiveApp: newLocalLiveAppList }; }); }, [setState]); const removeLocalManifestById = (0, react_1.useCallback)((manifestId) => { setState(discoverDB => { const newLocalLiveAppList = discoverDB.localLiveApp.filter(manifest => manifest.id !== manifestId); return { ...discoverDB, localLiveApp: newLocalLiveAppList }; }); }, [setState]); const getLocalLiveAppManifestById = (0, react_1.useCallback)((manifestId) => { return LocalLiveAppDb.find(manifest => manifest.id === manifestId); }, [LocalLiveAppDb]); return { state: LocalLiveAppDb, addLocalManifest, removeLocalManifestById, getLocalLiveAppManifestById, }; } exports.useLocalLiveApp = useLocalLiveApp; function calculateTimeDiff(usedAt) { const start = new Date(); const end = new Date(usedAt); const interval = (0, date_fns_1.intervalToDuration)({ start, end }); const units = [ "years", "months", "weeks", "days", "hours", "minutes", "seconds", ]; let timeDiff = { unit: undefined, diff: 0 }; for (const unit of units) { if (interval[unit] > 0) { timeDiff = { unit, diff: interval[unit] }; break; } } return timeDiff; } function useCacheBustedLiveApps([cacheBustedLiveAppsDb, setState]) { const getLatest = (0, react_1.useCallback)((manifestId) => { return cacheBustedLiveAppsDb?.[manifestId]; }, [cacheBustedLiveAppsDb]); const edit = (0, react_1.useCallback)((manifestId, cacheBustingId) => { const _cacheBustedLiveAppsDb = { ...cacheBustedLiveAppsDb, [manifestId]: cacheBustingId, init: 1, }; setState(state => { const newstate = { ...state, cacheBustedLiveApps: _cacheBustedLiveAppsDb }; return newstate; }); }, [setState, cacheBustedLiveAppsDb]); return { getLatest, edit }; } exports.useCacheBustedLiveApps = useCacheBustedLiveApps; function useRecentlyUsed(manifests, [recentlyUsedManifestsDb, setState]) { const data = (0, react_1.useMemo)(() => recentlyUsedManifestsDb .map(recentlyUsed => { const res = manifests.find(manifest => manifest.id === recentlyUsed.id); return res ? { ...res, usedAt: calculateTimeDiff(recentlyUsed.usedAt), } : res; }) .filter(manifest => manifest !== undefined), [recentlyUsedManifestsDb, manifests]); const append = (0, react_1.useCallback)((manifest) => { setState(state => { const index = state.recentlyUsed.findIndex(({ id }) => id === manifest.id); // Manifest already in first position if (index === 0) { return { ...state, recentlyUsed: [ { ...state.recentlyUsed[0], usedAt: new Date().toISOString() }, ...state.recentlyUsed.slice(1), ], }; } // Manifest present we move it to the first position // No need to check for MAX_LENGTH as we only move it if (index !== -1) { return { ...state, recentlyUsed: [ { id: manifest.id, usedAt: new Date().toISOString() }, ...state.recentlyUsed.slice(0, index), ...state.recentlyUsed.slice(index + 1), ], }; } // Manifest not preset we simply append and check for the length return { ...state, recentlyUsed: state.recentlyUsed.length >= constants_1.MAX_RECENTLY_USED_LENGTH ? [ { id: manifest.id, usedAt: new Date().toISOString() }, ...state.recentlyUsed.slice(0, -1), ] : [{ id: manifest.id, usedAt: new Date().toISOString() }, ...state.recentlyUsed], }; }); }, [setState]); const clear = (0, react_1.useCallback)(() => { setState(state => ({ ...state, recentlyUsed: [] })); }, [setState]); return { data, append, clear }; } exports.useRecentlyUsed = useRecentlyUsed; function useDisclaimerRaw({ isReadOnly = false, isDismissed, uiHook, appendRecentlyUsed, }) { const onConfirm = (0, react_1.useCallback)((manifest, isChecked) => { if (!manifest) return; if (isChecked) { uiHook.dismiss(); } uiHook.close(); appendRecentlyUsed(manifest); uiHook.openApp(manifest); }, [uiHook, appendRecentlyUsed]); const onSelect = (0, react_1.useCallback)((manifest) => { if (manifest.branch === "soon") { return; } if (!isDismissed && !isReadOnly && manifest.author !== "ledger") { uiHook.prompt(manifest, onConfirm); } else { appendRecentlyUsed(manifest); uiHook.openApp(manifest); } }, [isReadOnly, isDismissed, uiHook, appendRecentlyUsed, onConfirm]); return { onSelect, onConfirm, }; } exports.useDisclaimerRaw = useDisclaimerRaw; //# sourceMappingURL=react.js.map