@bsv/sdk 1.4.5 → 1.4.7

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 (34) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/overlay-tools/LookupResolver.js +1 -1
  3. package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js +13 -3
  4. package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
  5. package/dist/cjs/src/storage/StorageDownloader.js +5 -0
  6. package/dist/cjs/src/storage/StorageDownloader.js.map +1 -1
  7. package/dist/cjs/src/storage/StorageUploader.js +8 -4
  8. package/dist/cjs/src/storage/StorageUploader.js.map +1 -1
  9. package/dist/cjs/src/storage/__test/StorageDownloader.test.js +27 -0
  10. package/dist/cjs/src/storage/__test/StorageDownloader.test.js.map +1 -1
  11. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  12. package/dist/esm/src/overlay-tools/LookupResolver.js +1 -1
  13. package/dist/esm/src/overlay-tools/SHIPBroadcaster.js +13 -3
  14. package/dist/esm/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
  15. package/dist/esm/src/storage/StorageDownloader.js +5 -0
  16. package/dist/esm/src/storage/StorageDownloader.js.map +1 -1
  17. package/dist/esm/src/storage/StorageUploader.js +8 -4
  18. package/dist/esm/src/storage/StorageUploader.js.map +1 -1
  19. package/dist/esm/src/storage/__test/StorageDownloader.test.js +27 -0
  20. package/dist/esm/src/storage/__test/StorageDownloader.test.js.map +1 -1
  21. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  22. package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts.map +1 -1
  23. package/dist/types/src/storage/StorageDownloader.d.ts.map +1 -1
  24. package/dist/types/src/storage/StorageUploader.d.ts.map +1 -1
  25. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  26. package/dist/umd/bundle.js +1 -1
  27. package/docs/storage.md +4 -4
  28. package/package.json +1 -1
  29. package/src/overlay-tools/LookupResolver.ts +1 -1
  30. package/src/overlay-tools/SHIPBroadcaster.ts +13 -3
  31. package/src/overlay-tools/__tests/LookupResolver.test.ts +16 -16
  32. package/src/storage/StorageDownloader.ts +7 -0
  33. package/src/storage/StorageUploader.ts +11 -4
  34. package/src/storage/__test/StorageDownloader.test.ts +35 -1
package/docs/storage.md CHANGED
@@ -20,7 +20,7 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
20
20
 
21
21
  ```ts
22
22
  export interface DownloadResult {
23
- data: Buffer;
23
+ data: number[];
24
24
  mimeType: string | null;
25
25
  }
26
26
  ```
@@ -32,7 +32,7 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
32
32
 
33
33
  ```ts
34
34
  export interface DownloaderConfig {
35
- networkPreset: string;
35
+ networkPreset: "mainnet" | "testnet" | "local";
36
36
  }
37
37
  ```
38
38
 
@@ -96,11 +96,11 @@ Locates HTTP URLs where content can be downloaded. It uses the passed or the def
96
96
  export class StorageDownloader {
97
97
  constructor(config?: DownloaderConfig)
98
98
  public async resolve(uhrpUrl: string): Promise<string[]>
99
- public async download(uhrpUrl: string)
99
+ public async download(uhrpUrl: string): Promise<DownloadResult>
100
100
  }
101
101
  ```
102
102
 
103
- See also: [DownloaderConfig](./storage.md#interface-downloaderconfig)
103
+ See also: [DownloadResult](./storage.md#interface-downloadresult), [DownloaderConfig](./storage.md#interface-downloaderconfig)
104
104
 
105
105
  Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
106
106
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.4.5",
3
+ "version": "1.4.7",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -53,7 +53,7 @@ export const DEFAULT_TESTNET_SLAP_TRACKERS: string[] = [
53
53
  'https://testnet-users.bapp.dev'
54
54
  ]
55
55
 
56
- const MAX_TRACKER_WAIT_TIME = 1000
56
+ const MAX_TRACKER_WAIT_TIME = 5000
57
57
 
58
58
  /** Configuration options for the Lookup resolver. */
59
59
  export interface LookupResolverConfig {
@@ -86,6 +86,7 @@ export class HTTPSOverlayBroadcastFacilitator implements OverlayBroadcastFacilit
86
86
  }
87
87
 
88
88
  async send (url: string, taggedBEEF: TaggedBEEF): Promise<STEAK> {
89
+ console.log(url)
89
90
  if (!url.startsWith('https:') && !this.allowHTTP) {
90
91
  throw new Error(
91
92
  'HTTPS facilitator can only use URLs that start with "https:"'
@@ -153,6 +154,7 @@ export default class TopicBroadcaster implements Broadcaster {
153
154
  async broadcast (
154
155
  tx: Transaction
155
156
  ): Promise<BroadcastResponse | BroadcastFailure> {
157
+ console.log(tx)
156
158
  let beef: number[]
157
159
  try {
158
160
  beef = tx.toBEEF()
@@ -161,9 +163,8 @@ export default class TopicBroadcaster implements Broadcaster {
161
163
  'Transactions sent via SHIP to Overlay Services must be serializable to BEEF format.'
162
164
  )
163
165
  }
164
- const interestedHosts = this.networkPreset === 'local'
165
- ? [{ 'http://localhost:8080': new Set<string>(this.topics) }]
166
- : await this.findInterestedHosts()
166
+ const interestedHosts = await this.findInterestedHosts()
167
+ console.log(interestedHosts)
167
168
  if (Object.keys(interestedHosts).length === 0) {
168
169
  return {
169
170
  status: 'error',
@@ -183,6 +184,7 @@ export default class TopicBroadcaster implements Broadcaster {
183
184
  }
184
185
  return { host, success: true, steak }
185
186
  } catch (error) {
187
+ console.error(error)
186
188
  // Log error if needed
187
189
  return { host, success: false, error }
188
190
  }
@@ -438,6 +440,14 @@ export default class TopicBroadcaster implements Broadcaster {
438
440
  * @returns A mapping of URLs for hosts interested in this transaction. Keys are URLs, values are which of our topics the specific host cares about.
439
441
  */
440
442
  private async findInterestedHosts (): Promise<Record<string, Set<string>>> {
443
+ // Handle the local network preset
444
+ if (this.networkPreset === 'local') {
445
+ const resultSet = new Set<string>()
446
+ for (let i = 0; i < this.topics.length; i++) {
447
+ resultSet.add(this.topics[i])
448
+ }
449
+ return { 'http://localhost:8080': resultSet }
450
+ }
441
451
  // TODO: cache the list of interested hosts to avoid spamming SHIP trackers.
442
452
  // TODO: Monetize the operation of the SHIP tracker system.
443
453
  // TODO: Cache ship/slap lookup with expiry (every 5min)
@@ -108,7 +108,7 @@ describe('LookupResolver', () => {
108
108
  service: 'ls_foo'
109
109
  }
110
110
  },
111
- 1000
111
+ 5000
112
112
  ],
113
113
  [
114
114
  'https://slaphost.com',
@@ -214,7 +214,7 @@ describe('LookupResolver', () => {
214
214
  service: 'ls_foo'
215
215
  }
216
216
  },
217
- 1000
217
+ 5000
218
218
  ],
219
219
  [
220
220
  'https://slaphost.com',
@@ -474,7 +474,7 @@ describe('LookupResolver', () => {
474
474
  service: 'ls_foo'
475
475
  }
476
476
  },
477
- 1000
477
+ 5000
478
478
  ],
479
479
  [
480
480
  'https://mock.slap2',
@@ -484,7 +484,7 @@ describe('LookupResolver', () => {
484
484
  service: 'ls_foo'
485
485
  }
486
486
  },
487
- 1000
487
+ 5000
488
488
  ],
489
489
  [
490
490
  'https://slaphost1.com',
@@ -595,7 +595,7 @@ describe('LookupResolver', () => {
595
595
  service: 'ls_foo'
596
596
  }
597
597
  },
598
- 1000
598
+ 5000
599
599
  ],
600
600
  [
601
601
  'https://slaphost1.com',
@@ -707,7 +707,7 @@ describe('LookupResolver', () => {
707
707
  service: 'ls_foo'
708
708
  }
709
709
  },
710
- 1000
710
+ 5000
711
711
  ],
712
712
  [
713
713
  'https://slaphost1.com',
@@ -818,7 +818,7 @@ describe('LookupResolver', () => {
818
818
  service: 'ls_foo'
819
819
  }
820
820
  },
821
- 1000
821
+ 5000
822
822
  ],
823
823
  [
824
824
  'https://slaphost1.com',
@@ -873,7 +873,7 @@ describe('LookupResolver', () => {
873
873
  service: 'ls_foo'
874
874
  }
875
875
  },
876
- 1000
876
+ 5000
877
877
  ]
878
878
  ])
879
879
  })
@@ -968,7 +968,7 @@ describe('LookupResolver', () => {
968
968
  service: 'ls_foo'
969
969
  }
970
970
  },
971
- 1000
971
+ 5000
972
972
  ],
973
973
  [
974
974
  'https://slaphost1.com',
@@ -1066,7 +1066,7 @@ describe('LookupResolver', () => {
1066
1066
  service: 'ls_foo'
1067
1067
  }
1068
1068
  },
1069
- 1000
1069
+ 5000
1070
1070
  ]
1071
1071
  ])
1072
1072
  })
@@ -1161,7 +1161,7 @@ describe('LookupResolver', () => {
1161
1161
  service: 'ls_foo'
1162
1162
  }
1163
1163
  },
1164
- 1000
1164
+ 5000
1165
1165
  ],
1166
1166
  [
1167
1167
  'https://mock.slap2',
@@ -1171,7 +1171,7 @@ describe('LookupResolver', () => {
1171
1171
  service: 'ls_foo'
1172
1172
  }
1173
1173
  },
1174
- 1000
1174
+ 5000
1175
1175
  ],
1176
1176
  [
1177
1177
  'https://slaphost.com',
@@ -1292,7 +1292,7 @@ describe('LookupResolver', () => {
1292
1292
  service: 'ls_foo'
1293
1293
  }
1294
1294
  },
1295
- 1000
1295
+ 5000
1296
1296
  ],
1297
1297
  [
1298
1298
  'https://slaphost1.com',
@@ -1403,7 +1403,7 @@ describe('LookupResolver', () => {
1403
1403
  service: 'ls_foo'
1404
1404
  }
1405
1405
  },
1406
- 1000
1406
+ 5000
1407
1407
  ],
1408
1408
  [
1409
1409
  'https://slaphost1.com',
@@ -1513,7 +1513,7 @@ describe('LookupResolver', () => {
1513
1513
  service: 'ls_foo'
1514
1514
  }
1515
1515
  },
1516
- 1000
1516
+ 5000
1517
1517
  ],
1518
1518
  [
1519
1519
  'https://slaphost1.com',
@@ -1596,7 +1596,7 @@ describe('LookupResolver', () => {
1596
1596
  service: 'ls_foo'
1597
1597
  }
1598
1598
  },
1599
- 1000
1599
+ 5000
1600
1600
  ],
1601
1601
  [
1602
1602
  'https://slaphost.com',
@@ -39,9 +39,16 @@ export class StorageDownloader {
39
39
  throw new Error('Lookup answer must be an output list')
40
40
  }
41
41
  const decodedResults: string[] = []
42
+ const currentTime = Math.floor(Date.now() / 1000)
42
43
  for (let i = 0; i < response.outputs.length; i++) {
43
44
  const tx = Transaction.fromBEEF(response.outputs[i].beef)
44
45
  const { fields } = PushDrop.decode(tx.outputs[response.outputs[i].outputIndex].lockingScript)
46
+
47
+ const expiryTime = new Utils.Reader(fields[3]).readVarIntNum()
48
+ if (expiryTime < currentTime) {
49
+ continue
50
+ }
51
+
45
52
  decodedResults.push(Utils.toUTF8(fields[2]))
46
53
  }
47
54
  return decodedResults
@@ -31,6 +31,7 @@ export class StorageUploader {
31
31
  retentionPeriod: number
32
32
  ): Promise<{
33
33
  uploadURL: string
34
+ requiredHeaders: Record<string, string>
34
35
  amount?: number
35
36
  }> {
36
37
  const url = `${this.baseURL}/upload`
@@ -48,26 +49,32 @@ export class StorageUploader {
48
49
  status: string
49
50
  uploadURL: string
50
51
  amount?: number
52
+ requiredHeaders: Record<string, string>
51
53
  }
52
54
  if (data.status === 'error') {
53
55
  throw new Error('Upload route returned an error.')
54
56
  }
55
57
  return {
56
58
  uploadURL: data.uploadURL,
59
+ requiredHeaders: data.requiredHeaders,
57
60
  amount: data.amount
58
61
  }
59
62
  }
60
63
 
61
64
  private async uploadFile (
62
65
  uploadURL: string,
63
- file: UploadableFile
66
+ file: UploadableFile,
67
+ requiredHeaders: Record<string, string>
64
68
  ): Promise<UploadFileResult> {
65
69
  const body = Uint8Array.from(file.data)
66
70
 
67
71
  const response = await fetch(uploadURL, {
68
72
  method: 'PUT',
69
73
  body,
70
- headers: { 'Content-Type': file.type }
74
+ headers: {
75
+ 'Content-Type': file.type,
76
+ ...requiredHeaders
77
+ }
71
78
  })
72
79
  if (!response.ok) {
73
80
  throw new Error(`File upload failed: HTTP ${response.status}`)
@@ -102,7 +109,7 @@ export class StorageUploader {
102
109
  const { file, retentionPeriod } = params
103
110
  const fileSize = file.data.length
104
111
 
105
- const { uploadURL } = await this.getUploadInfo(fileSize, retentionPeriod)
106
- return await this.uploadFile(uploadURL, file)
112
+ const { uploadURL, requiredHeaders } = await this.getUploadInfo(fileSize, retentionPeriod)
113
+ return await this.uploadFile(uploadURL, file, requiredHeaders)
107
114
  }
108
115
  }
@@ -71,6 +71,8 @@ describe('StorageDownloader', () => {
71
71
  const resolved = await downloader.resolve('fakeUhrpUrl')
72
72
  expect(resolved).toEqual(['http://a.com', 'http://a.com'])
73
73
  })
74
+
75
+
74
76
  })
75
77
 
76
78
  describe('download()', () => {
@@ -103,7 +105,7 @@ describe('StorageDownloader', () => {
103
105
  193, 139, 142, 159, 142, 32, 8, 151, 20, 133,
104
106
  110, 226, 51, 179, 144, 42, 89, 29, 13, 95,
105
107
  41, 37
106
- ]
108
+ ]
107
109
  jest.spyOn(StorageUtils, 'getHashFromURL').mockReturnValue(knownHash)
108
110
 
109
111
  // Suppose two possible download URLs
@@ -166,5 +168,37 @@ describe('StorageDownloader', () => {
166
168
  .rejects
167
169
  .toThrow('Unable to download content from validButNoGoodHostUrl')
168
170
  })
171
+
172
+ it('throws if all entries are expired', async () => {
173
+ const currentTime = Math.floor(Date.now())
174
+
175
+ jest.spyOn(LookupResolver.prototype, 'query').mockResolvedValue({
176
+ type: 'output-list',
177
+ outputs: [
178
+ { beef: 'fake-beef-a', outputIndex: 0 },
179
+ { beef: 'fake-beef-b', outputIndex: 1 }
180
+ ]
181
+ } as any)
182
+
183
+ jest.spyOn(Transaction, 'fromBEEF').mockImplementation(() => {
184
+ return {
185
+ outputs: [
186
+ { lockingScript: {} },
187
+ { lockingScript: {} }
188
+ ]
189
+ } as any
190
+ })
191
+
192
+ jest.spyOn(PushDrop, 'decode').mockImplementation(() => {
193
+ return {
194
+ lockingPublicKey: {} as PublicKey,
195
+ fields: [[], [], [], [currentTime - 100]]
196
+ }
197
+ })
198
+
199
+ await expect(downloader.resolve('expiredUhrpUrl'))
200
+ .resolves
201
+ .toEqual(["", ""])
202
+ })
169
203
  })
170
204
  })