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

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 (35) 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 +89 -25
  4. package/__tests__/resourceBase64Boundary.test.ts +48 -0
  5. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  6. package/dist/api/UploadPortfolio.d.ts +1 -1
  7. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  8. package/dist/api/helpers/base64Data.d.ts +16 -0
  9. package/dist/api/helpers/base64Data.d.ts.map +1 -0
  10. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
  11. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  12. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
  13. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  14. package/dist/api/utils.d.ts.map +1 -1
  15. package/dist/index.d.ts +4 -12
  16. package/dist/index.js +157 -159
  17. package/dist/topLevelInject.d.ts.map +1 -1
  18. package/dist/types/api/protocolV2.d.ts +1 -1
  19. package/dist/types/api/protocolV2.d.ts.map +1 -1
  20. package/dist/utils/pro2Nft.d.ts +7 -0
  21. package/dist/utils/pro2Nft.d.ts.map +1 -1
  22. package/package.json +6 -4
  23. package/src/api/FirmwareUpdateV4.ts +8 -7
  24. package/src/api/UploadPortfolio.ts +9 -2
  25. package/src/api/helpers/base64Data.ts +85 -0
  26. package/src/api/protocol-v2/DeviceUploadNft.ts +56 -8
  27. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +37 -18
  28. package/src/api/utils.ts +2 -5
  29. package/src/topLevelInject.ts +2 -4
  30. package/src/types/api/protocolV2.ts +1 -1
  31. package/src/utils/pro2Nft.ts +31 -8
  32. package/__tests__/bridgeBinaryPayload.test.ts +0 -173
  33. package/dist/utils/bridgeBinaryPayload.d.ts +0 -3
  34. package/dist/utils/bridgeBinaryPayload.d.ts.map +0 -1
  35. package/src/utils/bridgeBinaryPayload.ts +0 -137
package/dist/index.js CHANGED
@@ -7,13 +7,14 @@ var sha256 = require('@noble/hashes/sha256');
7
7
  var utils = require('@noble/hashes/utils');
8
8
  var semver = require('semver');
9
9
  var hdTransport = require('@onekeyfe/hd-transport');
10
- var buffer = require('buffer');
11
10
  var axios = require('axios');
12
11
  var lodash = require('lodash');
13
12
  var ByteBuffer = require('bytebuffer');
14
13
  var blake2s = require('@noble/hashes/blake2s');
15
14
  var BigNumber = require('bignumber.js');
16
15
  var JSZip = require('jszip');
16
+ var buffer = require('buffer');
17
+ var jpegJs = require('jpeg-js');
17
18
  var sha3 = require('@noble/hashes/sha3');
18
19
  var blake2b = require('@noble/hashes/blake2b');
19
20
 
@@ -2219,132 +2220,14 @@ function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
2219
2220
  }
2220
2221
  }
2221
2222
 
2222
- const BRIDGE_BINARY_PAYLOAD_MARKER = '__onekey_hd_bridge_binary_payload__';
2223
- const BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
2224
- const invalidBinaryPayload = (message) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.CallMethodInvalidParameter, message);
2225
- function isPlainObject(value) {
2226
- if (!value || typeof value !== 'object' || Array.isArray(value))
2227
- return false;
2228
- const prototype = Object.getPrototypeOf(value);
2229
- return prototype === Object.prototype || prototype === null;
2230
- }
2231
- function isArrayBufferValue(value) {
2232
- return Boolean(typeof ArrayBuffer !== 'undefined' &&
2233
- (value instanceof ArrayBuffer ||
2234
- Object.prototype.toString.call(value) === '[object ArrayBuffer]'));
2235
- }
2236
- function isBlobValue(value) {
2237
- return Boolean(typeof Blob !== 'undefined' &&
2238
- (value instanceof Blob || Object.prototype.toString.call(value) === '[object Blob]') &&
2239
- typeof value.arrayBuffer === 'function');
2240
- }
2241
- function readBinaryPayload(value) {
2242
- if (!isPlainObject(value) || !(BRIDGE_BINARY_PAYLOAD_MARKER in value))
2243
- return undefined;
2244
- const payload = value;
2245
- if (payload[BRIDGE_BINARY_PAYLOAD_MARKER] !== 1 ||
2246
- (payload.type !== 'array-buffer' && payload.type !== 'uint8-array') ||
2247
- typeof payload.data !== 'string') {
2248
- throw invalidBinaryPayload('Invalid bridge binary payload');
2249
- }
2250
- return payload;
2251
- }
2252
- function bytesToPayload(bytes, type) {
2253
- return {
2254
- [BRIDGE_BINARY_PAYLOAD_MARKER]: 1,
2255
- data: buffer.Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('base64'),
2256
- type,
2257
- };
2258
- }
2259
- function payloadToBytes(payload) {
2260
- if (payload.data.length % 4 !== 0 || !BASE64_PATTERN.test(payload.data)) {
2261
- throw invalidBinaryPayload('Invalid bridge binary payload data');
2262
- }
2263
- const decoded = buffer.Buffer.from(payload.data, 'base64');
2264
- if (decoded.toString('base64') !== payload.data) {
2265
- throw invalidBinaryPayload('Invalid bridge binary payload data');
2266
- }
2267
- return Uint8Array.from(decoded);
2268
- }
2269
- function encodeValue(value, seen) {
2270
- return __awaiter(this, void 0, void 0, function* () {
2271
- if (isArrayBufferValue(value)) {
2272
- return bytesToPayload(new Uint8Array(value), 'array-buffer');
2273
- }
2274
- if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView(value)) {
2275
- return bytesToPayload(new Uint8Array(value.buffer, value.byteOffset, value.byteLength), 'uint8-array');
2276
- }
2277
- if (isBlobValue(value)) {
2278
- return bytesToPayload(new Uint8Array(yield value.arrayBuffer()), 'uint8-array');
2279
- }
2280
- const encodedPayload = readBinaryPayload(value);
2281
- if (encodedPayload)
2282
- return encodedPayload;
2283
- if (Array.isArray(value)) {
2284
- if (seen.has(value))
2285
- throw invalidBinaryPayload('Circular bridge payload');
2286
- seen.add(value);
2287
- try {
2288
- const encoded = [];
2289
- for (const item of value) {
2290
- encoded.push(yield encodeValue(item, seen));
2291
- }
2292
- return encoded.some((item, index) => item !== value[index]) ? encoded : value;
2293
- }
2294
- finally {
2295
- seen.delete(value);
2296
- }
2297
- }
2298
- if (!isPlainObject(value))
2299
- return value;
2300
- if (seen.has(value))
2301
- throw invalidBinaryPayload('Circular bridge payload');
2302
- seen.add(value);
2303
- try {
2304
- const entries = [];
2305
- for (const [key, item] of Object.entries(value)) {
2306
- entries.push([key, yield encodeValue(item, seen)]);
2307
- }
2308
- return entries.some(([key, item]) => item !== value[key]) ? Object.fromEntries(entries) : value;
2309
- }
2310
- finally {
2311
- seen.delete(value);
2312
- }
2313
- });
2314
- }
2315
- function decodeValue(value) {
2316
- const payload = readBinaryPayload(value);
2317
- if (payload) {
2318
- const bytes = payloadToBytes(payload);
2319
- return payload.type === 'array-buffer' ? bytes.buffer : bytes;
2320
- }
2321
- if (Array.isArray(value)) {
2322
- const decoded = value.map(decodeValue);
2323
- return decoded.some((item, index) => item !== value[index]) ? decoded : value;
2324
- }
2325
- if (!isPlainObject(value))
2326
- return value;
2327
- const entries = Object.entries(value).map(([key, item]) => [key, decodeValue(item)]);
2328
- return entries.some(([key, item]) => item !== value[key]) ? Object.fromEntries(entries) : value;
2329
- }
2330
- function encodeBridgeBinaryPayload(value) {
2331
- return __awaiter(this, void 0, void 0, function* () {
2332
- return encodeValue(value, new WeakSet());
2333
- });
2334
- }
2335
- function decodeBridgeBinaryPayload(value) {
2336
- return decodeValue(value);
2337
- }
2338
-
2339
2223
  const eventEmitter = new events.exports();
2340
2224
  const topLevelInject = () => {
2341
2225
  let lowLevelApi;
2342
- const call = (params) => __awaiter(void 0, void 0, void 0, function* () {
2226
+ const call = (params) => {
2343
2227
  if (!lowLevelApi)
2344
2228
  return Promise.resolve(undefined);
2345
- const encodedParams = yield encodeBridgeBinaryPayload(params);
2346
- return lowLevelApi.call(encodedParams);
2347
- });
2229
+ return lowLevelApi.call(params);
2230
+ };
2348
2231
  const protocolAwareCall = createProtocolAwareCall(call);
2349
2232
  const api = Object.assign(Object.assign({ on: (type, fn) => {
2350
2233
  eventEmitter.on(type, fn);
@@ -42841,18 +42724,14 @@ function getCompletePro2NftBasenames(childFiles) {
42841
42724
  function utf8Length(value) {
42842
42725
  return new TextEncoder().encode(value).byteLength;
42843
42726
  }
42844
- function assertImage(name, image, expectedWidth, expectedHeight) {
42845
- if (image.width !== expectedWidth || image.height !== expectedHeight) {
42846
- throw invalidParameter$1(`Pro2 NFT ${name} dimensions must be ${expectedWidth}x${expectedHeight}.`);
42727
+ function buildPro2NftBundleFromEncodedImages(options) {
42728
+ const { image, thumbnail, title, subtitle, timestampMs } = options;
42729
+ if (!(image instanceof Uint8Array) || image.byteLength === 0) {
42730
+ throw invalidParameter$1('Parameter [image] must contain encoded NFT image data.');
42847
42731
  }
42848
- if (!(image.rgba instanceof ArrayBuffer) && !ArrayBuffer.isView(image.rgba)) {
42849
- throw invalidParameter$1(`Parameter [${name}.rgba] must be an ArrayBuffer or Uint8Array.`);
42732
+ if (!(thumbnail instanceof Uint8Array) || thumbnail.byteLength === 0) {
42733
+ throw invalidParameter$1('Parameter [thumbnail] must contain encoded NFT thumbnail data.');
42850
42734
  }
42851
- }
42852
- function buildPro2NftBundle(options) {
42853
- const { image, thumbnail, title, subtitle, timestampMs } = options;
42854
- assertImage('image', image, PRO2_NFT_IMAGE_WIDTH, PRO2_NFT_IMAGE_HEIGHT);
42855
- assertImage('thumbnail', thumbnail, PRO2_NFT_THUMBNAIL_WIDTH, PRO2_NFT_THUMBNAIL_HEIGHT);
42856
42735
  const titleLength = typeof title === 'string' ? utf8Length(title) : 0;
42857
42736
  const subtitleLength = typeof subtitle === 'string' ? utf8Length(subtitle) : Number.POSITIVE_INFINITY;
42858
42737
  if (titleLength < 1 || titleLength > 63) {
@@ -42864,17 +42743,15 @@ function buildPro2NftBundle(options) {
42864
42743
  if (!Number.isSafeInteger(timestampMs) || timestampMs <= 0) {
42865
42744
  throw invalidParameter$1('Parameter [timestampMs] must be a positive safe integer.');
42866
42745
  }
42867
- const encodedImage = encodePro2Image(Object.assign(Object.assign({}, image), { alphaMode: 'black-background' })).data;
42868
- const encodedThumbnail = encodePro2Image(Object.assign(Object.assign({}, thumbnail), { alphaMode: 'black-background' })).data;
42869
42746
  const metadata = new TextEncoder().encode(JSON.stringify({ title, subtitle }));
42870
42747
  if (metadata.byteLength === 0 || metadata.byteLength > 512) {
42871
42748
  throw invalidParameter$1('Pro2 NFT metadata must contain 1 to 512 UTF-8 bytes.');
42872
42749
  }
42873
- const hash8 = utils.bytesToHex(blake2s.blake2s(encodedImage)).slice(0, 8);
42750
+ const hash8 = utils.bytesToHex(blake2s.blake2s(image)).slice(0, 8);
42874
42751
  return {
42875
42752
  basename: `nft-${hash8}-${timestampMs}`,
42876
- image: encodedImage,
42877
- thumbnail: encodedThumbnail,
42753
+ image,
42754
+ thumbnail,
42878
42755
  metadata,
42879
42756
  };
42880
42757
  }
@@ -53942,8 +53819,9 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
53942
53819
  }
53943
53820
  protocolV2StartFirmwareUpdate({ targets, }) {
53944
53821
  return __awaiter(this, void 0, void 0, function* () {
53945
- const commands = this.device.getCommands();
53946
- const startUpdate = () => commands.typedCall('DeviceFirmwareUpdateRequest', 'Success', { targets }, { timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT });
53822
+ const startUpdate = () => this.device
53823
+ .getCommands()
53824
+ .typedCall('DeviceFirmwareUpdateRequest', 'Success', { targets }, { timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT });
53947
53825
  let response;
53948
53826
  try {
53949
53827
  response = yield startUpdate();
@@ -54094,6 +53972,61 @@ class DeviceGetOnboardingStatus extends BaseMethod {
54094
53972
  }
54095
53973
  }
54096
53974
 
53975
+ const BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
53976
+ const JPEG_CONTAINER_OVERHEAD_BYTES = 64 * 1024;
53977
+ const JPEG_MAX_MEMORY_USAGE_IN_MB = 32;
53978
+ const JPEG_MAX_RESOLUTION_IN_MP = 1;
53979
+ function decodeCanonicalBase64({ value, parameterName, maxBytes, }) {
53980
+ if (typeof value !== 'string' || value.length === 0) {
53981
+ throw invalidParameter$1(`Parameter [${parameterName}] must be a non-empty Base64 string.`);
53982
+ }
53983
+ if (value.length > Math.ceil(maxBytes / 3) * 4) {
53984
+ throw invalidParameter$1(`Parameter [${parameterName}] exceeds the maximum supported size.`);
53985
+ }
53986
+ if (value.length % 4 !== 0 || !BASE64_PATTERN.test(value)) {
53987
+ throw invalidParameter$1(`Parameter [${parameterName}] must use canonical Base64 encoding.`);
53988
+ }
53989
+ const decoded = buffer.Buffer.from(value, 'base64');
53990
+ if (decoded.byteLength === 0 || decoded.byteLength > maxBytes) {
53991
+ throw invalidParameter$1(`Parameter [${parameterName}] exceeds the maximum supported size.`);
53992
+ }
53993
+ if (decoded.toString('base64') !== value) {
53994
+ throw invalidParameter$1(`Parameter [${parameterName}] must use canonical Base64 encoding.`);
53995
+ }
53996
+ return Uint8Array.from(decoded);
53997
+ }
53998
+ function decodeJpegBase64ToRgba({ jpegBase64, parameterName, expectedWidth, expectedHeight, }) {
53999
+ const jpegBytes = decodeCanonicalBase64({
54000
+ value: jpegBase64,
54001
+ parameterName,
54002
+ maxBytes: expectedWidth * expectedHeight * 8 + JPEG_CONTAINER_OVERHEAD_BYTES,
54003
+ });
54004
+ if (jpegBytes[0] !== 0xff || jpegBytes[1] !== 0xd8) {
54005
+ throw invalidParameter$1(`Parameter [${parameterName}] must contain a JPEG image.`);
54006
+ }
54007
+ let decoded;
54008
+ try {
54009
+ decoded = jpegJs.decode(jpegBytes, {
54010
+ useTArray: true,
54011
+ formatAsRGBA: true,
54012
+ tolerantDecoding: false,
54013
+ maxResolutionInMP: JPEG_MAX_RESOLUTION_IN_MP,
54014
+ maxMemoryUsageInMB: JPEG_MAX_MEMORY_USAGE_IN_MB,
54015
+ });
54016
+ }
54017
+ catch (_a) {
54018
+ throw invalidParameter$1(`Parameter [${parameterName}] must contain a valid JPEG image.`);
54019
+ }
54020
+ if (decoded.width !== expectedWidth || decoded.height !== expectedHeight) {
54021
+ throw invalidParameter$1(`Parameter [${parameterName}] must contain a ${expectedWidth}x${expectedHeight} JPEG image.`);
54022
+ }
54023
+ const expectedLength = expectedWidth * expectedHeight * 4;
54024
+ if (decoded.data.byteLength !== expectedLength) {
54025
+ throw invalidParameter$1(`Decoded parameter [${parameterName}] must contain ${expectedLength} RGBA bytes.`);
54026
+ }
54027
+ return decoded;
54028
+ }
54029
+
54097
54030
  const Log$4 = getLogger(exports.LoggerNames.Core);
54098
54031
  const MIN_FILE_CHUNK_SIZE = 64;
54099
54032
  const FILE_TRANSFER_RATE_WINDOW_MS = 1000;
@@ -54370,6 +54303,9 @@ function writeProtocolV2File(options) {
54370
54303
 
54371
54304
  const WALLPAPER_DIRECTORY = 'vol1:/wallpapers';
54372
54305
  const SAFE_FILE_NAME = /^[A-Za-z0-9_-]+(?:\.bin)?$/;
54306
+ const DEVICE_SETTINGS_SET_MESSAGE_TYPE = 60412;
54307
+ const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE$2 = 60805;
54308
+ const FILESYSTEM_DIR_MAKE_MESSAGE_TYPE = 60809;
54373
54309
  function normalizeFileName(fileName, data) {
54374
54310
  if (fileName !== undefined && (!fileName || !SAFE_FILE_NAME.test(fileName))) {
54375
54311
  throw invalidParameter$1('Parameter [fileName] may only contain letters, numbers, underscores, hyphens and an optional .bin suffix.');
@@ -54388,26 +54324,40 @@ class DeviceUploadWallpaper extends BaseMethod {
54388
54324
  return ['V2'];
54389
54325
  }
54390
54326
  init() {
54391
- const { width, height, rgba, fileName, chunkSize } = this.payload;
54392
- if (width !== PRO2_WALLPAPER_WIDTH || height !== PRO2_WALLPAPER_HEIGHT) {
54393
- throw invalidParameter$1(`Pro2 wallpaper dimensions must be ${PRO2_WALLPAPER_WIDTH}x${PRO2_WALLPAPER_HEIGHT}.`);
54394
- }
54395
- if (!(rgba instanceof ArrayBuffer) && !ArrayBuffer.isView(rgba)) {
54396
- throw invalidParameter$1('Parameter [rgba] must be an ArrayBuffer or Uint8Array.');
54397
- }
54327
+ const { jpegBase64, fileName, chunkSize } = this.payload;
54398
54328
  if (chunkSize !== undefined && (!Number.isInteger(chunkSize) || chunkSize <= 0)) {
54399
54329
  throw invalidParameter$1('Parameter [chunkSize] must be a positive integer.');
54400
54330
  }
54401
- const rgbaBytes = rgba instanceof ArrayBuffer
54402
- ? rgba
54403
- : new Uint8Array(rgba.buffer, rgba.byteOffset, rgba.byteLength);
54404
- this.encoded = encodePro2Wallpaper({ width, height, rgba: rgbaBytes });
54331
+ const decoded = decodeJpegBase64ToRgba({
54332
+ jpegBase64,
54333
+ parameterName: 'jpegBase64',
54334
+ expectedWidth: PRO2_WALLPAPER_WIDTH,
54335
+ expectedHeight: PRO2_WALLPAPER_HEIGHT,
54336
+ });
54337
+ this.encoded = encodePro2Wallpaper({
54338
+ width: PRO2_WALLPAPER_WIDTH,
54339
+ height: PRO2_WALLPAPER_HEIGHT,
54340
+ rgba: decoded.data,
54341
+ });
54405
54342
  this.path = `${WALLPAPER_DIRECTORY}/${normalizeFileName(fileName, this.encoded.data)}`;
54406
- this.params = { width, height, rgba: rgbaBytes, fileName, chunkSize };
54343
+ this.params = { jpegBase64, fileName, chunkSize };
54407
54344
  this.unlockPolicy = 'none';
54408
54345
  this.skipForceUpdateCheck = true;
54409
54346
  this.useDevicePassphraseState = false;
54410
54347
  }
54348
+ assertCapabilities() {
54349
+ return __awaiter(this, void 0, void 0, function* () {
54350
+ const protocolInfo = yield this.device.ensureProtocolV2RuntimeContext();
54351
+ const requiredMessageTypes = [
54352
+ DEVICE_SETTINGS_SET_MESSAGE_TYPE,
54353
+ FILESYSTEM_FILE_WRITE_MESSAGE_TYPE$2,
54354
+ FILESYSTEM_DIR_MAKE_MESSAGE_TYPE,
54355
+ ];
54356
+ if (requiredMessageTypes.some(messageType => !supportsProtocolV2Message(protocolInfo, messageType))) {
54357
+ throw hdShared.createDeviceNotSupportMethodError(this.name, this.device.getCurrentFirmwareType());
54358
+ }
54359
+ });
54360
+ }
54411
54361
  ensureDirectory() {
54412
54362
  return __awaiter(this, void 0, void 0, function* () {
54413
54363
  if (this.directoryReady)
@@ -54457,6 +54407,7 @@ class DeviceUploadWallpaper extends BaseMethod {
54457
54407
  const { encoded } = this;
54458
54408
  if (!encoded)
54459
54409
  throw invalidParameter$1('Wallpaper data has not been initialized.');
54410
+ yield this.assertCapabilities();
54460
54411
  yield this.ensureDirectory();
54461
54412
  yield this.upload();
54462
54413
  const response = yield this.device.commands.typedCall('DeviceSettingsSet', 'Success', {
@@ -54481,7 +54432,7 @@ class DeviceUploadNft extends BaseMethod {
54481
54432
  return ['V2'];
54482
54433
  }
54483
54434
  init() {
54484
- const { image, thumbnail, title, subtitle, timestampMs = Date.now(), chunkSize = PRO2_NFT_DEFAULT_CHUNK_SIZE, paceMs = PRO2_NFT_DEFAULT_PACE_MS, timeoutMs = PRO2_NFT_DEFAULT_TIMEOUT_MS, } = this.payload;
54435
+ const { imageJpegBase64, thumbnailJpegBase64, title, subtitle, timestampMs = Date.now(), chunkSize = PRO2_NFT_DEFAULT_CHUNK_SIZE, paceMs = PRO2_NFT_DEFAULT_PACE_MS, timeoutMs = PRO2_NFT_DEFAULT_TIMEOUT_MS, } = this.payload;
54485
54436
  if (!Number.isInteger(chunkSize) ||
54486
54437
  chunkSize < PRO2_NFT_MIN_CHUNK_SIZE ||
54487
54438
  chunkSize > PRO2_NFT_MAX_CHUNK_SIZE) {
@@ -54493,8 +54444,51 @@ class DeviceUploadNft extends BaseMethod {
54493
54444
  if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
54494
54445
  throw invalidParameter$1('Parameter [timeoutMs] must be a positive integer.');
54495
54446
  }
54496
- this.bundle = buildPro2NftBundle({ image, thumbnail, title, subtitle, timestampMs });
54497
- this.params = { image, thumbnail, title, subtitle, timestampMs, chunkSize, paceMs, timeoutMs };
54447
+ const encodedImage = (() => {
54448
+ const decoded = decodeJpegBase64ToRgba({
54449
+ jpegBase64: imageJpegBase64,
54450
+ parameterName: 'imageJpegBase64',
54451
+ expectedWidth: PRO2_NFT_IMAGE_WIDTH,
54452
+ expectedHeight: PRO2_NFT_IMAGE_HEIGHT,
54453
+ });
54454
+ return encodePro2Image({
54455
+ width: PRO2_NFT_IMAGE_WIDTH,
54456
+ height: PRO2_NFT_IMAGE_HEIGHT,
54457
+ rgba: decoded.data,
54458
+ alphaMode: 'black-background',
54459
+ }).data;
54460
+ })();
54461
+ const encodedThumbnail = (() => {
54462
+ const decoded = decodeJpegBase64ToRgba({
54463
+ jpegBase64: thumbnailJpegBase64,
54464
+ parameterName: 'thumbnailJpegBase64',
54465
+ expectedWidth: PRO2_NFT_THUMBNAIL_WIDTH,
54466
+ expectedHeight: PRO2_NFT_THUMBNAIL_HEIGHT,
54467
+ });
54468
+ return encodePro2Image({
54469
+ width: PRO2_NFT_THUMBNAIL_WIDTH,
54470
+ height: PRO2_NFT_THUMBNAIL_HEIGHT,
54471
+ rgba: decoded.data,
54472
+ alphaMode: 'black-background',
54473
+ }).data;
54474
+ })();
54475
+ this.bundle = buildPro2NftBundleFromEncodedImages({
54476
+ image: encodedImage,
54477
+ thumbnail: encodedThumbnail,
54478
+ title,
54479
+ subtitle,
54480
+ timestampMs,
54481
+ });
54482
+ this.params = {
54483
+ imageJpegBase64,
54484
+ thumbnailJpegBase64,
54485
+ title,
54486
+ subtitle,
54487
+ timestampMs,
54488
+ chunkSize,
54489
+ paceMs,
54490
+ timeoutMs,
54491
+ };
54498
54492
  this.unlockPolicy = 'none';
54499
54493
  this.skipForceUpdateCheck = true;
54500
54494
  this.useDevicePassphraseState = false;
@@ -54637,11 +54631,17 @@ class FileWrite extends BaseMethod {
54637
54631
 
54638
54632
  const PORTFOLIO_PENDING_PATH = 'vol1:/portfolio/portfolio.okpkg.pending';
54639
54633
  const PORTFOLIO_CHUNK_SIZE = 2048;
54634
+ const PORTFOLIO_PACKAGE_MAX_BYTES = 128 * 1024;
54640
54635
  const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
54641
54636
  const PORTFOLIO_UPDATE_MESSAGE_TYPE = 61400;
54642
54637
  class UploadPortfolio extends FileWrite {
54643
54638
  init() {
54644
- const { packageBytes, timeoutMs } = this.payload;
54639
+ const { packageBase64, timeoutMs } = this.payload;
54640
+ const packageBytes = decodeCanonicalBase64({
54641
+ value: packageBase64,
54642
+ parameterName: 'packageBase64',
54643
+ maxBytes: PORTFOLIO_PACKAGE_MAX_BYTES,
54644
+ });
54645
54645
  this.payload = Object.assign(Object.assign({}, this.payload), { path: PORTFOLIO_PENDING_PATH, offset: 0, data: packageBytes, chunkSize: PORTFOLIO_CHUNK_SIZE, overwrite: true, append: false, emitProgress: false, timeoutMs });
54646
54646
  super.init();
54647
54647
  this.unlockPolicy = 'none';
@@ -64541,15 +64541,13 @@ var ApiMethods = /*#__PURE__*/Object.freeze({
64541
64541
 
64542
64542
  const publicMethodRegistry = ApiMethods;
64543
64543
  function findMethod(message) {
64544
- const payload = decodeBridgeBinaryPayload(message.payload);
64545
- const normalizedMessage = payload === message.payload ? message : Object.assign(Object.assign({}, message), { payload });
64546
- const { method } = payload;
64544
+ const { method } = message.payload;
64547
64545
  if (typeof method !== 'string') {
64548
64546
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.CallMethodInvalidParameter, 'Method is not set');
64549
64547
  }
64550
64548
  const MethodConstructor = publicMethodRegistry[method];
64551
64549
  if (MethodConstructor) {
64552
- return new MethodConstructor(normalizedMessage);
64550
+ return new MethodConstructor(message);
64553
64551
  }
64554
64552
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.CallMethodInvalidParameter, `Method ${method} is not set`);
64555
64553
  }
@@ -1 +1 @@
1
- {"version":3,"file":"topLevelInject.d.ts","sourceRoot":"","sources":["../src/topLevelInject.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;CACvB;AAID,eAAO,MAAM,cAAc,eAqD1B,CAAC"}
1
+ {"version":3,"file":"topLevelInject.d.ts","sourceRoot":"","sources":["../src/topLevelInject.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;CACvB;AAID,eAAO,MAAM,cAAc,eAoD1B,CAAC"}
@@ -24,7 +24,7 @@ export declare function deviceGetOnboardingStatus(connectId: string, params?: Co
24
24
  export declare function deviceUploadWallpaper(connectId: string, params: CommonParams & DeviceUploadWallpaperParams): Response<DeviceUploadWallpaperResponse>;
25
25
  export declare function deviceUploadNft(connectId: string, params: CommonParams & DeviceUploadNftParams): Response<DeviceUploadNftResponse>;
26
26
  export declare function uploadPortfolio(connectId: string, params: {
27
- packageBytes: ArrayBuffer | Uint8Array | Blob;
27
+ packageBase64: string;
28
28
  timeoutMs?: number | string;
29
29
  }): Response<FileInfo & {
30
30
  portfolioUpdated: true;
@@ -1 +1 @@
1
- {"version":3,"file":"protocolV2.d.ts","sourceRoot":"","sources":["../../../src/types/api/protocolV2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,EACV,2BAA2B,EAC3B,6BAA6B,EAC9B,MAAM,6CAA6C,CAAC;AACrD,OAAO,KAAK,EACV,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,uCAAuC,CAAC;AAG/C,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACzF,YAAY,EACV,2BAA2B,EAC3B,6BAA6B,GAC9B,MAAM,6CAA6C,CAAC;AACrD,YAAY,EACV,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,uCAAuC,CAAC;AAI/C,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAIF,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG;IAEpD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,kBAAkB,CACxC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,wBAAwB,GAChC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAErB,MAAM,CAAC,OAAO,UAAU,YAAY,CAClC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,GAAG,kBAAkB,GACxC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAErB,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAC/C,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,YAAY,GACpB,QAAQ,CAAC,gBAAgB,CAAC,CAAC;AAE9B,MAAM,CAAC,OAAO,UAAU,qBAAqB,CAC3C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,GAAG,2BAA2B,GACjD,QAAQ,CAAC,6BAA6B,CAAC,CAAC;AAE3C,MAAM,CAAC,OAAO,UAAU,eAAe,CACrC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,GAAG,qBAAqB,GAC3C,QAAQ,CAAC,uBAAuB,CAAC,CAAC;AAErC,MAAM,CAAC,OAAO,UAAU,eAAe,CACrC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;IACN,YAAY,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,CAAC;IAC9C,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B,GACA,QAAQ,CAAC,QAAQ,GAAG;IAAE,gBAAgB,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"protocolV2.d.ts","sourceRoot":"","sources":["../../../src/types/api/protocolV2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,EACV,2BAA2B,EAC3B,6BAA6B,EAC9B,MAAM,6CAA6C,CAAC;AACrD,OAAO,KAAK,EACV,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,uCAAuC,CAAC;AAG/C,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACzF,YAAY,EACV,2BAA2B,EAC3B,6BAA6B,GAC9B,MAAM,6CAA6C,CAAC;AACrD,YAAY,EACV,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,uCAAuC,CAAC;AAI/C,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAIF,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG;IAEpD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,kBAAkB,CACxC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,wBAAwB,GAChC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAErB,MAAM,CAAC,OAAO,UAAU,YAAY,CAClC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,GAAG,kBAAkB,GACxC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAErB,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAC/C,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,YAAY,GACpB,QAAQ,CAAC,gBAAgB,CAAC,CAAC;AAE9B,MAAM,CAAC,OAAO,UAAU,qBAAqB,CAC3C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,GAAG,2BAA2B,GACjD,QAAQ,CAAC,6BAA6B,CAAC,CAAC;AAE3C,MAAM,CAAC,OAAO,UAAU,eAAe,CACrC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,GAAG,qBAAqB,GAC3C,QAAQ,CAAC,uBAAuB,CAAC,CAAC;AAErC,MAAM,CAAC,OAAO,UAAU,eAAe,CACrC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;IACN,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B,GACA,QAAQ,CAAC,QAAQ,GAAG;IAAE,gBAAgB,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC"}
@@ -28,4 +28,11 @@ export declare function buildPro2NftBundle(options: {
28
28
  subtitle: string;
29
29
  timestampMs: number;
30
30
  }): Pro2NftBundle;
31
+ export declare function buildPro2NftBundleFromEncodedImages(options: {
32
+ image: Uint8Array;
33
+ thumbnail: Uint8Array;
34
+ title: string;
35
+ subtitle: string;
36
+ timestampMs: number;
37
+ }): Pro2NftBundle;
31
38
  //# sourceMappingURL=pro2Nft.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pro2Nft.d.ts","sourceRoot":"","sources":["../../src/utils/pro2Nft.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,oBAAoB,MAAM,CAAC;AACxC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAC9C,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAChD,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAClD,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAC5C,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,GAAG,WAAW,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,UAAU,CAAC;IACtB,QAAQ,EAAE,UAAU,CAAC;CACtB,CAAC;AAiCF,wBAAgB,2BAA2B,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAiB5E;AAsBD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,YAAY,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,aAAa,CAkChB"}
1
+ {"version":3,"file":"pro2Nft.d.ts","sourceRoot":"","sources":["../../src/utils/pro2Nft.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,oBAAoB,MAAM,CAAC;AACxC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAC9C,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAChD,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAClD,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAC5C,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,GAAG,WAAW,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,UAAU,CAAC;IACtB,QAAQ,EAAE,UAAU,CAAC;CACtB,CAAC;AAiCF,wBAAgB,2BAA2B,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAiB5E;AAsBD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,YAAY,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,aAAa,CAgBhB;AAED,wBAAgB,mCAAmC,CAAC,OAAO,EAAE;IAC3D,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,UAAU,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,aAAa,CAiChB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-core",
3
- "version": "1.2.0-alpha.102",
3
+ "version": "1.2.0-alpha.104",
4
4
  "description": "Core processes and APIs for communicating with OneKey hardware devices.",
5
5
  "author": "OneKey",
6
6
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
@@ -25,11 +25,13 @@
25
25
  "url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
26
26
  },
27
27
  "dependencies": {
28
- "@onekeyfe/hd-shared": "1.2.0-alpha.102",
29
- "@onekeyfe/hd-transport": "1.2.0-alpha.102",
28
+ "@onekeyfe/hd-shared": "1.2.0-alpha.104",
29
+ "@onekeyfe/hd-transport": "1.2.0-alpha.104",
30
30
  "axios": "1.15.2",
31
31
  "bignumber.js": "^9.0.2",
32
+ "buffer": "^6.0.3",
32
33
  "bytebuffer": "^5.0.1",
34
+ "jpeg-js": "^0.4.4",
33
35
  "jszip": "^3.10.1",
34
36
  "parse-uri": "^1.0.7",
35
37
  "semver": "^7.3.7"
@@ -44,5 +46,5 @@
44
46
  "@types/w3c-web-usb": "^1.0.10",
45
47
  "@types/web-bluetooth": "^0.0.21"
46
48
  },
47
- "gitHead": "49f939a3fa496be9bd6763737a98b3ade81a2a45"
49
+ "gitHead": "32d75f15aadfa59e4f02f1acb24dc6e5aada94c8"
48
50
  }
@@ -3068,14 +3068,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
3068
3068
  }: {
3069
3069
  targets: Array<{ target_id: number; path: string }>;
3070
3070
  }) {
3071
- const commands = this.device.getCommands();
3072
3071
  const startUpdate = () =>
3073
- commands.typedCall(
3074
- 'DeviceFirmwareUpdateRequest',
3075
- 'Success',
3076
- { targets },
3077
- { timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT }
3078
- );
3072
+ this.device
3073
+ .getCommands()
3074
+ .typedCall(
3075
+ 'DeviceFirmwareUpdateRequest',
3076
+ 'Success',
3077
+ { targets },
3078
+ { timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT }
3079
+ );
3079
3080
  let response: ProtocolV2FirmwareUpdateStartResponse;
3080
3081
  try {
3081
3082
  response = await startUpdate();
@@ -1,21 +1,28 @@
1
1
  import { createDeviceNotSupportMethodError } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import { supportsProtocolV2Message } from '../protocols/protocol-v2/features';
4
+ import { decodeCanonicalBase64 } from './helpers/base64Data';
4
5
  import FileWrite from './FileWrite';
5
6
 
6
7
  export type UploadPortfolioParams = {
7
- packageBytes: ArrayBuffer | Uint8Array | Blob;
8
+ packageBase64: string;
8
9
  timeoutMs?: number | string;
9
10
  };
10
11
 
11
12
  const PORTFOLIO_PENDING_PATH = 'vol1:/portfolio/portfolio.okpkg.pending';
12
13
  const PORTFOLIO_CHUNK_SIZE = 2048;
14
+ const PORTFOLIO_PACKAGE_MAX_BYTES = 128 * 1024;
13
15
  const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
14
16
  const PORTFOLIO_UPDATE_MESSAGE_TYPE = 61400;
15
17
 
16
18
  export default class UploadPortfolio extends FileWrite {
17
19
  init() {
18
- const { packageBytes, timeoutMs } = this.payload as UploadPortfolioParams;
20
+ const { packageBase64, timeoutMs } = this.payload as UploadPortfolioParams;
21
+ const packageBytes = decodeCanonicalBase64({
22
+ value: packageBase64,
23
+ parameterName: 'packageBase64',
24
+ maxBytes: PORTFOLIO_PACKAGE_MAX_BYTES,
25
+ });
19
26
  this.payload = {
20
27
  ...this.payload,
21
28
  path: PORTFOLIO_PENDING_PATH,
@@ -0,0 +1,85 @@
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
+ }