@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.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/overlay-tools/LookupResolver.js +1 -1
- package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js +13 -3
- package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
- package/dist/cjs/src/storage/StorageDownloader.js +5 -0
- package/dist/cjs/src/storage/StorageDownloader.js.map +1 -1
- package/dist/cjs/src/storage/StorageUploader.js +8 -4
- package/dist/cjs/src/storage/StorageUploader.js.map +1 -1
- package/dist/cjs/src/storage/__test/StorageDownloader.test.js +27 -0
- package/dist/cjs/src/storage/__test/StorageDownloader.test.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/overlay-tools/LookupResolver.js +1 -1
- package/dist/esm/src/overlay-tools/SHIPBroadcaster.js +13 -3
- package/dist/esm/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
- package/dist/esm/src/storage/StorageDownloader.js +5 -0
- package/dist/esm/src/storage/StorageDownloader.js.map +1 -1
- package/dist/esm/src/storage/StorageUploader.js +8 -4
- package/dist/esm/src/storage/StorageUploader.js.map +1 -1
- package/dist/esm/src/storage/__test/StorageDownloader.test.js +27 -0
- package/dist/esm/src/storage/__test/StorageDownloader.test.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts.map +1 -1
- package/dist/types/src/storage/StorageDownloader.d.ts.map +1 -1
- package/dist/types/src/storage/StorageUploader.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/storage.md +4 -4
- package/package.json +1 -1
- package/src/overlay-tools/LookupResolver.ts +1 -1
- package/src/overlay-tools/SHIPBroadcaster.ts +13 -3
- package/src/overlay-tools/__tests/LookupResolver.test.ts +16 -16
- package/src/storage/StorageDownloader.ts +7 -0
- package/src/storage/StorageUploader.ts +11 -4
- 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:
|
|
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:
|
|
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
|
@@ -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 =
|
|
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.
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
876
|
+
5000
|
|
877
877
|
]
|
|
878
878
|
])
|
|
879
879
|
})
|
|
@@ -968,7 +968,7 @@ describe('LookupResolver', () => {
|
|
|
968
968
|
service: 'ls_foo'
|
|
969
969
|
}
|
|
970
970
|
},
|
|
971
|
-
|
|
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
|
-
|
|
1069
|
+
5000
|
|
1070
1070
|
]
|
|
1071
1071
|
])
|
|
1072
1072
|
})
|
|
@@ -1161,7 +1161,7 @@ describe('LookupResolver', () => {
|
|
|
1161
1161
|
service: 'ls_foo'
|
|
1162
1162
|
}
|
|
1163
1163
|
},
|
|
1164
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
})
|