@stacks/storage 4.3.6-pr.f9a3196.0 → 4.3.7-beta.1

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.
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@stacks/storage",
3
- "version": "4.3.6-pr.f9a3196.0",
3
+ "version": "4.3.7-beta.1",
4
4
  "description": "Stacks storage library",
5
5
  "license": "MIT",
6
6
  "author": "Hiro Systems PBC (https://hiro.so)",
7
7
  "homepage": "https://hiro.so/stacks-js",
8
8
  "scripts": {
9
- "build": "npm run clean && npm run build:cjs && npm run build:esm && npm run build:umd",
9
+ "build": "npm run clean && npm run build:cjs && npm run build:esm && npm run build:umd && npm run build:polyfill",
10
10
  "build:cjs": "tsc -b tsconfig.build.json",
11
11
  "build:esm": "tsc -p tsconfig.build.json --module ES6 --outDir ./dist/esm",
12
+ "build:polyfill": "NODE_OPTIONS=--max-old-space-size=8192 rollup -c ../../configs/rollup.config.js && rimraf dist/polyfill/dist",
12
13
  "build:umd": "NODE_OPTIONS=--max-old-space-size=8192 webpack --config webpack.config.js",
13
14
  "clean": "rimraf dist && tsc -b tsconfig.build.json --clean",
14
15
  "pack": "npm pack",
@@ -20,12 +21,11 @@
20
21
  "typecheck:watch": "npm run typecheck -- --watch"
21
22
  },
22
23
  "dependencies": {
23
- "@stacks/auth": "^4.3.6-pr.f9a3196.0",
24
- "@stacks/common": "^4.3.6-pr.f9a3196.0",
25
- "@stacks/encryption": "^4.3.6-pr.f9a3196.0",
26
- "@stacks/network": "^4.3.6-pr.f9a3196.0",
27
- "base64-js": "^1.5.1",
28
- "jsontokens": "^4.0.1"
24
+ "@stacks/auth": "^4.3.7-beta.1",
25
+ "@stacks/common": "^4.3.7-beta.1",
26
+ "@stacks/encryption": "^4.3.7-beta.1",
27
+ "@stacks/network": "^4.3.7-beta.1",
28
+ "jsontokens": "^3.1.1"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@stacks/network": "^4.1.0",
@@ -38,7 +38,12 @@
38
38
  "process": "^0.11.10",
39
39
  "rimraf": "^3.0.2",
40
40
  "stream-browserify": "^3.0.0",
41
- "ts-jest": "^26.5.5"
41
+ "ts-jest": "^26.5.5",
42
+ "ts-loader": "^9.1.1",
43
+ "typescript": "^4.2.4",
44
+ "webpack": "^5.36.1",
45
+ "webpack-bundle-analyzer": "^4.5.0",
46
+ "webpack-cli": "^4.6.0"
42
47
  },
43
48
  "sideEffects": false,
44
49
  "typings": "dist/index.d.ts",
@@ -60,5 +65,5 @@
60
65
  "bugs": {
61
66
  "url": "https://github.com/blockstack/blockstack.js/issues"
62
67
  },
63
- "gitHead": "f9a3196694795fdd3327149ac98357d27f65e82f"
68
+ "gitHead": "2d22629a8d0c29b5ba5b29871014c98f40bbda0f"
64
69
  }
@@ -1,5 +1,4 @@
1
- import { utf8ToBytes } from '@stacks/common';
2
-
1
+ import { Buffer } from '@stacks/common';
3
2
  /**
4
3
  * Retrieves the specified file from the app's data store.
5
4
  * @param {String} path - the path to the file to read
@@ -7,11 +6,11 @@ import { utf8ToBytes } from '@stacks/common';
7
6
  * or rejects with an error
8
7
  */
9
8
  export /** @ignore */
10
- type PutFileContent = string | Uint8Array | ArrayBufferView | ArrayBufferLike | Blob;
9
+ type PutFileContent = string | Buffer | ArrayBufferView | ArrayBufferLike | Blob;
11
10
 
12
11
  /** @ignore */
13
12
  export class FileContentLoader {
14
- readonly content: Uint8Array | Blob;
13
+ readonly content: Buffer | Blob;
15
14
 
16
15
  readonly wasString: boolean;
17
16
 
@@ -19,11 +18,11 @@ export class FileContentLoader {
19
18
 
20
19
  readonly contentByteLength: number;
21
20
 
22
- private loadedData?: Promise<Uint8Array>;
21
+ private loadedData?: Promise<Buffer>;
23
22
 
24
23
  static readonly supportedTypesMsg =
25
24
  'Supported types are: `string` (to be UTF8 encoded), ' +
26
- '`Blob`, `File`, `ArrayBuffer`, `UInt8Array` or any other typed array buffer. ';
25
+ '`Buffer`, `Blob`, `File`, `ArrayBuffer`, `UInt8Array` or any other typed array buffer. ';
27
26
 
28
27
  constructor(content: PutFileContent, contentType: string) {
29
28
  this.wasString = typeof content === 'string';
@@ -35,7 +34,7 @@ export class FileContentLoader {
35
34
  private static normalizeContentDataType(
36
35
  content: PutFileContent,
37
36
  contentType: string
38
- ): Uint8Array | Blob {
37
+ ): Buffer | Blob {
39
38
  try {
40
39
  if (typeof content === 'string') {
41
40
  // If a charset is specified it must be either utf8 or ascii, otherwise the encoded content
@@ -50,19 +49,17 @@ export class FileContentLoader {
50
49
  }
51
50
  if (typeof TextEncoder !== 'undefined') {
52
51
  const encodedString = new TextEncoder().encode(content);
53
- return new Uint8Array(encodedString.buffer);
52
+ return Buffer.from(encodedString.buffer);
54
53
  }
55
- return utf8ToBytes(content);
56
- } else if (content instanceof Uint8Array) {
54
+ return Buffer.from(content);
55
+ } else if (Buffer.isBuffer(content)) {
57
56
  return content;
58
57
  } else if (ArrayBuffer.isView(content)) {
59
- return new Uint8Array(
60
- content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength)
61
- );
58
+ return Buffer.from(content.buffer, content.byteOffset, content.byteLength);
62
59
  } else if (typeof Blob !== 'undefined' && content instanceof Blob) {
63
60
  return content;
64
61
  } else if (typeof ArrayBuffer !== 'undefined' && content instanceof ArrayBuffer) {
65
- return new Uint8Array(content);
62
+ return Buffer.from(content);
66
63
  } else if (Array.isArray(content)) {
67
64
  // Provided with a regular number `Array` -- this is either an (old) method
68
65
  // of representing an octet array, or a dev error. Perform basic check for octet array.
@@ -74,7 +71,7 @@ export class FileContentLoader {
74
71
  `Unexpected array values provided as file data: value "${content[0]}" at index 0 is not an octet number. ${this.supportedTypesMsg}`
75
72
  );
76
73
  }
77
- return new Uint8Array(content);
74
+ return Buffer.from(content);
78
75
  } else {
79
76
  const typeName = Object.prototype.toString.call(content);
80
77
  throw new Error(
@@ -98,7 +95,7 @@ export class FileContentLoader {
98
95
  }
99
96
 
100
97
  private detectContentLength(): number {
101
- if (ArrayBuffer.isView(this.content) || this.content instanceof Uint8Array) {
98
+ if (ArrayBuffer.isView(this.content) || Buffer.isBuffer(this.content)) {
102
99
  return this.content.byteLength;
103
100
  } else if (typeof Blob !== 'undefined' && this.content instanceof Blob) {
104
101
  return this.content.size;
@@ -109,29 +106,26 @@ export class FileContentLoader {
109
106
  throw error;
110
107
  }
111
108
 
112
- private async loadContent(): Promise<Uint8Array> {
109
+ private async loadContent(): Promise<Buffer> {
113
110
  try {
114
- if (this.content instanceof Uint8Array) {
111
+ if (Buffer.isBuffer(this.content)) {
115
112
  return this.content;
116
113
  } else if (ArrayBuffer.isView(this.content)) {
117
- return new Uint8Array(
118
- this.content.buffer,
119
- this.content.byteOffset,
120
- this.content.byteLength
121
- );
114
+ return Buffer.from(this.content.buffer, this.content.byteOffset, this.content.byteLength);
122
115
  } else if (typeof Blob !== 'undefined' && this.content instanceof Blob) {
123
116
  const reader = new FileReader();
124
- const readPromise = new Promise<Uint8Array>((resolve, reject) => {
117
+ const readPromise = new Promise<Buffer>((resolve, reject) => {
125
118
  reader.onerror = err => {
126
119
  reject(err);
127
120
  };
128
121
  reader.onload = () => {
129
122
  const arrayBuffer = reader.result as ArrayBuffer;
130
- resolve(new Uint8Array(arrayBuffer));
123
+ resolve(Buffer.from(arrayBuffer));
131
124
  };
132
125
  reader.readAsArrayBuffer(this.content as Blob);
133
126
  });
134
- return await readPromise;
127
+ const result = await readPromise;
128
+ return result;
135
129
  } else {
136
130
  const typeName = Object.prototype.toString.call(this.content);
137
131
  throw new Error(`Unexpected type ${typeName}`);
@@ -144,7 +138,7 @@ export class FileContentLoader {
144
138
  }
145
139
  }
146
140
 
147
- load(): Promise<Uint8Array | string> {
141
+ load(): Promise<Buffer | string> {
148
142
  if (this.loadedData === undefined) {
149
143
  this.loadedData = this.loadContent();
150
144
  }
package/src/hub.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BadPathError,
3
- bytesToHex,
3
+ Buffer,
4
4
  ConflictError,
5
5
  DoesNotExist,
6
6
  GaiaHubErrorResponse,
@@ -9,7 +9,6 @@ import {
9
9
  NotEnoughProofError,
10
10
  PayloadTooLargeError,
11
11
  PreconditionFailedError,
12
- utf8ToBytes,
13
12
  ValidationError,
14
13
  } from '@stacks/common';
15
14
  import {
@@ -22,7 +21,6 @@ import {
22
21
  Signature,
23
22
  } from '@stacks/encryption';
24
23
  import { createFetchFn, FetchFn } from '@stacks/network';
25
- import { fromByteArray } from 'base64-js';
26
24
  import { TokenSigner } from 'jsontokens';
27
25
 
28
26
  /**
@@ -59,7 +57,7 @@ interface UploadResponse {
59
57
  */
60
58
  export async function uploadToGaiaHub(
61
59
  filename: string,
62
- contents: Blob | Uint8Array | ArrayBufferView | string,
60
+ contents: Blob | Buffer | ArrayBufferView | string,
63
61
  hubConfig: GaiaHubConfig,
64
62
  contentType = 'application/octet-stream',
65
63
  newFile = true,
@@ -150,16 +148,19 @@ function makeLegacyAuthToken(challengeText: string, signerKeyHex: string): strin
150
148
  throw new Error('Failed in parsing legacy challenge text from the gaia hub.');
151
149
  }
152
150
  if (parsedChallenge[0] === 'gaiahub' && parsedChallenge[3] === 'blockstack_storage_please_sign') {
153
- const digest = hashSha256Sync(utf8ToBytes(challengeText));
154
- const signatureBytes = ecSign(digest, compressPrivateKey(signerKeyHex));
151
+ const digest = hashSha256Sync(Buffer.from(challengeText));
152
+ const signatureBuffer = ecSign(digest, compressPrivateKey(signerKeyHex));
155
153
  // We only want the DER encoding so use toDERHex provided by @noble/secp256k1
156
- const signature = Signature.fromCompact(bytesToHex(signatureBytes)).toDERHex();
154
+ const signature = Signature.fromCompact(signatureBuffer.toString('hex')).toDERHex();
157
155
 
158
156
  const publickey = getPublicKeyFromPrivate(signerKeyHex);
159
- const token = fromByteArray(utf8ToBytes(JSON.stringify({ publickey, signature })));
157
+ const token = Buffer.from(JSON.stringify({ publickey, signature })).toString('base64');
160
158
  return token;
159
+ } else {
160
+ throw new Error(
161
+ 'Failed to connect to legacy gaia hub. If you operate this hub, please update.'
162
+ );
161
163
  }
162
- throw new Error('Failed to connect to legacy gaia hub. If you operate this hub, please update.');
163
164
  }
164
165
 
165
166
  /**
@@ -186,7 +187,7 @@ function makeV1GaiaAuthToken(
186
187
  return makeLegacyAuthToken(challengeText, signerKeyHex);
187
188
  }
188
189
 
189
- const salt = bytesToHex(randomBytes(16));
190
+ const salt = randomBytes(16).toString('hex');
190
191
  const payload = {
191
192
  gaiaChallenge: challengeText,
192
193
  hubUrl,
package/src/storage.ts CHANGED
@@ -8,14 +8,12 @@ import {
8
8
  megabytesToBytes,
9
9
  PayloadTooLargeError,
10
10
  SignatureVerificationError,
11
- utf8ToBytes,
12
11
  } from '@stacks/common';
13
12
  import {
14
13
  eciesGetJsonStringLength,
15
14
  EncryptionOptions,
16
15
  getPublicKeyFromPrivate,
17
16
  publicKeyToAddress,
18
- publicKeyToBtcAddress,
19
17
  signECDSA,
20
18
  verifyECDSA,
21
19
  } from '@stacks/encryption';
@@ -323,19 +321,20 @@ export class Storage {
323
321
  this.getGaiaAddress(opt.app!, opt.username, opt.zoneFileLookupURL),
324
322
  ]);
325
323
 
326
- if (!fileContents) return fileContents;
327
-
328
- if (!gaiaAddress)
324
+ if (!fileContents) {
325
+ return fileContents;
326
+ }
327
+ if (!gaiaAddress) {
329
328
  throw new SignatureVerificationError(
330
329
  `Failed to get gaia address for verification of: ${path}`
331
330
  );
332
-
333
- if (!signatureContents || typeof signatureContents !== 'string')
331
+ }
332
+ if (!signatureContents || typeof signatureContents !== 'string') {
334
333
  throw new SignatureVerificationError(
335
334
  'Failed to obtain signature for file: ' +
336
335
  `${path} -- looked in ${path}${SIGNATURE_FILE_SUFFIX}`
337
336
  );
338
-
337
+ }
339
338
  let signature;
340
339
  let publicKey;
341
340
  try {
@@ -343,39 +342,40 @@ export class Storage {
343
342
  signature = sigObject.signature;
344
343
  publicKey = sigObject.publicKey;
345
344
  } catch (err) {
346
- if (err instanceof SyntaxError)
345
+ if (err instanceof SyntaxError) {
347
346
  throw new Error(
348
- `Failed to parse signature content JSON (path: ${path}${SIGNATURE_FILE_SUFFIX}) The content may be corrupted.`
347
+ 'Failed to parse signature content JSON ' +
348
+ `(path: ${path}${SIGNATURE_FILE_SUFFIX})` +
349
+ ' The content may be corrupted.'
349
350
  );
350
- throw err;
351
+ } else {
352
+ throw err;
353
+ }
351
354
  }
352
355
  const signerAddress = publicKeyToAddress(publicKey);
353
- if (gaiaAddress !== signerAddress)
356
+ if (gaiaAddress !== signerAddress) {
354
357
  throw new SignatureVerificationError(
355
- `Signer pubkey address (${signerAddress}) doesn't match gaia address (${gaiaAddress})`
358
+ `Signer pubkey address (${signerAddress}) doesn't` +
359
+ ` match gaia address (${gaiaAddress})`
356
360
  );
357
-
358
- if (
359
- !verifyECDSA(
360
- typeof fileContents === 'string'
361
- ? utf8ToBytes(fileContents)
362
- : new Uint8Array(fileContents),
363
- publicKey,
364
- signature
365
- )
366
- ) {
361
+ } else if (!verifyECDSA(fileContents, publicKey, signature)) {
367
362
  throw new SignatureVerificationError(
368
- `Contents do not match ECDSA signature: path: ${path}, signature: ${path}${SIGNATURE_FILE_SUFFIX}`
363
+ 'Contents do not match ECDSA signature: ' +
364
+ `path: ${path}, signature: ${path}${SIGNATURE_FILE_SUFFIX}`
369
365
  );
366
+ } else {
367
+ return fileContents;
370
368
  }
371
- return fileContents;
372
369
  } catch (err) {
373
370
  // For missing .sig files, throw `SignatureVerificationError` instead of `DoesNotExist` error.
374
- if (err instanceof DoesNotExist && err.message.indexOf(sigPath) >= 0)
371
+ if (err instanceof DoesNotExist && err.message.indexOf(sigPath) >= 0) {
375
372
  throw new SignatureVerificationError(
376
- `Failed to obtain signature for file: ${path} -- looked in ${path}${SIGNATURE_FILE_SUFFIX}`
373
+ 'Failed to obtain signature for file: ' +
374
+ `${path} -- looked in ${path}${SIGNATURE_FILE_SUFFIX}`
377
375
  );
378
- throw err;
376
+ } else {
377
+ throw err;
378
+ }
379
379
  }
380
380
  }
381
381
 
@@ -393,14 +393,18 @@ export class Storage {
393
393
  privateKey?: string,
394
394
  username?: string,
395
395
  zoneFileLookupURL?: string
396
- ): Promise<string | Uint8Array> {
396
+ // eslint-disable-next-line node/prefer-global/buffer
397
+ ): Promise<string | Buffer> {
397
398
  const appPrivateKey = privateKey || this.userSession.loadUserData().appPrivateKey;
398
399
 
399
400
  const appPublicKey = getPublicKeyFromPrivate(appPrivateKey);
400
401
 
401
- const address: string = username
402
- ? await this.getGaiaAddress(app, username, zoneFileLookupURL)
403
- : publicKeyToBtcAddress(appPublicKey);
402
+ let address: string;
403
+ if (username) {
404
+ address = await this.getGaiaAddress(app, username, zoneFileLookupURL);
405
+ } else {
406
+ address = publicKeyToAddress(appPublicKey);
407
+ }
404
408
  if (!address) {
405
409
  throw new SignatureVerificationError(
406
410
  `Failed to get gaia address for verification of: ${path}`
@@ -412,7 +416,9 @@ export class Storage {
412
416
  } catch (err) {
413
417
  if (err instanceof SyntaxError) {
414
418
  throw new Error(
415
- 'Failed to parse encrypted, signed content JSON. The content may not be encrypted. If using getFile, try passing { verify: false, decrypt: false }.'
419
+ 'Failed to parse encrypted, signed content JSON. The content may not ' +
420
+ 'be encrypted. If using getFile, try passing' +
421
+ ' { verify: false, decrypt: false }.'
416
422
  );
417
423
  } else {
418
424
  throw err;
@@ -425,15 +431,15 @@ export class Storage {
425
431
 
426
432
  if (!signerPublicKey || !cipherText || !signature) {
427
433
  throw new SignatureVerificationError(
428
- `Failed to get signature verification data from file: ${path}`
434
+ 'Failed to get signature verification data from file:' + ` ${path}`
429
435
  );
430
436
  } else if (signerAddress !== address) {
431
437
  throw new SignatureVerificationError(
432
- `Signer pubkey address (${signerAddress}) doesn't match gaia address (${address})`
438
+ `Signer pubkey address (${signerAddress}) doesn't` + ` match gaia address (${address})`
433
439
  );
434
440
  } else if (!verifyECDSA(cipherText, signerPublicKey, signature)) {
435
441
  throw new SignatureVerificationError(
436
- `Contents do not match ECDSA signature in file: ${path}`
442
+ 'Contents do not match ECDSA signature in file:' + ` ${path}`
437
443
  );
438
444
  } else if (typeof privateKey === 'string') {
439
445
  const decryptOpt = { privateKey };
@@ -446,7 +452,7 @@ export class Storage {
446
452
  /**
447
453
  * Stores the data provided in the app's data store to to the file specified.
448
454
  * @param {String} path - the path to store the data in
449
- * @param {String|Uint8Array} content - the data to store in the file
455
+ * @param {String|Buffer} content - the data to store in the file
450
456
  * @param options a [[PutFileOptions]] object
451
457
  *
452
458
  * @returns {Promise} that resolves if the operation succeed and rejects
@@ -454,7 +460,8 @@ export class Storage {
454
460
  */
455
461
  async putFile(
456
462
  path: string,
457
- content: string | Uint8Array | ArrayBufferView | Blob,
463
+ // eslint-disable-next-line node/prefer-global/buffer
464
+ content: string | Buffer | ArrayBufferView | Blob,
458
465
  options?: PutFileOptions
459
466
  ): Promise<string> {
460
467
  const defaults: PutFileOptions = {
@@ -550,7 +557,8 @@ export class Storage {
550
557
  };
551
558
  } else {
552
559
  // In all other cases, we only need one upload.
553
- let contentForUpload: string | Uint8Array | Blob;
560
+ // eslint-disable-next-line node/prefer-global/buffer
561
+ let contentForUpload: string | Buffer | Blob;
554
562
  if (!opt.encrypt && !opt.sign) {
555
563
  // If content does not need encrypted or signed, it can be passed directly
556
564
  // to the fetch request without loading into memory.