@dignetwork/dig-sdk 0.0.1-alpha.14 → 0.0.1-alpha.141

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 (80) hide show
  1. package/dist/DataIntegrityTree/DataIntegrityTree.d.ts +11 -2
  2. package/dist/DataIntegrityTree/DataIntegrityTree.d.ts.map +1 -1
  3. package/dist/DataIntegrityTree/DataIntegrityTree.js +106 -15
  4. package/dist/DigNetwork/ContentServer.d.ts +12 -5
  5. package/dist/DigNetwork/ContentServer.d.ts.map +1 -1
  6. package/dist/DigNetwork/ContentServer.js +172 -24
  7. package/dist/DigNetwork/DigNetwork.d.ts +6 -9
  8. package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
  9. package/dist/DigNetwork/DigNetwork.js +137 -189
  10. package/dist/DigNetwork/DigPeer.d.ts +8 -3
  11. package/dist/DigNetwork/DigPeer.d.ts.map +1 -1
  12. package/dist/DigNetwork/DigPeer.js +68 -115
  13. package/dist/DigNetwork/IncentiveServer.d.ts.map +1 -1
  14. package/dist/DigNetwork/IncentiveServer.js +6 -5
  15. package/dist/DigNetwork/PropagationServer.d.ts +83 -33
  16. package/dist/DigNetwork/PropagationServer.d.ts.map +1 -1
  17. package/dist/DigNetwork/PropagationServer.js +605 -356
  18. package/dist/blockchain/DataStore.d.ts +25 -7
  19. package/dist/blockchain/DataStore.d.ts.map +1 -1
  20. package/dist/blockchain/DataStore.js +114 -159
  21. package/dist/blockchain/DataStoreSerializer.d.ts +1 -1
  22. package/dist/blockchain/DataStoreSerializer.d.ts.map +1 -1
  23. package/dist/blockchain/FullNodePeer.d.ts +120 -18
  24. package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
  25. package/dist/blockchain/FullNodePeer.js +441 -146
  26. package/dist/blockchain/ServerCoin.d.ts +13 -5
  27. package/dist/blockchain/ServerCoin.d.ts.map +1 -1
  28. package/dist/blockchain/ServerCoin.js +90 -28
  29. package/dist/blockchain/StoreMonitorRegistry.d.ts +85 -0
  30. package/dist/blockchain/StoreMonitorRegistry.d.ts.map +1 -0
  31. package/dist/blockchain/StoreMonitorRegistry.js +242 -0
  32. package/dist/blockchain/Wallet.d.ts +2 -2
  33. package/dist/blockchain/Wallet.d.ts.map +1 -1
  34. package/dist/blockchain/Wallet.js +50 -18
  35. package/dist/blockchain/coins.d.ts +1 -2
  36. package/dist/blockchain/coins.d.ts.map +1 -1
  37. package/dist/blockchain/coins.js +1 -55
  38. package/dist/blockchain/index.d.ts +1 -0
  39. package/dist/blockchain/index.d.ts.map +1 -1
  40. package/dist/blockchain/index.js +1 -0
  41. package/dist/types.d.ts +2 -0
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/utils/ContentScanner.d.ts +63 -0
  44. package/dist/utils/ContentScanner.d.ts.map +1 -0
  45. package/dist/utils/ContentScanner.js +175 -0
  46. package/dist/utils/Environment.d.ts +21 -0
  47. package/dist/utils/Environment.d.ts.map +1 -0
  48. package/dist/utils/Environment.js +92 -0
  49. package/dist/utils/FileCache.d.ts +1 -1
  50. package/dist/utils/FileCache.d.ts.map +1 -1
  51. package/dist/utils/FileCache.js +2 -2
  52. package/dist/utils/FileTransfer.d.ts +47 -0
  53. package/dist/utils/FileTransfer.d.ts.map +1 -0
  54. package/dist/utils/FileTransfer.js +209 -0
  55. package/dist/utils/NconfManager.js +2 -1
  56. package/dist/utils/StoreArchiveManager.d.ts +45 -0
  57. package/dist/utils/StoreArchiveManager.d.ts.map +1 -0
  58. package/dist/utils/StoreArchiveManager.js +153 -0
  59. package/dist/utils/config.d.ts +4 -3
  60. package/dist/utils/config.d.ts.map +1 -1
  61. package/dist/utils/config.js +12 -7
  62. package/dist/utils/directoryUtils.d.ts.map +1 -1
  63. package/dist/utils/directoryUtils.js +9 -8
  64. package/dist/utils/index.d.ts +2 -1
  65. package/dist/utils/index.d.ts.map +1 -1
  66. package/dist/utils/index.js +2 -1
  67. package/dist/utils/merkle.d.ts +2 -0
  68. package/dist/utils/merkle.d.ts.map +1 -0
  69. package/dist/utils/merkle.js +28 -0
  70. package/dist/utils/network.d.ts +1 -0
  71. package/dist/utils/network.d.ts.map +1 -1
  72. package/dist/utils/network.js +38 -11
  73. package/dist/utils/promiseUtils.d.ts +17 -0
  74. package/dist/utils/promiseUtils.d.ts.map +1 -0
  75. package/dist/utils/promiseUtils.js +38 -0
  76. package/dist/utils/ssl.js +2 -2
  77. package/package.json +17 -5
  78. package/dist/utils/deltaUtils.d.ts +0 -2
  79. package/dist/utils/deltaUtils.d.ts.map +0 -1
  80. package/dist/utils/deltaUtils.js +0 -83
@@ -17,6 +17,7 @@ declare class DataIntegrityTree {
17
17
  files: Map<string, {
18
18
  hash: string;
19
19
  sha256: string;
20
+ bytes: string;
20
21
  }>;
21
22
  private tree;
22
23
  constructor(storeId: string, options?: DataIntegrityTreeOptions);
@@ -78,6 +79,9 @@ declare class DataIntegrityTree {
78
79
  * @returns The serialized Merkle tree.
79
80
  */
80
81
  serialize(rootHash?: string | null): object;
82
+ static getRootOfForeignTree(treeData: {
83
+ leaves: string[];
84
+ }): string;
81
85
  /**
82
86
  * Deserialize a JSON object to a Merkle tree.
83
87
  * @param rootHash - The root hash of the tree.
@@ -106,7 +110,7 @@ declare class DataIntegrityTree {
106
110
  * @param rootHash - The root hash of the tree. Defaults to the latest root hash.
107
111
  * @returns The readable stream for the file.
108
112
  */
109
- getValueStream(hexKey: string, rootHash?: string | null): Readable;
113
+ getValueStream(hexKey: string, rootHash?: string | null, byteOffset?: number | null, length?: number | null): Readable;
110
114
  /**
111
115
  * Delete all leaves from the Merkle tree.
112
116
  */
@@ -152,9 +156,14 @@ declare class DataIntegrityTree {
152
156
  * @param sha256 - The SHA-256 hash of the file.
153
157
  * @param serializedTree - The foreign serialized Merkle tree.
154
158
  * @param expectedRootHash - The expected root hash of the Merkle tree.
159
+ * @param dataDir - The directory where the data is stored.
160
+ * @param verifiedSha256 - A boolean indicating if the SHA-256 hash has already been verified.
155
161
  * @returns A boolean indicating if the SHA-256 is present in the foreign tree and the root hash matches.
156
162
  */
157
- static validateKeyIntegrityWithForeignTree(key: string, sha256: string, serializedTree: object, expectedRootHash: string): boolean;
163
+ static validateKeyIntegrityWithForeignTree(hexkey: string, sha256: string, serializedTree: object, expectedRootHash: string, dataDir: string, verifiedSha256?: boolean): Promise<boolean>;
164
+ private static verifyFileHash;
165
+ private static deserializeAndVerifyTree;
166
+ private static checkHashInTree;
158
167
  }
159
168
  export { DataIntegrityTree };
160
169
  //# sourceMappingURL=DataIntegrityTree.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DataIntegrityTree.d.ts","sourceRoot":"","sources":["../../src/DataIntegrityTree/DataIntegrityTree.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DataIntegrityTree.d.ts","sourceRoot":"","sources":["../../src/DataIntegrityTree/DataIntegrityTree.ts"],"names":[],"mappings":"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"}
@@ -94,8 +94,7 @@ class DataIntegrityTree {
94
94
  }
95
95
  this.files = new Map();
96
96
  if (options.rootHash) {
97
- const manifest = this._loadManifest();
98
- if (manifest.includes(options.rootHash)) {
97
+ if (fs.existsSync(path.join(this.storeDir, `${options.rootHash}.dat`))) {
99
98
  this.tree = this.deserializeTree(options.rootHash);
100
99
  }
101
100
  else {
@@ -111,7 +110,10 @@ class DataIntegrityTree {
111
110
  }
112
111
  }
113
112
  static from(storeId, options) {
114
- return new DataIntegrityTree(storeId, { ...options, disableInitialize: true });
113
+ return new DataIntegrityTree(storeId, {
114
+ ...options,
115
+ disableInitialize: true,
116
+ });
115
117
  }
116
118
  /**
117
119
  * Load the manifest file.
@@ -132,7 +134,10 @@ class DataIntegrityTree {
132
134
  const manifest = this._loadManifest();
133
135
  if (manifest.length > 0) {
134
136
  const latestRootHash = manifest[manifest.length - 1];
135
- return this.deserializeTree(latestRootHash);
137
+ if (latestRootHash && isHexString(latestRootHash)) {
138
+ return this.deserializeTree(latestRootHash);
139
+ }
140
+ return new merkletreejs_1.MerkleTree([], crypto_js_1.SHA256, { sortPairs: true });
136
141
  }
137
142
  else {
138
143
  return new merkletreejs_1.MerkleTree([], crypto_js_1.SHA256, { sortPairs: true });
@@ -186,11 +191,16 @@ class DataIntegrityTree {
186
191
  fs.mkdirSync(tempDir, { recursive: true });
187
192
  }
188
193
  const tempFilePath = path.join(tempDir, `${crypto.randomUUID()}.gz`);
194
+ let totalBytes = 0;
189
195
  return new Promise((resolve, reject) => {
190
196
  const tempWriteStream = fs.createWriteStream(tempFilePath);
191
197
  readStream.on("data", (chunk) => {
192
198
  uncompressedHash.update(chunk);
193
199
  });
200
+ // Now listen to the gzip stream for compressed data size
201
+ gzip.on("data", (chunk) => {
202
+ totalBytes += chunk.length; // This counts compressed bytes
203
+ });
194
204
  readStream.pipe(gzip).pipe(tempWriteStream);
195
205
  tempWriteStream.on("finish", async () => {
196
206
  sha256 = uncompressedHash.digest("hex");
@@ -219,15 +229,21 @@ class DataIntegrityTree {
219
229
  this.files.set(key, {
220
230
  hash: combinedHash,
221
231
  sha256: sha256,
232
+ bytes: totalBytes.toString(),
222
233
  });
234
+ await new Promise((resolve) => setTimeout(resolve, 100));
223
235
  this._rebuildTree();
236
+ await new Promise((resolve) => setTimeout(resolve, 100));
224
237
  resolve();
225
238
  }
226
239
  catch (err) {
227
240
  reject(err);
228
241
  }
242
+ tempWriteStream.end();
243
+ finalWriteStream.end();
229
244
  });
230
245
  tempWriteStream.on("error", (err) => {
246
+ tempWriteStream.end();
231
247
  reject(err);
232
248
  });
233
249
  readStream.on("error", (err) => {
@@ -305,6 +321,11 @@ class DataIntegrityTree {
305
321
  files: Object.fromEntries(this.files),
306
322
  };
307
323
  }
324
+ static getRootOfForeignTree(treeData) {
325
+ const leaves = treeData.leaves.map((leaf) => Buffer.from(leaf, "hex"));
326
+ const tree = new merkletreejs_1.MerkleTree(leaves, crypto_js_1.SHA256, { sortPairs: true });
327
+ return tree.getRoot().toString("hex");
328
+ }
308
329
  /**
309
330
  * Deserialize a JSON object to a Merkle tree.
310
331
  * @param rootHash - The root hash of the tree.
@@ -337,7 +358,9 @@ class DataIntegrityTree {
337
358
  ? fs.readFileSync(manifestPath, "utf-8").trim().split("\n")
338
359
  : [];
339
360
  // Check if the last entry is the same as the rootHash to avoid duplicates
340
- const latestRootHash = manifestContent.length > 0 ? manifestContent[manifestContent.length - 1] : null;
361
+ const latestRootHash = manifestContent.length > 0
362
+ ? manifestContent[manifestContent.length - 1]
363
+ : null;
341
364
  if (latestRootHash !== rootHash) {
342
365
  // Append the new rootHash if it is not the same as the last one
343
366
  fs.appendFileSync(manifestPath, `${rootHash}\n`);
@@ -413,7 +436,7 @@ class DataIntegrityTree {
413
436
  * @param rootHash - The root hash of the tree. Defaults to the latest root hash.
414
437
  * @returns The readable stream for the file.
415
438
  */
416
- getValueStream(hexKey, rootHash = null) {
439
+ getValueStream(hexKey, rootHash = null, byteOffset = null, length = null) {
417
440
  if (!isHexString(hexKey)) {
418
441
  throw new Error("key must be a valid hex string");
419
442
  }
@@ -436,8 +459,22 @@ class DataIntegrityTree {
436
459
  if (!fs.existsSync(filePath)) {
437
460
  throw new Error(`File at path ${filePath} does not exist`);
438
461
  }
439
- // Create a read stream and pipe it through a decompression stream using the same algorithm (gzip)
440
- const readStream = fs.createReadStream(filePath);
462
+ const fileSize = fs.statSync(filePath).size;
463
+ // Validate offset and length
464
+ if (byteOffset !== null && length !== null) {
465
+ if (byteOffset + length > fileSize) {
466
+ throw new Error(`Offset (${byteOffset}) and length (${length}) exceed the file size (${fileSize}).`);
467
+ }
468
+ }
469
+ // Create the read stream with optional byte range
470
+ const options = {};
471
+ if (byteOffset !== null) {
472
+ options.start = byteOffset;
473
+ }
474
+ if (length !== null && byteOffset !== null) {
475
+ options.end = byteOffset + length - 1; // `end` is inclusive, hence `byteOffset + length - 1`
476
+ }
477
+ const readStream = fs.createReadStream(filePath, options);
441
478
  const decompressStream = zlib.createGunzip();
442
479
  // Return the combined stream as a generic Readable stream
443
480
  return readStream.pipe(decompressStream);
@@ -579,14 +616,13 @@ class DataIntegrityTree {
579
616
  decompressStream.on("end", () => {
580
617
  const uncompressedSha256 = hash.digest("hex");
581
618
  const isValid = uncompressedSha256 === sha256;
582
- console.log(`SHA-256 of uncompressed file: ${uncompressedSha256}`);
583
619
  if (!isValid) {
584
620
  return resolve(false);
585
621
  }
586
622
  const tree = this.deserializeTree(rootHash);
587
623
  const combinedHash = crypto
588
624
  .createHash("sha256")
589
- .update(`${toHex(sha256)}/${sha256}`)
625
+ .update(sha256)
590
626
  .digest("hex");
591
627
  const leaf = Buffer.from(combinedHash, "hex");
592
628
  const isInTree = tree.getLeafIndex(leaf) !== -1;
@@ -608,10 +644,12 @@ class DataIntegrityTree {
608
644
  * @param sha256 - The SHA-256 hash of the file.
609
645
  * @param serializedTree - The foreign serialized Merkle tree.
610
646
  * @param expectedRootHash - The expected root hash of the Merkle tree.
647
+ * @param dataDir - The directory where the data is stored.
648
+ * @param verifiedSha256 - A boolean indicating if the SHA-256 hash has already been verified.
611
649
  * @returns A boolean indicating if the SHA-256 is present in the foreign tree and the root hash matches.
612
650
  */
613
- static validateKeyIntegrityWithForeignTree(key, sha256, serializedTree, expectedRootHash) {
614
- if (!isHexString(key)) {
651
+ static async validateKeyIntegrityWithForeignTree(hexkey, sha256, serializedTree, expectedRootHash, dataDir, verifiedSha256 = false) {
652
+ if (!isHexString(hexkey)) {
615
653
  throw new Error("key must be a valid hex string");
616
654
  }
617
655
  if (!isHexString(sha256)) {
@@ -620,6 +658,56 @@ class DataIntegrityTree {
620
658
  if (!isHexString(expectedRootHash)) {
621
659
  throw new Error("expectedRootHash must be a valid hex string");
622
660
  }
661
+ if (!verifiedSha256) {
662
+ // Verify the file's SHA-256 hash
663
+ const isFileValid = await this.verifyFileHash(sha256, dataDir);
664
+ if (!isFileValid) {
665
+ return false;
666
+ }
667
+ }
668
+ // Deserialize and verify the tree
669
+ const tree = this.deserializeAndVerifyTree(serializedTree, expectedRootHash);
670
+ if (!tree) {
671
+ return false;
672
+ }
673
+ // Check if the combined hash exists in the foreign tree's leaves
674
+ const isInTree = this.checkHashInTree(tree, hexkey, sha256);
675
+ return isInTree;
676
+ }
677
+ // Function to verify the file's SHA-256 hash
678
+ static async verifyFileHash(sha256, dataDir) {
679
+ const filePath = path.join(dataDir, sha256.match(/.{1,2}/g).join("/"));
680
+ // Check if the file exists
681
+ if (!fs.existsSync(filePath)) {
682
+ throw new Error(`File at path ${filePath} does not exist`);
683
+ }
684
+ const compressedReadStream = fs.createReadStream(filePath);
685
+ const decompressStream = zlib.createGunzip();
686
+ const hash = crypto.createHash("sha256");
687
+ // Process file decompression and hash comparison
688
+ return new Promise((resolve, reject) => {
689
+ compressedReadStream.pipe(decompressStream);
690
+ decompressStream.on("data", (chunk) => {
691
+ hash.update(chunk);
692
+ });
693
+ decompressStream.on("end", () => {
694
+ const uncompressedSha256 = hash.digest("hex");
695
+ if (uncompressedSha256 !== sha256) {
696
+ console.warn(`File hash mismatch. Expected: ${sha256}, got: ${uncompressedSha256}`);
697
+ return resolve(false);
698
+ }
699
+ resolve(true);
700
+ });
701
+ decompressStream.on("error", (err) => {
702
+ reject(err);
703
+ });
704
+ compressedReadStream.on("error", (err) => {
705
+ reject(err);
706
+ });
707
+ });
708
+ }
709
+ // Function to deserialize and verify the tree
710
+ static deserializeAndVerifyTree(serializedTree, expectedRootHash) {
623
711
  // Deserialize the foreign tree
624
712
  const leaves = serializedTree.leaves.map((leaf) => Buffer.from(leaf, "hex"));
625
713
  const tree = new merkletreejs_1.MerkleTree(leaves, crypto_js_1.SHA256, { sortPairs: true });
@@ -627,7 +715,7 @@ class DataIntegrityTree {
627
715
  const treeRootHash = tree.getRoot().toString("hex");
628
716
  if (treeRootHash !== expectedRootHash) {
629
717
  console.warn(`Expected root hash ${expectedRootHash}, but got ${treeRootHash}`);
630
- return false;
718
+ return null;
631
719
  }
632
720
  // Rebuild the files map from the serialized tree
633
721
  // @ts-ignore
@@ -635,10 +723,13 @@ class DataIntegrityTree {
635
723
  key,
636
724
  { hash: value.hash, sha256: value.sha256 },
637
725
  ]));
638
- // Check if the SHA-256 exists in the foreign tree's files
726
+ return tree;
727
+ }
728
+ // Function to check if the combined hash exists in the tree
729
+ static checkHashInTree(tree, hexkey, sha256) {
639
730
  const combinedHash = crypto
640
731
  .createHash("sha256")
641
- .update(`${toHex(key)}/${sha256}`)
732
+ .update(`${hexkey}/${sha256}`)
642
733
  .digest("hex");
643
734
  const leaf = Buffer.from(combinedHash, "hex");
644
735
  const isInTree = tree.getLeafIndex(leaf) !== -1;
@@ -3,26 +3,33 @@ import { Readable } from "stream";
3
3
  export declare class ContentServer {
4
4
  private ipAddress;
5
5
  private storeId;
6
+ private static certPath;
7
+ private static keyPath;
6
8
  private static readonly port;
7
9
  constructor(ipAddress: string, storeId: string);
8
10
  getKey(key: string, rootHash: string, challengeHex?: string): Promise<string>;
9
- getPaymentAddress(): Promise<string>;
11
+ getKeyChunk(key: string, rootHash: string): Promise<Buffer>;
12
+ getPaymentAddress(): Promise<string | null>;
10
13
  getWellKnown(): Promise<any>;
11
14
  getKnownStores(): Promise<any>;
12
15
  getStoresIndex(): Promise<any>;
13
- getKeysIndex(): Promise<any>;
14
- headKey(key: string): Promise<{
16
+ getKeysIndex(rootHash?: string): Promise<any>;
17
+ headKey(key: string, rootHash?: string): Promise<{
15
18
  success: boolean;
16
19
  headers?: http.IncomingHttpHeaders;
17
20
  }>;
18
- headStore(): Promise<{
21
+ headStore(options?: {
22
+ hasRootHash: string;
23
+ }): Promise<{
19
24
  success: boolean;
20
25
  headers?: http.IncomingHttpHeaders;
21
26
  }>;
22
- streamKey(key: string): Promise<Readable>;
27
+ hasRootHash(rootHash: string): Promise<boolean>;
28
+ streamKey(key: string, rootHash?: string): Promise<Readable>;
23
29
  private head;
24
30
  private fetchJson;
25
31
  private fetchWithRetries;
26
32
  private fetch;
33
+ private fetchFirstChunk;
27
34
  }
28
35
  //# sourceMappingURL=ContentServer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ContentServer.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/ContentServer.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAGlC,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAM;gBAEtB,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAMjC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAa7E,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAepC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC;IAM5B,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAM9B,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAM9B,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC;IAM5B,OAAO,CAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAA;KAAE,CAAC;IAMvD,SAAS,IAAI,OAAO,CAAC;QAChC,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAC;KACpC,CAAC;IAMK,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;YA0ClC,IAAI;YAuEJ,SAAS;YAMT,gBAAgB;YAiChB,KAAK;CAuDpB"}
1
+ {"version":3,"file":"ContentServer.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/ContentServer.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAIlC,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAS;IAChC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAS;IAC/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAQ;gBAExB,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAYjC,MAAM,CACjB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC;IAaJ,WAAW,CACvB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC;IAOL,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAe3C,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC;IAM5B,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAM9B,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAM9B,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAY7C,OAAO,CAClB,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAA;KAAE,CAAC;IAYvD,SAAS,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QACjE,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAC;KACpC,CAAC;IAUW,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUrD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;YAgDrD,IAAI;YA0EJ,SAAS;YAMT,gBAAgB;YAiChB,KAAK;YA+GL,eAAe;CA2G9B"}
@@ -4,23 +4,37 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ContentServer = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
7
8
  const http_1 = __importDefault(require("http"));
8
9
  const url_1 = require("url");
10
+ const ssl_1 = require("../utils/ssl");
11
+ const network_1 = require("../utils/network");
9
12
  class ContentServer {
10
13
  constructor(ipAddress, storeId) {
11
14
  this.ipAddress = ipAddress;
12
15
  this.storeId = storeId;
16
+ if (!ContentServer.certPath || !ContentServer.keyPath) {
17
+ const { certPath, keyPath } = (0, ssl_1.getOrCreateSSLCerts)();
18
+ ContentServer.certPath = certPath;
19
+ ContentServer.keyPath = keyPath;
20
+ }
13
21
  }
14
22
  // Method to get the content of a specified key from the peer, with optional challenge query
15
23
  async getKey(key, rootHash, challengeHex) {
16
24
  // Construct the base URL
17
- let url = `http://${this.ipAddress}/chia.${this.storeId}.${rootHash}/${key}`;
25
+ let url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/chia.${this.storeId}.${rootHash}/${key}`;
18
26
  // If a challenge is provided, append it as a query parameter
19
27
  if (challengeHex) {
20
28
  url += `?challenge=${challengeHex}`;
21
29
  }
22
30
  return this.fetchWithRetries(url);
23
31
  }
32
+ // New method to get only the first chunk of the content
33
+ async getKeyChunk(key, rootHash) {
34
+ // Construct the base URL
35
+ let url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/chia.${this.storeId}.${rootHash}/${key}`;
36
+ return this.fetchFirstChunk(url);
37
+ }
24
38
  // Method to get the payment address from the peer
25
39
  async getPaymentAddress() {
26
40
  console.log(`Fetching payment address from peer ${this.ipAddress}...`);
@@ -30,43 +44,66 @@ class ContentServer {
30
44
  }
31
45
  catch (error) {
32
46
  console.error(`Failed to fetch payment address from ${this.ipAddress}: ${error.message}`);
33
- throw new Error(`Failed to fetch payment address: ${error.message}`);
47
+ return null;
34
48
  }
35
49
  }
36
50
  // Method to get the .well-known information
37
51
  async getWellKnown() {
38
- const url = `http://${this.ipAddress}/.well-known`;
52
+ const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/.well-known`;
39
53
  return this.fetchJson(url);
40
54
  }
41
55
  // Method to get the list of known stores
42
56
  async getKnownStores() {
43
- const url = `http://${this.ipAddress}/.well-known/stores`;
57
+ const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/.well-known/stores`;
44
58
  return this.fetchJson(url);
45
59
  }
46
60
  // Method to get the index of all stores
47
61
  async getStoresIndex() {
48
- const url = `http://${this.ipAddress}/`;
62
+ const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/`;
49
63
  return this.fetchJson(url);
50
64
  }
51
65
  // Method to get the index of keys in a store
52
- async getKeysIndex() {
53
- const url = `http://${this.ipAddress}/${this.storeId}`;
66
+ async getKeysIndex(rootHash) {
67
+ let udi = `chia.${this.storeId}`;
68
+ if (rootHash) {
69
+ udi += `.${rootHash}`;
70
+ }
71
+ const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/${udi}`;
54
72
  return this.fetchJson(url);
55
73
  }
56
74
  // Method to check if a specific key exists (HEAD request)
57
- async headKey(key) {
58
- const url = `http://${this.ipAddress}/${this.storeId}/${key}`;
59
- return this.head(url); // Return the object from head method
75
+ async headKey(key, rootHash) {
76
+ let udi = `chia.${this.storeId}`;
77
+ if (rootHash) {
78
+ udi += `.${rootHash}`;
79
+ }
80
+ const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/${udi}/${key}`;
81
+ return this.head(url);
60
82
  }
61
83
  // Method to check if a specific store exists (HEAD request)
62
- async headStore() {
63
- const url = `http://${this.ipAddress}/${this.storeId}`;
64
- console.log({ url });
65
- return this.head(url); // Return the object from head method
84
+ async headStore(options) {
85
+ let url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/chia.${this.storeId}`;
86
+ if (options?.hasRootHash) {
87
+ url += `?hasRootHash=${options.hasRootHash}`;
88
+ }
89
+ return this.head(url);
66
90
  }
67
- streamKey(key) {
91
+ async hasRootHash(rootHash) {
92
+ const { success, headers } = await this.headStore({
93
+ hasRootHash: rootHash,
94
+ });
95
+ if (success) {
96
+ return headers?.["x-has-root-hash"] === "true";
97
+ }
98
+ return false;
99
+ }
100
+ streamKey(key, rootHash) {
101
+ let udi = `chia.${this.storeId}`;
102
+ if (rootHash) {
103
+ udi += `.${rootHash}`;
104
+ }
68
105
  return new Promise((resolve, reject) => {
69
- const url = `http://${this.ipAddress}/${this.storeId}/${key}`;
106
+ const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${ContentServer.port}/${udi}/${key}`;
70
107
  const urlObj = new url_1.URL(url);
71
108
  const requestOptions = {
72
109
  hostname: urlObj.hostname,
@@ -93,7 +130,7 @@ class ContentServer {
93
130
  }
94
131
  });
95
132
  request.on("error", (error) => {
96
- console.error(`Request error for ${url}:`, error);
133
+ console.error(`GET Request error for ${url}:`, error);
97
134
  reject(error);
98
135
  });
99
136
  request.end();
@@ -111,6 +148,9 @@ class ContentServer {
111
148
  (urlObj.protocol === "http:" ? 80 : ContentServer.port),
112
149
  path: urlObj.pathname + urlObj.search,
113
150
  method: "HEAD",
151
+ key: fs_1.default.readFileSync(ContentServer.keyPath),
152
+ cert: fs_1.default.readFileSync(ContentServer.certPath),
153
+ rejectUnauthorized: false,
114
154
  };
115
155
  const request = http_1.default.request(requestOptions, (response) => {
116
156
  const { statusCode, headers } = response;
@@ -149,7 +189,7 @@ class ContentServer {
149
189
  }
150
190
  });
151
191
  request.on("error", (error) => {
152
- console.error(`Request error for ${url}:`, error);
192
+ console.error(`HEAD ${url}:`, error.message);
153
193
  reject({ success: false });
154
194
  });
155
195
  request.end();
@@ -168,7 +208,7 @@ class ContentServer {
168
208
  // Helper method to fetch content with retries and redirection handling
169
209
  async fetchWithRetries(url) {
170
210
  let attempt = 0;
171
- const maxRetries = 5;
211
+ const maxRetries = 1;
172
212
  const initialDelay = 2000; // 2 seconds
173
213
  const maxDelay = 10000; // 10 seconds
174
214
  const delayMultiplier = 1.5;
@@ -192,23 +232,48 @@ class ContentServer {
192
232
  }
193
233
  throw new Error(`Failed to retrieve data from ${url} after ${maxRetries} attempts.`);
194
234
  }
195
- // Core method to fetch content from a URL
235
+ // Core method to fetch content from a URL with a 5-second inactivity timeout
196
236
  async fetch(url, maxRedirects = 5) {
197
237
  return new Promise((resolve, reject) => {
198
238
  const urlObj = new url_1.URL(url);
239
+ const timeoutDuration = 5000; // 5 seconds
240
+ let timeout = null; // Initialize timeout
199
241
  const requestOptions = {
200
242
  hostname: urlObj.hostname,
201
243
  port: urlObj.port || ContentServer.port,
202
244
  path: urlObj.pathname + urlObj.search, // Include query params
203
245
  method: "GET",
246
+ key: fs_1.default.readFileSync(ContentServer.keyPath),
247
+ cert: fs_1.default.readFileSync(ContentServer.certPath),
248
+ rejectUnauthorized: false,
204
249
  };
205
250
  const request = http_1.default.request(requestOptions, (response) => {
206
251
  let data = "";
252
+ // Set timeout for inactivity
253
+ timeout = setTimeout(() => {
254
+ console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
255
+ request.destroy(); // Use destroy instead of abort
256
+ reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
257
+ }, timeoutDuration);
258
+ const resetTimeout = () => {
259
+ if (timeout) {
260
+ clearTimeout(timeout);
261
+ }
262
+ timeout = setTimeout(() => {
263
+ console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
264
+ request.destroy(); // Use destroy instead of abort
265
+ reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
266
+ }, timeoutDuration);
267
+ };
207
268
  if (response.statusCode === 200) {
208
269
  response.on("data", (chunk) => {
209
270
  data += chunk;
271
+ resetTimeout(); // Reset the timeout every time data is received
210
272
  });
211
273
  response.on("end", () => {
274
+ if (timeout) {
275
+ clearTimeout(timeout);
276
+ }
212
277
  resolve(data);
213
278
  });
214
279
  }
@@ -217,8 +282,9 @@ class ContentServer {
217
282
  // Handle redirects
218
283
  if (maxRedirects > 0) {
219
284
  const redirectUrl = new url_1.URL(response.headers.location, url); // Resolve relative URLs based on the original URL
220
- // Recursively follow the redirect, passing the same query params
221
- // console.log(`Redirecting to: ${redirectUrl.toString()}`);
285
+ if (timeout) {
286
+ clearTimeout(timeout);
287
+ }
222
288
  this.fetch(redirectUrl.toString(), maxRedirects - 1)
223
289
  .then(resolve)
224
290
  .catch(reject);
@@ -228,11 +294,93 @@ class ContentServer {
228
294
  }
229
295
  }
230
296
  else {
297
+ if (timeout) {
298
+ clearTimeout(timeout);
299
+ }
231
300
  reject(new Error(`Failed to retrieve data from ${url}. Status code: ${response.statusCode}`));
232
301
  }
233
302
  });
234
303
  request.on("error", (error) => {
235
- console.error(`Request error for ${url}:`, error);
304
+ if (timeout) {
305
+ clearTimeout(timeout);
306
+ }
307
+ console.error(`GET ${url}:`, error.message);
308
+ reject(error);
309
+ });
310
+ request.end();
311
+ });
312
+ }
313
+ // New core method to fetch only the first chunk without retries
314
+ async fetchFirstChunk(url) {
315
+ return new Promise((resolve, reject) => {
316
+ const urlObj = new url_1.URL(url);
317
+ const timeoutDuration = 5000; // 5 seconds
318
+ let timeout = null;
319
+ const requestOptions = {
320
+ hostname: urlObj.hostname,
321
+ port: urlObj.port || ContentServer.port,
322
+ path: urlObj.pathname + urlObj.search, // Include query params
323
+ method: "GET",
324
+ key: fs_1.default.readFileSync(ContentServer.keyPath),
325
+ cert: fs_1.default.readFileSync(ContentServer.certPath),
326
+ rejectUnauthorized: false,
327
+ };
328
+ const request = http_1.default.request(requestOptions, (response) => {
329
+ // Set timeout for inactivity
330
+ timeout = setTimeout(() => {
331
+ console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
332
+ request.destroy(); // Use destroy instead of abort
333
+ reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
334
+ }, timeoutDuration);
335
+ const resetTimeout = () => {
336
+ if (timeout) {
337
+ clearTimeout(timeout);
338
+ }
339
+ timeout = setTimeout(() => {
340
+ console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
341
+ request.destroy(); // Use destroy instead of abort
342
+ reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
343
+ }, timeoutDuration);
344
+ };
345
+ if (response.statusCode === 200) {
346
+ response.once("data", (chunk) => {
347
+ if (timeout) {
348
+ clearTimeout(timeout);
349
+ }
350
+ response.destroy(); // Close the connection after receiving the first chunk
351
+ resolve(chunk);
352
+ });
353
+ response.on("end", () => {
354
+ if (timeout) {
355
+ clearTimeout(timeout);
356
+ }
357
+ // In case the response ends before any data is received
358
+ reject(new Error("No data received"));
359
+ });
360
+ }
361
+ else if ((response.statusCode === 301 || response.statusCode === 302) &&
362
+ response.headers.location) {
363
+ // Handle redirects
364
+ const redirectUrl = new url_1.URL(response.headers.location, url).toString(); // Resolve relative URLs
365
+ if (timeout) {
366
+ clearTimeout(timeout);
367
+ }
368
+ this.fetchFirstChunk(redirectUrl)
369
+ .then(resolve)
370
+ .catch(reject);
371
+ }
372
+ else {
373
+ if (timeout) {
374
+ clearTimeout(timeout);
375
+ }
376
+ reject(new Error(`Failed to retrieve data from ${url}. Status code: ${response.statusCode}`));
377
+ }
378
+ });
379
+ request.on("error", (error) => {
380
+ if (timeout) {
381
+ clearTimeout(timeout);
382
+ }
383
+ console.error(`GET ${url}:`, error.message);
236
384
  reject(error);
237
385
  });
238
386
  request.end();
@@ -240,4 +388,4 @@ class ContentServer {
240
388
  }
241
389
  }
242
390
  exports.ContentServer = ContentServer;
243
- ContentServer.port = 80;
391
+ ContentServer.port = 4161;