@onekeyfe/hd-transport 1.2.8-alpha.117 → 1.2.8-alpha.118

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -110,7 +110,7 @@ const createDevice = ({ reply } = {}) => {
110
110
  const request = requests[index];
111
111
  const { offset, total_size: totalSize, data } = request.message.file;
112
112
  // Decoded protobuf bytes are hex strings.
113
- const length = Number(typeof data === 'string' ? data.length / 2 : data.length);
113
+ const length = data.length / 2;
114
114
  const confirmation = encode('FilesystemFile', {
115
115
  path: PATH,
116
116
  offset,
@@ -154,10 +154,9 @@ const createSession = (device, options = {}) =>
154
154
  ...options,
155
155
  });
156
156
 
157
- const runPipeline = (session, chunks, callOptions = {}) => {
157
+ const runPipeline = (session, chunks, callOptions = {}, onResponse = jest.fn()) => {
158
158
  const [first, ...rest] = chunks;
159
159
  const next = jest.fn(() => Promise.resolve(rest.shift()));
160
- const onResponse = jest.fn();
161
160
  return {
162
161
  next,
163
162
  onResponse,
@@ -168,6 +167,28 @@ const runPipeline = (session, chunks, callOptions = {}) => {
168
167
  };
169
168
  };
170
169
 
170
+ const runManagedPipeline = (device, chunks) => {
171
+ const adapter = {
172
+ router: 1,
173
+ generation: 1,
174
+ prepareCall: jest.fn(),
175
+ writeFrame: device.writeFrame,
176
+ readFrame: device.readFrame,
177
+ reset: jest.fn(),
178
+ };
179
+ const manager = new ProtocolV2LinkManager({
180
+ getSchemas: () => schemas,
181
+ classifyError: () => 'link-fatal',
182
+ });
183
+ const [first, ...rest] = chunks;
184
+ return {
185
+ adapter,
186
+ result: manager.call('device-a', () => adapter, 'FilesystemFileWrite', first, {
187
+ fileWritePipeline: { next: () => Promise.resolve(rest.shift()), onResponse: jest.fn() },
188
+ }),
189
+ };
190
+ };
191
+
171
192
  describe('Protocol V2 file write pipeline', () => {
172
193
  test('keeps two ordered chunks in flight and confirms each one', async () => {
173
194
  const device = createDevice();
@@ -232,25 +253,9 @@ describe('Protocol V2 file write pipeline', () => {
232
253
  const device = createDevice({
233
254
  reply: ({ encode }) => [encode('Failure', { code: 9, message: 'Write offset mismatch' })],
234
255
  });
235
- const adapter = {
236
- router: 1,
237
- generation: 1,
238
- prepareCall: jest.fn(),
239
- writeFrame: device.writeFrame,
240
- readFrame: device.readFrame,
241
- reset: jest.fn(),
242
- };
243
- const manager = new ProtocolV2LinkManager({
244
- getSchemas: () => schemas,
245
- classifyError: () => 'link-fatal',
246
- });
247
- const [first, ...rest] = chunksOf(6, 2);
256
+ const { adapter, result } = runManagedPipeline(device, chunksOf(6, 2));
248
257
 
249
- await expect(
250
- manager.call('device-a', () => adapter, 'FilesystemFileWrite', first, {
251
- fileWritePipeline: { next: () => Promise.resolve(rest.shift()), onResponse: jest.fn() },
252
- })
253
- ).resolves.toMatchObject({ type: 'Failure' });
258
+ await expect(result).resolves.toMatchObject({ type: 'Failure' });
254
259
  expect(adapter.reset).not.toHaveBeenCalled();
255
260
  expect(adapter.prepareCall).toHaveBeenCalledTimes(1);
256
261
  });
@@ -262,25 +267,11 @@ describe('Protocol V2 file write pipeline', () => {
262
267
  ? [encode('FilesystemFile', { path: PATH, offset: 4, total_size: 6, processed_byte: 6 })]
263
268
  : [confirmation],
264
269
  });
265
- const adapter = {
266
- router: 1,
267
- generation: 1,
268
- prepareCall: jest.fn(),
269
- writeFrame: device.writeFrame,
270
- readFrame: device.readFrame,
271
- reset: jest.fn(() => Promise.resolve()),
272
- };
273
- const manager = new ProtocolV2LinkManager({
274
- getSchemas: () => schemas,
275
- classifyError: () => 'link-fatal',
276
- });
277
- const [first, ...rest] = chunksOf(6, 2);
270
+ const { adapter, result } = runManagedPipeline(device, chunksOf(6, 2));
278
271
 
279
- await expect(
280
- manager.call('device-a', () => adapter, 'FilesystemFileWrite', first, {
281
- fileWritePipeline: { next: () => Promise.resolve(rest.shift()), onResponse: jest.fn() },
282
- })
283
- ).rejects.toEqual(expect.objectContaining({ name: 'ProtocolV2LinkError', code: 'frame' }));
272
+ await expect(result).rejects.toEqual(
273
+ expect.objectContaining({ name: 'ProtocolV2LinkError', code: 'frame' })
274
+ );
284
275
  expect(adapter.reset).toHaveBeenCalled();
285
276
  });
286
277
 
@@ -302,9 +293,79 @@ describe('Protocol V2 file write pipeline', () => {
302
293
  const { result } = runPipeline(session, [chunk(0, 2, 8), chunk(4, 2, 8)]);
303
294
 
304
295
  await expect(result).rejects.toThrow('chunk out of order: expected offset 2, got 4');
296
+ await expect(result).rejects.toEqual(
297
+ expect.objectContaining({ name: 'ProtocolV2LinkError', code: 'frame' })
298
+ );
305
299
  expect(device.requests).toHaveLength(1);
306
300
  });
307
301
 
302
+ test.each([
303
+ ['path', { path: 'vol0:/application_p2.bin' }],
304
+ ['total size', { total_size: 9 }],
305
+ ])('rejects a source chunk with another %s before writing it', async (_field, file) => {
306
+ const device = createDevice();
307
+ const session = createSession(device);
308
+ const second = chunk(2, 2, 8);
309
+ const { result } = runPipeline(session, [
310
+ chunk(0, 2, 8),
311
+ { ...second, file: { ...second.file, ...file } },
312
+ ]);
313
+
314
+ await expect(result).rejects.toThrow('chunk belongs to a different file');
315
+ await expect(result).rejects.toEqual(
316
+ expect.objectContaining({ name: 'ProtocolV2LinkError', code: 'frame' })
317
+ );
318
+ expect(device.requests).toHaveLength(1);
319
+ });
320
+
321
+ test.each([
322
+ ['path', { path: 'vol0:/application_p2.bin' }],
323
+ ['total size', { total_size: 8 }],
324
+ ])('invalidates the link when a confirmation echoes another %s', async (_field, echo) => {
325
+ const device = createDevice({
326
+ reply: ({ index, confirmation, encode }) =>
327
+ index === 0
328
+ ? [
329
+ encode('FilesystemFile', {
330
+ path: PATH,
331
+ offset: 0,
332
+ total_size: 6,
333
+ processed_byte: 2,
334
+ ...echo,
335
+ }),
336
+ ]
337
+ : [confirmation],
338
+ });
339
+ const { adapter, result } = runManagedPipeline(device, chunksOf(6, 2));
340
+
341
+ await expect(result).rejects.toEqual(
342
+ expect.objectContaining({ name: 'ProtocolV2LinkError', code: 'frame' })
343
+ );
344
+ expect(adapter.reset).toHaveBeenCalled();
345
+ });
346
+
347
+ test.each(['readFrame', 'writeFrame'])(
348
+ 'passes a %s transport error through unchanged with chunks in flight',
349
+ async method => {
350
+ const device = createDevice();
351
+ const linkLost = new Error('BLE device disconnected');
352
+ const original = device[method].getMockImplementation();
353
+ let calls = 0;
354
+ device[method].mockImplementation((...args) => {
355
+ calls += 1;
356
+ // The second write, or the first read, happens with chunk 0 still in flight.
357
+ return calls === (method === 'writeFrame' ? 2 : 1)
358
+ ? Promise.reject(linkLost)
359
+ : original(...args);
360
+ });
361
+ const session = createSession(device);
362
+ const { result } = runPipeline(session, chunksOf(6, 2));
363
+
364
+ await expect(result).rejects.toBe(linkLost);
365
+ expect(device.requests.length).toBeGreaterThan(0);
366
+ }
367
+ );
368
+
308
369
  test('drains the in-flight chunk and returns the last confirmation when the source stops', async () => {
309
370
  const device = createDevice();
310
371
  const session = createSession(device);
@@ -317,7 +378,7 @@ describe('Protocol V2 file write pipeline', () => {
317
378
  expect(onResponse).toHaveBeenCalledTimes(2);
318
379
  });
319
380
 
320
- test('detects a chunk lost before a bootloader that appends at its own cursor', async () => {
381
+ test('detects a lost chunk when the bootloader appends at its own cursor', async () => {
321
382
  let cursor = 0;
322
383
  const device = createDevice({
323
384
  reply: ({ index, request, encode }) => {
@@ -366,6 +427,9 @@ describe('Protocol V2 file write pipeline', () => {
366
427
  const { result } = runPipeline(session, [chunk(0, 128, 128)]);
367
428
 
368
429
  await expect(result).rejects.toThrow('Protocol V2 frame too large for transport');
430
+ await expect(result).rejects.not.toEqual(
431
+ expect.objectContaining({ name: 'ProtocolV2LinkError' })
432
+ );
369
433
  expect(device.requests).toHaveLength(0);
370
434
  });
371
435
 
@@ -401,17 +465,14 @@ describe('Protocol V2 file write pipeline', () => {
401
465
  test('stops when the caller rejects a confirmed chunk', async () => {
402
466
  const device = createDevice();
403
467
  const session = createSession(device);
404
- const [first, ...rest] = chunksOf(10, 2);
405
- const result = session.call('FilesystemFileWrite', first, {
406
- fileWritePipeline: {
407
- next: () => Promise.resolve(rest.shift()),
408
- onResponse: () => {
409
- throw new Error('cancelled');
410
- },
411
- },
468
+ const { result } = runPipeline(session, chunksOf(10, 2), {}, () => {
469
+ throw new Error('cancelled');
412
470
  });
413
471
 
414
472
  await expect(result).rejects.toThrow('cancelled');
473
+ await expect(result).rejects.toEqual(
474
+ expect.objectContaining({ name: 'ProtocolV2LinkError', code: 'frame' })
475
+ );
415
476
  expect(device.requests).toHaveLength(2);
416
477
  });
417
478
  });
package/dist/index.d.ts CHANGED
@@ -323,6 +323,12 @@ type OneKeyDeviceInfoWithSession = OneKeyUsbDeviceInfo & {
323
323
  type OneKeyMobileDeviceInfo = {
324
324
  id: string;
325
325
  name: string | null;
326
+ /**
327
+ * Color letter of the serial number ('A'-'Z') from the Pro2 / Neo BLE advertisement,
328
+ * possibly seen in an earlier scan. Set by the React Native and Electron BLE
329
+ * transports; absent when unknown.
330
+ */
331
+ bleColorCode?: string;
326
332
  };
327
333
  type OneKeyDeviceInfoBase = {
328
334
  commType: OneKeyDeviceCommType;
@@ -6869,7 +6875,10 @@ declare class ProtocolV2Session {
6869
6875
  call(name: string, data: Record<string, unknown>, callOptions?: ProtocolV2CallOptions): Promise<MessageFromOneKey>;
6870
6876
  sendFlowControl(name: string, data: Record<string, unknown>): Promise<MessageFromOneKey>;
6871
6877
  private serializeWrite;
6878
+ private reportWriteCompleted;
6879
+ private createCallTimeoutError;
6872
6880
  private executeCall;
6881
+ private encodeRequestFrame;
6873
6882
  /**
6874
6883
  * Validates one received frame for this session. Returns undefined for an ACK of one of
6875
6884
  * `ackSequences`, otherwise the decoded response.
@@ -6881,9 +6890,7 @@ declare class ProtocolV2Session {
6881
6890
  * answers every chunk in order, so responses are matched to chunks by position. Upstream
6882
6891
  * bootloaders append at their own cursor without checking the request offset, so the echoed
6883
6892
  * offset is what exposes a lost chunk. A device Failure, or `next` returning undefined, stops
6884
- * sending; the remaining in-flight responses are drained so the link stays usable. The call
6885
- * then returns the Failure like a serial call, or the last confirmation, which the caller
6886
- * checks for completeness.
6893
+ * sending; the remaining in-flight responses are drained so the link stays usable.
6887
6894
  */
6888
6895
  private executeFileWritePipeline;
6889
6896
  }
package/dist/index.js CHANGED
@@ -1108,7 +1108,8 @@ function withProtocolTimeout(promise, timeoutMs, createTimeoutError, onTimeout,
1108
1108
  }
1109
1109
  });
1110
1110
  }
1111
- function readFileWriteChunk(request) {
1111
+ const resolveProtocolV2CallTimeoutMs = (timeoutMs) => timeoutMs && timeoutMs > 0 ? timeoutMs : PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
1112
+ function parseFileWriteChunk(request) {
1112
1113
  var _a;
1113
1114
  const file = request.file;
1114
1115
  const offset = Number(file === null || file === void 0 ? void 0 : file.offset);
@@ -1167,46 +1168,47 @@ class ProtocolV2Session {
1167
1168
  this.pendingWrite = result.catch(() => undefined);
1168
1169
  return result;
1169
1170
  }
1171
+ reportWriteCompleted(callOptions, writeStartedAt, frameBytes) {
1172
+ var _a, _b;
1173
+ try {
1174
+ (_a = callOptions.onWriteCompleted) === null || _a === void 0 ? void 0 : _a.call(callOptions, {
1175
+ elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
1176
+ frameBytes,
1177
+ });
1178
+ }
1179
+ catch (error) {
1180
+ const { logger, logPrefix = 'ProtocolV2' } = this.options;
1181
+ (_b = logger === null || logger === void 0 ? void 0 : logger.error) === null || _b === void 0 ? void 0 : _b.call(logger, `${logPrefix} write metrics callback failed: ${String(error)}`);
1182
+ }
1183
+ }
1184
+ createCallTimeoutError(name, timeoutMs) {
1185
+ const { createTimeoutError } = this.options;
1186
+ return createTimeoutError
1187
+ ? createTimeoutError(name, timeoutMs)
1188
+ : new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
1189
+ }
1170
1190
  executeCall(name, data, callOptions) {
1171
1191
  if (callOptions.fileWritePipeline) {
1172
1192
  return this.executeFileWritePipeline(name, data, callOptions, callOptions.fileWritePipeline);
1173
1193
  }
1174
- const { schemas, router, packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND, maxFrameBytes, prepareCall, writeFrame, readFrame, logger, logPrefix = 'ProtocolV2', createTimeoutError, generation = 0, } = this.options;
1175
- const timeoutMs = callOptions.timeoutMs && callOptions.timeoutMs > 0
1176
- ? callOptions.timeoutMs
1177
- : PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
1194
+ const { prepareCall, writeFrame, readFrame, logger, logPrefix = 'ProtocolV2', generation = 0, } = this.options;
1195
+ const timeoutMs = resolveProtocolV2CallTimeoutMs(callOptions.timeoutMs);
1178
1196
  const abortController = new AbortController();
1179
1197
  const shouldReduceDebug = shouldReduceProtocolV2Debug(name);
1180
1198
  const baseCallContext = Object.assign(Object.assign({ messageName: name, timeoutMs, highThroughput: isProtocolV2HighThroughputCall(name) }, (callOptions.writeWithResponse === undefined
1181
1199
  ? {}
1182
1200
  : { writeWithResponse: callOptions.writeWithResponse })), { generation, signal: abortController.signal });
1183
1201
  const runCall = () => __awaiter(this, void 0, void 0, function* () {
1184
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1202
+ var _a, _b, _c, _d, _e, _f, _g;
1185
1203
  if (!this.pendingResponseAfterWrite) {
1186
1204
  yield (prepareCall === null || prepareCall === void 0 ? void 0 : prepareCall(baseCallContext));
1187
1205
  }
1188
- const protoSeq = this.sequenceCursor.next();
1189
- const frame = ProtocolV2.encodeFrame(schemas, name, data, {
1190
- packetSrc,
1191
- router,
1192
- seq: protoSeq,
1193
- });
1194
- if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
1195
- throw new Error(`Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`);
1196
- }
1206
+ const { seq: protoSeq, frame } = this.encodeRequestFrame(name, data);
1197
1207
  const writeStartedAt = Date.now();
1198
1208
  yield this.serializeWrite(() => writeFrame(frame, baseCallContext));
1199
- try {
1200
- (_a = callOptions.onWriteCompleted) === null || _a === void 0 ? void 0 : _a.call(callOptions, {
1201
- elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
1202
- frameBytes: frame.byteLength,
1203
- });
1204
- }
1205
- catch (error) {
1206
- (_b = logger === null || logger === void 0 ? void 0 : logger.error) === null || _b === void 0 ? void 0 : _b.call(logger, `${logPrefix} write metrics callback failed: ${String(error)}`);
1207
- }
1209
+ this.reportWriteCompleted(callOptions, writeStartedAt, frame.byteLength);
1208
1210
  if (callOptions.returnAfterWrite) {
1209
- if ((_c = callOptions.expectedTypes) === null || _c === void 0 ? void 0 : _c.length) {
1211
+ if ((_a = callOptions.expectedTypes) === null || _a === void 0 ? void 0 : _a.length) {
1210
1212
  this.pendingResponseAfterWrite = {
1211
1213
  expectedTypes: new Set(callOptions.expectedTypes),
1212
1214
  onResponse: callOptions.onResponseAfterWrite,
@@ -1228,34 +1230,41 @@ class ProtocolV2Session {
1228
1230
  this.pendingResponseAfterWrite = undefined;
1229
1231
  if (belongsToWriteOnlyCall) {
1230
1232
  try {
1231
- (_d = pendingResponseAfterWrite.onResponse) === null || _d === void 0 ? void 0 : _d.call(pendingResponseAfterWrite, response);
1233
+ (_b = pendingResponseAfterWrite.onResponse) === null || _b === void 0 ? void 0 : _b.call(pendingResponseAfterWrite, response);
1232
1234
  }
1233
1235
  catch (error) {
1234
- (_e = logger === null || logger === void 0 ? void 0 : logger.error) === null || _e === void 0 ? void 0 : _e.call(logger, `${logPrefix} delayed response callback failed: ${String(error)}`);
1236
+ (_c = logger === null || logger === void 0 ? void 0 : logger.error) === null || _c === void 0 ? void 0 : _c.call(logger, `${logPrefix} delayed response callback failed: ${String(error)}`);
1235
1237
  }
1236
1238
  }
1237
1239
  }
1238
1240
  }
1239
1241
  if (belongsToWriteOnlyCall) ;
1240
- else if ((_f = callOptions.intermediateTypes) === null || _f === void 0 ? void 0 : _f.includes(response.type)) {
1241
- (_g = callOptions.onIntermediateResponse) === null || _g === void 0 ? void 0 : _g.call(callOptions, response);
1242
+ else if ((_d = callOptions.intermediateTypes) === null || _d === void 0 ? void 0 : _d.includes(response.type)) {
1243
+ (_e = callOptions.onIntermediateResponse) === null || _e === void 0 ? void 0 : _e.call(callOptions, response);
1242
1244
  }
1243
1245
  else if (isExpectedTerminalResponse(response, callOptions.expectedTypes)) {
1244
1246
  return response;
1245
1247
  }
1246
1248
  else if (!shouldReduceDebug) {
1247
- (_h = logger === null || logger === void 0 ? void 0 : logger.debug) === null || _h === void 0 ? void 0 : _h.call(logger, `[${logPrefix}] skip unexpected response for ${name}: expected=${(_j = callOptions.expectedTypes) === null || _j === void 0 ? void 0 : _j.join('|')} got=${response.type}`);
1249
+ (_f = logger === null || logger === void 0 ? void 0 : logger.debug) === null || _f === void 0 ? void 0 : _f.call(logger, `[${logPrefix}] skip unexpected response for ${name}: expected=${(_g = callOptions.expectedTypes) === null || _g === void 0 ? void 0 : _g.join('|')} got=${response.type}`);
1248
1250
  }
1249
1251
  }
1250
1252
  }
1251
1253
  throw new Error(`Protocol V2 read loop cancelled after timeout for ${name}`);
1252
1254
  });
1253
- return withProtocolTimeout(runCall(), timeoutMs, () => createTimeoutError
1254
- ? createTimeoutError(name, timeoutMs)
1255
- : new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`), () => {
1255
+ return withProtocolTimeout(runCall(), timeoutMs, () => this.createCallTimeoutError(name, timeoutMs), () => {
1256
1256
  abortController.abort();
1257
1257
  });
1258
1258
  }
1259
+ encodeRequestFrame(name, data) {
1260
+ const { schemas, router, packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND, maxFrameBytes, } = this.options;
1261
+ const seq = this.sequenceCursor.next();
1262
+ const frame = ProtocolV2.encodeFrame(schemas, name, data, { packetSrc, router, seq });
1263
+ if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
1264
+ throw new Error(`Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`);
1265
+ }
1266
+ return { seq, frame };
1267
+ }
1259
1268
  parseResponseFrame(rxFrame, ackSequences) {
1260
1269
  const { schemas, router, packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND } = this.options;
1261
1270
  let header;
@@ -1298,7 +1307,6 @@ class ProtocolV2Session {
1298
1307
  return call(decoded);
1299
1308
  }
1300
1309
  executeFileWritePipeline(name, data, callOptions, pipeline) {
1301
- var _a;
1302
1310
  return __awaiter(this, void 0, void 0, function* () {
1303
1311
  if (name !== 'FilesystemFileWrite') {
1304
1312
  throw new Error(`Protocol V2 file write pipeline does not support ${name}`);
@@ -1306,110 +1314,102 @@ class ProtocolV2Session {
1306
1314
  if (this.pendingResponseAfterWrite) {
1307
1315
  throw new Error('Protocol V2 file write pipeline cannot start while a response is pending');
1308
1316
  }
1309
- const { schemas, router, packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND, maxFrameBytes, prepareCall, writeFrame, readFrame, logger, logPrefix = 'ProtocolV2', createTimeoutError, generation = 0, } = this.options;
1310
- const timeoutMs = callOptions.timeoutMs && callOptions.timeoutMs > 0
1311
- ? callOptions.timeoutMs
1312
- : PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
1317
+ const { prepareCall, writeFrame, readFrame, generation = 0 } = this.options;
1318
+ const timeoutMs = resolveProtocolV2CallTimeoutMs(callOptions.timeoutMs);
1313
1319
  const abortController = new AbortController();
1314
1320
  const context = Object.assign(Object.assign({ messageName: name, timeoutMs, highThroughput: true }, (callOptions.writeWithResponse === undefined
1315
1321
  ? {}
1316
1322
  : { writeWithResponse: callOptions.writeWithResponse })), { generation, signal: abortController.signal });
1317
- const withStepTimeout = (promise, stepTimeoutMs = timeoutMs) => withProtocolTimeout(promise, stepTimeoutMs, () => createTimeoutError
1318
- ? createTimeoutError(name, timeoutMs)
1319
- : new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`), () => {
1323
+ let transportFailure;
1324
+ const withStepTimeout = (promise, stepTimeoutMs = timeoutMs) => withProtocolTimeout(promise, stepTimeoutMs, () => this.createCallTimeoutError(name, timeoutMs), () => {
1320
1325
  abortController.abort();
1326
+ }).catch((error) => {
1327
+ transportFailure = error;
1328
+ throw error;
1321
1329
  });
1322
1330
  let progressDeadline = 0;
1323
1331
  const markProgress = () => {
1324
1332
  progressDeadline = Date.now() + timeoutMs;
1325
1333
  };
1326
1334
  const inFlight = [];
1327
- let firstRequest = data;
1328
1335
  let path;
1329
1336
  let totalSize;
1330
1337
  let nextOffset;
1331
1338
  let sourceDone = false;
1332
1339
  let failure;
1333
1340
  let lastConfirmed;
1334
- const sendNextChunk = () => __awaiter(this, void 0, void 0, function* () {
1335
- var _b, _c;
1336
- const request = firstRequest !== null && firstRequest !== void 0 ? firstRequest : (yield pipeline.next());
1337
- firstRequest = undefined;
1338
- if (!request) {
1339
- sourceDone = true;
1340
- return;
1341
+ const sendChunk = (request) => __awaiter(this, void 0, void 0, function* () {
1342
+ const chunk = parseFileWriteChunk(request);
1343
+ if (path !== undefined && (chunk.path !== path || chunk.totalSize !== totalSize)) {
1344
+ throw new Error('Protocol V2 file write pipeline chunk belongs to a different file');
1341
1345
  }
1342
- const chunk = readFileWriteChunk(request);
1343
- if ((path !== undefined && chunk.path !== path) ||
1344
- (totalSize !== undefined && chunk.totalSize !== totalSize) ||
1345
- (nextOffset !== undefined && chunk.offset !== nextOffset)) {
1346
- throw new Error(`Protocol V2 file write pipeline chunk out of order: expected offset ${String(nextOffset)}, got ${chunk.offset}`);
1346
+ if (nextOffset !== undefined && chunk.offset !== nextOffset) {
1347
+ throw new Error(`Protocol V2 file write pipeline chunk out of order: expected offset ${nextOffset}, got ${chunk.offset}`);
1347
1348
  }
1348
1349
  path = chunk.path;
1349
1350
  totalSize = chunk.totalSize;
1350
1351
  nextOffset = chunk.end;
1351
- const seq = this.sequenceCursor.next();
1352
- const frame = ProtocolV2.encodeFrame(schemas, name, request, {
1353
- packetSrc,
1354
- router,
1355
- seq,
1356
- });
1357
- if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
1358
- throw new Error(`Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`);
1359
- }
1352
+ const { seq, frame } = this.encodeRequestFrame(name, request);
1360
1353
  const writeStartedAt = Date.now();
1361
1354
  yield withStepTimeout(this.serializeWrite(() => writeFrame(frame, context)));
1362
- inFlight.push({ seq, path: chunk.path, offset: chunk.offset, end: chunk.end });
1355
+ inFlight.push({ seq, offset: chunk.offset, end: chunk.end });
1363
1356
  markProgress();
1364
- try {
1365
- (_b = callOptions.onWriteCompleted) === null || _b === void 0 ? void 0 : _b.call(callOptions, {
1366
- elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
1367
- frameBytes: frame.byteLength,
1368
- });
1369
- }
1370
- catch (error) {
1371
- (_c = logger === null || logger === void 0 ? void 0 : logger.error) === null || _c === void 0 ? void 0 : _c.call(logger, `${logPrefix} write metrics callback failed: ${String(error)}`);
1357
+ this.reportWriteCompleted(callOptions, writeStartedAt, frame.byteLength);
1358
+ });
1359
+ const sendNextChunk = () => __awaiter(this, void 0, void 0, function* () {
1360
+ const request = yield pipeline.next();
1361
+ if (!request) {
1362
+ sourceDone = true;
1363
+ return;
1372
1364
  }
1365
+ yield sendChunk(request);
1373
1366
  });
1374
1367
  yield withStepTimeout(Promise.resolve().then(() => prepareCall === null || prepareCall === void 0 ? void 0 : prepareCall(context)));
1375
- while (!sourceDone && inFlight.length < PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW) {
1376
- yield sendNextChunk();
1377
- }
1378
- while (inFlight.length > 0) {
1379
- const rxFrame = yield withStepTimeout(readFrame(context), Math.max(progressDeadline - Date.now(), 1));
1380
- const response = this.parseResponseFrame(rxFrame, inFlight.map(item => item.seq));
1381
- if (!response) ;
1382
- else if (response.type === 'FilesystemFile') {
1383
- const [expected] = inFlight;
1384
- const { message } = response;
1385
- if (Number(message.processed_byte) !== expected.end ||
1386
- message.path !== expected.path ||
1387
- Number(message.offset) !== expected.offset ||
1388
- Number(message.total_size) !== totalSize) {
1389
- throw new ProtocolV2LinkError('frame', `Protocol V2 file write response does not match chunk ${expected.offset}-${expected.end}: got offset ${String(message.offset)} processed_byte ${String(message.processed_byte)}`);
1368
+ try {
1369
+ yield sendChunk(data);
1370
+ while (!sourceDone && inFlight.length < PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW) {
1371
+ yield sendNextChunk();
1372
+ }
1373
+ while (inFlight.length > 0) {
1374
+ const rxFrame = yield withStepTimeout(readFrame(context), Math.max(progressDeadline - Date.now(), 1));
1375
+ const response = this.parseResponseFrame(rxFrame, inFlight.map(item => item.seq));
1376
+ if (!response) {
1390
1377
  }
1391
- inFlight.shift();
1392
- markProgress();
1393
- if (!failure) {
1394
- lastConfirmed = response;
1395
- pipeline.onResponse(response);
1396
- if (!sourceDone) {
1397
- yield sendNextChunk();
1378
+ else if (response.type === 'FilesystemFile') {
1379
+ const [expected] = inFlight;
1380
+ const { message } = response;
1381
+ if (Number(message.processed_byte) !== expected.end ||
1382
+ message.path !== path ||
1383
+ Number(message.offset) !== expected.offset ||
1384
+ Number(message.total_size) !== totalSize) {
1385
+ throw new ProtocolV2LinkError('frame', `Protocol V2 file write response does not match chunk ${expected.offset}-${expected.end}: got offset ${String(message.offset)} processed_byte ${String(message.processed_byte)}`);
1386
+ }
1387
+ inFlight.shift();
1388
+ markProgress();
1389
+ if (!failure) {
1390
+ lastConfirmed = response;
1391
+ pipeline.onResponse(response);
1392
+ if (!sourceDone) {
1393
+ yield sendNextChunk();
1394
+ }
1398
1395
  }
1399
1396
  }
1397
+ else if (response.type === 'Failure') {
1398
+ inFlight.shift();
1399
+ markProgress();
1400
+ failure = failure !== null && failure !== void 0 ? failure : response;
1401
+ sourceDone = true;
1402
+ }
1403
+ else if (COMMON_TERMINAL_RESPONSE_TYPES.has(response.type)) {
1404
+ throw new ProtocolV2LinkError('frame', `Protocol V2 file write pipeline received unexpected ${response.type}`);
1405
+ }
1400
1406
  }
1401
- else if (response.type === 'Failure') {
1402
- inFlight.shift();
1403
- markProgress();
1404
- failure = failure !== null && failure !== void 0 ? failure : response;
1405
- sourceDone = true;
1406
- }
1407
- else if (COMMON_TERMINAL_RESPONSE_TYPES.has(response.type)) {
1408
- throw new ProtocolV2LinkError('frame', `Protocol V2 file write pipeline received unexpected ${response.type}`);
1409
- }
1410
- else {
1411
- (_a = logger === null || logger === void 0 ? void 0 : logger.debug) === null || _a === void 0 ? void 0 : _a.call(logger, `[${logPrefix}] skip unexpected response for ${name}: expected=FilesystemFile got=${response.type}`);
1407
+ }
1408
+ catch (error) {
1409
+ if (inFlight.length === 0 || error === transportFailure || isProtocolV2LinkError(error)) {
1410
+ throw error;
1412
1411
  }
1412
+ throw new ProtocolV2LinkError('frame', `Protocol V2 file write pipeline aborted with chunks in flight: ${getErrorMessage(error)}`, error);
1413
1413
  }
1414
1414
  const terminal = failure !== null && failure !== void 0 ? failure : lastConfirmed;
1415
1415
  if (!terminal) {
@@ -67,7 +67,10 @@ export declare class ProtocolV2Session {
67
67
  call(name: string, data: Record<string, unknown>, callOptions?: ProtocolV2CallOptions): Promise<MessageFromOneKey>;
68
68
  sendFlowControl(name: string, data: Record<string, unknown>): Promise<MessageFromOneKey>;
69
69
  private serializeWrite;
70
+ private reportWriteCompleted;
71
+ private createCallTimeoutError;
70
72
  private executeCall;
73
+ private encodeRequestFrame;
71
74
  private parseResponseFrame;
72
75
  private executeFileWritePipeline;
73
76
  }
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/protocols/v2/session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/protocols/v2/session.ts"],"names":[],"mappings":"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"}
@@ -23,6 +23,7 @@ export type OneKeyDeviceInfoWithSession = OneKeyUsbDeviceInfo & {
23
23
  export type OneKeyMobileDeviceInfo = {
24
24
  id: string;
25
25
  name: string | null;
26
+ bleColorCode?: string;
26
27
  };
27
28
  export type OneKeyDeviceInfoBase = {
28
29
  commType: OneKeyDeviceCommType;
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/types/transport.ts"],"names":[],"mappings":";;;AAAA,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AAEvC,eAAO,MAAM,eAAe;;CAElB,CAAC;AAEX,MAAM,MAAM,8BAA8B,GAAG;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,IAAI,CAAC;AAEvC,MAAM,MAAM,oBAAoB,GAC5B,KAAK,GACL,QAAQ,GACR,KAAK,GACL,QAAQ,GACR,cAAc,GACd,QAAQ,GACR,UAAU,CAAC;AAEf,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,mBAAmB,GAAG;IAC9D,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,oBAAoB,CAAC;CAChC,CAAC;AAGF,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GACjD,2BAA2B,GAC3B,sBAAsB,GAAG;IACvB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC;AAEJ,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,gBAAgB,CAAC,EAAE,YAAY,CAAC;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;IAK5B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AASF,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAEzD,UAAU,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAE/D,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAE5D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAE7D,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAM5B,iBAAiB,CAAC,EAAE,2BAA2B,CAAC;CACjD,CAAC;AAEF,KAAK,gBAAgB,GAAG,CACtB,MAAM,CAAC,EAAE,GAAG,EACZ,OAAO,CAAC,EAAE,YAAY,EACtB,MAAM,CAAC,EAAE,6BAA6B,KACnC,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB,MAAM,MAAM,SAAS,GAAG;IACtB,SAAS,IAAI,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC9C,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,gBAAgB,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACxE,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAM9C,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjF,SAAS,CAAC,UAAU,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,mBAAmB,CAAC,EAAE,CAAC,UAAU,EAAE,IAAI,GAAG,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1E,IAAI,CACF,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9B,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAClD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAIxB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAMhD,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,YAAY,GAAG,SAAS,CAAC;IAG5D,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,SAAS,GAAG,eAAe,GAAG,IAAI,CAAC,CAAC;IAGvE,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,oBAAoB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACjF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,SAAS,EAAE,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAC3C,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7F,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5C,2BAA2B,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAEjG,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/types/transport.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport",
3
- "version": "1.2.8-alpha.117",
3
+ "version": "1.2.8-alpha.118",
4
4
  "description": "Transport layer abstractions and utilities for OneKey hardware SDK.",
5
5
  "author": "OneKey",
6
6
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
@@ -28,5 +28,5 @@
28
28
  "long": "^4.0.0",
29
29
  "protobufjs": "^6.11.2"
30
30
  },
31
- "gitHead": "610f1a979c682817acf9b4d195365dc5ed806138"
31
+ "gitHead": "7b5fe42a7c29ff0aa4c8340c1d5da93026a1b590"
32
32
  }
@@ -10,6 +10,7 @@ import {
10
10
  createProtocolV2LinkDisabledError,
11
11
  isProtocolV2LinkDisabledError,
12
12
  isProtocolV2LinkDisabledFailure,
13
+ isProtocolV2LinkError,
13
14
  } from './errors';
14
15
  import { ProtocolV2 } from '..';
15
16
  import * as check from '../../utils/highlevel-checks';
@@ -220,7 +221,10 @@ export async function withProtocolTimeout<T>(
220
221
  }
221
222
  }
222
223
 
223
- function readFileWriteChunk(request: Record<string, unknown>) {
224
+ const resolveProtocolV2CallTimeoutMs = (timeoutMs?: number) =>
225
+ timeoutMs && timeoutMs > 0 ? timeoutMs : PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
226
+
227
+ function parseFileWriteChunk(request: Record<string, unknown>) {
224
228
  const file = request.file as
225
229
  | { path?: unknown; offset?: unknown; total_size?: unknown; data?: { byteLength?: unknown } }
226
230
  | undefined;
@@ -320,6 +324,29 @@ export class ProtocolV2Session {
320
324
  return result;
321
325
  }
322
326
 
327
+ private reportWriteCompleted(
328
+ callOptions: ProtocolV2CallOptions,
329
+ writeStartedAt: number,
330
+ frameBytes: number
331
+ ) {
332
+ try {
333
+ callOptions.onWriteCompleted?.({
334
+ elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
335
+ frameBytes,
336
+ });
337
+ } catch (error) {
338
+ const { logger, logPrefix = 'ProtocolV2' } = this.options;
339
+ logger?.error?.(`${logPrefix} write metrics callback failed: ${String(error)}`);
340
+ }
341
+ }
342
+
343
+ private createCallTimeoutError(name: string, timeoutMs: number): Error {
344
+ const { createTimeoutError } = this.options;
345
+ return createTimeoutError
346
+ ? createTimeoutError(name, timeoutMs)
347
+ : new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
348
+ }
349
+
323
350
  private executeCall(
324
351
  name: string,
325
352
  data: Record<string, unknown>,
@@ -329,22 +356,14 @@ export class ProtocolV2Session {
329
356
  return this.executeFileWritePipeline(name, data, callOptions, callOptions.fileWritePipeline);
330
357
  }
331
358
  const {
332
- schemas,
333
- router,
334
- packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND,
335
- maxFrameBytes,
336
359
  prepareCall,
337
360
  writeFrame,
338
361
  readFrame,
339
362
  logger,
340
363
  logPrefix = 'ProtocolV2',
341
- createTimeoutError,
342
364
  generation = 0,
343
365
  } = this.options;
344
- const timeoutMs =
345
- callOptions.timeoutMs && callOptions.timeoutMs > 0
346
- ? callOptions.timeoutMs
347
- : PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
366
+ const timeoutMs = resolveProtocolV2CallTimeoutMs(callOptions.timeoutMs);
348
367
  const abortController = new AbortController();
349
368
 
350
369
  const shouldReduceDebug = shouldReduceProtocolV2Debug(name);
@@ -365,29 +384,11 @@ export class ProtocolV2Session {
365
384
  if (!this.pendingResponseAfterWrite) {
366
385
  await prepareCall?.(baseCallContext);
367
386
  }
368
- const protoSeq = this.sequenceCursor.next();
369
- const frame = ProtocolV2.encodeFrame(schemas, name, data, {
370
- packetSrc,
371
- router,
372
- seq: protoSeq,
373
- });
374
-
375
- if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
376
- throw new Error(
377
- `Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`
378
- );
379
- }
387
+ const { seq: protoSeq, frame } = this.encodeRequestFrame(name, data);
380
388
 
381
389
  const writeStartedAt = Date.now();
382
390
  await this.serializeWrite(() => writeFrame(frame, baseCallContext));
383
- try {
384
- callOptions.onWriteCompleted?.({
385
- elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
386
- frameBytes: frame.byteLength,
387
- });
388
- } catch (error) {
389
- logger?.error?.(`${logPrefix} write metrics callback failed: ${String(error)}`);
390
- }
391
+ this.reportWriteCompleted(callOptions, writeStartedAt, frame.byteLength);
391
392
 
392
393
  if (callOptions.returnAfterWrite) {
393
394
  if (callOptions.expectedTypes?.length) {
@@ -447,16 +448,30 @@ export class ProtocolV2Session {
447
448
  return withProtocolTimeout(
448
449
  runCall(),
449
450
  timeoutMs,
450
- () =>
451
- createTimeoutError
452
- ? createTimeoutError(name, timeoutMs)
453
- : new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`),
451
+ () => this.createCallTimeoutError(name, timeoutMs),
454
452
  () => {
455
453
  abortController.abort();
456
454
  }
457
455
  );
458
456
  }
459
457
 
458
+ private encodeRequestFrame(name: string, data: Record<string, unknown>) {
459
+ const {
460
+ schemas,
461
+ router,
462
+ packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND,
463
+ maxFrameBytes,
464
+ } = this.options;
465
+ const seq = this.sequenceCursor.next();
466
+ const frame = ProtocolV2.encodeFrame(schemas, name, data, { packetSrc, router, seq });
467
+ if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
468
+ throw new Error(
469
+ `Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`
470
+ );
471
+ }
472
+ return { seq, frame };
473
+ }
474
+
460
475
  /**
461
476
  * Validates one received frame for this session. Returns undefined for an ACK of one of
462
477
  * `ackSequences`, otherwise the decoded response.
@@ -537,9 +552,7 @@ export class ProtocolV2Session {
537
552
  * answers every chunk in order, so responses are matched to chunks by position. Upstream
538
553
  * bootloaders append at their own cursor without checking the request offset, so the echoed
539
554
  * offset is what exposes a lost chunk. A device Failure, or `next` returning undefined, stops
540
- * sending; the remaining in-flight responses are drained so the link stays usable. The call
541
- * then returns the Failure like a serial call, or the last confirmation, which the caller
542
- * checks for completeness.
555
+ * sending; the remaining in-flight responses are drained so the link stays usable.
543
556
  */
544
557
  private async executeFileWritePipeline(
545
558
  name: string,
@@ -553,23 +566,8 @@ export class ProtocolV2Session {
553
566
  if (this.pendingResponseAfterWrite) {
554
567
  throw new Error('Protocol V2 file write pipeline cannot start while a response is pending');
555
568
  }
556
- const {
557
- schemas,
558
- router,
559
- packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND,
560
- maxFrameBytes,
561
- prepareCall,
562
- writeFrame,
563
- readFrame,
564
- logger,
565
- logPrefix = 'ProtocolV2',
566
- createTimeoutError,
567
- generation = 0,
568
- } = this.options;
569
- const timeoutMs =
570
- callOptions.timeoutMs && callOptions.timeoutMs > 0
571
- ? callOptions.timeoutMs
572
- : PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
569
+ const { prepareCall, writeFrame, readFrame, generation = 0 } = this.options;
570
+ const timeoutMs = resolveProtocolV2CallTimeoutMs(callOptions.timeoutMs);
573
571
  const abortController = new AbortController();
574
572
  const context: ProtocolV2CallContext = {
575
573
  messageName: name,
@@ -581,27 +579,28 @@ export class ProtocolV2Session {
581
579
  generation,
582
580
  signal: abortController.signal,
583
581
  };
582
+ // Transport errors and timeouts propagate unchanged: each transport classifies its own.
583
+ let transportFailure: unknown;
584
584
  // One file can take far longer than one chunk, so the watchdog bounds each step instead.
585
585
  const withStepTimeout = <T>(promise: Promise<T>, stepTimeoutMs = timeoutMs) =>
586
586
  withProtocolTimeout(
587
587
  promise,
588
588
  stepTimeoutMs,
589
- () =>
590
- createTimeoutError
591
- ? createTimeoutError(name, timeoutMs)
592
- : new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`),
589
+ () => this.createCallTimeoutError(name, timeoutMs),
593
590
  () => {
594
591
  abortController.abort();
595
592
  }
596
- );
593
+ ).catch((error: unknown) => {
594
+ transportFailure = error;
595
+ throw error;
596
+ });
597
597
  // ACKs and skipped frames must not extend the wait for an in-flight chunk's response.
598
598
  let progressDeadline = 0;
599
599
  const markProgress = () => {
600
600
  progressDeadline = Date.now() + timeoutMs;
601
601
  };
602
602
 
603
- const inFlight: Array<{ seq: number; path: string; offset: number; end: number }> = [];
604
- let firstRequest: Record<string, unknown> | undefined = data;
603
+ const inFlight: Array<{ seq: number; offset: number; end: number }> = [];
605
604
  let path: string | undefined;
606
605
  let totalSize: number | undefined;
607
606
  let nextOffset: number | undefined;
@@ -609,112 +608,106 @@ export class ProtocolV2Session {
609
608
  let failure: MessageFromOneKey | undefined;
610
609
  let lastConfirmed: MessageFromOneKey | undefined;
611
610
 
612
- const sendNextChunk = async () => {
613
- const request = firstRequest ?? (await pipeline.next());
614
- firstRequest = undefined;
615
- if (!request) {
616
- sourceDone = true;
617
- return;
611
+ const sendChunk = async (request: Record<string, unknown>) => {
612
+ const chunk = parseFileWriteChunk(request);
613
+ if (path !== undefined && (chunk.path !== path || chunk.totalSize !== totalSize)) {
614
+ throw new Error('Protocol V2 file write pipeline chunk belongs to a different file');
618
615
  }
619
- const chunk = readFileWriteChunk(request);
620
- if (
621
- (path !== undefined && chunk.path !== path) ||
622
- (totalSize !== undefined && chunk.totalSize !== totalSize) ||
623
- (nextOffset !== undefined && chunk.offset !== nextOffset)
624
- ) {
616
+ if (nextOffset !== undefined && chunk.offset !== nextOffset) {
625
617
  throw new Error(
626
- `Protocol V2 file write pipeline chunk out of order: expected offset ${String(
627
- nextOffset
628
- )}, got ${chunk.offset}`
618
+ `Protocol V2 file write pipeline chunk out of order: expected offset ${nextOffset}, got ${chunk.offset}`
629
619
  );
630
620
  }
631
621
  path = chunk.path;
632
622
  totalSize = chunk.totalSize;
633
623
  nextOffset = chunk.end;
634
624
 
635
- const seq = this.sequenceCursor.next();
636
- const frame = ProtocolV2.encodeFrame(schemas, name, request, {
637
- packetSrc,
638
- router,
639
- seq,
640
- });
641
- if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
642
- throw new Error(
643
- `Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`
644
- );
645
- }
625
+ const { seq, frame } = this.encodeRequestFrame(name, request);
646
626
  const writeStartedAt = Date.now();
647
627
  await withStepTimeout(this.serializeWrite(() => writeFrame(frame, context)));
648
- inFlight.push({ seq, path: chunk.path, offset: chunk.offset, end: chunk.end });
628
+ inFlight.push({ seq, offset: chunk.offset, end: chunk.end });
649
629
  markProgress();
650
- try {
651
- callOptions.onWriteCompleted?.({
652
- elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
653
- frameBytes: frame.byteLength,
654
- });
655
- } catch (error) {
656
- logger?.error?.(`${logPrefix} write metrics callback failed: ${String(error)}`);
630
+ this.reportWriteCompleted(callOptions, writeStartedAt, frame.byteLength);
631
+ };
632
+ const sendNextChunk = async () => {
633
+ const request = await pipeline.next();
634
+ if (!request) {
635
+ sourceDone = true;
636
+ return;
657
637
  }
638
+ await sendChunk(request);
658
639
  };
659
640
 
660
641
  await withStepTimeout(Promise.resolve().then(() => prepareCall?.(context)));
661
- while (!sourceDone && inFlight.length < PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW) {
662
- await sendNextChunk();
663
- }
642
+ try {
643
+ await sendChunk(data);
644
+ while (!sourceDone && inFlight.length < PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW) {
645
+ await sendNextChunk();
646
+ }
664
647
 
665
- while (inFlight.length > 0) {
666
- const rxFrame = await withStepTimeout(
667
- readFrame(context),
668
- Math.max(progressDeadline - Date.now(), 1)
669
- );
670
- const response = this.parseResponseFrame(
671
- rxFrame,
672
- inFlight.map(item => item.seq)
673
- );
674
- if (!response) {
675
- // ACK of an in-flight chunk; its FilesystemFile response follows.
676
- } else if (response.type === 'FilesystemFile') {
677
- const [expected] = inFlight;
678
- const { message } = response;
679
- if (
680
- Number(message.processed_byte) !== expected.end ||
681
- message.path !== expected.path ||
682
- Number(message.offset) !== expected.offset ||
683
- Number(message.total_size) !== totalSize
684
- ) {
648
+ while (inFlight.length > 0) {
649
+ // Floor at 1 ms: withProtocolTimeout treats 0 as "no timeout", so an expired deadline
650
+ // must still time out.
651
+ const rxFrame = await withStepTimeout(
652
+ readFrame(context),
653
+ Math.max(progressDeadline - Date.now(), 1)
654
+ );
655
+ const response = this.parseResponseFrame(
656
+ rxFrame,
657
+ inFlight.map(item => item.seq)
658
+ );
659
+ if (!response) {
660
+ // ACK of an in-flight chunk; its FilesystemFile response follows.
661
+ } else if (response.type === 'FilesystemFile') {
662
+ const [expected] = inFlight;
663
+ const { message } = response;
664
+ if (
665
+ Number(message.processed_byte) !== expected.end ||
666
+ message.path !== path ||
667
+ Number(message.offset) !== expected.offset ||
668
+ Number(message.total_size) !== totalSize
669
+ ) {
670
+ throw new ProtocolV2LinkError(
671
+ 'frame',
672
+ `Protocol V2 file write response does not match chunk ${expected.offset}-${
673
+ expected.end
674
+ }: got offset ${String(message.offset)} processed_byte ${String(
675
+ message.processed_byte
676
+ )}`
677
+ );
678
+ }
679
+ inFlight.shift();
680
+ markProgress();
681
+ if (!failure) {
682
+ lastConfirmed = response;
683
+ pipeline.onResponse(response);
684
+ if (!sourceDone) {
685
+ await sendNextChunk();
686
+ }
687
+ }
688
+ } else if (response.type === 'Failure') {
689
+ inFlight.shift();
690
+ markProgress();
691
+ failure = failure ?? response;
692
+ sourceDone = true;
693
+ } else if (COMMON_TERMINAL_RESPONSE_TYPES.has(response.type)) {
685
694
  throw new ProtocolV2LinkError(
686
695
  'frame',
687
- `Protocol V2 file write response does not match chunk ${expected.offset}-${
688
- expected.end
689
- }: got offset ${String(message.offset)} processed_byte ${String(
690
- message.processed_byte
691
- )}`
696
+ `Protocol V2 file write pipeline received unexpected ${response.type}`
692
697
  );
693
698
  }
694
- inFlight.shift();
695
- markProgress();
696
- if (!failure) {
697
- lastConfirmed = response;
698
- pipeline.onResponse(response);
699
- if (!sourceDone) {
700
- await sendNextChunk();
701
- }
702
- }
703
- } else if (response.type === 'Failure') {
704
- inFlight.shift();
705
- markProgress();
706
- failure = failure ?? response;
707
- sourceDone = true;
708
- } else if (COMMON_TERMINAL_RESPONSE_TYPES.has(response.type)) {
709
- throw new ProtocolV2LinkError(
710
- 'frame',
711
- `Protocol V2 file write pipeline received unexpected ${response.type}`
712
- );
713
- } else {
714
- logger?.debug?.(
715
- `[${logPrefix}] skip unexpected response for ${name}: expected=FilesystemFile got=${response.type}`
716
- );
717
699
  }
700
+ } catch (error) {
701
+ // Unread responses of in-flight chunks would answer the next call on this link, so a
702
+ // session or caller error raised with chunks in flight must invalidate it.
703
+ if (inFlight.length === 0 || error === transportFailure || isProtocolV2LinkError(error)) {
704
+ throw error;
705
+ }
706
+ throw new ProtocolV2LinkError(
707
+ 'frame',
708
+ `Protocol V2 file write pipeline aborted with chunks in flight: ${getErrorMessage(error)}`,
709
+ error
710
+ );
718
711
  }
719
712
 
720
713
  const terminal = failure ?? lastConfirmed;
@@ -34,6 +34,12 @@ export type OneKeyDeviceInfoWithSession = OneKeyUsbDeviceInfo & {
34
34
  export type OneKeyMobileDeviceInfo = {
35
35
  id: string;
36
36
  name: string | null;
37
+ /**
38
+ * Color letter of the serial number ('A'-'Z') from the Pro2 / Neo BLE advertisement,
39
+ * possibly seen in an earlier scan. Set by the React Native and Electron BLE
40
+ * transports; absent when unknown.
41
+ */
42
+ bleColorCode?: string;
37
43
  };
38
44
 
39
45
  export type OneKeyDeviceInfoBase = {