@onekeyfe/hd-core 1.2.0-alpha.106 → 1.2.0-alpha.107

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 (61) hide show
  1. package/__tests__/DeviceCommands.test.ts +28 -100
  2. package/__tests__/device-lifecycle-events.test.ts +2 -113
  3. package/__tests__/device-pool-state.test.ts +0 -25
  4. package/__tests__/deviceSettings.test.ts +1 -0
  5. package/__tests__/deviceUploadNft.test.ts +4 -33
  6. package/__tests__/get-device-state.test.ts +13 -52
  7. package/__tests__/logBlockEvent.test.ts +1 -13
  8. package/__tests__/protocol-v2.test.ts +121 -758
  9. package/__tests__/refresh-device-state.test.ts +1 -3
  10. package/__tests__/search-devices.test.ts +38 -32
  11. package/dist/api/FirmwareUpdateV4.d.ts +3 -10
  12. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  13. package/dist/api/UploadPortfolio.d.ts +1 -1
  14. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  15. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +3 -2
  16. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  17. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +3 -2
  18. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  19. package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
  20. package/dist/core/index.d.ts.map +1 -1
  21. package/dist/device/Device.d.ts +2 -7
  22. package/dist/device/Device.d.ts.map +1 -1
  23. package/dist/device/DeviceCommands.d.ts.map +1 -1
  24. package/dist/device/DevicePool.d.ts.map +1 -1
  25. package/dist/events/logBlockEvent.d.ts.map +1 -1
  26. package/dist/index.d.ts +16 -12
  27. package/dist/index.js +201 -446
  28. package/dist/protocols/protocol-v2/features.d.ts +1 -9
  29. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  30. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  31. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  32. package/dist/types/api/protocolV2.d.ts +1 -1
  33. package/dist/types/api/protocolV2.d.ts.map +1 -1
  34. package/dist/types/device.d.ts.map +1 -1
  35. package/dist/utils/pro2Nft.d.ts +0 -7
  36. package/dist/utils/pro2Nft.d.ts.map +1 -1
  37. package/package.json +4 -6
  38. package/src/api/FirmwareUpdateV4.ts +174 -312
  39. package/src/api/SearchDevices.ts +2 -2
  40. package/src/api/UploadPortfolio.ts +2 -9
  41. package/src/api/protocol-v2/DeviceUploadNft.ts +8 -56
  42. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +18 -37
  43. package/src/api/ton/TonSignMessage.ts +3 -5
  44. package/src/core/index.ts +2 -6
  45. package/src/device/Device.ts +31 -53
  46. package/src/device/DeviceCommands.ts +14 -4
  47. package/src/device/DevicePool.ts +2 -6
  48. package/src/events/logBlockEvent.ts +1 -14
  49. package/src/protocols/protocol-v2/features.ts +2 -19
  50. package/src/protocols/protocol-v2/index.ts +0 -2
  51. package/src/types/api/protocolV2.ts +1 -1
  52. package/src/types/device.ts +2 -1
  53. package/src/utils/deviceSettings.ts +1 -1
  54. package/src/utils/pro2Nft.ts +8 -31
  55. package/__tests__/base64Data.test.ts +0 -70
  56. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +0 -145
  57. package/__tests__/resourceBase64Boundary.test.ts +0 -48
  58. package/__tests__/ton-sign-message.test.ts +0 -84
  59. package/dist/api/helpers/base64Data.d.ts +0 -16
  60. package/dist/api/helpers/base64Data.d.ts.map +0 -1
  61. package/src/api/helpers/base64Data.ts +0 -85
@@ -109,34 +109,6 @@ 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
- }
140
112
  const titleLength = typeof title === 'string' ? utf8Length(title) : 0;
141
113
  const subtitleLength =
142
114
  typeof subtitle === 'string' ? utf8Length(subtitle) : Number.POSITIVE_INFINITY;
@@ -150,16 +122,21 @@ export function buildPro2NftBundleFromEncodedImages(options: {
150
122
  throw invalidParameter('Parameter [timestampMs] must be a positive safe integer.');
151
123
  }
152
124
 
125
+ const encodedImage = encodePro2Image({ ...image, alphaMode: 'black-background' }).data;
126
+ const encodedThumbnail = encodePro2Image({
127
+ ...thumbnail,
128
+ alphaMode: 'black-background',
129
+ }).data;
153
130
  const metadata = new TextEncoder().encode(JSON.stringify({ title, subtitle }));
154
131
  if (metadata.byteLength === 0 || metadata.byteLength > 512) {
155
132
  throw invalidParameter('Pro2 NFT metadata must contain 1 to 512 UTF-8 bytes.');
156
133
  }
157
134
 
158
- const hash8 = bytesToHex(blake2s(image)).slice(0, 8);
135
+ const hash8 = bytesToHex(blake2s(encodedImage)).slice(0, 8);
159
136
  return {
160
137
  basename: `nft-${hash8}-${timestampMs}`,
161
- image,
162
- thumbnail,
138
+ image: encodedImage,
139
+ thumbnail: encodedThumbnail,
163
140
  metadata,
164
141
  };
165
142
  }
@@ -1,70 +0,0 @@
1
- import { Buffer } from 'buffer';
2
-
3
- import { encode as encodeJpeg } from 'jpeg-js';
4
-
5
- import { decodeCanonicalBase64, decodeJpegBase64ToRgba } from '../src/api/helpers/base64Data';
6
-
7
- const createJpegBase64 = (width: number, height: number) => {
8
- const rgba = new Uint8Array(width * height * 4).fill(0xff);
9
- return encodeJpeg({ width, height, data: rgba }, 80).data.toString('base64');
10
- };
11
-
12
- describe('Base64 resource data', () => {
13
- test('decodes canonical Base64 to a detached Uint8Array', () => {
14
- const decoded = decodeCanonicalBase64({
15
- value: 'AQID',
16
- parameterName: 'packageBase64',
17
- maxBytes: 3,
18
- });
19
-
20
- expect(decoded).toBeInstanceOf(Uint8Array);
21
- expect(Array.from(decoded)).toEqual([1, 2, 3]);
22
- });
23
-
24
- test.each(['', 'not-base64', 'AB==', 'data:image/jpeg;base64,AQID'])(
25
- 'rejects non-canonical Base64 input %p',
26
- value => {
27
- expect(() =>
28
- decodeCanonicalBase64({ value, parameterName: 'data', maxBytes: 1024 })
29
- ).toThrow();
30
- }
31
- );
32
-
33
- test('rejects oversized Base64 before decoding', () => {
34
- const value = Buffer.alloc(64 * 1024 + 17).toString('base64');
35
- expect(() =>
36
- decodeCanonicalBase64({ value, parameterName: 'data', maxBytes: 64 * 1024 })
37
- ).toThrow('maximum supported size');
38
- });
39
-
40
- test('decodes and validates a JPEG with the expected dimensions', () => {
41
- const decoded = decodeJpegBase64ToRgba({
42
- jpegBase64: createJpegBase64(2, 1),
43
- parameterName: 'jpegBase64',
44
- expectedWidth: 2,
45
- expectedHeight: 1,
46
- });
47
-
48
- expect(decoded).toMatchObject({ width: 2, height: 1 });
49
- expect(decoded.data).toHaveLength(8);
50
- });
51
-
52
- test('rejects non-JPEG bytes and unexpected dimensions', () => {
53
- expect(() =>
54
- decodeJpegBase64ToRgba({
55
- jpegBase64: 'AQID',
56
- parameterName: 'jpegBase64',
57
- expectedWidth: 2,
58
- expectedHeight: 1,
59
- })
60
- ).toThrow('JPEG image');
61
- expect(() =>
62
- decodeJpegBase64ToRgba({
63
- jpegBase64: createJpegBase64(2, 1),
64
- parameterName: 'jpegBase64',
65
- expectedWidth: 1,
66
- expectedHeight: 1,
67
- })
68
- ).toThrow('1x1');
69
- });
70
- });
@@ -1,145 +0,0 @@
1
- import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
-
3
- import FirmwareUpdateV4 from '../../src/api/FirmwareUpdateV4';
4
- import { DataManager } from '../../src/data-manager';
5
-
6
- import type { Device } from '../../src/device/Device';
7
-
8
- jest.mock('../../src/data/config', () => ({
9
- DEFAULT_DOMAIN: 'https://example.com/',
10
- getSDKVersion: () => '0.0.0-test',
11
- }));
12
-
13
- describe('FirmwareUpdateV4 install polling', () => {
14
- let getSettingsSpy: jest.SpyInstance;
15
-
16
- beforeEach(() => {
17
- getSettingsSpy = jest
18
- .spyOn(DataManager, 'getSettings')
19
- .mockReturnValue('react-native' as never);
20
- });
21
-
22
- afterEach(() => {
23
- jest.restoreAllMocks();
24
- });
25
-
26
- test('polls status instead of replaying install after React Native BLE releases', async () => {
27
- const method = new FirmwareUpdateV4({
28
- id: 1,
29
- payload: {
30
- method: 'firmwareUpdateV4',
31
- connectId: 'pro2-ble',
32
- },
33
- });
34
- const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
35
- const typedCall = jest
36
- .fn()
37
- .mockRejectedValueOnce(new Error('React Native BLE transport released'))
38
- .mockResolvedValueOnce({
39
- message: {
40
- records: [
41
- {
42
- target_id: 4,
43
- status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
44
- path: 'vol0:/application_p1.bin',
45
- },
46
- ],
47
- },
48
- });
49
-
50
- method.device = {
51
- getCommands: () => ({ typedCall }),
52
- } as unknown as Device;
53
- method.postTipMessage = jest.fn();
54
- method.postProgressMessage = jest.fn();
55
-
56
- const firmwareUpdate = method as unknown as {
57
- protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<unknown>;
58
- waitForProtocolV2FirmwareUpdateComplete: (value: typeof targets) => Promise<void>;
59
- reconnectProtocolV2Device: () => Promise<void>;
60
- verifyProtocolV2ReconnectIdentity: () => Promise<Record<string, never>>;
61
- };
62
- firmwareUpdate.reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
63
- firmwareUpdate.verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue({});
64
-
65
- await expect(
66
- firmwareUpdate.protocolV2StartFirmwareUpdate({ targets })
67
- ).resolves.toBeUndefined();
68
- await expect(
69
- firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets)
70
- ).resolves.toBeUndefined();
71
-
72
- expect(typedCall).toHaveBeenCalledTimes(2);
73
- expect(typedCall.mock.calls[0]?.[0]).toBe('DeviceFirmwareUpdateRequest');
74
- expect(typedCall.mock.calls[1]?.[0]).toBe('DeviceFirmwareUpdateStatusGet');
75
- });
76
-
77
- test('does not treat an explicit workflow cancellation as an install reboot', async () => {
78
- const method = new FirmwareUpdateV4({
79
- id: 1,
80
- payload: {
81
- method: 'firmwareUpdateV4',
82
- connectId: 'pro2-ble',
83
- },
84
- });
85
- const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
86
- const typedCall = jest.fn().mockRejectedValue(new Error('React Native BLE transport released'));
87
- const abortController = new AbortController();
88
- abortController.abort();
89
-
90
- method.abortSignal = abortController.signal;
91
- method.device = {
92
- getCommands: () => ({ typedCall }),
93
- } as unknown as Device;
94
-
95
- await expect(
96
- (
97
- method as unknown as {
98
- protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<unknown>;
99
- }
100
- ).protocolV2StartFirmwareUpdate({ targets })
101
- ).rejects.toMatchObject({
102
- errorCode: HardwareErrorCode.CallQueueActionCancelled,
103
- });
104
-
105
- expect(typedCall).toHaveBeenCalledTimes(1);
106
- });
107
-
108
- test.each([
109
- ['webusb', 'React Native BLE transport released'],
110
- ['react-native', 'Connection error has occured: Device disconnected'],
111
- ])(
112
- 'requires both a BLE environment and an explicit transport release signal: %s / %s',
113
- async (env, message) => {
114
- getSettingsSpy.mockReturnValue(env);
115
- const method = new FirmwareUpdateV4({
116
- id: 1,
117
- payload: {
118
- method: 'firmwareUpdateV4',
119
- connectId: 'pro2-device',
120
- },
121
- });
122
- const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
123
- const typedCall = jest.fn().mockRejectedValue(new Error(message));
124
-
125
- method.device = {
126
- getCommands: () => ({ typedCall }),
127
- } as unknown as Device;
128
- method.postTipMessage = jest.fn();
129
- method.postProgressMessage = jest.fn();
130
-
131
- await expect(
132
- (
133
- method as unknown as {
134
- protocolV2StartFirmwareUpdate: (params: {
135
- targets: typeof targets;
136
- }) => Promise<unknown>;
137
- }
138
- ).protocolV2StartFirmwareUpdate({ targets })
139
- ).rejects.toThrow(message);
140
-
141
- expect(method.postTipMessage).not.toHaveBeenCalled();
142
- expect(method.postProgressMessage).not.toHaveBeenCalled();
143
- }
144
- );
145
- });
@@ -1,48 +0,0 @@
1
- import { HardwareTopLevelSdk } from '../src';
2
- import { findMethod } from '../src/api/utils';
3
-
4
- import type { LowLevelCoreApi } from '../src';
5
-
6
- jest.mock('../src/data/config', () => ({
7
- DEFAULT_DOMAIN: 'https://example.com/',
8
- getSDKVersion: () => '0.0.0-test',
9
- }));
10
-
11
- const crossJsonOnlyBridge = (value: unknown): unknown => JSON.parse(JSON.stringify(value));
12
-
13
- describe('Base64 resource boundary', () => {
14
- test('keeps Portfolio, wallpaper and NFT payloads JSON-safe across TopLevel and LowLevel', async () => {
15
- const restoredPayloads: Array<Record<string, any>> = [];
16
- const lowLevelApi = {
17
- call: jest.fn(params => {
18
- const method = findMethod({ id: 1, payload: crossJsonOnlyBridge(params) } as any);
19
- restoredPayloads.push(method.payload);
20
- return Promise.resolve({ success: true, payload: {} });
21
- }),
22
- init: jest.fn(() => Promise.resolve(true)),
23
- } as unknown as LowLevelCoreApi;
24
- const sdk = HardwareTopLevelSdk();
25
- await sdk.init({}, lowLevelApi);
26
-
27
- await sdk.uploadPortfolio('connect-id', { packageBase64: 'AQID' });
28
- await sdk.deviceUploadWallpaper('connect-id', { jpegBase64: '/9j/' });
29
- await sdk.deviceUploadNft('connect-id', {
30
- imageJpegBase64: '/9j/',
31
- thumbnailJpegBase64: '/9j/',
32
- title: 'NFT',
33
- subtitle: '',
34
- });
35
-
36
- expect(restoredPayloads.map(payload => payload.method)).toEqual([
37
- 'uploadPortfolio',
38
- 'deviceUploadWallpaper',
39
- 'deviceUploadNft',
40
- ]);
41
- expect(restoredPayloads[0].packageBase64).toBe('AQID');
42
- expect(restoredPayloads[1].jpegBase64).toBe('/9j/');
43
- expect(restoredPayloads[2]).toMatchObject({
44
- imageJpegBase64: '/9j/',
45
- thumbnailJpegBase64: '/9j/',
46
- });
47
- });
48
- });
@@ -1,84 +0,0 @@
1
- import { EDeviceType } from '@onekeyfe/hd-shared';
2
-
3
- import TonSignMessage from '../src/api/ton/TonSignMessage';
4
-
5
- jest.mock('../src/data/config', () => ({
6
- getSDKVersion: jest.fn(() => '1.0.0'),
7
- DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
8
- }));
9
-
10
- const createMethod = (deviceType: EDeviceType) => {
11
- const method = new TonSignMessage({
12
- id: 1,
13
- payload: {
14
- method: 'tonSignMessage',
15
- },
16
- });
17
-
18
- Object.defineProperty(method, 'device', {
19
- value: {
20
- getCurrentDeviceType: () => deviceType,
21
- },
22
- });
23
-
24
- return method;
25
- };
26
-
27
- describe('TonSignMessage response compatibility', () => {
28
- test.each([EDeviceType.Pro2, EDeviceType.Neo])(
29
- 'uses the corrected signing_message field for %s',
30
- async deviceType => {
31
- const method = createMethod(deviceType);
32
-
33
- await expect(
34
- method.processTxRequest(
35
- {
36
- signature: 'signature',
37
- signing_message: 'serialized-message',
38
- },
39
- ''
40
- )
41
- ).resolves.toEqual({
42
- signature: 'signature',
43
- signing_message: 'serialized-message',
44
- skip_validate: false,
45
- });
46
- }
47
- );
48
-
49
- test('normalizes the legacy Protocol V1 field without removing it', async () => {
50
- const method = createMethod(EDeviceType.Classic1s);
51
-
52
- await expect(
53
- method.processTxRequest(
54
- {
55
- signature: 'signature',
56
- signning_message: 'message-hash',
57
- },
58
- ''
59
- )
60
- ).resolves.toEqual({
61
- signature: 'signature',
62
- signning_message: 'message-hash',
63
- signing_message: 'message-hash',
64
- skip_validate: true,
65
- });
66
- });
67
-
68
- test('skips validation when blind signing omits the signing message', async () => {
69
- const method = createMethod(EDeviceType.Pro2);
70
-
71
- await expect(
72
- method.processTxRequest(
73
- {
74
- signature: 'signature',
75
- },
76
- ''
77
- )
78
- ).resolves.toEqual({
79
- signature: 'signature',
80
- signing_message: undefined,
81
- skip_validate: true,
82
- });
83
- });
84
- });
@@ -1,16 +0,0 @@
1
- export declare function decodeCanonicalBase64({ value, parameterName, maxBytes, }: {
2
- value: unknown;
3
- parameterName: string;
4
- maxBytes: number;
5
- }): Uint8Array;
6
- export declare function decodeJpegBase64ToRgba({ jpegBase64, parameterName, expectedWidth, expectedHeight, }: {
7
- jpegBase64: unknown;
8
- parameterName: string;
9
- expectedWidth: number;
10
- expectedHeight: number;
11
- }): {
12
- width: number;
13
- height: number;
14
- data: Uint8Array;
15
- };
16
- //# sourceMappingURL=base64Data.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"base64Data.d.ts","sourceRoot":"","sources":["../../../src/api/helpers/base64Data.ts"],"names":[],"mappings":"AAUA,wBAAgB,qBAAqB,CAAC,EACpC,KAAK,EACL,aAAa,EACb,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,UAAU,CAmBb;AAED,wBAAgB,sBAAsB,CAAC,EACrC,UAAU,EACV,aAAa,EACb,aAAa,EACb,cAAc,GACf,EAAE;IACD,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAmCtD"}
@@ -1,85 +0,0 @@
1
- import { Buffer } from 'buffer';
2
- import { decode as decodeJpeg } from 'jpeg-js';
3
-
4
- import { invalidParameter } from './filesystemValidation';
5
-
6
- const BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
7
- const JPEG_CONTAINER_OVERHEAD_BYTES = 64 * 1024;
8
- const JPEG_MAX_MEMORY_USAGE_IN_MB = 32;
9
- const JPEG_MAX_RESOLUTION_IN_MP = 1;
10
-
11
- export function decodeCanonicalBase64({
12
- value,
13
- parameterName,
14
- maxBytes,
15
- }: {
16
- value: unknown;
17
- parameterName: string;
18
- maxBytes: number;
19
- }): Uint8Array {
20
- if (typeof value !== 'string' || value.length === 0) {
21
- throw invalidParameter(`Parameter [${parameterName}] must be a non-empty Base64 string.`);
22
- }
23
- if (value.length > Math.ceil(maxBytes / 3) * 4) {
24
- throw invalidParameter(`Parameter [${parameterName}] exceeds the maximum supported size.`);
25
- }
26
- if (value.length % 4 !== 0 || !BASE64_PATTERN.test(value)) {
27
- throw invalidParameter(`Parameter [${parameterName}] must use canonical Base64 encoding.`);
28
- }
29
-
30
- const decoded = Buffer.from(value, 'base64');
31
- if (decoded.byteLength === 0 || decoded.byteLength > maxBytes) {
32
- throw invalidParameter(`Parameter [${parameterName}] exceeds the maximum supported size.`);
33
- }
34
- if (decoded.toString('base64') !== value) {
35
- throw invalidParameter(`Parameter [${parameterName}] must use canonical Base64 encoding.`);
36
- }
37
- return Uint8Array.from(decoded);
38
- }
39
-
40
- export function decodeJpegBase64ToRgba({
41
- jpegBase64,
42
- parameterName,
43
- expectedWidth,
44
- expectedHeight,
45
- }: {
46
- jpegBase64: unknown;
47
- parameterName: string;
48
- expectedWidth: number;
49
- expectedHeight: number;
50
- }): { width: number; height: number; data: Uint8Array } {
51
- const jpegBytes = decodeCanonicalBase64({
52
- value: jpegBase64,
53
- parameterName,
54
- maxBytes: expectedWidth * expectedHeight * 8 + JPEG_CONTAINER_OVERHEAD_BYTES,
55
- });
56
- if (jpegBytes[0] !== 0xff || jpegBytes[1] !== 0xd8) {
57
- throw invalidParameter(`Parameter [${parameterName}] must contain a JPEG image.`);
58
- }
59
-
60
- let decoded: { width: number; height: number; data: Uint8Array };
61
- try {
62
- decoded = decodeJpeg(jpegBytes, {
63
- useTArray: true,
64
- formatAsRGBA: true,
65
- tolerantDecoding: false,
66
- maxResolutionInMP: JPEG_MAX_RESOLUTION_IN_MP,
67
- maxMemoryUsageInMB: JPEG_MAX_MEMORY_USAGE_IN_MB,
68
- });
69
- } catch {
70
- throw invalidParameter(`Parameter [${parameterName}] must contain a valid JPEG image.`);
71
- }
72
-
73
- if (decoded.width !== expectedWidth || decoded.height !== expectedHeight) {
74
- throw invalidParameter(
75
- `Parameter [${parameterName}] must contain a ${expectedWidth}x${expectedHeight} JPEG image.`
76
- );
77
- }
78
- const expectedLength = expectedWidth * expectedHeight * 4;
79
- if (decoded.data.byteLength !== expectedLength) {
80
- throw invalidParameter(
81
- `Decoded parameter [${parameterName}] must contain ${expectedLength} RGBA bytes.`
82
- );
83
- }
84
- return decoded;
85
- }