@onekeyfe/hd-core 1.2.0-alpha.102 → 1.2.0-alpha.103

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.
Files changed (33) hide show
  1. package/__tests__/base64Data.test.ts +70 -0
  2. package/__tests__/deviceUploadNft.test.ts +33 -4
  3. package/__tests__/protocol-v2.test.ts +75 -16
  4. package/__tests__/resourceBase64Boundary.test.ts +48 -0
  5. package/dist/api/UploadPortfolio.d.ts +1 -1
  6. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  7. package/dist/api/helpers/base64Data.d.ts +16 -0
  8. package/dist/api/helpers/base64Data.d.ts.map +1 -0
  9. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
  10. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  11. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
  12. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  13. package/dist/api/utils.d.ts.map +1 -1
  14. package/dist/index.d.ts +4 -12
  15. package/dist/index.js +154 -157
  16. package/dist/topLevelInject.d.ts.map +1 -1
  17. package/dist/types/api/protocolV2.d.ts +1 -1
  18. package/dist/types/api/protocolV2.d.ts.map +1 -1
  19. package/dist/utils/pro2Nft.d.ts +7 -0
  20. package/dist/utils/pro2Nft.d.ts.map +1 -1
  21. package/package.json +6 -4
  22. package/src/api/UploadPortfolio.ts +9 -2
  23. package/src/api/helpers/base64Data.ts +85 -0
  24. package/src/api/protocol-v2/DeviceUploadNft.ts +56 -8
  25. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +37 -18
  26. package/src/api/utils.ts +2 -5
  27. package/src/topLevelInject.ts +2 -4
  28. package/src/types/api/protocolV2.ts +1 -1
  29. package/src/utils/pro2Nft.ts +31 -8
  30. package/__tests__/bridgeBinaryPayload.test.ts +0 -173
  31. package/dist/utils/bridgeBinaryPayload.d.ts +0 -3
  32. package/dist/utils/bridgeBinaryPayload.d.ts.map +0 -1
  33. package/src/utils/bridgeBinaryPayload.ts +0 -137
@@ -7,21 +7,26 @@ import {
7
7
  PRO2_NFT_DEFAULT_PACE_MS,
8
8
  PRO2_NFT_DEFAULT_TIMEOUT_MS,
9
9
  PRO2_NFT_DIRECTORY,
10
+ PRO2_NFT_IMAGE_HEIGHT,
11
+ PRO2_NFT_IMAGE_WIDTH,
10
12
  PRO2_NFT_MAX_CHUNK_SIZE,
11
13
  PRO2_NFT_MAX_ITEMS,
12
14
  PRO2_NFT_MIN_CHUNK_SIZE,
15
+ PRO2_NFT_THUMBNAIL_HEIGHT,
16
+ PRO2_NFT_THUMBNAIL_WIDTH,
13
17
  type Pro2NftBundle,
14
- type Pro2NftImage,
15
- buildPro2NftBundle,
18
+ buildPro2NftBundleFromEncodedImages,
16
19
  getCompletePro2NftBasenames,
17
20
  } from '../../utils/pro2Nft';
21
+ import { encodePro2Image } from '../../utils/pro2Wallpaper';
18
22
  import { BaseMethod } from '../BaseMethod';
23
+ import { decodeJpegBase64ToRgba } from '../helpers/base64Data';
19
24
  import { invalidParameter } from '../helpers/filesystemValidation';
20
25
  import { writeProtocolV2File } from '../helpers/protocolV2FileWrite';
21
26
 
22
27
  export type DeviceUploadNftParams = {
23
- image: Pro2NftImage;
24
- thumbnail: Pro2NftImage;
28
+ imageJpegBase64: string;
29
+ thumbnailJpegBase64: string;
25
30
  title: string;
26
31
  subtitle: string;
27
32
  timestampMs?: number;
@@ -54,8 +59,8 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
54
59
 
55
60
  init() {
56
61
  const {
57
- image,
58
- thumbnail,
62
+ imageJpegBase64,
63
+ thumbnailJpegBase64,
59
64
  title,
60
65
  subtitle,
61
66
  timestampMs = Date.now(),
@@ -79,8 +84,51 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
79
84
  throw invalidParameter('Parameter [timeoutMs] must be a positive integer.');
80
85
  }
81
86
 
82
- this.bundle = buildPro2NftBundle({ image, thumbnail, title, subtitle, timestampMs });
83
- this.params = { image, thumbnail, title, subtitle, timestampMs, chunkSize, paceMs, timeoutMs };
87
+ const encodedImage = (() => {
88
+ const decoded = decodeJpegBase64ToRgba({
89
+ jpegBase64: imageJpegBase64,
90
+ parameterName: 'imageJpegBase64',
91
+ expectedWidth: PRO2_NFT_IMAGE_WIDTH,
92
+ expectedHeight: PRO2_NFT_IMAGE_HEIGHT,
93
+ });
94
+ return encodePro2Image({
95
+ width: PRO2_NFT_IMAGE_WIDTH,
96
+ height: PRO2_NFT_IMAGE_HEIGHT,
97
+ rgba: decoded.data,
98
+ alphaMode: 'black-background',
99
+ }).data;
100
+ })();
101
+ const encodedThumbnail = (() => {
102
+ const decoded = decodeJpegBase64ToRgba({
103
+ jpegBase64: thumbnailJpegBase64,
104
+ parameterName: 'thumbnailJpegBase64',
105
+ expectedWidth: PRO2_NFT_THUMBNAIL_WIDTH,
106
+ expectedHeight: PRO2_NFT_THUMBNAIL_HEIGHT,
107
+ });
108
+ return encodePro2Image({
109
+ width: PRO2_NFT_THUMBNAIL_WIDTH,
110
+ height: PRO2_NFT_THUMBNAIL_HEIGHT,
111
+ rgba: decoded.data,
112
+ alphaMode: 'black-background',
113
+ }).data;
114
+ })();
115
+ this.bundle = buildPro2NftBundleFromEncodedImages({
116
+ image: encodedImage,
117
+ thumbnail: encodedThumbnail,
118
+ title,
119
+ subtitle,
120
+ timestampMs,
121
+ });
122
+ this.params = {
123
+ imageJpegBase64,
124
+ thumbnailJpegBase64,
125
+ title,
126
+ subtitle,
127
+ timestampMs,
128
+ chunkSize,
129
+ paceMs,
130
+ timeoutMs,
131
+ };
84
132
  this.unlockPolicy = 'none';
85
133
  this.skipForceUpdateCheck = true;
86
134
  this.useDevicePassphraseState = false;
@@ -1,10 +1,13 @@
1
1
  import { blake2s } from '@noble/hashes/blake2s';
2
2
  import { bytesToHex } from '@noble/hashes/utils';
3
+ import { createDeviceNotSupportMethodError } from '@onekeyfe/hd-shared';
3
4
 
4
5
  import { BaseMethod } from '../BaseMethod';
6
+ import { decodeJpegBase64ToRgba } from '../helpers/base64Data';
5
7
  import { invalidParameter } from '../helpers/filesystemValidation';
6
8
  import { writeProtocolV2File } from '../helpers/protocolV2FileWrite';
7
9
  import { UI_REQUEST, createUiMessage } from '../../events/ui-request';
10
+ import { supportsProtocolV2Message } from '../../protocols/protocol-v2/features';
8
11
  import {
9
12
  PRO2_WALLPAPER_HEIGHT,
10
13
  PRO2_WALLPAPER_WIDTH,
@@ -13,9 +16,7 @@ import {
13
16
  } from '../../utils/pro2Wallpaper';
14
17
 
15
18
  export type DeviceUploadWallpaperParams = {
16
- width: number;
17
- height: number;
18
- rgba: Uint8Array | ArrayBuffer;
19
+ jpegBase64: string;
19
20
  fileName?: string;
20
21
  chunkSize?: number;
21
22
  };
@@ -29,6 +30,9 @@ export type DeviceUploadWallpaperResponse = {
29
30
 
30
31
  const WALLPAPER_DIRECTORY = 'vol1:/wallpapers';
31
32
  const SAFE_FILE_NAME = /^[A-Za-z0-9_-]+(?:\.bin)?$/;
33
+ const DEVICE_SETTINGS_SET_MESSAGE_TYPE = 60412;
34
+ const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
35
+ const FILESYSTEM_DIR_MAKE_MESSAGE_TYPE = 60809;
32
36
 
33
37
  function normalizeFileName(fileName: string | undefined, data: Uint8Array): string {
34
38
  if (fileName !== undefined && (!fileName || !SAFE_FILE_NAME.test(fileName))) {
@@ -54,31 +58,45 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
54
58
  private path = '';
55
59
 
56
60
  init() {
57
- const { width, height, rgba, fileName, chunkSize } = this.payload;
58
- if (width !== PRO2_WALLPAPER_WIDTH || height !== PRO2_WALLPAPER_HEIGHT) {
59
- throw invalidParameter(
60
- `Pro2 wallpaper dimensions must be ${PRO2_WALLPAPER_WIDTH}x${PRO2_WALLPAPER_HEIGHT}.`
61
- );
62
- }
63
- if (!(rgba instanceof ArrayBuffer) && !ArrayBuffer.isView(rgba)) {
64
- throw invalidParameter('Parameter [rgba] must be an ArrayBuffer or Uint8Array.');
65
- }
61
+ const { jpegBase64, fileName, chunkSize } = this.payload;
66
62
  if (chunkSize !== undefined && (!Number.isInteger(chunkSize) || chunkSize <= 0)) {
67
63
  throw invalidParameter('Parameter [chunkSize] must be a positive integer.');
68
64
  }
69
65
 
70
- const rgbaBytes =
71
- rgba instanceof ArrayBuffer
72
- ? rgba
73
- : new Uint8Array(rgba.buffer, rgba.byteOffset, rgba.byteLength);
74
- this.encoded = encodePro2Wallpaper({ width, height, rgba: rgbaBytes });
66
+ const decoded = decodeJpegBase64ToRgba({
67
+ jpegBase64,
68
+ parameterName: 'jpegBase64',
69
+ expectedWidth: PRO2_WALLPAPER_WIDTH,
70
+ expectedHeight: PRO2_WALLPAPER_HEIGHT,
71
+ });
72
+ this.encoded = encodePro2Wallpaper({
73
+ width: PRO2_WALLPAPER_WIDTH,
74
+ height: PRO2_WALLPAPER_HEIGHT,
75
+ rgba: decoded.data,
76
+ });
75
77
  this.path = `${WALLPAPER_DIRECTORY}/${normalizeFileName(fileName, this.encoded.data)}`;
76
- this.params = { width, height, rgba: rgbaBytes, fileName, chunkSize };
78
+ this.params = { jpegBase64, fileName, chunkSize };
77
79
  this.unlockPolicy = 'none';
78
80
  this.skipForceUpdateCheck = true;
79
81
  this.useDevicePassphraseState = false;
80
82
  }
81
83
 
84
+ private async assertCapabilities() {
85
+ const protocolInfo = await this.device.ensureProtocolV2RuntimeContext();
86
+ const requiredMessageTypes = [
87
+ DEVICE_SETTINGS_SET_MESSAGE_TYPE,
88
+ FILESYSTEM_FILE_WRITE_MESSAGE_TYPE,
89
+ FILESYSTEM_DIR_MAKE_MESSAGE_TYPE,
90
+ ];
91
+ if (
92
+ requiredMessageTypes.some(
93
+ messageType => !supportsProtocolV2Message(protocolInfo, messageType)
94
+ )
95
+ ) {
96
+ throw createDeviceNotSupportMethodError(this.name, this.device.getCurrentFirmwareType());
97
+ }
98
+ }
99
+
82
100
  private async ensureDirectory() {
83
101
  if (this.directoryReady) return;
84
102
  try {
@@ -119,6 +137,7 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
119
137
  async run(): Promise<DeviceUploadWallpaperResponse> {
120
138
  const { encoded } = this;
121
139
  if (!encoded) throw invalidParameter('Wallpaper data has not been initialized.');
140
+ await this.assertCapabilities();
122
141
  await this.ensureDirectory();
123
142
  await this.upload();
124
143
  const response = await this.device.commands.typedCall('DeviceSettingsSet', 'Success', {
package/src/api/utils.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import * as ApiMethods from './index';
4
- import { decodeBridgeBinaryPayload } from '../utils/bridgeBinaryPayload';
5
4
 
6
5
  import type { BaseMethod } from './BaseMethod';
7
6
  import type { IFrameCallMessage } from '../events';
@@ -12,16 +11,14 @@ type MethodConstructor = new (message: IFrameCallMessage & { id?: number }) => B
12
11
  const publicMethodRegistry = ApiMethods as unknown as Record<string, MethodConstructor>;
13
12
 
14
13
  export function findMethod(message: IFrameCallMessage & { id?: number }): BaseMethod<any> {
15
- const payload = decodeBridgeBinaryPayload(message.payload) as IFrameCallMessage['payload'];
16
- const normalizedMessage = payload === message.payload ? message : { ...message, payload };
17
- const { method } = payload;
14
+ const { method } = message.payload;
18
15
  if (typeof method !== 'string') {
19
16
  throw ERRORS.TypedError(HardwareErrorCode.CallMethodInvalidParameter, 'Method is not set');
20
17
  }
21
18
 
22
19
  const MethodConstructor = publicMethodRegistry[method];
23
20
  if (MethodConstructor) {
24
- return new MethodConstructor(normalizedMessage);
21
+ return new MethodConstructor(message);
25
22
  }
26
23
 
27
24
  throw ERRORS.TypedError(
@@ -2,7 +2,6 @@ import EventEmitter from 'events';
2
2
 
3
3
  import { createCoreApi, createProtocolAwareCall } from './inject';
4
4
  import { unregisterFirmwareUpdateHostBinding } from './api/firmware/FirmwareHostBinding';
5
- import { encodeBridgeBinaryPayload } from './utils/bridgeBinaryPayload';
6
5
 
7
6
  import type { ConnectSettings } from './types/settings';
8
7
  import type { CoreApi } from './types/api';
@@ -17,10 +16,9 @@ const eventEmitter = new EventEmitter();
17
16
 
18
17
  export const topLevelInject = () => {
19
18
  let lowLevelApi: LowLevelCoreApi | undefined;
20
- const call = async (params: any) => {
19
+ const call = (params: any) => {
21
20
  if (!lowLevelApi) return Promise.resolve(undefined);
22
- const encodedParams = await encodeBridgeBinaryPayload(params);
23
- return lowLevelApi.call(encodedParams as any);
21
+ return lowLevelApi.call(params);
24
22
  };
25
23
  const protocolAwareCall = createProtocolAwareCall(call);
26
24
  const api: CoreApi = {
@@ -68,7 +68,7 @@ export declare function deviceUploadNft(
68
68
  export declare function uploadPortfolio(
69
69
  connectId: string,
70
70
  params: {
71
- packageBytes: ArrayBuffer | Uint8Array | Blob;
71
+ packageBase64: string;
72
72
  timeoutMs?: number | string;
73
73
  }
74
74
  ): Response<FileInfo & { portfolioUpdated: true }>;
@@ -109,6 +109,34 @@ export function buildPro2NftBundle(options: {
109
109
  const { image, thumbnail, title, subtitle, timestampMs } = options;
110
110
  assertImage('image', image, PRO2_NFT_IMAGE_WIDTH, PRO2_NFT_IMAGE_HEIGHT);
111
111
  assertImage('thumbnail', thumbnail, PRO2_NFT_THUMBNAIL_WIDTH, PRO2_NFT_THUMBNAIL_HEIGHT);
112
+ const encodedImage = encodePro2Image({ ...image, alphaMode: 'black-background' }).data;
113
+ const encodedThumbnail = encodePro2Image({
114
+ ...thumbnail,
115
+ alphaMode: 'black-background',
116
+ }).data;
117
+ return buildPro2NftBundleFromEncodedImages({
118
+ image: encodedImage,
119
+ thumbnail: encodedThumbnail,
120
+ title,
121
+ subtitle,
122
+ timestampMs,
123
+ });
124
+ }
125
+
126
+ export function buildPro2NftBundleFromEncodedImages(options: {
127
+ image: Uint8Array;
128
+ thumbnail: Uint8Array;
129
+ title: string;
130
+ subtitle: string;
131
+ timestampMs: number;
132
+ }): Pro2NftBundle {
133
+ const { image, thumbnail, title, subtitle, timestampMs } = options;
134
+ if (!(image instanceof Uint8Array) || image.byteLength === 0) {
135
+ throw invalidParameter('Parameter [image] must contain encoded NFT image data.');
136
+ }
137
+ if (!(thumbnail instanceof Uint8Array) || thumbnail.byteLength === 0) {
138
+ throw invalidParameter('Parameter [thumbnail] must contain encoded NFT thumbnail data.');
139
+ }
112
140
  const titleLength = typeof title === 'string' ? utf8Length(title) : 0;
113
141
  const subtitleLength =
114
142
  typeof subtitle === 'string' ? utf8Length(subtitle) : Number.POSITIVE_INFINITY;
@@ -122,21 +150,16 @@ export function buildPro2NftBundle(options: {
122
150
  throw invalidParameter('Parameter [timestampMs] must be a positive safe integer.');
123
151
  }
124
152
 
125
- const encodedImage = encodePro2Image({ ...image, alphaMode: 'black-background' }).data;
126
- const encodedThumbnail = encodePro2Image({
127
- ...thumbnail,
128
- alphaMode: 'black-background',
129
- }).data;
130
153
  const metadata = new TextEncoder().encode(JSON.stringify({ title, subtitle }));
131
154
  if (metadata.byteLength === 0 || metadata.byteLength > 512) {
132
155
  throw invalidParameter('Pro2 NFT metadata must contain 1 to 512 UTF-8 bytes.');
133
156
  }
134
157
 
135
- const hash8 = bytesToHex(blake2s(encodedImage)).slice(0, 8);
158
+ const hash8 = bytesToHex(blake2s(image)).slice(0, 8);
136
159
  return {
137
160
  basename: `nft-${hash8}-${timestampMs}`,
138
- image: encodedImage,
139
- thumbnail: encodedThumbnail,
161
+ image,
162
+ thumbnail,
140
163
  metadata,
141
164
  };
142
165
  }
@@ -1,173 +0,0 @@
1
- import { HardwareTopLevelSdk } from '../src';
2
- import { findMethod } from '../src/api/utils';
3
- import {
4
- decodeBridgeBinaryPayload,
5
- encodeBridgeBinaryPayload,
6
- } from '../src/utils/bridgeBinaryPayload';
7
-
8
- import type { LowLevelCoreApi } from '../src';
9
-
10
- jest.mock('../src/data/config', () => ({
11
- DEFAULT_DOMAIN: 'https://example.com/',
12
- getSDKVersion: () => '0.0.0-test',
13
- }));
14
-
15
- const crossJsonOnlyBridge = (value: unknown): unknown => JSON.parse(JSON.stringify(value));
16
-
17
- describe('bridge binary payload', () => {
18
- test('preserves nested ArrayBuffer and sliced Uint8Array values', async () => {
19
- const arrayBuffer = new Uint8Array([1, 2, 3]).buffer;
20
- const backing = new Uint8Array([90, 4, 5, 6, 91]);
21
- const encoded = await encodeBridgeBinaryPayload({
22
- arrayBuffer,
23
- bytes: backing.subarray(1, 4),
24
- });
25
- const decoded = decodeBridgeBinaryPayload(crossJsonOnlyBridge(encoded)) as {
26
- arrayBuffer: ArrayBuffer;
27
- bytes: Uint8Array;
28
- };
29
-
30
- expect(decoded.arrayBuffer).toBeInstanceOf(ArrayBuffer);
31
- expect(Array.from(new Uint8Array(decoded.arrayBuffer))).toEqual([1, 2, 3]);
32
- expect(decoded.bytes).toBeInstanceOf(Uint8Array);
33
- expect(Array.from(decoded.bytes)).toEqual([4, 5, 6]);
34
- });
35
-
36
- test('encodes Blob input as byte data', async () => {
37
- const encoded = await encodeBridgeBinaryPayload(new Blob([new Uint8Array([7, 8])]));
38
- const decoded = decodeBridgeBinaryPayload(crossJsonOnlyBridge(encoded));
39
-
40
- expect(decoded).toBeInstanceOf(Uint8Array);
41
- expect(Array.from(decoded as Uint8Array)).toEqual([7, 8]);
42
- });
43
-
44
- test('routes portfolio, wallpaper and NFT bytes through the top-level boundary', async () => {
45
- const restoredPayloads: Array<Record<string, any>> = [];
46
- const lowLevelApi = {
47
- call: jest.fn(params => {
48
- const wirePayload = crossJsonOnlyBridge(params);
49
- const method = findMethod({ id: 1, payload: wirePayload } as any);
50
- restoredPayloads.push(method.payload);
51
- return Promise.resolve({ success: true, payload: {} });
52
- }),
53
- init: jest.fn(() => Promise.resolve(true)),
54
- } as unknown as LowLevelCoreApi;
55
- const sdk = HardwareTopLevelSdk();
56
- await sdk.init({}, lowLevelApi);
57
-
58
- const portfolioBytes = new Uint8Array([1, 2, 3]).buffer;
59
- const wallpaperBacking = new Uint8Array([90, 4, 5, 6, 91]);
60
- const nftBacking = new Uint8Array([80, 7, 8, 9, 10, 81]);
61
- await sdk.uploadPortfolio('connect-id', { packageBytes: portfolioBytes });
62
- await sdk.deviceUploadWallpaper('connect-id', {
63
- width: 604,
64
- height: 1024,
65
- rgba: wallpaperBacking.subarray(1, 4),
66
- });
67
- await sdk.deviceUploadNft('connect-id', {
68
- image: { width: 540, height: 540, rgba: nftBacking.subarray(1, 3) },
69
- thumbnail: { width: 263, height: 263, rgba: nftBacking.subarray(3, 5) },
70
- title: 'NFT',
71
- subtitle: '',
72
- });
73
-
74
- expect(Array.from(new Uint8Array(restoredPayloads[0].packageBytes))).toEqual([1, 2, 3]);
75
- expect(Array.from(restoredPayloads[1].rgba)).toEqual([4, 5, 6]);
76
- expect(Array.from(restoredPayloads[2].image.rgba)).toEqual([7, 8]);
77
- expect(Array.from(restoredPayloads[2].thumbnail.rgba)).toEqual([9, 10]);
78
- });
79
-
80
- test('routes firmware update binaries through the top-level boundary', async () => {
81
- const restoredPayloads: Array<Record<string, any>> = [];
82
- const lowLevelApi = {
83
- call: jest.fn(params => {
84
- const wirePayload = crossJsonOnlyBridge(params);
85
- const method = findMethod({ id: 1, payload: wirePayload } as any);
86
- restoredPayloads.push(method.payload);
87
- return Promise.resolve({ success: true, payload: {} });
88
- }),
89
- init: jest.fn(() => Promise.resolve(true)),
90
- } as unknown as LowLevelCoreApi;
91
- const sdk = HardwareTopLevelSdk();
92
- await sdk.init({}, lowLevelApi);
93
- const binary = (...bytes: number[]) => Uint8Array.from(bytes).buffer;
94
-
95
- await sdk.firmwareUpdate('connect-id', {
96
- binary: binary(1),
97
- updateType: 'firmware',
98
- });
99
- await sdk.firmwareUpdateV2('connect-id', {
100
- binary: binary(2),
101
- updateType: 'firmware',
102
- platform: 'ext',
103
- });
104
- await sdk.firmwareUpdateV3('connect-id', {
105
- platform: 'ext',
106
- bleBinary: binary(3),
107
- firmwareBinary: binary(4),
108
- bootloaderBinary: binary(5),
109
- resourceBinary: binary(6),
110
- });
111
- await sdk.firmwareUpdateV4('connect-id', {
112
- platform: 'ext',
113
- targetsToUpdate: [
114
- 'boot',
115
- 'app_v1',
116
- 'app_v2',
117
- 'coprocessor',
118
- 'se01',
119
- 'se02',
120
- 'se03',
121
- 'se04',
122
- 'resource',
123
- ],
124
- romloaderBinary: binary(7),
125
- bootloaderBinary: binary(8),
126
- applicationP1Binary: binary(9),
127
- applicationP2Binary: binary(10),
128
- coprocessorBinary: binary(11),
129
- se01Binary: binary(12),
130
- se02Binary: binary(13),
131
- se03Binary: binary(14),
132
- se04Binary: binary(15),
133
- resourceArchiveBinary: binary(16),
134
- });
135
- await sdk.deviceUpdateBootloader('connect-id', { binary: binary(17) });
136
- await sdk.deviceFullyUploadResource('connect-id', { binary: binary(18) });
137
-
138
- expect(restoredPayloads).toHaveLength(6);
139
- const restoredBinaries = [
140
- restoredPayloads[0].binary,
141
- restoredPayloads[1].binary,
142
- restoredPayloads[2].bleBinary,
143
- restoredPayloads[2].firmwareBinary,
144
- restoredPayloads[2].bootloaderBinary,
145
- restoredPayloads[2].resourceBinary,
146
- restoredPayloads[3].romloaderBinary,
147
- restoredPayloads[3].bootloaderBinary,
148
- restoredPayloads[3].applicationP1Binary,
149
- restoredPayloads[3].applicationP2Binary,
150
- restoredPayloads[3].coprocessorBinary,
151
- restoredPayloads[3].se01Binary,
152
- restoredPayloads[3].se02Binary,
153
- restoredPayloads[3].se03Binary,
154
- restoredPayloads[3].se04Binary,
155
- restoredPayloads[3].resourceArchiveBinary,
156
- restoredPayloads[4].binary,
157
- restoredPayloads[5].binary,
158
- ];
159
- expect(restoredBinaries.map(value => Array.from(new Uint8Array(value)))).toEqual(
160
- Array.from({ length: 18 }, (_, index) => [index + 1])
161
- );
162
- });
163
-
164
- test('rejects malformed tagged binary data', () => {
165
- expect(() =>
166
- decodeBridgeBinaryPayload({
167
- __onekey_hd_bridge_binary_payload__: 1,
168
- data: 'not-base64',
169
- type: 'uint8-array',
170
- })
171
- ).toThrow('Invalid bridge binary payload data');
172
- });
173
- });
@@ -1,3 +0,0 @@
1
- export declare function encodeBridgeBinaryPayload(value: unknown): Promise<unknown>;
2
- export declare function decodeBridgeBinaryPayload(value: unknown): unknown;
3
- //# sourceMappingURL=bridgeBinaryPayload.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bridgeBinaryPayload.d.ts","sourceRoot":"","sources":["../../src/utils/bridgeBinaryPayload.ts"],"names":[],"mappings":"AAkIA,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAEhF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEjE"}
@@ -1,137 +0,0 @@
1
- import { Buffer } from 'buffer';
2
- import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
3
-
4
- // Top-level and low-level SDK instances can be separated by JSON-only hosts,
5
- // including browser extension background/offscreen message bridges.
6
- const BRIDGE_BINARY_PAYLOAD_MARKER = '__onekey_hd_bridge_binary_payload__';
7
-
8
- type BridgeBinaryPayload = {
9
- [BRIDGE_BINARY_PAYLOAD_MARKER]: 1;
10
- data: string;
11
- type: 'array-buffer' | 'uint8-array';
12
- };
13
-
14
- const BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
15
-
16
- const invalidBinaryPayload = (message: string) =>
17
- ERRORS.TypedError(HardwareErrorCode.CallMethodInvalidParameter, message);
18
-
19
- function isPlainObject(value: unknown): value is Record<string, unknown> {
20
- if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
21
- const prototype = Object.getPrototypeOf(value);
22
- return prototype === Object.prototype || prototype === null;
23
- }
24
-
25
- function isArrayBufferValue(value: unknown): value is ArrayBuffer {
26
- return Boolean(
27
- typeof ArrayBuffer !== 'undefined' &&
28
- (value instanceof ArrayBuffer ||
29
- Object.prototype.toString.call(value) === '[object ArrayBuffer]')
30
- );
31
- }
32
-
33
- function isBlobValue(value: unknown): value is Blob {
34
- return Boolean(
35
- typeof Blob !== 'undefined' &&
36
- (value instanceof Blob || Object.prototype.toString.call(value) === '[object Blob]') &&
37
- typeof (value as Blob).arrayBuffer === 'function'
38
- );
39
- }
40
-
41
- function readBinaryPayload(value: unknown): BridgeBinaryPayload | undefined {
42
- if (!isPlainObject(value) || !(BRIDGE_BINARY_PAYLOAD_MARKER in value)) return undefined;
43
- const payload = value as Partial<BridgeBinaryPayload>;
44
- if (
45
- payload[BRIDGE_BINARY_PAYLOAD_MARKER] !== 1 ||
46
- (payload.type !== 'array-buffer' && payload.type !== 'uint8-array') ||
47
- typeof payload.data !== 'string'
48
- ) {
49
- throw invalidBinaryPayload('Invalid bridge binary payload');
50
- }
51
- return payload as BridgeBinaryPayload;
52
- }
53
-
54
- function bytesToPayload(bytes: Uint8Array, type: BridgeBinaryPayload['type']): BridgeBinaryPayload {
55
- return {
56
- [BRIDGE_BINARY_PAYLOAD_MARKER]: 1,
57
- data: Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('base64'),
58
- type,
59
- };
60
- }
61
-
62
- function payloadToBytes(payload: BridgeBinaryPayload): Uint8Array {
63
- if (payload.data.length % 4 !== 0 || !BASE64_PATTERN.test(payload.data)) {
64
- throw invalidBinaryPayload('Invalid bridge binary payload data');
65
- }
66
- const decoded = Buffer.from(payload.data, 'base64');
67
- if (decoded.toString('base64') !== payload.data) {
68
- throw invalidBinaryPayload('Invalid bridge binary payload data');
69
- }
70
- return Uint8Array.from(decoded);
71
- }
72
-
73
- async function encodeValue(value: unknown, seen: WeakSet<object>): Promise<unknown> {
74
- if (isArrayBufferValue(value)) {
75
- return bytesToPayload(new Uint8Array(value), 'array-buffer');
76
- }
77
- if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView(value)) {
78
- return bytesToPayload(
79
- new Uint8Array(value.buffer, value.byteOffset, value.byteLength),
80
- 'uint8-array'
81
- );
82
- }
83
- if (isBlobValue(value)) {
84
- return bytesToPayload(new Uint8Array(await value.arrayBuffer()), 'uint8-array');
85
- }
86
-
87
- const encodedPayload = readBinaryPayload(value);
88
- if (encodedPayload) return encodedPayload;
89
- if (Array.isArray(value)) {
90
- if (seen.has(value)) throw invalidBinaryPayload('Circular bridge payload');
91
- seen.add(value);
92
- try {
93
- const encoded: unknown[] = [];
94
- for (const item of value) {
95
- encoded.push(await encodeValue(item, seen));
96
- }
97
- return encoded.some((item, index) => item !== value[index]) ? encoded : value;
98
- } finally {
99
- seen.delete(value);
100
- }
101
- }
102
- if (!isPlainObject(value)) return value;
103
- if (seen.has(value)) throw invalidBinaryPayload('Circular bridge payload');
104
- seen.add(value);
105
- try {
106
- const entries: [string, unknown][] = [];
107
- for (const [key, item] of Object.entries(value)) {
108
- entries.push([key, await encodeValue(item, seen)]);
109
- }
110
- return entries.some(([key, item]) => item !== value[key]) ? Object.fromEntries(entries) : value;
111
- } finally {
112
- seen.delete(value);
113
- }
114
- }
115
-
116
- function decodeValue(value: unknown): unknown {
117
- const payload = readBinaryPayload(value);
118
- if (payload) {
119
- const bytes = payloadToBytes(payload);
120
- return payload.type === 'array-buffer' ? bytes.buffer : bytes;
121
- }
122
- if (Array.isArray(value)) {
123
- const decoded = value.map(decodeValue);
124
- return decoded.some((item, index) => item !== value[index]) ? decoded : value;
125
- }
126
- if (!isPlainObject(value)) return value;
127
- const entries = Object.entries(value).map(([key, item]) => [key, decodeValue(item)] as const);
128
- return entries.some(([key, item]) => item !== value[key]) ? Object.fromEntries(entries) : value;
129
- }
130
-
131
- export async function encodeBridgeBinaryPayload(value: unknown): Promise<unknown> {
132
- return encodeValue(value, new WeakSet());
133
- }
134
-
135
- export function decodeBridgeBinaryPayload(value: unknown): unknown {
136
- return decodeValue(value);
137
- }