@trezor/transport
Version:
Low level library facilitating protocol buffers based communication with Trezor devices
272 lines • 10.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BridgeTransport = 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 receive_1 = require("../thp/receive");
const bridgeApiCall_1 = require("../utils/bridgeApiCall");
const bridgeApiResult = tslib_1.__importStar(require("../utils/bridgeApiResult"));
const bridgeProtocolMessage_1 = require("../utils/bridgeProtocolMessage");
const receive_2 = require("../utils/receive");
const send_1 = require("../utils/send");
const DEFAULT_URL = 'http://127.0.0.1:21325';
class BridgeTransport extends abstract_1.AbstractTransport {
useProtocolMessages = false;
url;
name = 'BridgeTransport';
apiType = 'usb';
constructor(params) {
const { url = DEFAULT_URL, ...rest } = params || {};
super(rest);
this.url = url;
}
ping({ signal } = {}) {
return this.scheduleAction(signal => this.post('/', { signal }), { signal })
.then(({ success }) => success)
.catch(() => false);
}
init({ signal } = {}) {
return this.scheduleAction(async (signal) => {
const response = await this.post('/', {
signal,
});
if (!response.success) {
return response;
}
this.version = response.payload.version;
if (this.version.startsWith('3')) {
this.isOutdated = false;
}
else {
this.isOutdated =
!['2.0.27', '2.0.33'].includes(this.version);
}
this.useProtocolMessages = !!response.payload.protocolMessages;
this.stopped = false;
return this.success(undefined);
}, { signal });
}
listen() {
if (this.listening) {
return this.error({ error: ERRORS.ALREADY_LISTENING });
}
this.listening = true;
this.listenLoop();
return this.success(undefined);
}
async listenLoop() {
while (!this.stopped) {
const response = await this.post('/listen', {
body: this.descriptors,
signal: this.abortController.signal,
});
if (!response.success) {
this.emit(constants_1.TRANSPORT.ERROR, response.error);
}
else {
this.handleDescriptorsChange(response.payload);
}
}
}
enumerate({ signal } = {}) {
return this.scheduleAction(signal => this.post('/enumerate', { signal }), { signal });
}
acquire({ input, signal }) {
return this.scheduleAction(async (signal) => {
const response = await this.post('/acquire', {
params: `${input.path}/${input.previous ?? 'null'}`,
signal,
body: {
sessionOwner: this.id,
},
});
return response;
}, { signal }, [ERRORS.DEVICE_DISCONNECTED_DURING_ACTION, ERRORS.SESSION_WRONG_PREVIOUS]);
}
release({ path: _, session, signal }) {
return this.scheduleAction(async (signal) => {
const response = await this.post('/release', {
params: session,
signal,
});
return response.success ? this.success(null) : response;
}, { signal });
}
releaseSync(session) {
if (typeof navigator !== 'undefined' && 'sendBeacon' in navigator) {
navigator.sendBeacon(`${this.url}/release/${session}?beacon=1`);
}
else {
this.post('/release', { params: session });
}
}
releaseDevice() {
return Promise.resolve(this.success(undefined));
}
getProtocol(customProtocol) {
if (!this.useProtocolMessages) {
return protocol_1.bridge;
}
return customProtocol || protocol_1.v1;
}
getRequestBody(body, protocol, thpState) {
return (0, bridgeProtocolMessage_1.createProtocolMessage)(body, this.useProtocolMessages ? protocol : undefined, thpState?.serialize());
}
call({ session, name, data, protocol: customProtocol, thpState, signal, timeout, }) {
return this.scheduleAction(async (signal) => {
const protocol = this.getProtocol(customProtocol);
const bytes = (0, send_1.buildMessage)({
messages: this.messages,
name,
data,
protocol,
thpState,
});
const response = await this.post(`/call`, {
params: session,
body: this.getRequestBody(bytes, protocol, thpState),
signal,
});
if (!response.success) {
return response;
}
const respBytes = Buffer.from(response.payload.data, 'hex');
if (protocol.name === 'v2') {
thpState?.sync('send', name);
const message = (0, receive_1.parseThpMessage)({
decoded: protocol.decode(respBytes),
messages: this.messages,
thpState,
});
thpState?.sync('recv', message.type);
return this.success(message);
}
return (0, receive_2.receiveAndParse)(this.messages, () => Promise.resolve(this.success(respBytes)), protocol);
}, { signal, timeout });
}
send({ session, name, data, protocol: customProtocol, thpState, signal, timeout, }) {
return this.scheduleAction(async (signal) => {
const protocol = this.getProtocol(customProtocol);
const bytes = (0, send_1.buildMessage)({
messages: this.messages,
name,
data,
protocol,
thpState,
});
const response = await this.post('/post', {
params: session,
body: this.getRequestBody(bytes, protocol, thpState),
signal,
});
if (!response.success) {
return response;
}
if (protocol.name === 'v2') {
thpState?.sync('send', name);
}
return this.success(undefined);
}, { signal, timeout });
}
receive({ session, protocol: customProtocol, thpState, signal, timeout, }) {
return this.scheduleAction(async (signal) => {
const protocol = this.getProtocol(customProtocol);
const response = await this.post('/read', {
params: session,
body: this.getRequestBody(Buffer.alloc(0), protocol, thpState),
signal,
});
if (!response.success) {
return response;
}
const respBytes = Buffer.from(response.payload.data, 'hex');
if (protocol.name === 'v2') {
const message = (0, receive_1.parseThpMessage)({
decoded: protocol.decode(respBytes),
messages: this.messages,
thpState,
});
thpState?.sync('recv', message.type);
return this.success(message);
}
return (0, receive_2.receiveAndParse)(this.messages, () => Promise.resolve(this.success(respBytes)), protocol);
}, { signal, timeout });
}
async post(endpoint, options) {
const response = await (0, bridgeApiCall_1.bridgeApiCall)({
...options,
method: 'POST',
url: `${this.url + endpoint}${options?.params ? `/${options.params}` : ''}`,
skipContentTypeHeader: true,
});
if (!response.success) {
if (response.error === ERRORS.UNEXPECTED_ERROR) {
return this.unknownError(response.error);
}
if (response.error === ERRORS.HTTP_ERROR) {
return this.error({ error: response.error });
}
switch (endpoint) {
case '/':
return this.unknownError(response.error);
case '/acquire':
return this.unknownError(response.error, [
ERRORS.SESSION_WRONG_PREVIOUS,
ERRORS.DEVICE_NOT_FOUND,
ERRORS.INTERFACE_UNABLE_TO_OPEN_DEVICE,
ERRORS.DEVICE_DISCONNECTED_DURING_ACTION,
ERRORS.LIBUSB_ERROR_ACCESS,
]);
case '/call':
case '/read':
case '/post':
return this.unknownError(response.error, [
ERRORS.SESSION_NOT_FOUND,
ERRORS.DEVICE_DISCONNECTED_DURING_ACTION,
ERRORS.OTHER_CALL_IN_PROGRESS,
ERRORS.INTERFACE_DATA_TRANSFER,
protocol_1.PROTOCOL_MALFORMED,
]);
case '/enumerate':
case '/listen':
return this.unknownError(response.error);
case '/release':
return this.unknownError(response.error, [
ERRORS.SESSION_NOT_FOUND,
ERRORS.DEVICE_DISCONNECTED_DURING_ACTION,
]);
default:
return this.error({
error: ERRORS.WRONG_RESULT_TYPE,
message: 'just for type safety, should never happen',
});
}
}
switch (endpoint) {
case '/':
return bridgeApiResult.info(response.payload);
case '/acquire':
return bridgeApiResult.acquire(response.payload);
case '/read':
case '/call':
return bridgeApiResult.call(response.payload);
case '/post':
return bridgeApiResult.post(response.payload);
case '/enumerate':
case '/listen':
return bridgeApiResult.devices(response.payload);
case '/release':
return bridgeApiResult.empty(response.payload);
default:
return this.error({
error: ERRORS.WRONG_RESULT_TYPE,
message: 'just for type safety, should never happen',
});
}
}
}
exports.BridgeTransport = BridgeTransport;
//# sourceMappingURL=bridge.js.map