1 | "use strict";
|
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4 | return new (P || (P = Promise))(function (resolve, reject) {
|
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9 | });
|
10 | };
|
11 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
12 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
13 | };
|
14 | Object.defineProperty(exports, "__esModule", { value: true });
|
15 | exports.getAltStatusMessage = exports.StatusCodes = exports.TransportStatusError = exports.TransportError = void 0;
|
16 | const events_1 = __importDefault(require("events"));
|
17 | const errors_1 = require("@ledgerhq/errors");
|
18 | Object.defineProperty(exports, "TransportError", { enumerable: true, get: function () { return errors_1.TransportError; } });
|
19 | Object.defineProperty(exports, "StatusCodes", { enumerable: true, get: function () { return errors_1.StatusCodes; } });
|
20 | Object.defineProperty(exports, "getAltStatusMessage", { enumerable: true, get: function () { return errors_1.getAltStatusMessage; } });
|
21 | Object.defineProperty(exports, "TransportStatusError", { enumerable: true, get: function () { return errors_1.TransportStatusError; } });
|
22 | const logs_1 = require("@ledgerhq/logs");
|
23 | const DEFAULT_LOG_TYPE = "transport";
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | class Transport {
|
30 | constructor({ context, logType } = {}) {
|
31 | this.exchangeTimeout = 30000;
|
32 | this.unresponsiveTimeout = 15000;
|
33 | this.deviceModel = null;
|
34 | this._events = new events_1.default();
|
35 | |
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | this.send = (cla, ins, p1, p2, data = Buffer.alloc(0), statusList = [errors_1.StatusCodes.OK], { abortTimeoutMs } = {}) => __awaiter(this, void 0, void 0, function* () {
|
50 | const tracer = this.tracer.withUpdatedContext({ function: "send" });
|
51 | if (data.length >= 256) {
|
52 | tracer.trace("data.length exceeded 256 bytes limit", { dataLength: data.length });
|
53 | throw new errors_1.TransportError("data.length exceed 256 bytes limit. Got: " + data.length, "DataLengthTooBig");
|
54 | }
|
55 | tracer.trace("Starting an exchange", { abortTimeoutMs });
|
56 | const response = yield this.exchange(
|
57 |
|
58 | Buffer.concat([Buffer.from([cla, ins, p1, p2]), Buffer.from([data.length]), data]), { abortTimeoutMs });
|
59 | tracer.trace("Received response from exchange");
|
60 | const sw = response.readUInt16BE(response.length - 2);
|
61 | if (!statusList.some(s => s === sw)) {
|
62 | throw new errors_1.TransportStatusError(sw);
|
63 | }
|
64 | return response;
|
65 | });
|
66 | this._appAPIlock = null;
|
67 | this.tracer = new logs_1.LocalTracer(logType !== null && logType !== void 0 ? logType : DEFAULT_LOG_TYPE, context);
|
68 | }
|
69 | |
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | exchange(_apdu, { abortTimeoutMs: _abortTimeoutMs } = {}) {
|
79 | throw new Error("exchange not implemented");
|
80 | }
|
81 | |
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 | exchangeBulk(apdus, observer) {
|
89 | let unsubscribed = false;
|
90 | const unsubscribe = () => {
|
91 | unsubscribed = true;
|
92 | };
|
93 | const main = () => __awaiter(this, void 0, void 0, function* () {
|
94 | if (unsubscribed)
|
95 | return;
|
96 | for (const apdu of apdus) {
|
97 | const r = yield this.exchange(apdu);
|
98 | if (unsubscribed)
|
99 | return;
|
100 | const status = r.readUInt16BE(r.length - 2);
|
101 | if (status !== errors_1.StatusCodes.OK) {
|
102 | throw new errors_1.TransportStatusError(status);
|
103 | }
|
104 | observer.next(r);
|
105 | }
|
106 | });
|
107 | main().then(() => !unsubscribed && observer.complete(), e => !unsubscribed && observer.error(e));
|
108 | return { unsubscribe };
|
109 | }
|
110 | |
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 | setScrambleKey(_key) { }
|
119 | |
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 | close() {
|
129 | return Promise.resolve();
|
130 | }
|
131 | |
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 | on(eventName, cb) {
|
139 | this._events.on(eventName, cb);
|
140 | }
|
141 | |
142 |
|
143 |
|
144 | off(eventName, cb) {
|
145 | this._events.removeListener(eventName, cb);
|
146 | }
|
147 | emit(event, ...args) {
|
148 | this._events.emit(event, ...args);
|
149 | }
|
150 | |
151 |
|
152 |
|
153 | setDebugMode() {
|
154 | console.warn("setDebugMode is deprecated. use @ledgerhq/logs instead. No logs are emitted in this anymore.");
|
155 | }
|
156 | |
157 |
|
158 |
|
159 | setExchangeTimeout(exchangeTimeout) {
|
160 | this.exchangeTimeout = exchangeTimeout;
|
161 | }
|
162 | |
163 |
|
164 |
|
165 | setExchangeUnresponsiveTimeout(unresponsiveTimeout) {
|
166 | this.unresponsiveTimeout = unresponsiveTimeout;
|
167 | }
|
168 | |
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 | static create(openTimeout = 3000, listenTimeout) {
|
176 | return new Promise((resolve, reject) => {
|
177 | let found = false;
|
178 | const sub = this.listen({
|
179 | next: e => {
|
180 | found = true;
|
181 | if (sub)
|
182 | sub.unsubscribe();
|
183 | if (listenTimeoutId)
|
184 | clearTimeout(listenTimeoutId);
|
185 | this.open(e.descriptor, openTimeout).then(resolve, reject);
|
186 | },
|
187 | error: e => {
|
188 | if (listenTimeoutId)
|
189 | clearTimeout(listenTimeoutId);
|
190 | reject(e);
|
191 | },
|
192 | complete: () => {
|
193 | if (listenTimeoutId)
|
194 | clearTimeout(listenTimeoutId);
|
195 | if (!found) {
|
196 | reject(new errors_1.TransportError(this.ErrorMessage_NoDeviceFound, "NoDeviceFound"));
|
197 | }
|
198 | },
|
199 | });
|
200 | const listenTimeoutId = listenTimeout
|
201 | ? setTimeout(() => {
|
202 | sub.unsubscribe();
|
203 | reject(new errors_1.TransportError(this.ErrorMessage_ListenTimeout, "ListenTimeout"));
|
204 | }, listenTimeout)
|
205 | : null;
|
206 | });
|
207 | }
|
208 | |
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 | exchangeAtomicImpl(f) {
|
217 | return __awaiter(this, void 0, void 0, function* () {
|
218 | const tracer = this.tracer.withUpdatedContext({
|
219 | function: "exchangeAtomicImpl",
|
220 | unresponsiveTimeout: this.unresponsiveTimeout,
|
221 | });
|
222 | if (this.exchangeBusyPromise) {
|
223 | tracer.trace("Atomic exchange is already busy");
|
224 | throw new errors_1.TransportPendingOperation("An action was already pending on the Ledger device. Please deny or reconnect.");
|
225 | }
|
226 |
|
227 | let resolveBusy;
|
228 | const busyPromise = new Promise(r => {
|
229 | resolveBusy = r;
|
230 | });
|
231 | this.exchangeBusyPromise = busyPromise;
|
232 |
|
233 | let unresponsiveReached = false;
|
234 | const timeout = setTimeout(() => {
|
235 | tracer.trace(`Timeout reached, emitting Transport event "unresponsive"`, {
|
236 | unresponsiveTimeout: this.unresponsiveTimeout,
|
237 | });
|
238 | unresponsiveReached = true;
|
239 | this.emit("unresponsive");
|
240 | }, this.unresponsiveTimeout);
|
241 | try {
|
242 | const res = yield f();
|
243 | if (unresponsiveReached) {
|
244 | tracer.trace("Device was unresponsive, emitting responsive");
|
245 | this.emit("responsive");
|
246 | }
|
247 | return res;
|
248 | }
|
249 | finally {
|
250 | tracer.trace("Finalize, clearing busy guard");
|
251 | clearTimeout(timeout);
|
252 | if (resolveBusy)
|
253 | resolveBusy();
|
254 | this.exchangeBusyPromise = null;
|
255 | }
|
256 | });
|
257 | }
|
258 | decorateAppAPIMethods(self, methods, scrambleKey) {
|
259 | for (const methodName of methods) {
|
260 | self[methodName] = this.decorateAppAPIMethod(methodName, self[methodName], self, scrambleKey);
|
261 | }
|
262 | }
|
263 | decorateAppAPIMethod(methodName, f, ctx, scrambleKey) {
|
264 | return (...args) => __awaiter(this, void 0, void 0, function* () {
|
265 | const { _appAPIlock } = this;
|
266 | if (_appAPIlock) {
|
267 | return Promise.reject(new errors_1.TransportError("Ledger Device is busy (lock " + _appAPIlock + ")", "TransportLocked"));
|
268 | }
|
269 | try {
|
270 | this._appAPIlock = methodName;
|
271 | this.setScrambleKey(scrambleKey);
|
272 | return yield f.apply(ctx, args);
|
273 | }
|
274 | finally {
|
275 | this._appAPIlock = null;
|
276 | }
|
277 | });
|
278 | }
|
279 | |
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 | setTraceContext(context) {
|
288 | this.tracer = this.tracer.withContext(context);
|
289 | }
|
290 | |
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 | updateTraceContext(contextToAdd) {
|
298 | this.tracer.updateContext(contextToAdd);
|
299 | }
|
300 | |
301 |
|
302 |
|
303 | getTraceContext() {
|
304 | return this.tracer.getContext();
|
305 | }
|
306 | }
|
307 | Transport.ErrorMessage_ListenTimeout = "No Ledger device found (timeout)";
|
308 | Transport.ErrorMessage_NoDeviceFound = "No Ledger device found";
|
309 | exports.default = Transport;
|
310 |
|
\ | No newline at end of file |