trezor-link
Version:
Trezor Link for browser
411 lines (370 loc) • 11.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = undefined;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _desc, _value, _class;
exports.storageGet = storageGet;
exports.storageSet = storageSet;
var _debugDecorator = require('../debug-decorator');
var _defered = require('../defered');
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
var desc = {};
Object['ke' + 'ys'](descriptor).forEach(function (key) {
desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;
if ('value' in desc || desc.initializer) {
desc.writable = true;
}
desc = decorators.slice().reverse().reduce(function (desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
desc.initializer = undefined;
}
if (desc.initializer === void 0) {
Object['define' + 'Property'](target, property, desc);
desc = null;
}
return desc;
}
var TREZOR_DESCS = [{
vendorId: 0x534c,
productId: 0x0001
}, {
vendorId: 0x1209,
productId: 0x53c1
}];
var FORBIDDEN_DESCRIPTORS = [0xf1d0, 0xff01];
var REPORT_ID = 63;
function deviceToJson(device) {
return {
path: device.deviceId.toString()
};
}
function hidEnumerate() {
return new Promise(function (resolve, reject) {
try {
chrome.hid.getDevices({ filters: TREZOR_DESCS }, function (devices) {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError));
} else {
resolve(devices);
}
});
} catch (e) {
reject(e);
}
});
}
function hidSend(id, reportId, data) {
// if send (of one data packet!) does not happen within 10 seconds, the connection
// probably failed in an unexpected way => return an error!!
var rejecting = (0, _defered.rejectTimeoutPromise)(10 * 1000, new Error('Cannot send data to device, check the cable.'));
var sendRes = new Promise(function (resolve, reject) {
try {
chrome.hid.send(id, reportId, data, function () {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError));
} else {
resolve();
}
});
} catch (e) {
reject(e);
}
});
return Promise.race([rejecting, sendRes]);
}
function hidReceive(id) {
return new Promise(function (resolve, reject) {
try {
chrome.hid.receive(id, function (reportId, data) {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve({ data: data, reportId: reportId });
}
});
} catch (e) {
reject(e);
}
});
}
function hidConnect(id) {
return new Promise(function (resolve, reject) {
try {
chrome.hid.connect(id, function (connection) {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve(connection.connectionId);
}
});
} catch (e) {
reject(e);
}
});
}
// Disconnects from trezor.
// First parameter is connection ID (*not* device ID!)
function hidDisconnect(id) {
return new Promise(function (resolve, reject) {
try {
chrome.hid.disconnect(id, function () {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve();
}
});
} catch (e) {
reject(e);
}
});
}
// encapsulating chrome's platform info into Promise API
function platformInfo() {
return new Promise(function (resolve, reject) {
try {
chrome.runtime.getPlatformInfo(function (info) {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
if (info == null) {
reject(new Error('info is null'));
} else {
resolve(info);
}
}
});
} catch (e) {
reject(e);
}
});
}
function storageGet(key) {
return new Promise(function (resolve, reject) {
try {
chrome.storage.local.get(key, function (items) {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
if (items[key] === null || items[key] === undefined) {
resolve(null);
} else {
resolve(items[key]);
}
resolve(items);
}
});
} catch (e) {
reject(e);
}
});
}
// Set to storage
function storageSet(key, value) {
return new Promise(function (resolve, reject) {
try {
var obj = {};
obj[key] = value;
chrome.storage.local.set(obj, function () {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve(undefined);
}
});
} catch (e) {
reject(e);
}
});
}
var ChromeHidPlugin = (_class = function () {
function ChromeHidPlugin() {
_classCallCheck(this, ChromeHidPlugin);
this.name = 'ChromeHidPlugin';
this._hasReportId = {};
this._udevError = false;
this.version = "0.2.109";
this.debug = false;
this.allowsWriteAndEnumerate = true;
this.requestNeeded = false;
}
_createClass(ChromeHidPlugin, [{
key: 'init',
value: function init(debug) {
this.debug = !!debug;
try {
if (chrome.hid.getDevices == null) {
return Promise.reject(new Error('chrome.hid.getDevices is null'));
} else {
return Promise.resolve();
}
} catch (e) {
return Promise.reject(e);
}
}
}, {
key: '_catchUdevError',
value: function _catchUdevError(error) {
var errMessage = error;
if (errMessage.message !== undefined) {
errMessage = error.message;
}
// A little heuristics.
// If error message is one of these and the type of original message is initialization, it's
// probably udev error.
if (errMessage === 'Failed to open HID device.' || errMessage === 'Transfer failed.') {
this._udevError = true;
}
throw error;
}
}, {
key: '_isLinux',
value: function _isLinux() {
var _this = this;
if (this._isLinuxCached != null) {
return Promise.resolve(this._isLinuxCached);
}
return platformInfo().then(function (info) {
var linux = info.os === 'linux';
_this._isLinuxCached = linux;
return linux;
});
}
}, {
key: '_isAfterInstall',
value: function _isAfterInstall() {
return storageGet('afterInstall').then(function (afterInstall) {
return afterInstall !== false;
});
}
}, {
key: 'showUdevError',
value: function showUdevError() {
var _this2 = this;
return this._isLinux().then(function (isLinux) {
if (!isLinux) {
return false;
}
return _this2._isAfterInstall().then(function (isAfterInstall) {
if (isAfterInstall) {
return true;
} else {
return _this2._udevError;
}
});
});
}
}, {
key: 'clearUdevError',
value: function clearUdevError() {
this._udevError = false;
return storageSet('afterInstall', true);
}
}, {
key: 'enumerate',
value: function enumerate() {
var _this3 = this;
return hidEnumerate().then(function (devices) {
return devices.filter(function (device) {
return !FORBIDDEN_DESCRIPTORS.some(function (des) {
return des === device.collections[0].usagePage;
});
});
}).then(function (devices) {
_this3._hasReportId = {};
devices.forEach(function (device) {
_this3._hasReportId[device.deviceId.toString()] = device.collections[0].reportIds.length !== 0;
});
return devices;
}).then(function (devices) {
return devices.map(function (device) {
return deviceToJson(device);
});
});
}
}, {
key: 'send',
value: function send(device, session, data) {
var _this4 = this;
var sessionNu = parseInt(session);
if (isNaN(sessionNu)) {
return Promise.reject(new Error('Session ' + session + ' is not a number'));
}
var hasReportId = this._hasReportId[device.toString()];
var reportId = hasReportId ? REPORT_ID : 0;
var ab = data;
if (!hasReportId) {
var newArray = new Uint8Array(64);
newArray[0] = 63;
newArray.set(new Uint8Array(data), 1);
ab = newArray.buffer;
}
return hidSend(sessionNu, reportId, ab).catch(function (e) {
return _this4._catchUdevError(e);
});
}
}, {
key: 'receive',
value: function receive(device, session) {
var _this5 = this;
var sessionNu = parseInt(session);
if (isNaN(sessionNu)) {
return Promise.reject(new Error('Session ' + session + ' is not a number'));
}
return hidReceive(sessionNu).then(function (_ref) {
var data = _ref.data,
reportId = _ref.reportId;
if (reportId !== 0) {
return data;
} else {
return data.slice(1);
}
}).then(function (res) {
return _this5.clearUdevError().then(function () {
return res;
});
}).catch(function (e) {
return _this5._catchUdevError(e);
});
}
}, {
key: 'connect',
value: function connect(device) {
var _this6 = this;
var deviceNu = parseInt(device);
if (isNaN(deviceNu)) {
return Promise.reject(new Error('Device ' + deviceNu + ' is not a number'));
}
return hidConnect(deviceNu).then(function (d) {
return d.toString();
}).catch(function (e) {
return _this6._catchUdevError(e);
});
}
}, {
key: 'disconnect',
value: function disconnect(path, session) {
var sessionNu = parseInt(session);
if (isNaN(sessionNu)) {
return Promise.reject(new Error('Session ' + session + ' is not a number'));
}
return hidDisconnect(sessionNu);
}
}, {
key: 'requestDevice',
value: function requestDevice() {
return Promise.reject();
}
}]);
return ChromeHidPlugin;
}(), (_applyDecoratedDescriptor(_class.prototype, 'init', [_debugDecorator.debugInOut], Object.getOwnPropertyDescriptor(_class.prototype, 'init'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'connect', [_debugDecorator.debugInOut], Object.getOwnPropertyDescriptor(_class.prototype, 'connect'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'disconnect', [_debugDecorator.debugInOut], Object.getOwnPropertyDescriptor(_class.prototype, 'disconnect'), _class.prototype)), _class);
exports.default = ChromeHidPlugin;