@trezor/transport
Version:
Low level library facilitating protocol buffers based communication with Trezor devices
284 lines • 11.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AbstractApiTransport = void 0;
const tslib_1 = require("tslib");
const protocol_1 = require("@trezor/protocol");
const abstract_1 = require("./abstract");
const constants_1 = require("../constants");
const ERRORS = tslib_1.__importStar(require("../errors"));
const background_1 = require("../sessions/background");
const client_1 = require("../sessions/client");
const thp_1 = require("../thp");
const receive_1 = require("../utils/receive");
const send_1 = require("../utils/send");
class AbstractApiTransport extends abstract_1.AbstractTransport {
sessionsClient;
sessionsBackground;
api;
constructor({ api, ...rest }) {
super(rest);
this.api = api;
this.sessionsBackground = new background_1.SessionsBackground();
this.sessionsClient = new client_1.SessionsClient(this.sessionsBackground);
}
init({ signal } = {}) {
return this.scheduleAction(async () => {
this.sessionsClient.setBackground(this.sessionsBackground);
const handshakeRes = await this.sessionsClient.handshake();
this.stopped = !handshakeRes.success;
return handshakeRes;
}, { signal });
}
listen() {
if (this.listening) {
return this.error({ error: ERRORS.ALREADY_LISTENING });
}
this.api.listen();
this.listening = true;
this.api.on('transport-interface-change', descriptors => {
this.logger?.debug('new descriptors from api', descriptors);
this.sessionsClient.enumerateDone({
descriptors,
});
});
this.sessionsClient.on('descriptors', descriptors => {
this.logger?.debug('new descriptors from background', descriptors);
this.handleDescriptorsChange(descriptors);
});
this.sessionsClient.on('releaseRequest', descriptor => {
this.deviceEvents.emit(descriptor.path, { type: constants_1.TRANSPORT.DEVICE_REQUEST_RELEASE });
});
return this.success(undefined);
}
enumerate({ signal } = {}) {
return this.scheduleAction(async (signal) => {
const enumerateResult = await this.api.enumerate(signal);
if (!enumerateResult.success) {
return enumerateResult;
}
const descriptors = enumerateResult.payload;
const enumerateDoneResponse = await this.sessionsClient.enumerateDone({
descriptors,
});
return this.success(enumerateDoneResponse.payload.descriptors);
}, { signal });
}
acquire({ input, signal }) {
return this.scheduleAction(async (signal) => {
const { path } = input;
const acquireIntentResponse = await this.sessionsClient.acquireIntent(input);
if (!acquireIntentResponse.success) {
return this.error({ error: acquireIntentResponse.error });
}
const reset = !!input.previous;
const openDeviceResult = await this.api.openDevice(acquireIntentResponse.payload.path, reset, signal);
if (!openDeviceResult.success) {
return openDeviceResult;
}
this.sessionsClient.acquireDone({ path, sessionOwner: this.id });
return this.success(acquireIntentResponse.payload.session);
}, { signal }, [ERRORS.DEVICE_DISCONNECTED_DURING_ACTION, ERRORS.SESSION_WRONG_PREVIOUS]);
}
release({ path: _, session, signal }) {
return this.scheduleAction(async () => {
const releaseIntentResponse = await this.sessionsClient.releaseIntent({
session,
});
if (!releaseIntentResponse.success) {
return this.error({ error: releaseIntentResponse.error });
}
await this.api.closeDevice(releaseIntentResponse.payload.path);
await this.sessionsClient.releaseDone({
path: releaseIntentResponse.payload.path,
});
return this.success(null);
}, { signal });
}
releaseSync(session) {
this.sessionsClient.releaseIntent({ session }).then(res => {
if (res.success)
this.api.closeDevice(res.payload.path);
});
}
call({ session, name, data, protocol: customProtocol, thpState, signal, timeout, }) {
return this.scheduleAction(async (signal) => {
const handleError = (error) => {
if (error === ERRORS.DEVICE_DISCONNECTED_DURING_ACTION) {
this.enumerate();
}
};
const getPathBySessionResponse = await this.sessionsClient.getPathBySession({
session,
});
if (!getPathBySessionResponse.success) {
if (getPathBySessionResponse.error === 'session not found') {
return this.error({ error: ERRORS.DEVICE_DISCONNECTED_DURING_ACTION });
}
return this.error({ error: ERRORS.UNEXPECTED_ERROR });
}
const { path } = getPathBySessionResponse.payload;
const protocol = customProtocol || protocol_1.v1;
const bytes = (0, send_1.buildMessage)({
messages: this.messages,
name,
data,
protocol,
thpState,
});
const [, chunkHeader] = protocol.getHeaders(bytes);
const chunks = (0, send_1.createChunks)(bytes, chunkHeader, this.api.chunkSize);
let progress = 0;
const apiWrite = (chunk, attemptSignal) => {
if (chunks.length > 1) {
progress++;
this.emit(constants_1.TRANSPORT.SEND_MESSAGE_PROGRESS, progress / chunks.length);
}
return this.api.write(path, chunk, attemptSignal || signal);
};
const apiRead = (attemptSignal) => this.api.read(path, attemptSignal || signal);
if (protocol.name === 'v2') {
const callResult = await (0, thp_1.callThpMessage)({
thpState,
chunks,
apiWrite,
apiRead,
signal,
logger: this.logger,
});
if (!callResult.success) {
handleError(callResult.error);
return callResult;
}
thpState?.sync('send', name);
const message = (0, thp_1.parseThpMessage)({
messages: this.messages,
decoded: callResult.payload,
thpState,
});
thpState?.sync('recv', message.type);
return this.success(message);
}
const sendResult = await (0, send_1.sendChunks)(chunks, apiWrite);
if (!sendResult.success) {
handleError(sendResult.error);
return sendResult;
}
const readResult = await (0, receive_1.receiveAndParse)(this.messages, apiRead, protocol);
if (!readResult.success) {
handleError(readResult.error);
return readResult;
}
return readResult;
}, { signal, timeout });
}
send({ data, session, name, protocol: customProtocol, thpState, signal, timeout, }) {
return this.scheduleAction(async (signal) => {
const getPathBySessionResponse = await this.sessionsClient.getPathBySession({
session,
});
if (!getPathBySessionResponse.success) {
return this.error({ error: getPathBySessionResponse.error });
}
const { path } = getPathBySessionResponse.payload;
const protocol = customProtocol || protocol_1.v1;
const bytes = (0, send_1.buildMessage)({
messages: this.messages,
name,
data,
protocol,
thpState,
});
const [_, chunkHeader] = protocol.getHeaders(bytes);
const chunks = (0, send_1.createChunks)(bytes, chunkHeader, this.api.chunkSize);
let progress = 0;
const apiWrite = (chunk) => {
if (chunks.length > 1) {
progress++;
this.emit(constants_1.TRANSPORT.SEND_MESSAGE_PROGRESS, progress / chunks.length);
}
return this.api.write(path, chunk, signal);
};
let sendResult;
if (protocol.name === 'v2') {
sendResult = await (0, thp_1.sendThpMessage)({
thpState,
skipAck: true,
chunks,
apiWrite,
apiRead: attemptSignal => this.api.read(path, attemptSignal || signal),
signal,
logger: this.logger,
});
}
else {
sendResult = await (0, send_1.sendChunks)(chunks, apiWrite);
}
if (!sendResult.success) {
if (sendResult.error === ERRORS.DEVICE_DISCONNECTED_DURING_ACTION) {
this.enumerate();
}
}
return sendResult;
}, { signal, timeout });
}
receive({ session, protocol: customProtocol, thpState, signal, timeout, }) {
return this.scheduleAction(async (signal) => {
const getPathBySessionResponse = await this.sessionsClient.getPathBySession({
session,
});
if (!getPathBySessionResponse.success) {
return this.error({ error: getPathBySessionResponse.error });
}
const { path } = getPathBySessionResponse.payload;
const apiRead = (attemptSignal) => this.api.read(path, attemptSignal || signal);
const protocol = customProtocol || protocol_1.v1;
if (protocol.name === 'v2') {
const decoded = await (0, thp_1.receiveThpMessage)({
thpState,
skipAck: true,
apiWrite: (chunk, attemptSignal) => this.api.write(path, chunk, attemptSignal || signal),
apiRead,
signal,
});
if (!decoded.success) {
return decoded;
}
const message = (0, thp_1.parseThpMessage)({
messages: this.messages,
decoded: decoded.payload,
thpState,
});
return this.success(message);
}
const message = await (0, receive_1.receiveAndParse)(this.messages, apiRead, protocol);
if (!message.success) {
if (message.error === ERRORS.DEVICE_DISCONNECTED_DURING_ACTION) {
this.enumerate();
}
}
return message;
}, { signal, timeout });
}
releaseDevice(session) {
return this.sessionsClient
.getPathBySession({
session,
})
.then(response => {
if (response.success) {
return this.api.closeDevice(response.payload.path);
}
return this.success(undefined);
});
}
stop() {
super.stop();
this.api.on('transport-interface-change', () => {
this.logger?.debug('device connected after transport stopped');
});
this.sessionsClient.dispose();
this.api.dispose();
}
}
exports.AbstractApiTransport = AbstractApiTransport;
//# sourceMappingURL=abstractApi.js.map