@bsv/sdk 1.0.36 → 1.0.38
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/primitives/AESGCM.js +13 -4
- package/dist/cjs/src/primitives/AESGCM.js.map +1 -1
- package/dist/cjs/src/primitives/PrivateKey.js +5 -3
- package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
- package/dist/cjs/src/primitives/PublicKey.js +14 -1
- package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
- package/dist/cjs/src/script/templates/P2PKH.js +1 -0
- package/dist/cjs/src/script/templates/P2PKH.js.map +1 -1
- package/dist/cjs/src/transaction/Transaction.js +38 -8
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/src/transaction/broadcasters/ARC.js +7 -1
- package/dist/cjs/src/transaction/broadcasters/ARC.js.map +1 -1
- package/dist/cjs/src/transaction/http/FetchHttpClient.js +1 -1
- package/dist/cjs/src/transaction/http/FetchHttpClient.js.map +1 -1
- package/dist/cjs/src/transaction/http/NodejsHttpClient.js +1 -1
- package/dist/cjs/src/transaction/http/NodejsHttpClient.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/AESGCM.js +13 -4
- package/dist/esm/src/primitives/AESGCM.js.map +1 -1
- package/dist/esm/src/primitives/PrivateKey.js +5 -3
- package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
- package/dist/esm/src/primitives/PublicKey.js +14 -1
- package/dist/esm/src/primitives/PublicKey.js.map +1 -1
- package/dist/esm/src/script/templates/P2PKH.js +1 -0
- package/dist/esm/src/script/templates/P2PKH.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +38 -8
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/src/transaction/broadcasters/ARC.js +8 -1
- package/dist/esm/src/transaction/broadcasters/ARC.js.map +1 -1
- package/dist/esm/src/transaction/http/FetchHttpClient.js +1 -1
- package/dist/esm/src/transaction/http/FetchHttpClient.js.map +1 -1
- package/dist/esm/src/transaction/http/NodejsHttpClient.js +1 -1
- package/dist/esm/src/transaction/http/NodejsHttpClient.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/AESGCM.d.ts.map +1 -1
- package/dist/types/src/primitives/PrivateKey.d.ts +6 -4
- package/dist/types/src/primitives/PrivateKey.d.ts.map +1 -1
- package/dist/types/src/primitives/PublicKey.d.ts +4 -2
- package/dist/types/src/primitives/PublicKey.d.ts.map +1 -1
- package/dist/types/src/script/templates/P2PKH.d.ts.map +1 -1
- package/dist/types/src/transaction/Transaction.d.ts +9 -2
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/src/transaction/TransactionInput.d.ts +4 -0
- package/dist/types/src/transaction/TransactionInput.d.ts.map +1 -1
- package/dist/types/src/transaction/broadcasters/ARC.d.ts +3 -0
- package/dist/types/src/transaction/broadcasters/ARC.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/docs/examples/EXAMPLE_UTXOS_TX.md +85 -0
- package/docs/primitives.md +12 -8
- package/docs/transaction.md +29 -4
- package/package.json +1 -1
- package/src/messages/__tests/EncryptedMessage.test.ts +26 -0
- package/src/primitives/AESGCM.ts +10 -4
- package/src/primitives/PrivateKey.ts +6 -4
- package/src/primitives/PublicKey.ts +13 -2
- package/src/script/templates/P2PKH.ts +1 -0
- package/src/transaction/Transaction.ts +39 -8
- package/src/transaction/TransactionInput.ts +4 -0
- package/src/transaction/__tests/Transaction.test.ts +61 -0
- package/src/transaction/broadcasters/ARC.ts +11 -1
- package/src/transaction/broadcasters/__tests/ARC.test.ts +2 -2
- package/src/transaction/http/FetchHttpClient.ts +1 -1
- package/src/transaction/http/NodejsHttpClient.ts +1 -1
|
@@ -11,6 +11,32 @@ describe('EncryptedMessage', () => {
|
|
|
11
11
|
const decrypted = decrypt(encrypted, recipient)
|
|
12
12
|
expect(decrypted).toEqual(message)
|
|
13
13
|
})
|
|
14
|
+
it('Encrypts a message for a recipient with rare key length', () => {
|
|
15
|
+
const recipient = new PrivateKey(21)
|
|
16
|
+
{
|
|
17
|
+
// Rare length case... Leading zeros in key BigNumber array.
|
|
18
|
+
const encrypted = [
|
|
19
|
+
66,66,16,51,2,215,146,77,79,125,
|
|
20
|
+
67,234,150,90,70,90,227,9,95,244,
|
|
21
|
+
17,49,229,148,111,60,133,247,158,68,
|
|
22
|
+
173,188,248,226,126,8,14,2,53,43,
|
|
23
|
+
191,74,76,221,18,86,79,147,250,51,
|
|
24
|
+
44,227,51,48,29,154,212,2,113,248,
|
|
25
|
+
16,113,129,52,10,239,37,190,89,213,
|
|
26
|
+
|
|
27
|
+
75,148,8,235,104,137,80,129,55,68,
|
|
28
|
+
182,141,118,212,215,121,161,107,62,247,
|
|
29
|
+
12,172,244,170,208,37,213,198,103,118,
|
|
30
|
+
75,166,166,131,191,105,48,232,101,223,
|
|
31
|
+
255,169,176,204,126,249,78,178,10,51,
|
|
32
|
+
13,163,58,232,122,111,210,218,187,247,
|
|
33
|
+
164,101,207,15,37,227,108,82,70,35,
|
|
34
|
+
5,148,18,162,120,64,46,40,227,197,
|
|
35
|
+
6,112,207,200,238,81
|
|
36
|
+
]
|
|
37
|
+
expect(() => decrypt(encrypted, recipient)).not.toThrow()
|
|
38
|
+
}
|
|
39
|
+
})
|
|
14
40
|
it('Fails to decrypt a message with wrong version', () => {
|
|
15
41
|
const sender = new PrivateKey(15)
|
|
16
42
|
const recipient = new PrivateKey(21)
|
package/src/primitives/AESGCM.ts
CHANGED
|
@@ -178,17 +178,23 @@ export function AES (input: number[], key: number[]): number[] {
|
|
|
178
178
|
const state = [[], [], [], []]
|
|
179
179
|
const output = []
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
// Since the BigNumber representation of keys ignores big endian zeroes,
|
|
182
|
+
// extend incoming key arrays with zeros to the smallest standard key size.
|
|
183
|
+
let ekey = Array.from(key)
|
|
184
|
+
if (ekey.length <= 16) {
|
|
185
|
+
while (ekey.length < 16) ekey.unshift(0)
|
|
182
186
|
roundLimit = 11
|
|
183
|
-
} else if (
|
|
187
|
+
} else if (ekey.length <= 24) {
|
|
188
|
+
while (ekey.length < 24) ekey.unshift(0)
|
|
184
189
|
roundLimit = 13
|
|
185
|
-
} else if (key.length
|
|
190
|
+
} else if (key.length <= 32) {
|
|
191
|
+
while (ekey.length < 32) ekey.unshift(0)
|
|
186
192
|
roundLimit = 15
|
|
187
193
|
} else {
|
|
188
194
|
throw new Error('Illegal key length: ' + String(key.length))
|
|
189
195
|
}
|
|
190
196
|
|
|
191
|
-
const w = keyExpansion(roundLimit,
|
|
197
|
+
const w = keyExpansion(roundLimit, ekey)
|
|
192
198
|
|
|
193
199
|
for (i = 0; i < 15; i++) {
|
|
194
200
|
state[parseInt(String(i / 4))].push(input[(i * 4) % 15])
|
|
@@ -209,15 +209,17 @@ export default class PrivateKey extends BigNumber {
|
|
|
209
209
|
* Base58Check encodes the hash of the public key associated with this private key with a prefix to indicate locking script type.
|
|
210
210
|
* Defaults to P2PKH for mainnet, otherwise known as a "Bitcoin Address".
|
|
211
211
|
*
|
|
212
|
-
* @param prefix defaults to [0x00] for mainnet, set to [0x6f] for testnet
|
|
212
|
+
* @param prefix defaults to [0x00] for mainnet, set to [0x6f] for testnet or use the strings 'testnet' or 'mainnet'
|
|
213
213
|
*
|
|
214
214
|
* @returns Returns the address encoding associated with the hash of the public key associated with this private key.
|
|
215
215
|
*
|
|
216
216
|
* @example
|
|
217
|
-
* const address =
|
|
218
|
-
* const
|
|
217
|
+
* const address = privkey.toAddress()
|
|
218
|
+
* const address = privkey.toAddress('mainnet')
|
|
219
|
+
* const testnetAddress = privkey.toAddress([0x6f])
|
|
220
|
+
* const testnetAddress = privkey.toAddress('testnet')
|
|
219
221
|
*/
|
|
220
|
-
toAddress (prefix: number[] = [0x00]): string {
|
|
222
|
+
toAddress (prefix: number[] | string = [0x00]): string {
|
|
221
223
|
return this.toPublicKey().toAddress(prefix)
|
|
222
224
|
}
|
|
223
225
|
|
|
@@ -145,15 +145,26 @@ export default class PublicKey extends Point {
|
|
|
145
145
|
* Base58Check encodes the hash of the public key with a prefix to indicate locking script type.
|
|
146
146
|
* Defaults to P2PKH for mainnet, otherwise known as a "Bitcoin Address".
|
|
147
147
|
*
|
|
148
|
-
* @param prefix defaults to [0x00] for mainnet, set to [0x6f] for testnet
|
|
148
|
+
* @param prefix defaults to [0x00] for mainnet, set to [0x6f] for testnet or use the strings 'mainnet' or 'testnet'
|
|
149
149
|
*
|
|
150
150
|
* @returns Returns the address encoding associated with the hash of the public key.
|
|
151
151
|
*
|
|
152
152
|
* @example
|
|
153
153
|
* const address = pubkey.toAddress()
|
|
154
|
+
* const address = pubkey.toAddress('mainnet')
|
|
154
155
|
* const testnetAddress = pubkey.toAddress([0x6f])
|
|
156
|
+
* const testnetAddress = pubkey.toAddress('testnet')
|
|
155
157
|
*/
|
|
156
|
-
toAddress (prefix: number[] = [0x00]): string {
|
|
158
|
+
toAddress (prefix: number[] | string = [0x00]): string {
|
|
159
|
+
if (typeof prefix === 'string') {
|
|
160
|
+
if (prefix === 'testnet' || prefix === 'test') {
|
|
161
|
+
prefix = [0x6f]
|
|
162
|
+
} else if (prefix === 'mainnet' || prefix === 'main') {
|
|
163
|
+
prefix = [0x00]
|
|
164
|
+
} else {
|
|
165
|
+
throw new Error(`Invalid prefix ${prefix}`)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
157
168
|
return toBase58Check(this.toHash() as number[], prefix)
|
|
158
169
|
}
|
|
159
170
|
|
|
@@ -91,6 +91,7 @@ export default class P2PKH implements ScriptTemplate {
|
|
|
91
91
|
)
|
|
92
92
|
}
|
|
93
93
|
sourceSatoshis ||= input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis
|
|
94
|
+
sourceSatoshis ||= input.sourceSatoshis
|
|
94
95
|
if (!sourceSatoshis) {
|
|
95
96
|
throw new Error(
|
|
96
97
|
'The sourceSatoshis or input sourceTransaction is required for transaction signing.'
|
|
@@ -293,26 +293,35 @@ export default class Transaction {
|
|
|
293
293
|
/**
|
|
294
294
|
* Computes fees prior to signing.
|
|
295
295
|
* If no fee model is provided, uses a SatoshisPerKilobyte fee model that pays 10 sat/kb.
|
|
296
|
+
* If fee is a number, the transaction uses that value as fee.
|
|
296
297
|
*
|
|
297
|
-
* @param
|
|
298
|
+
* @param modelOrFee - The initialized fee model to use or fixed fee for the transaction
|
|
298
299
|
* @param changeDistribution - Specifies how the change should be distributed
|
|
299
300
|
* amongst the change outputs
|
|
300
301
|
*
|
|
301
302
|
* TODO: Benford's law change distribution.
|
|
302
303
|
*/
|
|
303
|
-
async fee (
|
|
304
|
+
async fee (modelOrFee?: FeeModel | number, changeDistribution: 'equal' | 'random' = 'equal'): Promise<void> {
|
|
304
305
|
this.cachedHash = undefined
|
|
305
|
-
if (typeof
|
|
306
|
-
|
|
306
|
+
if (typeof modelOrFee === 'undefined') {
|
|
307
|
+
modelOrFee = new SatoshisPerKilobyte(10)
|
|
307
308
|
}
|
|
308
|
-
|
|
309
|
+
if (typeof modelOrFee === 'number') {
|
|
310
|
+
const sats = modelOrFee
|
|
311
|
+
modelOrFee = {
|
|
312
|
+
computeFee: async () => sats
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const fee = await modelOrFee.computeFee(this)
|
|
309
316
|
// change = inputs - fee - non-change outputs
|
|
310
317
|
let change = 0
|
|
311
318
|
for (const input of this.inputs) {
|
|
312
|
-
if (typeof input.sourceTransaction !== 'object') {
|
|
313
|
-
throw new Error('Source transactions are required for all inputs during fee computation')
|
|
319
|
+
if (typeof input.sourceTransaction !== 'object' && typeof input.sourceSatoshis !== 'number') {
|
|
320
|
+
throw new Error('Source transactions or sourceSatoshis are required for all inputs during fee computation')
|
|
314
321
|
}
|
|
315
|
-
change += input.sourceTransaction
|
|
322
|
+
change += input.sourceTransaction
|
|
323
|
+
? input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis
|
|
324
|
+
: input.sourceSatoshis
|
|
316
325
|
}
|
|
317
326
|
change -= fee
|
|
318
327
|
let changeCount = 0
|
|
@@ -350,6 +359,28 @@ export default class Transaction {
|
|
|
350
359
|
}
|
|
351
360
|
}
|
|
352
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Utility method that returns the current fee based on inputs and outputs
|
|
364
|
+
*
|
|
365
|
+
* @returns The current transaction fee
|
|
366
|
+
*/
|
|
367
|
+
getFee (): number {
|
|
368
|
+
let totalIn = 0
|
|
369
|
+
for (const input of this.inputs) {
|
|
370
|
+
if (typeof input.sourceTransaction !== 'object' && typeof input.sourceSatoshis !== 'number') {
|
|
371
|
+
throw new Error('Source transactions or sourceSatoshis are required for all inputs to calculate fee')
|
|
372
|
+
}
|
|
373
|
+
totalIn += input.sourceTransaction
|
|
374
|
+
? input.sourceTransaction.outputs[input.sourceOutputIndex].satoshis
|
|
375
|
+
: input.sourceSatoshis || 0
|
|
376
|
+
}
|
|
377
|
+
let totalOut = 0
|
|
378
|
+
for (const output of this.outputs) {
|
|
379
|
+
totalOut += output.satoshis || 0
|
|
380
|
+
}
|
|
381
|
+
return totalIn - totalOut
|
|
382
|
+
}
|
|
383
|
+
|
|
353
384
|
/**
|
|
354
385
|
* Signs a transaction, hydrating all its unlocking scripts based on the provided script templates where they are available.
|
|
355
386
|
*/
|
|
@@ -16,6 +16,9 @@ import Transaction from './Transaction.js'
|
|
|
16
16
|
* @property {number} sourceOutputIndex - The index of the output in the source transaction
|
|
17
17
|
* that this input is spending. It is zero-based, indicating the position of the
|
|
18
18
|
* output in the array of outputs of the source transaction.
|
|
19
|
+
* @property {number} [sourceSatoshis] - The amount of satoshis of the source transaction
|
|
20
|
+
* output that this input is spending, used for fee calculation and signing when
|
|
21
|
+
* source transaction is not present
|
|
19
22
|
* @property {UnlockingScript} [unlockingScript] - Optional. The script that 'unlocks' the
|
|
20
23
|
* source output for spending. This script typically contains signatures and
|
|
21
24
|
* public keys that evidence the ownership of the output.
|
|
@@ -54,6 +57,7 @@ export default interface TransactionInput {
|
|
|
54
57
|
sourceTransaction?: Transaction
|
|
55
58
|
sourceTXID?: string
|
|
56
59
|
sourceOutputIndex: number
|
|
60
|
+
sourceSatoshis?: number
|
|
57
61
|
unlockingScript?: UnlockingScript
|
|
58
62
|
unlockingScriptTemplate?: {
|
|
59
63
|
sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>
|
|
@@ -317,6 +317,32 @@ describe('Transaction', () => {
|
|
|
317
317
|
// 4000 sats in - 1000 sats out - 1033 sats fee = expected 1967 sats change
|
|
318
318
|
expect(spendTx.outputs[1].satoshis).toEqual(1967)
|
|
319
319
|
})
|
|
320
|
+
it('Computes fee using FixedFee model', async () => {
|
|
321
|
+
const privateKey = new PrivateKey(1)
|
|
322
|
+
const publicKey = new Curve().g.mul(privateKey)
|
|
323
|
+
const publicKeyHash = hash160(publicKey.encode(true)) as number[]
|
|
324
|
+
const p2pkh = new P2PKH()
|
|
325
|
+
const sourceTx = new Transaction(1, [], [{
|
|
326
|
+
lockingScript: p2pkh.lock(publicKeyHash),
|
|
327
|
+
satoshis: 4000
|
|
328
|
+
}], 0)
|
|
329
|
+
const spendTx = new Transaction(1, [{
|
|
330
|
+
sourceTransaction: sourceTx,
|
|
331
|
+
sourceOutputIndex: 0,
|
|
332
|
+
unlockingScriptTemplate: p2pkh.unlock(privateKey),
|
|
333
|
+
sequence: 0xffffffff
|
|
334
|
+
}], [{
|
|
335
|
+
satoshis: 1000,
|
|
336
|
+
lockingScript: p2pkh.lock(publicKeyHash)
|
|
337
|
+
}, {
|
|
338
|
+
lockingScript: p2pkh.lock(publicKeyHash),
|
|
339
|
+
change: true
|
|
340
|
+
}], 0)
|
|
341
|
+
expect(spendTx.outputs[1].satoshis).not.toBeDefined()
|
|
342
|
+
await spendTx.fee(1033)
|
|
343
|
+
// 4000 sats in - 1000 sats out - 1033 sats fee = expected 1967 sats change
|
|
344
|
+
expect(spendTx.outputs[1].satoshis).toEqual(1967)
|
|
345
|
+
})
|
|
320
346
|
it('Distributes change among multiple change outputs', async () => {
|
|
321
347
|
const privateKey = new PrivateKey(1)
|
|
322
348
|
const publicKey = new Curve().g.mul(privateKey)
|
|
@@ -352,6 +378,41 @@ describe('Transaction', () => {
|
|
|
352
378
|
expect(spendTx.outputs[1].satoshis).toEqual(983)
|
|
353
379
|
expect(spendTx.outputs[2].satoshis).toEqual(983)
|
|
354
380
|
})
|
|
381
|
+
it('Calculates fee for utxo based transaction', async () => {
|
|
382
|
+
const utxos = [ // WoC format utxos
|
|
383
|
+
{
|
|
384
|
+
height: 1600000,
|
|
385
|
+
tx_pos: 0,
|
|
386
|
+
tx_hash: '672dd6a93fa5d7ba6794e0bdf8b479440b95a55ec10ad3d9e03585ecb5628d8d',
|
|
387
|
+
value: 10000
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
height: 1600000,
|
|
391
|
+
tx_pos: 0,
|
|
392
|
+
tx_hash: 'f33505acf37a7726cc37d391bc6f889b8684ac2a2d581c4be2a4b1c8b46609bc',
|
|
393
|
+
value: 10000
|
|
394
|
+
},
|
|
395
|
+
]
|
|
396
|
+
const priv = PrivateKey.fromRandom()
|
|
397
|
+
const tx = new Transaction()
|
|
398
|
+
utxos.forEach(utxo => {
|
|
399
|
+
const script = new P2PKH().lock(priv.toPublicKey().toHash())
|
|
400
|
+
tx.addInput({
|
|
401
|
+
sourceTXID: utxo.tx_hash,
|
|
402
|
+
sourceOutputIndex: utxo.tx_pos,
|
|
403
|
+
sourceSatoshis: utxo.value,
|
|
404
|
+
unlockingScriptTemplate: new P2PKH()
|
|
405
|
+
.unlock(priv, 'all', false, utxo.value, script)
|
|
406
|
+
})
|
|
407
|
+
})
|
|
408
|
+
tx.addOutput({
|
|
409
|
+
lockingScript: new P2PKH().lock(priv.toAddress()),
|
|
410
|
+
change: true
|
|
411
|
+
})
|
|
412
|
+
await tx.fee({ computeFee: async () => 10 })
|
|
413
|
+
expect(tx.outputs[0].satoshis).toEqual(20000 - 10)
|
|
414
|
+
expect(tx.getFee()).toEqual(10)
|
|
415
|
+
})
|
|
355
416
|
})
|
|
356
417
|
|
|
357
418
|
describe('Broadcast', () => {
|
|
@@ -13,6 +13,8 @@ export interface ArcConfig {
|
|
|
13
13
|
deploymentId?: string
|
|
14
14
|
/** The HTTP client used to make requests to the ARC API. */
|
|
15
15
|
httpClient?: HttpClient
|
|
16
|
+
/** The headers to be attached to all tx submissions. */
|
|
17
|
+
headers?: Record<string, string>
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
|
|
@@ -27,6 +29,7 @@ export default class ARC implements Broadcaster {
|
|
|
27
29
|
readonly URL: string
|
|
28
30
|
readonly apiKey: string | undefined
|
|
29
31
|
readonly deploymentId: string
|
|
32
|
+
readonly headers: Record<string, string> | undefined
|
|
30
33
|
private readonly httpClient: HttpClient;
|
|
31
34
|
|
|
32
35
|
/**
|
|
@@ -50,10 +53,11 @@ export default class ARC implements Broadcaster {
|
|
|
50
53
|
this.apiKey = config
|
|
51
54
|
this.httpClient = defaultHttpClient()
|
|
52
55
|
} else {
|
|
53
|
-
const {apiKey, deploymentId, httpClient} = config ?? {} as ArcConfig
|
|
56
|
+
const {apiKey, deploymentId, headers, httpClient} = config ?? {} as ArcConfig
|
|
54
57
|
this.httpClient = httpClient ?? defaultHttpClient()
|
|
55
58
|
this.deploymentId = deploymentId ?? defaultDeploymentId()
|
|
56
59
|
this.apiKey = apiKey
|
|
60
|
+
this.headers = headers
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
|
|
@@ -118,6 +122,12 @@ export default class ARC implements Broadcaster {
|
|
|
118
122
|
headers['Authorization'] = `Bearer ${this.apiKey}`
|
|
119
123
|
}
|
|
120
124
|
|
|
125
|
+
if (!!this.headers) {
|
|
126
|
+
for (const key in this.headers) {
|
|
127
|
+
headers[key] = this.headers[key]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
121
131
|
return headers
|
|
122
132
|
}
|
|
123
133
|
}
|
|
@@ -189,7 +189,7 @@ describe('ARC Broadcaster', () => {
|
|
|
189
189
|
headers: {
|
|
190
190
|
get(key: string) {
|
|
191
191
|
if (key === 'Content-Type') {
|
|
192
|
-
return 'application/json'
|
|
192
|
+
return 'application/json; charset=UTF-8'
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
},
|
|
@@ -205,7 +205,7 @@ describe('ARC Broadcaster', () => {
|
|
|
205
205
|
statusCode: response.status,
|
|
206
206
|
statusMessage: response.status == 200 ? 'OK' : 'Bad request',
|
|
207
207
|
headers: {
|
|
208
|
-
'content-type': 'application/json'
|
|
208
|
+
'content-type': 'application/json; charset=UTF-8'
|
|
209
209
|
},
|
|
210
210
|
on: (event, handler) => {
|
|
211
211
|
if (event === 'data') handler(JSON.stringify(response.data))
|
|
@@ -38,7 +38,7 @@ export class FetchHttpClient implements HttpClient {
|
|
|
38
38
|
|
|
39
39
|
const res = await this.fetch(url, fetchOptions);
|
|
40
40
|
const mediaType = res.headers.get('Content-Type');
|
|
41
|
-
const data = mediaType
|
|
41
|
+
const data = mediaType.startsWith('application/json') ? await res.json() : await res.text();
|
|
42
42
|
|
|
43
43
|
return {
|
|
44
44
|
ok: res.ok,
|
|
@@ -32,7 +32,7 @@ export class NodejsHttpClient implements HttpClient {
|
|
|
32
32
|
res.on('end', () => {
|
|
33
33
|
const ok = res.statusCode >= 200 && res.statusCode <= 299;
|
|
34
34
|
const mediaType = res.headers['content-type'];
|
|
35
|
-
const data = body && mediaType
|
|
35
|
+
const data = body && mediaType.startsWith('application/json') ? JSON.parse(body) : body;
|
|
36
36
|
resolve({
|
|
37
37
|
status: res.statusCode,
|
|
38
38
|
statusText: res.statusMessage,
|