UNPKG

@trezor/transport

Version:

Low level library facilitating protocol buffers based communication with Trezor devices

207 lines 7.96 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SessionsBackground = void 0; const tslib_1 = require("tslib"); const utils_1 = require("@trezor/utils"); const ERRORS = tslib_1.__importStar(require("../errors")); const types_1 = require("../types"); const lockDuration = 1000 * 4; class SessionsBackground extends utils_1.TypedEmitter { descriptors = {}; pathInternalPathPublicMap = {}; locksQueue = []; locksTimeoutQueue = []; lastSessionId = 0; lastPathId = 0; async handleMessage(message) { let result; try { switch (message.type) { case 'handshake': result = this.handshake(); break; case 'enumerateDone': result = await this.enumerateDone(message.payload); break; case 'acquireIntent': result = await this.acquireIntent(message.payload); break; case 'acquireDone': result = await this.acquireDone(message.payload); break; case 'getSessions': result = await this.getSessions(); break; case 'releaseIntent': result = await this.releaseIntent(message.payload); break; case 'releaseDone': result = await this.releaseDone(message.payload); break; case 'getPathBySession': result = this.getPathBySession(message.payload); break; case 'dispose': this.dispose(); break; default: throw new Error(ERRORS.UNEXPECTED_ERROR); } result = JSON.parse(JSON.stringify({ ...result, id: message.id })); return result; } catch (err) { console.error('Session background error', err); return { ...this.error(ERRORS.UNEXPECTED_ERROR), id: message.type, }; } finally { if (result && result.success && result.payload) { if ('descriptors' in result.payload) { const { descriptors } = result.payload; this.emit('descriptors', descriptors); } if ('releaseRequest' in result.payload && result.payload.releaseRequest) { const { releaseRequest } = result.payload; this.emit('releaseRequest', releaseRequest); } } } } handshake() { return this.success(undefined); } enumerateDone(payload) { const disconnectedDevices = (0, utils_1.typedObjectKeys)(this.descriptors).filter(pathInternal => !payload.descriptors.find(d => d.path === pathInternal)); disconnectedDevices.forEach(d => { delete this.descriptors[d]; delete this.pathInternalPathPublicMap[d]; }); payload.descriptors.forEach(d => { if (!this.pathInternalPathPublicMap[d.path]) { this.pathInternalPathPublicMap[d.path] = (0, types_1.PathPublic)(`${(this.lastPathId += 1)}`); } if (!this.descriptors[d.path]) { this.descriptors[d.path] = { ...d, path: this.pathInternalPathPublicMap[d.path], session: null, }; } }); return Promise.resolve(this.success({ descriptors: Object.values(this.descriptors) })); } async acquireIntent(payload) { const pathInternal = this.getInternal(payload.path); if (!pathInternal) { return this.error(ERRORS.DEVICE_NOT_FOUND); } const previous = this.descriptors[pathInternal]; if (!previous) { return this.error(ERRORS.DEVICE_NOT_FOUND); } if (payload.previous !== previous.session) { return this.error(ERRORS.SESSION_WRONG_PREVIOUS); } await this.waitInQueue(); if (previous.session !== this.descriptors[pathInternal]?.session) { this.clearLock(); return this.error(ERRORS.SESSION_WRONG_PREVIOUS); } this.lastSessionId++; const session = (0, types_1.Session)(`${this.lastSessionId}`); const releaseRequest = previous.session !== null ? this.descriptors[pathInternal] : undefined; return this.success({ session, path: pathInternal, releaseRequest }); } acquireDone(payload) { this.clearLock(); const pathInternal = this.getInternal(payload.path); if (!pathInternal || !this.descriptors[pathInternal]) { return this.error(ERRORS.DEVICE_NOT_FOUND); } this.descriptors[pathInternal].session = (0, types_1.Session)(`${this.lastSessionId}`); this.descriptors[pathInternal].sessionOwner = payload.sessionOwner; return Promise.resolve(this.success({ descriptors: Object.values(this.descriptors) })); } async releaseIntent(payload) { const pathResult = this.getPathBySession({ session: payload.session }); if (!pathResult.success) { return pathResult; } const { path } = pathResult.payload; await this.waitInQueue(); return this.success({ path }); } releaseDone(payload) { this.descriptors[payload.path].session = null; this.descriptors[payload.path].sessionOwner = undefined; this.clearLock(); return Promise.resolve(this.success({ descriptors: Object.values(this.descriptors) })); } getSessions() { return Promise.resolve(this.success({ descriptors: Object.values(this.descriptors) })); } getPathBySession({ session }) { const path = (0, utils_1.typedObjectKeys)(this.descriptors).find(pathKey => this.descriptors[pathKey]?.session === session); if (!path) { return this.error(ERRORS.SESSION_NOT_FOUND); } return this.success({ path }); } startLock() { const dfd = (0, utils_1.createDeferred)(); const timeout = setTimeout(() => { dfd.resolve(undefined); }, lockDuration); this.locksQueue.push({ id: timeout, dfd }); this.locksTimeoutQueue.push(timeout); return this.locksQueue.length - 1; } clearLock() { const lock = this.locksQueue[0]; if (lock) { this.locksQueue[0].dfd.resolve(undefined); this.locksQueue.shift(); clearTimeout(this.locksTimeoutQueue[0]); this.locksTimeoutQueue.shift(); } } async waitForUnlocked(myIndex) { if (myIndex > 0) { const beforeMe = this.locksQueue.slice(0, myIndex); if (beforeMe.length) { await Promise.all(beforeMe.map(lock => lock.dfd.promise)); } } } async waitInQueue() { const myIndex = this.startLock(); await this.waitForUnlocked(myIndex); } success(payload) { return { success: true, payload, }; } error(error) { return { success: false, error, }; } getInternal(pathPublic) { return (0, utils_1.typedObjectKeys)(this.pathInternalPathPublicMap).find(internal => this.pathInternalPathPublicMap[internal] === pathPublic); } dispose() { this.locksQueue.forEach(lock => clearTimeout(lock.id)); this.locksTimeoutQueue.forEach(timeout => clearTimeout(timeout)); this.descriptors = {}; this.lastSessionId = 0; this.removeAllListeners(); } } exports.SessionsBackground = SessionsBackground; //# sourceMappingURL=background.js.map