UNPKG

@ledgerhq/live-common

Version:
395 lines • 15.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createAction = exports.currentMode = void 0; exports.setDeviceMode = setDeviceMode; exports.dependenciesToAppRequests = dependenciesToAppRequests; const invariant_1 = __importDefault(require("invariant")); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const react_1 = require("react"); const logs_1 = require("@ledgerhq/logs"); const derivation_1 = require("@ledgerhq/ledger-wallet-framework/derivation"); const observable_1 = require("../../observable"); const apps_1 = require("../../apps"); const account_1 = __importDefault(require("../../generated/account")); const implementations_1 = require("./implementations"); const accountName_1 = require("@ledgerhq/live-wallet/accountName"); const mapResult = ({ opened, device, appAndVersion, displayUpgradeWarning, }) => opened && device && !displayUpgradeWarning ? { device, appAndVersion, } : null; const getInitialState = (device, request) => ({ isLoading: !!device, requestQuitApp: false, requestOpenApp: null, unresponsive: false, isLocked: false, requiresAppInstallation: null, allowOpeningRequestedWording: null, allowOpeningGranted: false, allowManagerRequested: false, allowManagerGranted: false, device: null, deviceInfo: null, deviceId: null, latestFirmware: null, opened: false, appAndVersion: null, error: null, derivation: null, displayUpgradeWarning: false, installingApp: false, listingApps: false, request, currentAppOp: undefined, installQueue: [], listedApps: false, // Nb maybe expose the result skippedAppOps: [], itemProgress: 0, progress: undefined, }); const reducer = (state, e) => { switch (e.type) { case "deprecation": return { ...state, deviceDeprecationRules: e.deprecate }; case "unresponsiveDevice": return { ...state, unresponsive: true }; case "lockedDevice": return { ...state, isLocked: true }; // This event does not set isLocked and unresponsive properties, as // by itself it does not request anything from the device case "device-update-last-seen": return { ...state, deviceInfo: e.deviceInfo, latestFirmware: e.latestFirmware, }; case "disconnected": // disconnected event can happen for example: // - when the wired device is unplugged // - before a ask-open-app event when an other app is already open return { ...getInitialState(null, state.request), isLoading: !!e.expected, }; case "deviceChange": return { ...getInitialState(e.device, state.request), device: e.device }; case "some-apps-skipped": return { ...state, skippedAppOps: e.skippedAppOps, installQueue: state.installQueue, }; case "inline-install": return { ...getInitialState(state.device, state.request), isLoading: false, allowOpeningGranted: true, allowManagerRequested: false, allowManagerGranted: true, device: state.device, installingApp: true, progress: e.progress || 0, deviceInfo: undefined, latestFirmware: undefined, request: state.request, skippedAppOps: state.skippedAppOps, currentAppOp: e.currentAppOp, listedApps: state.listedApps, itemProgress: e.itemProgress || 0, installQueue: e.installQueue || [], }; case "listing-apps": return { ...state, listedApps: false, listingApps: true, unresponsive: false, isLocked: false, }; case "error": return { ...getInitialState(state.device, state.request), device: state.device || null, error: e.error, isLoading: false, listingApps: false, request: state.request, skippedAppOps: state.skippedAppOps, }; case "ask-open-app": return { ...getInitialState(state.device, state.request), isLoading: false, device: state.device, requestOpenApp: e.appName, deviceInfo: undefined, latestFirmware: undefined, installingApp: undefined, listingApps: undefined, installQueue: undefined, listedApps: undefined, itemProgress: undefined, skippedAppOps: state.skippedAppOps, }; case "ask-quit-app": return { ...getInitialState(state.device, state.request), isLoading: false, device: state.device, requestQuitApp: true, installingApp: undefined, listingApps: undefined, installQueue: undefined, listedApps: undefined, itemProgress: undefined, skippedAppOps: state.skippedAppOps, }; case "device-permission-requested": return { ...getInitialState(state.device, state.request), isLoading: false, device: state.device, allowManagerRequested: true, deviceInfo: undefined, latestFirmware: undefined, installingApp: undefined, listingApps: undefined, listedApps: undefined, itemProgress: undefined, skippedAppOps: state.skippedAppOps, installQueue: state.installQueue, }; case "device-permission-granted": return { ...getInitialState(state.device, state.request), isLoading: false, device: state.device, allowOpeningGranted: true, allowManagerGranted: true, deviceInfo: undefined, latestFirmware: undefined, installingApp: undefined, listingApps: undefined, itemProgress: undefined, skippedAppOps: state.skippedAppOps, installQueue: state.installQueue, listedApps: state.listedApps, }; case "device-id": return { ...state, deviceId: e.deviceId, }; case "app-not-installed": return { ...getInitialState(state.device, state.request), isLoading: false, device: state.device, requiresAppInstallation: { appNames: e.appNames, appName: e.appName, }, deviceInfo: undefined, latestFirmware: undefined, installingApp: undefined, listingApps: undefined, installQueue: undefined, listedApps: undefined, itemProgress: undefined, skippedAppOps: state.skippedAppOps, }; case "listed-apps": return { ...state, listedApps: true, installQueue: e.installQueue, }; case "opened": return { ...getInitialState(state.device, state.request), isLoading: false, device: state.device, opened: true, appAndVersion: e.app, derivation: e.derivation, deviceInfo: undefined, latestFirmware: undefined, installingApp: undefined, listingApps: undefined, installQueue: undefined, itemProgress: undefined, request: state.request, skippedAppOps: state.skippedAppOps, listedApps: state.listedApps, displayUpgradeWarning: state.device && e.app ? (0, apps_1.shouldUpgrade)(e.app.name, e.app.version) : false, }; } return state; }; /** * Map between an AppRequest and a ConnectAppRequest, allowing us to * specify an account or a currency without resolving manually the actual * applications we depend on in order to access the flow. */ function inferCommandParams(appRequest) { let derivationMode; let derivationPath; const { account, requireLatestFirmware, allowPartialDependencies = false, dependencies: appDependencies, } = appRequest; let { appName, currency } = appRequest; if (!currency && account) { currency = account.currency; } if (!appName && currency) { appName = currency.managerAppName; } (0, invariant_1.default)(appName, "appName or currency or account is missing"); let dependencies = undefined; if (appDependencies) { dependencies = appDependencies.map(d => inferCommandParams(d).appName); } if (!currency) { return { appName, dependencies, requireLatestFirmware, allowPartialDependencies, }; } let extra; if (account) { derivationMode = account.derivationMode; derivationPath = account.freshAddressPath; const m = account_1.default[account.currency.family]; if (m && m.injectGetAddressParams) { extra = m.injectGetAddressParams(account); } } else { const modes = (0, derivation_1.getDerivationModesForCurrency)(currency); derivationMode = modes[modes.length - 1]; derivationPath = (0, derivation_1.runDerivationScheme)((0, derivation_1.getDerivationScheme)({ currency, derivationMode, }), currency); } return { appName, dependencies, requireLatestFirmware, requiresDerivation: { derivationMode, path: derivationPath, currencyId: currency.id, ...extra, }, allowPartialDependencies, }; } exports.currentMode = "event"; function setDeviceMode(mode) { exports.currentMode = mode; } const createAction = (connectAppExec) => { const useHook = (device, appRequest) => { const dependenciesResolvedRef = (0, react_1.useRef)(false); const firmwareResolvedRef = (0, react_1.useRef)(false); const outdatedAppRef = (0, react_1.useRef)(undefined); const request = (0, react_1.useMemo)(() => inferCommandParams(appRequest), // for now i don't have better // eslint-disable-next-line react-hooks/exhaustive-deps [ appRequest.appName, appRequest.account?.id, appRequest.currency?.id, appRequest.dependencies, ]); const task = (0, react_1.useCallback)(({ deviceId, deviceName, request }) => { //To avoid redundant checks, we remove passed checks from the request. const { dependencies, requireLatestFirmware } = request; return connectAppExec({ deviceId, deviceName, request: { ...request, dependencies: dependenciesResolvedRef.current ? undefined : dependencies, requireLatestFirmware: firmwareResolvedRef.current ? undefined : requireLatestFirmware, outdatedApp: outdatedAppRef.current, }, }).pipe((0, operators_1.tap)(e => { // These events signal the resolution of pending checks. if (e.type === "dependencies-resolved") { dependenciesResolvedRef.current = true; } else if (e.type === "latest-firmware-resolved") { firmwareResolvedRef.current = true; } else if (e.type === "has-outdated-app") { outdatedAppRef.current = e.outdatedApp; } })); }, []); // repair modal will interrupt everything and be rendered instead of the background content const [state, setState] = (0, react_1.useState)(() => getInitialState(device)); const [resetIndex, setResetIndex] = (0, react_1.useState)(0); const deviceSubject = (0, observable_1.useReplaySubject)(device); (0, react_1.useEffect)(() => { if (state.opened) return; const impl = (0, implementations_1.getImplementation)(exports.currentMode)({ deviceSubject, task, request, }); const sub = impl .pipe((0, operators_1.tap)((e) => (0, logs_1.log)("actions-app-event", e.type, e)), (0, operators_1.debounce)((e) => ("replaceable" in e && e.replaceable ? (0, rxjs_1.interval)(100) : (0, rxjs_1.of)(null))), (0, operators_1.scan)(reducer, getInitialState()), (0, operators_1.takeWhile)((s) => !s.requiresAppInstallation && !s.error, true)) .subscribe(setState); return () => { sub.unsubscribe(); }; }, [deviceSubject, state.opened, resetIndex, task, request]); const onRetry = (0, react_1.useCallback)(() => { // After an error we can't guarantee resolutions. dependenciesResolvedRef.current = false; firmwareResolvedRef.current = false; // The nonce change triggers a refresh. setResetIndex(i => i + 1); setState(getInitialState(device)); }, [device]); const passWarning = (0, react_1.useCallback)(() => { setState(currState => ({ ...currState, displayUpgradeWarning: false, })); }, []); return { ...state, inWrongDeviceForAccount: state.derivation && appRequest.account ? state.derivation.address !== appRequest.account.freshAddress && state.derivation.address !== appRequest.account.seedIdentifier // Use-case added for Hedera ? { accountName: (0, accountName_1.getDefaultAccountName)(appRequest.account), } : null : null, onRetry, passWarning, }; }; return { useHook, mapResult, }; }; exports.createAction = createAction; function dependenciesToAppRequests(dependencies) { if (!dependencies) { return []; } return dependencies.map(appName => ({ appName })); } //# sourceMappingURL=app.js.map