@bsv/sdk 1.8.6 → 1.8.8

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 (65) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/auth/Peer.js +21 -6
  3. package/dist/cjs/src/auth/Peer.js.map +1 -1
  4. package/dist/cjs/src/auth/clients/AuthFetch.js +229 -13
  5. package/dist/cjs/src/auth/clients/AuthFetch.js.map +1 -1
  6. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js +189 -0
  7. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js.map +1 -0
  8. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js +162 -36
  9. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js.map +1 -1
  10. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +134 -0
  11. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +1 -0
  12. package/dist/cjs/src/kvstore/GlobalKVStore.js +26 -4
  13. package/dist/cjs/src/kvstore/GlobalKVStore.js.map +1 -1
  14. package/dist/cjs/src/kvstore/kvStoreInterpreter.js +7 -3
  15. package/dist/cjs/src/kvstore/kvStoreInterpreter.js.map +1 -1
  16. package/dist/cjs/src/kvstore/types.js +2 -1
  17. package/dist/cjs/src/kvstore/types.js.map +1 -1
  18. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  19. package/dist/esm/src/auth/Peer.js +21 -6
  20. package/dist/esm/src/auth/Peer.js.map +1 -1
  21. package/dist/esm/src/auth/clients/AuthFetch.js +229 -13
  22. package/dist/esm/src/auth/clients/AuthFetch.js.map +1 -1
  23. package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js +187 -0
  24. package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js.map +1 -0
  25. package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js +162 -36
  26. package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js.map +1 -1
  27. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +109 -0
  28. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +1 -0
  29. package/dist/esm/src/kvstore/GlobalKVStore.js +26 -4
  30. package/dist/esm/src/kvstore/GlobalKVStore.js.map +1 -1
  31. package/dist/esm/src/kvstore/kvStoreInterpreter.js +7 -3
  32. package/dist/esm/src/kvstore/kvStoreInterpreter.js.map +1 -1
  33. package/dist/esm/src/kvstore/types.js +2 -1
  34. package/dist/esm/src/kvstore/types.js.map +1 -1
  35. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  36. package/dist/types/src/auth/Peer.d.ts +1 -0
  37. package/dist/types/src/auth/Peer.d.ts.map +1 -1
  38. package/dist/types/src/auth/clients/AuthFetch.d.ts +37 -0
  39. package/dist/types/src/auth/clients/AuthFetch.d.ts.map +1 -1
  40. package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts +2 -0
  41. package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts.map +1 -0
  42. package/dist/types/src/auth/transports/SimplifiedFetchTransport.d.ts +6 -0
  43. package/dist/types/src/auth/transports/SimplifiedFetchTransport.d.ts.map +1 -1
  44. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts +2 -0
  45. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts.map +1 -0
  46. package/dist/types/src/kvstore/GlobalKVStore.d.ts.map +1 -1
  47. package/dist/types/src/kvstore/kvStoreInterpreter.d.ts +2 -1
  48. package/dist/types/src/kvstore/kvStoreInterpreter.d.ts.map +1 -1
  49. package/dist/types/src/kvstore/types.d.ts +10 -0
  50. package/dist/types/src/kvstore/types.d.ts.map +1 -1
  51. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  52. package/dist/umd/bundle.js +3 -3
  53. package/dist/umd/bundle.js.map +1 -1
  54. package/docs/reference/kvstore.md +9 -2
  55. package/package.json +1 -1
  56. package/src/auth/Peer.ts +25 -18
  57. package/src/auth/__tests/Peer.test.ts +238 -1
  58. package/src/auth/clients/AuthFetch.ts +327 -18
  59. package/src/auth/clients/__tests__/AuthFetch.test.ts +262 -0
  60. package/src/auth/transports/SimplifiedFetchTransport.ts +185 -35
  61. package/src/auth/transports/__tests__/SimplifiedFetchTransport.test.ts +126 -0
  62. package/src/kvstore/GlobalKVStore.ts +33 -8
  63. package/src/kvstore/__tests/GlobalKVStore.test.ts +129 -0
  64. package/src/kvstore/kvStoreInterpreter.ts +8 -3
  65. package/src/kvstore/types.ts +11 -1
@@ -169,6 +169,7 @@ export interface KVStoreEntry {
169
169
  value: string;
170
170
  controller: PubKeyHex;
171
171
  protocolID: WalletProtocol;
172
+ tags?: string[];
172
173
  token?: KVStoreToken;
173
174
  history?: string[];
174
175
  }
@@ -246,6 +247,7 @@ export interface KVStoreQuery {
246
247
  key?: string;
247
248
  controller?: PubKeyHex;
248
249
  protocolID?: WalletProtocol;
250
+ tags?: string[];
249
251
  limit?: number;
250
252
  skip?: number;
251
253
  sortOrder?: "asc" | "desc";
@@ -279,6 +281,7 @@ export interface KVStoreSetOptions {
279
281
  tokenSetDescription?: string;
280
282
  tokenUpdateDescription?: string;
281
283
  tokenAmount?: number;
284
+ tags?: string[];
282
285
  }
283
286
  ```
284
287
 
@@ -577,7 +580,8 @@ kvProtocol = {
577
580
  key: 1,
578
581
  value: 2,
579
582
  controller: 3,
580
- signature: 4
583
+ tags: 4,
584
+ signature: 5
581
585
  }
582
586
  ```
583
587
 
@@ -595,7 +599,10 @@ kvStoreInterpreter: InterpreterFunction<string, KVContext> = async (transaction:
595
599
  if (ctx == null || ctx.key == null)
596
600
  return undefined;
597
601
  const decoded = PushDrop.decode(output.lockingScript);
598
- if (decoded.fields.length !== Object.keys(kvProtocol).length)
602
+ const expectedFieldCount = Object.keys(kvProtocol).length;
603
+ const hasTagsField = decoded.fields.length === expectedFieldCount;
604
+ const isOldFormat = decoded.fields.length === expectedFieldCount - 1;
605
+ if (!isOldFormat && !hasTagsField)
599
606
  return undefined;
600
607
  const key = Utils.toUTF8(decoded.fields[kvProtocol.key]);
601
608
  const protocolID = Utils.toUTF8(decoded.fields[kvProtocol.protocolID]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.8.6",
3
+ "version": "1.8.8",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
package/src/auth/Peer.ts CHANGED
@@ -149,13 +149,8 @@ export class Peer {
149
149
 
150
150
  try {
151
151
  await this.transport.send(generalMessage)
152
- } catch (error: any) {
153
- const e = new Error(
154
- `Failed to send message to peer ${peerSession.peerIdentityKey ?? 'unknown'
155
- }: ${String(error.message)}`
156
- )
157
- e.stack = error.stack
158
- throw e
152
+ } catch (error: unknown) {
153
+ this.propagateTransportError(peerSession.peerIdentityKey, error)
159
154
  }
160
155
  }
161
156
 
@@ -215,11 +210,8 @@ export class Peer {
215
210
 
216
211
  try {
217
212
  await this.transport.send(certRequestMessage)
218
- } catch (error: any) {
219
- throw new Error(
220
- `Failed to send certificate request message to peer ${peerSession.peerIdentityKey ?? 'unknown'
221
- }: ${String(error.message)}`
222
- )
213
+ } catch (error: unknown) {
214
+ this.propagateTransportError(peerSession.peerIdentityKey, error)
223
215
  }
224
216
  }
225
217
 
@@ -424,6 +416,25 @@ export class Peer {
424
416
  this.onInitialResponseReceivedCallbacks.delete(callbackID)
425
417
  }
426
418
 
419
+ private propagateTransportError (peerIdentityKey: string | undefined, error: unknown): never {
420
+ if (error instanceof Error) {
421
+ if (peerIdentityKey != null) {
422
+ const existingDetails = (error as any).details
423
+ if (existingDetails != null && typeof existingDetails === 'object') {
424
+ if (existingDetails.peerIdentityKey == null) {
425
+ existingDetails.peerIdentityKey = peerIdentityKey
426
+ }
427
+ } else {
428
+ (error as any).details = { peerIdentityKey }
429
+ }
430
+ }
431
+ throw error
432
+ }
433
+
434
+ const message = `Failed to send message to peer ${peerIdentityKey ?? 'unknown'}: ${String(error)}`
435
+ throw new Error(message)
436
+ }
437
+
427
438
  /**
428
439
  * Handles incoming messages from the transport.
429
440
  *
@@ -734,12 +745,8 @@ export class Peer {
734
745
 
735
746
  try {
736
747
  await this.transport.send(certificateResponse)
737
- } catch (error: any) {
738
- const errorMessage = error instanceof Error ? error.message : String(error)
739
- throw new Error(
740
- `Failed to send certificate response message to peer ${peerSession.peerIdentityKey ?? 'unknown'
741
- }: ${errorMessage}`
742
- )
748
+ } catch (error: unknown) {
749
+ this.propagateTransportError(peerSession.peerIdentityKey, error)
743
750
  }
744
751
  }
745
752
 
@@ -7,6 +7,7 @@ import { VerifiableCertificate } from '../../auth/certificates/VerifiableCertifi
7
7
  import { MasterCertificate } from '../../auth/certificates/MasterCertificate.js'
8
8
  import { getVerifiableCertificates } from '../../auth/utils/getVerifiableCertificates.js'
9
9
  import { CompletedProtoWallet } from '../certificates/__tests/CompletedProtoWallet.js'
10
+ import { SimplifiedFetchTransport } from '../../auth/transports/SimplifiedFetchTransport.js'
10
11
 
11
12
  const certifierPrivKey = new PrivateKey(21)
12
13
  const alicePrivKey = new PrivateKey(22)
@@ -355,7 +356,7 @@ describe('Peer class mutual authentication and certificate exchange', () => {
355
356
  // console.error(e)
356
357
  })
357
358
  })
358
- })
359
+ })
359
360
 
360
361
  await alice.toPeer(Utils.toArray('Hello Bob!'))
361
362
  await bobReceivedGeneralMessage
@@ -364,6 +365,57 @@ describe('Peer class mutual authentication and certificate exchange', () => {
364
365
  expect(certificatesReceivedByBob).toEqual([aliceVerifiableCertificate])
365
366
  }, 15000)
366
367
 
368
+ describe('propagateTransportError', () => {
369
+ const createPeerInstance = (): Peer => {
370
+ const transport: Transport = {
371
+ send: jest.fn(async (_message: AuthMessage) => {}),
372
+ onData: async () => {}
373
+ }
374
+ return new Peer({} as WalletInterface, transport)
375
+ }
376
+
377
+ it('adds peer identity details to existing errors', () => {
378
+ const peer = createPeerInstance()
379
+ const originalError = new Error('send failed')
380
+
381
+ let thrown: Error | undefined
382
+ try {
383
+ (peer as any).propagateTransportError('peer-public-key', originalError)
384
+ } catch (error) {
385
+ thrown = error as Error
386
+ }
387
+
388
+ expect(thrown).toBe(originalError)
389
+ expect((thrown as any).details).toEqual({ peerIdentityKey: 'peer-public-key' })
390
+ })
391
+
392
+ it('preserves existing details when appending peer identity', () => {
393
+ const peer = createPeerInstance()
394
+ const originalError = new Error('existing details')
395
+ ;(originalError as any).details = { status: 503 }
396
+
397
+ let thrown: Error | undefined
398
+ try {
399
+ (peer as any).propagateTransportError('peer-public-key', originalError)
400
+ } catch (error) {
401
+ thrown = error as Error
402
+ }
403
+
404
+ expect(thrown).toBe(originalError)
405
+ expect((thrown as any).details).toEqual({
406
+ status: 503,
407
+ peerIdentityKey: 'peer-public-key'
408
+ })
409
+ })
410
+
411
+ it('wraps non-error values with a helpful message', () => {
412
+ const peer = createPeerInstance()
413
+ expect(() => (peer as any).propagateTransportError(undefined, 'timeout')).toThrow(
414
+ 'Failed to send message to peer unknown: timeout'
415
+ )
416
+ })
417
+ })
418
+
367
419
  it('Alice requests Bob to present his library card before lending him a book', async () => {
368
420
  const alicePubKey = (await walletA.getPublicKey({ identityKey: true }))
369
421
  .publicKey
@@ -818,4 +870,189 @@ describe('Peer class mutual authentication and certificate exchange', () => {
818
870
  expect(certificatesReceivedByAlice).toEqual([bobVerifiableCertificate])
819
871
  expect(certificatesReceivedByBob).toEqual([aliceVerifiableCertificate])
820
872
  }, 20000)
873
+
874
+ describe('Transport Error Handling', () => {
875
+ const privKey = PrivateKey.fromRandom()
876
+
877
+ test('Should trigger "Failed to send message to peer" error with network failure', async () => {
878
+ // Create a mock fetch that always fails
879
+ const failingFetch = (jest.fn() as any).mockRejectedValue(new Error('Network connection failed'))
880
+
881
+ // Create a transport that will fail
882
+ const transport = new SimplifiedFetchTransport('http://localhost:9999', failingFetch)
883
+
884
+ // Create a peer with the failing transport
885
+ const wallet = new CompletedProtoWallet(privKey)
886
+ const peer = new Peer(wallet, transport)
887
+
888
+ // Register a dummy onData callback (required before sending)
889
+ await transport.onData(async (message) => {
890
+ // This won't be called due to network failure
891
+ })
892
+
893
+ // Try to send a message to peer - this should fail and trigger the error
894
+ try {
895
+ await peer.toPeer([1, 2, 3, 4], '03abc123def456')
896
+ fail('Expected error to be thrown')
897
+ } catch (error: any) {
898
+ expect(error.message).toContain('Network error while sending authenticated request')
899
+ expect(error.message).toContain('Network connection failed')
900
+ }
901
+ }, 15000)
902
+
903
+ test('Should trigger error with connection timeout', async () => {
904
+ // Create a fetch that times out
905
+ const timeoutFetch = (jest.fn() as any).mockImplementation(() => {
906
+ return new Promise((_, reject) => {
907
+ setTimeout(() => {
908
+ reject(new Error('Request timeout'))
909
+ }, 100)
910
+ })
911
+ })
912
+
913
+ const transport = new SimplifiedFetchTransport('http://localhost:9999', timeoutFetch)
914
+ const wallet = new CompletedProtoWallet(privKey)
915
+ const peer = new Peer(wallet, transport)
916
+
917
+ await transport.onData(async (message) => {})
918
+
919
+ try {
920
+ await peer.toPeer([5, 6, 7, 8], '03def789abc123')
921
+ fail('Expected error to be thrown')
922
+ } catch (error: any) {
923
+ expect(error.message).toContain('Network error while sending authenticated request')
924
+ expect(error.message).toContain('Request timeout')
925
+ }
926
+ }, 15000)
927
+
928
+ test('Should trigger error with DNS resolution failure', async () => {
929
+ // Create a fetch that fails with DNS error
930
+ const dnsFetch = (jest.fn() as any).mockRejectedValue({
931
+ code: 'ENOTFOUND',
932
+ errno: -3008,
933
+ message: 'getaddrinfo ENOTFOUND nonexistent.domain'
934
+ })
935
+
936
+ const transport = new SimplifiedFetchTransport('http://nonexistent.domain:3000', dnsFetch)
937
+ const wallet = new CompletedProtoWallet(privKey)
938
+ const peer = new Peer(wallet, transport)
939
+
940
+ await transport.onData(async (message) => {})
941
+
942
+ try {
943
+ await peer.toPeer([9, 10, 11, 12], '03xyz987fed654')
944
+ fail('Expected error to be thrown')
945
+ } catch (error: any) {
946
+ expect(error.message).toContain('Network error while sending authenticated request')
947
+ expect(error.message).toContain('[object Object]')
948
+ }
949
+ }, 15000)
950
+
951
+ test('Should trigger error during certificate request send', async () => {
952
+ // Create a failing fetch
953
+ const failingFetch = (jest.fn() as any).mockRejectedValue(new Error('Connection reset by peer'))
954
+
955
+ const transport = new SimplifiedFetchTransport('http://localhost:9999', failingFetch)
956
+ const wallet = new CompletedProtoWallet(privKey)
957
+ const peer = new Peer(wallet, transport)
958
+
959
+ await transport.onData(async (message) => {})
960
+
961
+ try {
962
+ // Try to send a certificate request - this should also trigger the error
963
+ await peer.requestCertificates({
964
+ certifiers: ['03certifier123'],
965
+ types: { 'type1': ['field1'] }
966
+ }, '03abc123def456')
967
+ fail('Expected error to be thrown')
968
+ } catch (error: any) {
969
+ expect(error.message).toContain('Network error while sending authenticated request')
970
+ expect(error.message).toContain('Connection reset by peer')
971
+ }
972
+ }, 15000)
973
+
974
+ test('Should trigger error during certificate response send', async () => {
975
+ // Create a failing fetch
976
+ const failingFetch = (jest.fn() as any).mockRejectedValue(new Error('Socket hang up'))
977
+
978
+ const transport = new SimplifiedFetchTransport('http://localhost:9999', failingFetch)
979
+ const wallet = new CompletedProtoWallet(privKey)
980
+ const peer = new Peer(wallet, transport)
981
+
982
+ await transport.onData(async (message) => {})
983
+
984
+ try {
985
+ // Try to send a certificate response - this should also trigger the error
986
+ await peer.sendCertificateResponse('03verifier123', [])
987
+ fail('Expected error to be thrown')
988
+ } catch (error: any) {
989
+ expect(error.message).toContain('Network error while sending authenticated request')
990
+ expect(error.message).toContain('Socket hang up')
991
+ }
992
+ }, 15000)
993
+
994
+ test('Should propagate network errors with proper details', async () => {
995
+ // Create a fetch that throws a custom error
996
+ const customError = new Error('Custom transport error')
997
+ customError.stack = 'Custom stack trace'
998
+ const failingFetch = (jest.fn() as any).mockRejectedValue(customError)
999
+
1000
+ const transport = new SimplifiedFetchTransport('http://localhost:9999', failingFetch)
1001
+ const wallet = new CompletedProtoWallet(privKey)
1002
+ const peer = new Peer(wallet, transport)
1003
+
1004
+ await transport.onData(async (message) => {})
1005
+
1006
+ try {
1007
+ await peer.toPeer([13, 14, 15, 16], '03peer123456')
1008
+ fail('Expected error to be thrown')
1009
+ } catch (error: any) {
1010
+ // Should create a network error wrapping the original error
1011
+ expect(error.message).toContain('Network error while sending authenticated request')
1012
+ expect(error.message).toContain('Custom transport error')
1013
+ expect(error.cause).toBeDefined()
1014
+ expect(error.cause.message).toBe('Custom transport error')
1015
+ }
1016
+ }, 15000)
1017
+
1018
+ test('Should handle non-Error transport failures', async () => {
1019
+ // Create a fetch that throws a non-Error object
1020
+ const failingFetch = (jest.fn() as any).mockRejectedValue('String error message')
1021
+
1022
+ const transport = new SimplifiedFetchTransport('http://localhost:9999', failingFetch)
1023
+ const wallet = new CompletedProtoWallet(privKey)
1024
+ const peer = new Peer(wallet, transport)
1025
+
1026
+ await transport.onData(async (message) => {})
1027
+
1028
+ try {
1029
+ await peer.toPeer([17, 18, 19, 20], '03peer789abc')
1030
+ fail('Expected error to be thrown')
1031
+ } catch (error: any) {
1032
+ // Should create network error for non-Error objects
1033
+ expect(error.message).toContain('Network error while sending authenticated request')
1034
+ expect(error.message).toContain('String error message')
1035
+ }
1036
+ }, 15000)
1037
+
1038
+ test('Should handle undefined peer identity gracefully', async () => {
1039
+ // Create a failing fetch
1040
+ const failingFetch = (jest.fn() as any).mockRejectedValue('Network failure')
1041
+
1042
+ const transport = new SimplifiedFetchTransport('http://localhost:9999', failingFetch)
1043
+ const wallet = new CompletedProtoWallet(privKey)
1044
+ const peer = new Peer(wallet, transport)
1045
+
1046
+ await transport.onData(async (message) => {})
1047
+
1048
+ try {
1049
+ // Try to send to an undefined peer (this might happen in some edge cases)
1050
+ await peer.toPeer([21, 22, 23, 24], undefined as any)
1051
+ fail('Expected error to be thrown')
1052
+ } catch (error: any) {
1053
+ expect(error.message).toContain('Network error while sending authenticated request')
1054
+ expect(error.message).toContain('Network failure')
1055
+ }
1056
+ }, 15000)
1057
+ })
821
1058
  })