@bsv/sdk 1.0.32 → 1.0.33

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 (70) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/primitives/PublicKey.js +36 -0
  3. package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
  4. package/dist/cjs/src/primitives/Signature.js +6 -6
  5. package/dist/cjs/src/primitives/Signature.js.map +1 -1
  6. package/dist/cjs/src/script/Script.js.map +1 -1
  7. package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
  8. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  9. package/dist/cjs/src/transaction/broadcasters/ARC.js +9 -39
  10. package/dist/cjs/src/transaction/broadcasters/ARC.js.map +1 -1
  11. package/dist/cjs/src/transaction/broadcasters/DefaultHttpClient.js +33 -0
  12. package/dist/cjs/src/transaction/broadcasters/DefaultHttpClient.js.map +1 -0
  13. package/dist/cjs/src/transaction/broadcasters/HttpClient.js +3 -0
  14. package/dist/cjs/src/transaction/broadcasters/HttpClient.js.map +1 -0
  15. package/dist/cjs/src/transaction/broadcasters/NodejsHttpClient.js +39 -0
  16. package/dist/cjs/src/transaction/broadcasters/NodejsHttpClient.js.map +1 -0
  17. package/dist/cjs/src/transaction/broadcasters/index.js +5 -1
  18. package/dist/cjs/src/transaction/broadcasters/index.js.map +1 -1
  19. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  20. package/dist/esm/src/primitives/PublicKey.js +36 -0
  21. package/dist/esm/src/primitives/PublicKey.js.map +1 -1
  22. package/dist/esm/src/primitives/Signature.js +6 -6
  23. package/dist/esm/src/primitives/Signature.js.map +1 -1
  24. package/dist/esm/src/script/Script.js.map +1 -1
  25. package/dist/esm/src/transaction/MerklePath.js.map +1 -1
  26. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  27. package/dist/esm/src/transaction/broadcasters/ARC.js +7 -39
  28. package/dist/esm/src/transaction/broadcasters/ARC.js.map +1 -1
  29. package/dist/esm/src/transaction/broadcasters/DefaultHttpClient.js +30 -0
  30. package/dist/esm/src/transaction/broadcasters/DefaultHttpClient.js.map +1 -0
  31. package/dist/esm/src/transaction/broadcasters/HttpClient.js +2 -0
  32. package/dist/esm/src/transaction/broadcasters/HttpClient.js.map +1 -0
  33. package/dist/esm/src/transaction/broadcasters/NodejsHttpClient.js +36 -0
  34. package/dist/esm/src/transaction/broadcasters/NodejsHttpClient.js.map +1 -0
  35. package/dist/esm/src/transaction/broadcasters/index.js +2 -0
  36. package/dist/esm/src/transaction/broadcasters/index.js.map +1 -1
  37. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  38. package/dist/types/src/primitives/PublicKey.d.ts +18 -0
  39. package/dist/types/src/primitives/PublicKey.d.ts.map +1 -1
  40. package/dist/types/src/primitives/Signature.d.ts +3 -3
  41. package/dist/types/src/script/Script.d.ts.map +1 -1
  42. package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
  43. package/dist/types/src/transaction/Transaction.d.ts +4 -4
  44. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  45. package/dist/types/src/transaction/broadcasters/ARC.d.ts +4 -3
  46. package/dist/types/src/transaction/broadcasters/ARC.d.ts.map +1 -1
  47. package/dist/types/src/transaction/broadcasters/DefaultHttpClient.d.ts +6 -0
  48. package/dist/types/src/transaction/broadcasters/DefaultHttpClient.d.ts.map +1 -0
  49. package/dist/types/src/transaction/broadcasters/HttpClient.d.ts +33 -0
  50. package/dist/types/src/transaction/broadcasters/HttpClient.d.ts.map +1 -0
  51. package/dist/types/src/transaction/broadcasters/NodejsHttpClient.d.ts +21 -0
  52. package/dist/types/src/transaction/broadcasters/NodejsHttpClient.d.ts.map +1 -0
  53. package/dist/types/src/transaction/broadcasters/index.d.ts +4 -0
  54. package/dist/types/src/transaction/broadcasters/index.d.ts.map +1 -1
  55. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  56. package/docs/examples/EXAMPLE_SIMPLE_TX.md +43 -1
  57. package/docs/primitives.md +36 -3
  58. package/docs/transaction.md +48 -0
  59. package/package.json +1 -1
  60. package/src/primitives/PublicKey.ts +39 -0
  61. package/src/primitives/Signature.ts +6 -6
  62. package/src/script/Script.ts +19 -19
  63. package/src/transaction/MerklePath.ts +9 -9
  64. package/src/transaction/Transaction.ts +8 -10
  65. package/src/transaction/broadcasters/ARC.ts +8 -42
  66. package/src/transaction/broadcasters/DefaultHttpClient.ts +29 -0
  67. package/src/transaction/broadcasters/HttpClient.ts +34 -0
  68. package/src/transaction/broadcasters/NodejsHttpClient.ts +55 -0
  69. package/src/transaction/broadcasters/__tests/ARC.test.ts +76 -38
  70. package/src/transaction/broadcasters/index.ts +4 -0
@@ -21,7 +21,7 @@ export default class Script {
21
21
  * @example
22
22
  * const script = Script.fromASM("OP_DUP OP_HASH160 abcd... OP_EQUALVERIFY OP_CHECKSIG")
23
23
  */
24
- static fromASM(asm: string): Script {
24
+ static fromASM (asm: string): Script {
25
25
  const chunks: ScriptChunk[] = []
26
26
  const tokens = asm.split(' ')
27
27
  let i = 0
@@ -100,7 +100,7 @@ export default class Script {
100
100
  * @example
101
101
  * const script = Script.fromHex("76a9...");
102
102
  */
103
- static fromHex(hex: string): Script {
103
+ static fromHex (hex: string): Script {
104
104
  if (hex.length === 0) return Script.fromBinary([])
105
105
  if (hex.length % 2 !== 0) throw new Error('There is an uneven number of characters in the string which suggests it is not hex encoded.')
106
106
  if (!/^[0-9a-fA-F]+$/.test(hex)) throw new Error('Some elements in this string are not hex encoded.')
@@ -115,7 +115,7 @@ export default class Script {
115
115
  * @example
116
116
  * const script = Script.fromBinary([0x76, 0xa9, ...])
117
117
  */
118
- static fromBinary(bin: number[]): Script {
118
+ static fromBinary (bin: number[]): Script {
119
119
  bin = [...bin]
120
120
  const chunks: ScriptChunk[] = []
121
121
 
@@ -179,7 +179,7 @@ export default class Script {
179
179
  * Constructs a new Script object.
180
180
  * @param chunks=[] - An array of script chunks to directly initialize the script.
181
181
  */
182
- constructor(chunks: ScriptChunk[] = []) {
182
+ constructor (chunks: ScriptChunk[] = []) {
183
183
  this.chunks = chunks
184
184
  }
185
185
 
@@ -188,7 +188,7 @@ export default class Script {
188
188
  * Serializes the script to an ASM formatted string.
189
189
  * @returns The script in ASM string format.
190
190
  */
191
- toASM(): string {
191
+ toASM (): string {
192
192
  let str = ''
193
193
  for (let i = 0; i < this.chunks.length; i++) {
194
194
  const chunk = this.chunks[i]
@@ -203,7 +203,7 @@ export default class Script {
203
203
  * Serializes the script to a hexadecimal string.
204
204
  * @returns The script in hexadecimal format.
205
205
  */
206
- toHex(): string {
206
+ toHex (): string {
207
207
  return encode(this.toBinary(), 'hex') as string
208
208
  }
209
209
 
@@ -212,7 +212,7 @@ export default class Script {
212
212
  * Serializes the script to a binary array.
213
213
  * @returns The script in binary array format.
214
214
  */
215
- toBinary(): number[] {
215
+ toBinary (): number[] {
216
216
  const writer = new Writer()
217
217
 
218
218
  for (let i = 0; i < this.chunks.length; i++) {
@@ -244,7 +244,7 @@ export default class Script {
244
244
  * @param script - The script to append.
245
245
  * @returns This script instance for chaining.
246
246
  */
247
- writeScript(script: Script): Script {
247
+ writeScript (script: Script): Script {
248
248
  this.chunks = this.chunks.concat(script.chunks)
249
249
  return this
250
250
  }
@@ -255,7 +255,7 @@ export default class Script {
255
255
  * @param op - The opcode to append.
256
256
  * @returns This script instance for chaining.
257
257
  */
258
- writeOpCode(op: number): Script {
258
+ writeOpCode (op: number): Script {
259
259
  this.chunks.push({ op })
260
260
  return this
261
261
  }
@@ -267,7 +267,7 @@ export default class Script {
267
267
  * @param op - The opcode to set.
268
268
  * @returns This script instance for chaining.
269
269
  */
270
- setChunkOpCode(i: number, op: number): Script {
270
+ setChunkOpCode (i: number, op: number): Script {
271
271
  this.chunks[i] = { op }
272
272
  return this
273
273
  }
@@ -278,7 +278,7 @@ export default class Script {
278
278
  * @param bn - The BigNumber to append.
279
279
  * @returns This script instance for chaining.
280
280
  */
281
- writeBn(bn: BigNumber): Script {
281
+ writeBn (bn: BigNumber): Script {
282
282
  if (bn.cmpn(0) === OP.OP_0) {
283
283
  this.chunks.push({
284
284
  op: OP.OP_0
@@ -306,7 +306,7 @@ export default class Script {
306
306
  * @returns This script instance for chaining.
307
307
  * @throws {Error} Throws an error if the data is too large to be pushed.
308
308
  */
309
- writeBin(bin: number[]): Script {
309
+ writeBin (bin: number[]): Script {
310
310
  let op
311
311
  if (bin.length > 0 && bin.length < OP.OP_PUSHDATA1) {
312
312
  op = bin.length
@@ -334,7 +334,7 @@ export default class Script {
334
334
  * @param num - The number to append.
335
335
  * @returns This script instance for chaining.
336
336
  */
337
- writeNumber(num: number): Script {
337
+ writeNumber (num: number): Script {
338
338
  this.writeBn(new BigNumber(num))
339
339
  return this
340
340
  }
@@ -344,7 +344,7 @@ export default class Script {
344
344
  * Removes all OP_CODESEPARATOR opcodes from the script.
345
345
  * @returns This script instance for chaining.
346
346
  */
347
- removeCodeseparators(): Script {
347
+ removeCodeseparators (): Script {
348
348
  const chunks = []
349
349
  for (let i = 0; i < this.chunks.length; i++) {
350
350
  if (this.chunks[i].op !== OP.OP_CODESEPARATOR) {
@@ -362,7 +362,7 @@ export default class Script {
362
362
  *
363
363
  * @returns This script instance for chaining.
364
364
  */
365
- findAndDelete(script: Script): Script {
365
+ findAndDelete (script: Script): Script {
366
366
  const buf = script.toHex()
367
367
  for (let i = 0; i < this.chunks.length; i++) {
368
368
  const script2 = new Script([this.chunks[i]])
@@ -379,7 +379,7 @@ export default class Script {
379
379
  * Checks if the script contains only push data operations.
380
380
  * @returns True if the script is push-only, otherwise false.
381
381
  */
382
- isPushOnly(): boolean {
382
+ isPushOnly (): boolean {
383
383
  for (let i = 0; i < this.chunks.length; i++) {
384
384
  const chunk = this.chunks[i]
385
385
  const opCodeNum = chunk.op
@@ -395,7 +395,7 @@ export default class Script {
395
395
  * Determines if the script is a locking script.
396
396
  * @returns True if the script is a locking script, otherwise false.
397
397
  */
398
- isLockingScript(): boolean {
398
+ isLockingScript (): boolean {
399
399
  throw new Error('Not implemented')
400
400
  }
401
401
 
@@ -404,7 +404,7 @@ export default class Script {
404
404
  * Determines if the script is an unlocking script.
405
405
  * @returns True if the script is an unlocking script, otherwise false.
406
406
  */
407
- isUnlockingScript(): boolean {
407
+ isUnlockingScript (): boolean {
408
408
  throw new Error('Not implemented')
409
409
  }
410
410
 
@@ -415,7 +415,7 @@ export default class Script {
415
415
  * @param chunk - The script chunk.
416
416
  * @returns The string representation of the chunk.
417
417
  */
418
- private _chunkToString(chunk: ScriptChunk): string {
418
+ private _chunkToString (chunk: ScriptChunk): string {
419
419
  const op = chunk.op
420
420
  let str = ''
421
421
  if (typeof chunk.data === 'undefined') {
@@ -39,11 +39,11 @@ export default class MerklePath {
39
39
  * @param {string} hex - The hexadecimal string representation of the Merkle Path.
40
40
  * @returns {MerklePath} - A new MerklePath instance.
41
41
  */
42
- static fromHex(hex: string): MerklePath {
42
+ static fromHex (hex: string): MerklePath {
43
43
  return MerklePath.fromBinary(toArray(hex, 'hex'))
44
44
  }
45
45
 
46
- static fromReader(reader: Reader): MerklePath {
46
+ static fromReader (reader: Reader): MerklePath {
47
47
  const blockHeight = reader.readVarIntNum()
48
48
  const treeHeight = reader.readUInt8()
49
49
  const path = Array(treeHeight).fill(0).map(() => ([]))
@@ -82,12 +82,12 @@ export default class MerklePath {
82
82
  * @param {number[]} bump - The binary array representation of the Merkle Path.
83
83
  * @returns {MerklePath} - A new MerklePath instance.
84
84
  */
85
- static fromBinary(bump: number[]): MerklePath {
85
+ static fromBinary (bump: number[]): MerklePath {
86
86
  const reader = new Reader(bump)
87
87
  return MerklePath.fromReader(reader)
88
88
  }
89
89
 
90
- constructor(blockHeight: number, path: Array<Array<{
90
+ constructor (blockHeight: number, path: Array<Array<{
91
91
  offset: number
92
92
  hash?: string
93
93
  txid?: boolean
@@ -135,7 +135,7 @@ export default class MerklePath {
135
135
  *
136
136
  * @returns {number[]} - The binary array representation of the Merkle Path.
137
137
  */
138
- toBinary(): number[] {
138
+ toBinary (): number[] {
139
139
  const writer = new Writer()
140
140
  writer.writeVarIntNum(this.blockHeight)
141
141
  const treeHeight = this.path.length
@@ -166,7 +166,7 @@ export default class MerklePath {
166
166
  *
167
167
  * @returns {string} - The hexadecimal string representation of the Merkle Path.
168
168
  */
169
- toHex(): string {
169
+ toHex (): string {
170
170
  return toHex(this.toBinary())
171
171
  }
172
172
 
@@ -177,7 +177,7 @@ export default class MerklePath {
177
177
  * @returns {string} - The computed Merkle root as a hexadecimal string.
178
178
  * @throws {Error} - If the transaction ID is not part of the Merkle Path.
179
179
  */
180
- computeRoot(txid?: string): string {
180
+ computeRoot (txid?: string): string {
181
181
  if (typeof txid !== 'string') {
182
182
  txid = this.path[0].find(leaf => Boolean(leaf?.hash)).hash
183
183
  }
@@ -216,7 +216,7 @@ export default class MerklePath {
216
216
  * @param {ChainTracker} chainTracker - The ChainTracker instance used to verify the Merkle root.
217
217
  * @returns {boolean} - True if the transaction ID is valid within the Merkle Path at the specified block height.
218
218
  */
219
- async verify(txid: string, chainTracker: ChainTracker): Promise<boolean> {
219
+ async verify (txid: string, chainTracker: ChainTracker): Promise<boolean> {
220
220
  const root = this.computeRoot(txid)
221
221
  // Use the chain tracker to determine whether this is a valid merkle root at the given block height
222
222
  return await chainTracker.isValidRootForHeight(root, this.blockHeight)
@@ -228,7 +228,7 @@ export default class MerklePath {
228
228
  * @param {MerklePath} other - Another MerklePath to combine with this path.
229
229
  * @throws {Error} - If the paths have different block heights or roots.
230
230
  */
231
- combine(other: MerklePath): void {
231
+ combine (other: MerklePath): void {
232
232
  if (this.blockHeight !== other.blockHeight) {
233
233
  throw new Error('You cannot combine paths which do not have the same block height.')
234
234
  }
@@ -125,25 +125,23 @@ export default class Transaction {
125
125
  * any application seeking to validate data in output scripts must store the entire transaction as well.
126
126
  * Since the transaction data includes the output script data, saving a second copy of potentially
127
127
  * large scripts can bloat application storage requirements.
128
- *
128
+ *
129
129
  * This function efficiently parses binary transaction data to determine the offsets and lengths of each script.
130
130
  * This supports the efficient retreival of script data from transaction data.
131
- *
131
+ *
132
132
  * @param bin binary transaction data
133
133
  * @returns {
134
134
  * inputs: { vin: number, offset: number, length: number }[]
135
135
  * outputs: { vout: number, offset: number, length: number }[]
136
136
  * }
137
137
  */
138
- static parseScriptOffsets(bin: number[])
139
- : {
140
- inputs: { vin: number, offset: number, length: number }[]
141
- outputs: { vout: number, offset: number, length: number }[]
142
- }
143
- {
138
+ static parseScriptOffsets (bin: number[]): {
139
+ inputs: Array<{ vin: number, offset: number, length: number }>
140
+ outputs: Array<{ vout: number, offset: number, length: number }>
141
+ } {
144
142
  const br = new Reader(bin)
145
- const inputs: { vin: number, offset: number, length: number }[] = []
146
- const outputs: { vout: number, offset: number, length: number }[] = []
143
+ const inputs: Array<{ vin: number, offset: number, length: number }> = []
144
+ const outputs: Array<{ vout: number, offset: number, length: number }> = []
147
145
 
148
146
  br.pos += 4 // version
149
147
  const inputsLength = br.readVarIntNum()
@@ -1,5 +1,7 @@
1
1
  import { BroadcastResponse, BroadcastFailure, Broadcaster } from '../Broadcaster.js'
2
2
  import Transaction from '../Transaction.js'
3
+ import {HttpClient} from "./HttpClient.js";
4
+ import defaultHttpClient from "./DefaultHttpClient.js";
3
5
 
4
6
  /**
5
7
  * Represents an ARC transaction broadcaster.
@@ -7,16 +9,19 @@ import Transaction from '../Transaction.js'
7
9
  export default class ARC implements Broadcaster {
8
10
  URL: string
9
11
  apiKey: string
12
+ private httpClient: HttpClient;
10
13
 
11
14
  /**
12
15
  * Constructs an instance of the ARC broadcaster.
13
16
  *
14
17
  * @param {string} URL - The URL endpoint for the ARC API.
15
18
  * @param {string} apiKey - The API key used for authorization with the ARC API.
19
+ * @param {HttpClient} httpClient - The HTTP client used to make requests to the ARC API.
16
20
  */
17
- constructor (URL: string, apiKey: string) {
21
+ constructor (URL: string, apiKey: string, httpClient: HttpClient = defaultHttpClient()) {
18
22
  this.URL = URL
19
23
  this.apiKey = apiKey
24
+ this.httpClient = httpClient
20
25
  }
21
26
 
22
27
  /**
@@ -48,23 +53,8 @@ export default class ARC implements Broadcaster {
48
53
  }
49
54
 
50
55
  try {
51
- let response
52
- let data: any = {}
53
-
54
- if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
55
- // Use fetch in a browser environment
56
- response = await window.fetch(`${this.URL}/v1/tx`, requestOptions)
57
- data = await response.json()
58
- } else if (typeof require !== 'undefined') {
59
- // Use Node.js https module
60
- // eslint-disable-next-line
61
- const https = require('https')
62
- response = await this.nodeFetch(https, requestOptions)
63
- data = JSON.parse(response)
64
- } else {
65
- throw new Error('No method available to perform HTTP request')
66
- }
67
-
56
+ const response = await this.httpClient.fetch(`${this.URL}/v1/tx`, requestOptions)
57
+ const data = await response.json()
68
58
  if (data.txid as boolean || response.ok as boolean || response.statusCode === 200) {
69
59
  return {
70
60
  status: 'success',
@@ -88,28 +78,4 @@ export default class ARC implements Broadcaster {
88
78
  }
89
79
  }
90
80
  }
91
-
92
- /** Helper function for Node.js HTTPS requests */
93
- private async nodeFetch (https, requestOptions): Promise<any> {
94
- return await new Promise((resolve, reject) => {
95
- const req = https.request(`${this.URL}/v1/tx`, requestOptions, res => {
96
- let data = ''
97
- res.on('data', (chunk: string) => {
98
- data += chunk
99
- })
100
- res.on('end', () => {
101
- resolve(data)
102
- })
103
- })
104
-
105
- req.on('error', error => {
106
- reject(error)
107
- })
108
-
109
- if (requestOptions.body as boolean) {
110
- req.write(requestOptions.body)
111
- }
112
- req.end()
113
- })
114
- }
115
81
  }
@@ -0,0 +1,29 @@
1
+ import {HttpClient, HttpClientResponse} from "./HttpClient.js";
2
+ import {NodejsHttpClient} from "./NodejsHttpClient.js";
3
+
4
+ /**
5
+ * Returns a default HttpClient implementation based on the environment that it is run on.
6
+ */
7
+ export default function defaultHttpClient(): HttpClient {
8
+ const noHttpClient:HttpClient = {
9
+ fetch(..._): Promise<HttpClientResponse> {
10
+ throw new Error('No method available to perform HTTP request')
11
+ }
12
+ }
13
+
14
+ if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
15
+ // Use fetch in a browser environment
16
+ return window
17
+ } else if (typeof require !== 'undefined') {
18
+ // Use Node.js https module
19
+ // eslint-disable-next-line
20
+ try {
21
+ const https = require('https')
22
+ return new NodejsHttpClient(https)
23
+ } catch (e) {
24
+ return noHttpClient
25
+ }
26
+ } else {
27
+ return noHttpClient
28
+ }
29
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * An interface for HTTP client used by ARC to make HTTP requests.
3
+ */
4
+ export interface HttpClient {
5
+ /**
6
+ * Makes a request to the server.
7
+ * @param url The URL to make the request to.
8
+ * @param options The request configuration.
9
+ */
10
+ fetch(url: string, options: HttpClientRequestOptions): Promise<HttpClientResponse>
11
+ }
12
+
13
+ /**
14
+ * An interface for configuration of the request to be passed to the fetch method.
15
+ */
16
+ export interface HttpClientRequestOptions {
17
+ /** A string to set request's method. */
18
+ method?: string;
19
+ /** An object literal set request's headers. */
20
+ headers?: Record<string, string>;
21
+ /** An object or null to set request's body. */
22
+ body?: string | null;
23
+ }
24
+
25
+ /**
26
+ * An interface for the response returned by the fetch method.
27
+ */
28
+ export interface HttpClientResponse {
29
+ /** The status code of the response. */
30
+ statusCode?: number;
31
+ /** A flag indicating whether the request ends with success status or not. */
32
+ ok?: boolean;
33
+ json(): Promise<any>;
34
+ }
@@ -0,0 +1,55 @@
1
+ import {HttpClient, HttpClientRequestOptions, HttpClientResponse} from "./HttpClient.js";
2
+
3
+ /** Node.js Https module interface limited to options needed by ts-sdk */
4
+ export interface HttpsNodejs {
5
+ request(url: string, options: HttpClientRequestOptions, callback: (res: any) => void): NodejsHttpClientRequest;
6
+ }
7
+
8
+ /** Nodejs result of the Node.js https.request call limited to options needed by ts-sdk */
9
+ export interface NodejsHttpClientRequest {
10
+ write(chunk: string): void;
11
+
12
+ end(): void;
13
+
14
+ on(event: string, callback: (data: any) => void): void;
15
+
16
+ end(): void;
17
+ }
18
+
19
+ /**
20
+ * Adapter for Node.js Https module to be used as HttpClient
21
+ */
22
+ export class NodejsHttpClient implements HttpClient {
23
+ constructor(private https: HttpsNodejs) {
24
+ }
25
+
26
+ async fetch(url: string, requestOptions: HttpClientRequestOptions): Promise<HttpClientResponse> {
27
+ return await new Promise((resolve, reject) => {
28
+ const req = this.https.request(url, requestOptions, res => {
29
+ let data = ''
30
+ res.on('data', (chunk: string) => {
31
+ data += chunk
32
+ })
33
+ res.on('end', () => {
34
+ resolve({
35
+ ok: res.statusCode >= 200 && res.statusCode <= 299,
36
+ statusCode: res.statusCode,
37
+ async json() {
38
+ return JSON.parse(data)
39
+ }
40
+ })
41
+ })
42
+ })
43
+
44
+ req.on('error', error => {
45
+ reject(error)
46
+ })
47
+
48
+ if (!!requestOptions.body) {
49
+ req.write(requestOptions.body)
50
+ }
51
+ req.end()
52
+ })
53
+ }
54
+ }
55
+
@@ -1,5 +1,6 @@
1
1
  import ARC from '../../../../dist/cjs/src/transaction/broadcasters/ARC.js'
2
2
  import Transaction from '../../../../dist/cjs/src/transaction/Transaction.js'
3
+ import {NodejsHttpClient} from "../NodejsHttpClient";
3
4
 
4
5
  // Mock Transaction
5
6
  jest.mock('../../Transaction', () => {
@@ -15,26 +16,24 @@ jest.mock('../../Transaction', () => {
15
16
  describe('ARC Broadcaster', () => {
16
17
  const URL = 'https://example.com'
17
18
  const apiKey = 'test_api_key'
18
- let broadcaster: ARC
19
+ const successResponse = {
20
+ txid: 'mocked_txid',
21
+ txStatus: 'success',
22
+ extraInfo: 'received'
23
+ }
24
+
19
25
  let transaction: Transaction
20
26
 
21
27
  beforeEach(() => {
22
- broadcaster = new ARC(URL, apiKey)
23
28
  transaction = new Transaction()
24
29
  })
25
30
 
26
31
  it('should broadcast successfully using window.fetch', async () => {
27
32
  // Mocking window.fetch
28
- const mockFetch = jest.fn().mockResolvedValue({
29
- ok: true,
30
- json: async () => await Promise.resolve({
31
- txid: 'mocked_txid',
32
- txStatus: 'success',
33
- extraInfo: 'received'
34
- })
35
- })
33
+ const mockFetch = mockedFetch(successResponse)
36
34
  global.window = { fetch: mockFetch } as any
37
35
 
36
+ const broadcaster = new ARC(URL, apiKey)
38
37
  const response = await broadcaster.broadcast(transaction)
39
38
 
40
39
  expect(mockFetch).toHaveBeenCalled()
@@ -47,29 +46,39 @@ describe('ARC Broadcaster', () => {
47
46
 
48
47
  it('should broadcast successfully using Node.js https', async () => {
49
48
  // Mocking Node.js https module
50
- jest.mock('https', () => ({
51
- request: (url, options, callback) => {
52
- // eslint-disable-next-line
53
- callback({
54
- statusCode: 200,
55
- on: (event, handler) => {
56
- if (event === 'data') handler(JSON.stringify({
57
- txid: 'mocked_txid',
58
- txStatus: 'success',
59
- extraInfo: 'received'
60
- }))
61
- if (event === 'end') handler()
62
- }
63
- })
64
- return {
65
- on: jest.fn(),
66
- write: jest.fn(),
67
- end: jest.fn()
68
- }
69
- }
70
- }))
71
-
49
+ mockedHttps(successResponse)
72
50
  delete global.window
51
+
52
+ const broadcaster = new ARC(URL, apiKey)
53
+ const response = await broadcaster.broadcast(transaction)
54
+
55
+ expect(response).toEqual({
56
+ status: 'success',
57
+ txid: 'mocked_txid',
58
+ message: 'success received'
59
+ })
60
+ })
61
+
62
+ it('should broadcast successfully using provided fetch', async () => {
63
+
64
+ const mockFetch = mockedFetch(successResponse)
65
+
66
+ const broadcaster = new ARC(URL, apiKey, { fetch: mockFetch })
67
+ const response = await broadcaster.broadcast(transaction)
68
+
69
+ expect(mockFetch).toHaveBeenCalled()
70
+ expect(response).toEqual({
71
+ status: 'success',
72
+ txid: 'mocked_txid',
73
+ message: 'success received'
74
+ })
75
+ })
76
+
77
+ it('should broadcast successfully using provided https', async () => {
78
+
79
+ const mockHttps = mockedHttps(successResponse)
80
+ const broadcaster = new ARC(URL, apiKey, new NodejsHttpClient(mockHttps))
81
+
73
82
  const response = await broadcaster.broadcast(transaction)
74
83
 
75
84
  expect(response).toEqual({
@@ -83,6 +92,7 @@ describe('ARC Broadcaster', () => {
83
92
  const mockFetch = jest.fn().mockRejectedValue(new Error('Network error'))
84
93
  global.window = { fetch: mockFetch } as any
85
94
 
95
+ const broadcaster = new ARC(URL, apiKey)
86
96
  const response = await broadcaster.broadcast(transaction)
87
97
 
88
98
  expect(mockFetch).toHaveBeenCalled()
@@ -94,15 +104,13 @@ describe('ARC Broadcaster', () => {
94
104
  })
95
105
 
96
106
  it('should handle non-200 responses', async () => {
97
- const mockFetch = jest.fn().mockResolvedValue({
98
- ok: false,
99
- json: async () => await Promise.resolve({
100
- status: '400',
101
- detail: 'Bad request'
102
- })
107
+ const mockFetch = mockedFetch({
108
+ status: '400',
109
+ detail: 'Bad request'
103
110
  })
104
111
  global.window = { fetch: mockFetch } as any
105
112
 
113
+ const broadcaster = new ARC(URL, apiKey)
106
114
  const response = await broadcaster.broadcast(transaction)
107
115
 
108
116
  expect(mockFetch).toHaveBeenCalled()
@@ -112,4 +120,34 @@ describe('ARC Broadcaster', () => {
112
120
  description: 'Bad request'
113
121
  })
114
122
  })
123
+
124
+ function mockedFetch(response) {
125
+ return jest.fn().mockResolvedValue({
126
+ ok: response.status === '200',
127
+ json: async () => response
128
+ });
129
+ }
130
+
131
+ function mockedHttps(response) {
132
+ const https = {
133
+ request: (url, options, callback) => {
134
+ // eslint-disable-next-line
135
+ callback({
136
+ statusCode: 200,
137
+ on: (event, handler) => {
138
+ if (event === 'data') handler(JSON.stringify(response))
139
+ if (event === 'end') handler()
140
+ }
141
+ })
142
+ return {
143
+ on: jest.fn(),
144
+ write: jest.fn(),
145
+ end: jest.fn()
146
+ }
147
+ }
148
+ }
149
+ jest.mock('https', () => https)
150
+ return https
151
+ }
115
152
  })
153
+
@@ -1 +1,5 @@
1
1
  export { default as ARC } from './ARC.js'
2
+ export type { HttpClient, HttpClientResponse, HttpClientRequestOptions } from './HttpClient.js'
3
+ export { default as defaultHttpClient } from './DefaultHttpClient.js'
4
+ export { NodejsHttpClient } from './NodejsHttpClient.js'
5
+ export type { HttpsNodejs, NodejsHttpClientRequest } from './NodejsHttpClient.js'