@notabene/javascript-sdk 2.8.0-next.4 → 2.8.0-next.6

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 (134) hide show
  1. package/README.md +30 -0
  2. package/dist/cjs/notabene.cjs +1 -1
  3. package/dist/cjs/notabene.d.ts +92 -8
  4. package/dist/cjs/package.json +1 -1
  5. package/dist/esm/notabene.d.ts +92 -8
  6. package/dist/esm/notabene.js +282 -137
  7. package/dist/esm/package.json +1 -1
  8. package/dist/notabene.d.ts +92 -8
  9. package/dist/notabene.js +282 -137
  10. package/docs/README.md +30 -0
  11. package/docs/_media/TransactionOptions.md +6 -6
  12. package/docs/ivms/types/enumerations/PayloadVersionCode.md +2 -2
  13. package/docs/ivms/types/interfaces/PayloadMetadata.md +2 -2
  14. package/docs/ivms/types/type-aliases/Address.md +1 -1
  15. package/docs/ivms/types/type-aliases/AddressTypeCode.md +1 -1
  16. package/docs/ivms/types/type-aliases/Beneficiary.md +1 -1
  17. package/docs/ivms/types/type-aliases/BeneficiaryVASP.md +1 -1
  18. package/docs/ivms/types/type-aliases/DateAndPlaceOfBirth.md +1 -1
  19. package/docs/ivms/types/type-aliases/ISOCountryCode.md +1 -1
  20. package/docs/ivms/types/type-aliases/ISODate.md +1 -1
  21. package/docs/ivms/types/type-aliases/IVMS101.md +1 -1
  22. package/docs/ivms/types/type-aliases/IntermediaryVASP.md +1 -1
  23. package/docs/ivms/types/type-aliases/LegalPerson.md +1 -1
  24. package/docs/ivms/types/type-aliases/LegalPersonName.md +1 -1
  25. package/docs/ivms/types/type-aliases/LegalPersonNameID.md +1 -1
  26. package/docs/ivms/types/type-aliases/LegalPersonNameTypeCode.md +1 -1
  27. package/docs/ivms/types/type-aliases/LocalLegalPersonNameID.md +1 -1
  28. package/docs/ivms/types/type-aliases/LocalNaturalPersonNameID.md +1 -1
  29. package/docs/ivms/types/type-aliases/NationalIdentification.md +1 -1
  30. package/docs/ivms/types/type-aliases/NationalIdentifierTypeCode.md +1 -1
  31. package/docs/ivms/types/type-aliases/NaturalPerson.md +1 -1
  32. package/docs/ivms/types/type-aliases/NaturalPersonName.md +1 -1
  33. package/docs/ivms/types/type-aliases/NaturalPersonNameID.md +1 -1
  34. package/docs/ivms/types/type-aliases/NaturalPersonNameTypeCode.md +1 -1
  35. package/docs/ivms/types/type-aliases/OriginatingVASP.md +1 -1
  36. package/docs/ivms/types/type-aliases/Originator.md +1 -1
  37. package/docs/ivms/types/type-aliases/Person.md +1 -1
  38. package/docs/ivms/types/type-aliases/TransferPath.md +1 -1
  39. package/docs/ivms/types/type-aliases/TransliterationMethodCode.md +1 -1
  40. package/docs/notabene/README.md +5 -0
  41. package/docs/notabene/classes/ConnectionManager.md +165 -0
  42. package/docs/notabene/classes/EmbeddedComponent.md +17 -17
  43. package/docs/notabene/classes/default.md +7 -7
  44. package/docs/notabene/functions/decodeFragmentToObject.md +1 -1
  45. package/docs/notabene/interfaces/ConnectionData.md +41 -0
  46. package/docs/notabene/interfaces/ConnectionMetadata.md +37 -0
  47. package/docs/notabene/interfaces/ConnectionResponse.md +61 -0
  48. package/docs/notabene/interfaces/NotabeneConfig.md +5 -5
  49. package/docs/notabene/type-aliases/MessageCallback.md +1 -1
  50. package/docs/notabene/type-aliases/TransactionType.md +13 -0
  51. package/docs/types/README.md +1 -0
  52. package/docs/types/enumerations/AgentType.md +2 -2
  53. package/docs/types/enumerations/CMType.md +7 -7
  54. package/docs/types/enumerations/ErrorIdentifierCode.md +4 -4
  55. package/docs/types/enumerations/HMType.md +2 -2
  56. package/docs/types/enumerations/PersonType.md +3 -3
  57. package/docs/types/enumerations/ProofStatus.md +4 -4
  58. package/docs/types/enumerations/ProofTypes.md +16 -16
  59. package/docs/types/enumerations/Status.md +5 -5
  60. package/docs/types/enumerations/VASPSearchControl.md +2 -2
  61. package/docs/types/enumerations/ValidationSections.md +4 -4
  62. package/docs/types/interfaces/Agent.md +6 -6
  63. package/docs/types/interfaces/CallbackOptions.md +2 -2
  64. package/docs/types/interfaces/ComponentRequest.md +2 -2
  65. package/docs/types/interfaces/ComponentResponse.md +4 -4
  66. package/docs/types/interfaces/ConnectionRecord.md +7 -7
  67. package/docs/types/interfaces/ConnectionRequest.md +3 -3
  68. package/docs/types/interfaces/Counterparty.md +10 -10
  69. package/docs/types/interfaces/DeclarationProof.md +6 -6
  70. package/docs/types/interfaces/Deposit.md +11 -11
  71. package/docs/types/interfaces/DepositRequest.md +7 -7
  72. package/docs/types/interfaces/DepositRequestOptions.md +1 -1
  73. package/docs/types/interfaces/DepositTransaction.md +9 -9
  74. package/docs/types/interfaces/LegalPerson.md +13 -13
  75. package/docs/types/interfaces/MicroTransferProof.md +9 -9
  76. package/docs/types/interfaces/NaturalPerson.md +13 -13
  77. package/docs/types/interfaces/OwnershipProof.md +4 -4
  78. package/docs/types/interfaces/RefreshSource.md +2 -2
  79. package/docs/types/interfaces/Refreshable.md +1 -1
  80. package/docs/types/interfaces/ScreenshotProof.md +5 -5
  81. package/docs/types/interfaces/SignatureProof.md +9 -9
  82. package/docs/types/interfaces/ThresholdOptions.md +3 -3
  83. package/docs/types/interfaces/Transaction.md +8 -8
  84. package/docs/types/interfaces/TransactionOptions.md +6 -6
  85. package/docs/types/interfaces/TransactionResponse.md +9 -9
  86. package/docs/types/interfaces/VASP.md +9 -9
  87. package/docs/types/interfaces/Wallet.md +8 -8
  88. package/docs/types/interfaces/Withdrawal.md +10 -10
  89. package/docs/types/type-aliases/BlockchainAddress.md +1 -1
  90. package/docs/types/type-aliases/CAIP10.md +1 -1
  91. package/docs/types/type-aliases/CAIP19.md +1 -1
  92. package/docs/types/type-aliases/CAIP2.md +1 -1
  93. package/docs/types/type-aliases/CAIP220.md +1 -1
  94. package/docs/types/type-aliases/Cancel.md +1 -1
  95. package/docs/types/type-aliases/Completed.md +1 -1
  96. package/docs/types/type-aliases/ComponentMessage.md +1 -1
  97. package/docs/types/type-aliases/ConnectionOptions.md +1 -1
  98. package/docs/types/type-aliases/CryptoCredential.md +1 -1
  99. package/docs/types/type-aliases/DID.md +1 -1
  100. package/docs/types/type-aliases/DTI.md +1 -1
  101. package/docs/types/type-aliases/Destination.md +1 -1
  102. package/docs/types/type-aliases/Error.md +1 -1
  103. package/docs/types/type-aliases/FieldOptions.md +1 -1
  104. package/docs/types/type-aliases/FieldTypes.md +1 -1
  105. package/docs/types/type-aliases/HostMessage.md +1 -1
  106. package/docs/types/type-aliases/ISOCurrency.md +1 -1
  107. package/docs/types/type-aliases/InvalidValue.md +1 -1
  108. package/docs/types/type-aliases/LEI.md +1 -1
  109. package/docs/types/type-aliases/LegalPersonFieldName.md +1 -1
  110. package/docs/types/type-aliases/LegalPersonFields.md +3 -3
  111. package/docs/types/type-aliases/NationalIdentifierTypeFieldOptions.md +21 -0
  112. package/docs/types/type-aliases/NaturalPersonFieldName.md +1 -1
  113. package/docs/types/type-aliases/NaturalPersonFields.md +3 -3
  114. package/docs/types/type-aliases/NotabeneAsset.md +1 -1
  115. package/docs/types/type-aliases/Ready.md +1 -1
  116. package/docs/types/type-aliases/ResizeRequest.md +1 -1
  117. package/docs/types/type-aliases/Source.md +1 -1
  118. package/docs/types/type-aliases/Theme.md +1 -1
  119. package/docs/types/type-aliases/TransactionAsset.md +1 -1
  120. package/docs/types/type-aliases/TravelAddress.md +1 -1
  121. package/docs/types/type-aliases/URI.md +1 -1
  122. package/docs/types/type-aliases/UUID.md +1 -1
  123. package/docs/types/type-aliases/UpdateValue.md +1 -1
  124. package/docs/types/type-aliases/V1Asset.md +1 -1
  125. package/docs/types/type-aliases/V1Transaction.md +1 -1
  126. package/docs/types/type-aliases/VASPOptions.md +1 -1
  127. package/docs/types/type-aliases/ValidationError.md +1 -1
  128. package/package.json +1 -1
  129. package/src/notabene.ts +7 -0
  130. package/src/types.ts +35 -7
  131. package/src/utils/__tests__/connections.test.ts +284 -0
  132. package/src/utils/__tests__/encryption.test.ts +79 -0
  133. package/src/utils/connections.ts +174 -0
  134. package/src/utils/encryption.ts +111 -0
@@ -26,4 +26,4 @@ Options for which VASPs to be searchable
26
26
 
27
27
  ## Defined in
28
28
 
29
- [types.ts:749](https://gitlab.com/notabene/open-source/javascript-sdk/-/blob/80aada173fac6a9b5f05869abafa3c1c6cc56aa9/src/types.ts#L749)
29
+ [types.ts:777](https://gitlab.com/notabene/open-source/javascript-sdk/-/blob/27e5c6c27d8a3a0bdafefd6e32c1654e964838a3/src/types.ts#L777)
@@ -22,4 +22,4 @@ Validation error
22
22
 
23
23
  ## Defined in
24
24
 
25
- [types.ts:705](https://gitlab.com/notabene/open-source/javascript-sdk/-/blob/80aada173fac6a9b5f05869abafa3c1c6cc56aa9/src/types.ts#L705)
25
+ [types.ts:733](https://gitlab.com/notabene/open-source/javascript-sdk/-/blob/27e5c6c27d8a3a0bdafefd6e32c1654e964838a3/src/types.ts#L733)
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "author": "Notabene <developers@notabene.id>",
11
11
  "license": "MIT",
12
12
  "packageManager": "yarn@4.5.1",
13
- "version": "2.8.0-next.4",
13
+ "version": "2.8.0-next.6",
14
14
  "source": "src/notabene.ts",
15
15
  "main": "dist/cjs/notabene.cjs",
16
16
  "module": "dist/esm/notabene.js",
package/src/notabene.ts CHANGED
@@ -73,6 +73,13 @@ import { type MessageCallback } from './utils/MessageEventManager';
73
73
  import { decodeFragmentToObject, encodeObjectToFragment } from './utils/urls';
74
74
  // Must be exported for React Native SDK to use
75
75
  export { default as EmbeddedComponent } from './components/EmbeddedComponent';
76
+ export {
77
+ ConnectionManager,
78
+ type ConnectionData,
79
+ type ConnectionMetadata,
80
+ type ConnectionResponse,
81
+ type TransactionType,
82
+ } from './utils/connections';
76
83
  export {
77
84
  AgentType,
78
85
  CMType,
package/src/types.ts CHANGED
@@ -5,6 +5,7 @@ import type {
5
5
  ISODate,
6
6
  IVMS101,
7
7
  NationalIdentification,
8
+ NationalIdentifierTypeCode,
8
9
  Originator,
9
10
  } from './ivms/types';
10
11
 
@@ -392,13 +393,31 @@ export type NaturalPersonFieldName =
392
393
  | 'countryOfResidence'; // ISO Country code of residence of Natural Person
393
394
 
394
395
  /**
395
- * Field properties by field name for Natural persons
396
+ * Field properties for national identifier type selection
396
397
  * @public
397
398
  */
398
- export type NaturalPersonFields = {
399
- [name in NaturalPersonFieldName]?: FieldOptions;
399
+ export type NationalIdentifierTypeFieldOptions = {
400
+ values?: NationalIdentifierTypeCode[];
400
401
  };
401
402
 
403
+ /**
404
+ * Field properties for natural person fields
405
+ * @public
406
+ */
407
+ export type NaturalPersonFields = Partial<{
408
+ name: FieldOptions;
409
+ website: FieldOptions;
410
+ email: FieldOptions;
411
+ phone: FieldOptions;
412
+ geographicAddress: FieldOptions;
413
+ nationalIdentification: FieldOptions & {
414
+ nationalIdentifierType: NationalIdentifierTypeFieldOptions;
415
+ };
416
+ dateOfBirth: FieldOptions;
417
+ placeOfBirth: FieldOptions;
418
+ countryOfResidence: FieldOptions;
419
+ }>;
420
+
402
421
  /**
403
422
  * Interface representing a legal entity (organization/company) involved in a transaction
404
423
  *
@@ -438,12 +457,21 @@ export type LegalPersonFieldName =
438
457
  | 'countryOfRegistration'; // ISO Country code of registration of Legal Person
439
458
 
440
459
  /**
441
- * Field properties by field name
460
+ * Field properties for legal person fields
442
461
  * @public
443
462
  */
444
- export type LegalPersonFields = {
445
- [name in LegalPersonFieldName]?: FieldOptions;
446
- };
463
+ export type LegalPersonFields = Partial<{
464
+ name: FieldOptions;
465
+ lei: FieldOptions;
466
+ website: FieldOptions;
467
+ email: FieldOptions;
468
+ phone: FieldOptions;
469
+ geographicAddress: FieldOptions;
470
+ nationalIdentification: FieldOptions & {
471
+ nationalIdentifierType: NationalIdentifierTypeFieldOptions;
472
+ };
473
+ countryOfRegistration: FieldOptions;
474
+ }>;
447
475
 
448
476
  /**
449
477
  * Fields specific to the originator of a transaction
@@ -0,0 +1,284 @@
1
+ import fc from 'fast-check';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import type { DID } from '../../types';
4
+ import { ConnectionManager, TransactionType } from '../connections';
5
+ import { seal } from '../encryption';
6
+
7
+ // Mock fetch globally
8
+ const fetchMock = vi.fn();
9
+ global.fetch = fetchMock;
10
+
11
+ // Helper to generate valid DID strings
12
+ const arbDID = fc
13
+ .tuple(fc.string(), fc.string())
14
+ .map(([method, id]) => `did:${method}:${id}` as DID);
15
+
16
+ // Test helper to create arbitrary ComponentRequests
17
+ const arbComponentRequest = fc.record({
18
+ tx: fc.record({
19
+ requestId: fc.option(fc.uuid(), { nil: undefined }),
20
+ customer: fc.option(
21
+ fc.record({
22
+ name: fc.string(),
23
+ email: fc.option(fc.string(), { nil: undefined }),
24
+ phone: fc.option(fc.string(), { nil: undefined }),
25
+ type: fc.constant(undefined), // Optional PersonType
26
+ accountNumber: fc.option(fc.string(), { nil: undefined }),
27
+ did: fc.option(arbDID, { nil: undefined }),
28
+ verified: fc.option(fc.boolean(), { nil: undefined }),
29
+ website: fc.option(fc.webUrl(), { nil: undefined }),
30
+ geographicAddress: fc.option(fc.constant(undefined), {
31
+ nil: undefined,
32
+ }),
33
+ nationalIdentification: fc.option(fc.constant(undefined), {
34
+ nil: undefined,
35
+ }),
36
+ }),
37
+ { nil: undefined },
38
+ ),
39
+ }),
40
+ authToken: fc.option(fc.string(), { nil: undefined }),
41
+ txOptions: fc.option(fc.record({}), { nil: undefined }),
42
+ });
43
+
44
+ // New arbitrary for ConnectionMetadata
45
+ const arbConnectionMetadata = fc.record({
46
+ participants: fc.array(fc.string(), { minLength: 1 }), // At least one participant
47
+ nodeUrl: fc.webUrl(),
48
+ transactionType: fc.constantFrom<TransactionType>('withdraw', 'deposit'),
49
+ });
50
+
51
+ describe('ConnectionManager', () => {
52
+ let manager: ConnectionManager;
53
+ const testEndpoint = 'https://test-endpoint.com';
54
+
55
+ beforeEach(() => {
56
+ manager = new ConnectionManager(testEndpoint);
57
+ fetchMock.mockReset();
58
+ });
59
+
60
+ describe('create', () => {
61
+ it('should successfully create a new connection', async () => {
62
+ await fc.assert(
63
+ fc.asyncProperty(
64
+ arbComponentRequest,
65
+ arbConnectionMetadata,
66
+ async (request, metadata) => {
67
+ // Mock successful response
68
+ const mockResponse = {
69
+ id: 'test-id',
70
+ version: 1,
71
+ metadata,
72
+ sealed: ['encrypted-data'],
73
+ };
74
+
75
+ fetchMock.mockResolvedValueOnce({
76
+ ok: true,
77
+ json: async () => mockResponse,
78
+ });
79
+
80
+ const result = await manager.create(request, metadata);
81
+
82
+ // Verify fetch was called correctly
83
+ expect(fetchMock).toHaveBeenCalledWith(testEndpoint, {
84
+ method: 'POST',
85
+ headers: {
86
+ 'Content-Type': 'application/json',
87
+ },
88
+ body: expect.any(String),
89
+ });
90
+
91
+ // Verify response structure
92
+ expect(result).toEqual({
93
+ id: mockResponse.id,
94
+ metadata: mockResponse.metadata,
95
+ version: mockResponse.version,
96
+ data: request,
97
+ key: expect.any(String),
98
+ });
99
+ },
100
+ ),
101
+ );
102
+ });
103
+
104
+ it('should throw error on failed creation', async () => {
105
+ await fc.assert(
106
+ fc.asyncProperty(
107
+ arbComponentRequest,
108
+ arbConnectionMetadata,
109
+ async (request, metadata) => {
110
+ // Mock failed response
111
+ fetchMock.mockResolvedValueOnce({
112
+ ok: false,
113
+ text: async () => 'Creation failed',
114
+ });
115
+
116
+ await expect(manager.create(request, metadata)).rejects.toThrow(
117
+ 'Failed to create connection',
118
+ );
119
+ },
120
+ ),
121
+ );
122
+ });
123
+ });
124
+
125
+ describe('update', () => {
126
+ it('should successfully update an existing connection', async () => {
127
+ await fc.assert(
128
+ fc.asyncProperty(
129
+ fc.uuid(),
130
+ arbComponentRequest,
131
+ fc.integer(),
132
+ async (id, request, version) => {
133
+ // Mock successful response
134
+ const mockResponse = {
135
+ id,
136
+ metadata: arbConnectionMetadata,
137
+ version: version + 1,
138
+ sealed: ['new-encrypted-data'],
139
+ };
140
+
141
+ fetchMock.mockResolvedValueOnce({
142
+ ok: true,
143
+ json: async () => mockResponse,
144
+ });
145
+
146
+ const result = await manager.update(id, request, version);
147
+
148
+ // Verify fetch was called correctly
149
+ expect(fetchMock).toHaveBeenCalledWith(`${testEndpoint}/${id}`, {
150
+ method: 'PATCH',
151
+ headers: {
152
+ 'Content-Type': 'application/json',
153
+ },
154
+ body: expect.any(String),
155
+ });
156
+
157
+ // Verify response structure
158
+ expect(result).toEqual({
159
+ id: mockResponse.id,
160
+ version: mockResponse.version,
161
+ metadata: mockResponse.metadata,
162
+ data: request,
163
+ key: expect.any(String),
164
+ });
165
+ },
166
+ ),
167
+ );
168
+ });
169
+
170
+ it('should throw error on failed update', async () => {
171
+ await fc.assert(
172
+ fc.asyncProperty(
173
+ fc.uuid(),
174
+ arbComponentRequest,
175
+ fc.integer(),
176
+ async (id, request, version) => {
177
+ // Mock failed response
178
+ fetchMock.mockResolvedValueOnce({
179
+ ok: false,
180
+ text: async () => 'Update failed',
181
+ });
182
+
183
+ await expect(manager.update(id, request, version)).rejects.toThrow(
184
+ 'Failed to update connection',
185
+ );
186
+ },
187
+ ),
188
+ );
189
+ });
190
+ });
191
+
192
+ describe('get', () => {
193
+ it('should successfully retrieve and decrypt connection data', async () => {
194
+ // Create a real sealed object to use in our test
195
+ const testData = { requestId: 'test-123' };
196
+ const sealed = await seal(testData);
197
+
198
+ await fc.assert(
199
+ fc.asyncProperty(fc.uuid(), async (id) => {
200
+ // Mock successful response with real encrypted data
201
+ const mockResponse = {
202
+ id,
203
+ version: 1,
204
+ metadata: arbConnectionMetadata,
205
+ sealed: [sealed.ciphertext], // Use the real ciphertext
206
+ };
207
+
208
+ fetchMock.mockResolvedValueOnce({
209
+ ok: true,
210
+ json: async () => mockResponse,
211
+ });
212
+
213
+ const result = await manager.get(id, sealed.key); // Use the matching key
214
+
215
+ // Verify fetch was called correctly
216
+ expect(fetchMock).toHaveBeenCalledWith(`${testEndpoint}/${id}`, {
217
+ method: 'GET',
218
+ });
219
+
220
+ // Verify response structure
221
+ expect(result).toEqual({
222
+ id: mockResponse.id,
223
+ version: mockResponse.version,
224
+ metadata: mockResponse.metadata,
225
+ data: testData, // Should match our original test data
226
+ key: sealed.key,
227
+ });
228
+ }),
229
+ );
230
+ });
231
+
232
+ it('should throw error on failed retrieval', async () => {
233
+ await fc.assert(
234
+ fc.asyncProperty(fc.uuid(), fc.string(), async (id, key) => {
235
+ // Mock failed response
236
+ fetchMock.mockResolvedValueOnce({
237
+ ok: false,
238
+ text: async () => 'Retrieval failed',
239
+ });
240
+
241
+ await expect(manager.get(id, key)).rejects.toThrow(
242
+ 'Failed to get connection',
243
+ );
244
+ }),
245
+ );
246
+ });
247
+ });
248
+
249
+ describe('close', () => {
250
+ it('should successfully close a connection', async () => {
251
+ await fc.assert(
252
+ fc.asyncProperty(fc.uuid(), async (id) => {
253
+ // Mock successful response
254
+ fetchMock.mockResolvedValueOnce({
255
+ ok: true,
256
+ });
257
+
258
+ await manager.close(id);
259
+
260
+ // Verify fetch was called correctly
261
+ expect(fetchMock).toHaveBeenCalledWith(`${testEndpoint}/${id}`, {
262
+ method: 'DELETE',
263
+ });
264
+ }),
265
+ );
266
+ });
267
+
268
+ it('should throw error on failed closure', async () => {
269
+ await fc.assert(
270
+ fc.asyncProperty(fc.uuid(), async (id) => {
271
+ // Mock failed response
272
+ fetchMock.mockResolvedValueOnce({
273
+ ok: false,
274
+ text: async () => 'Closure failed',
275
+ });
276
+
277
+ await expect(manager.close(id)).rejects.toThrow(
278
+ 'Failed to close connection',
279
+ );
280
+ }),
281
+ );
282
+ });
283
+ });
284
+ });
@@ -0,0 +1,79 @@
1
+ import * as fc from 'fast-check';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { Agent, AgentType } from '../../types';
4
+ import { seal, unseal } from '../encryption';
5
+
6
+ // Helper to validate that two objects are deeply equal
7
+ function deepEqual(
8
+ obj1: Record<string, unknown>,
9
+ obj2: Record<string, unknown>,
10
+ ): boolean {
11
+ return JSON.stringify(obj1) === JSON.stringify(obj2);
12
+ }
13
+
14
+ describe('normal tests', () => {
15
+ it('should correctly seal and unseal a Javascript object', async () => {
16
+ const agent: Agent = { type: AgentType.VASP, did: 'did:web:hello.com' };
17
+ const sealed = await seal<Agent>(agent);
18
+ const unsealed = await unseal<Agent>(sealed);
19
+ expect(unsealed).toEqual(agent);
20
+ });
21
+ });
22
+ describe('seal and unseal functions with combined IV and ciphertext', () => {
23
+ it('should correctly seal and unseal a JavaScript object', async () => {
24
+ await fc.assert(
25
+ fc.asyncProperty(
26
+ fc.object(), // Generate random objects
27
+ async (originalObject) => {
28
+ const { ciphertext, key } =
29
+ await seal<Record<string, unknown>>(originalObject); // Seal the object
30
+ const unsealedObject = await unseal<Record<string, unknown>>({
31
+ ciphertext,
32
+ key,
33
+ }); // Unseal the ciphertext
34
+
35
+ // Validate that the unsealed object matches the original
36
+ expect(deepEqual(originalObject, unsealedObject)).toBe(true);
37
+ },
38
+ ),
39
+ );
40
+ });
41
+
42
+ it('should throw an error if the key is invalid', async () => {
43
+ await fc.assert(
44
+ fc.asyncProperty(
45
+ fc.object(),
46
+ fc.string(), // Generate a random invalid key
47
+ async (originalObject, invalidKey) => {
48
+ const { ciphertext } =
49
+ await seal<Record<string, unknown>>(originalObject);
50
+
51
+ await expect(
52
+ unseal<Record<string, unknown>>({ ciphertext, key: invalidKey }),
53
+ ).rejects.toThrowError();
54
+ },
55
+ ),
56
+ );
57
+ });
58
+
59
+ it('should throw an error if the ciphertext is tampered with', async () => {
60
+ await fc.assert(
61
+ fc.asyncProperty(fc.object(), async (originalObject) => {
62
+ const { ciphertext, key } =
63
+ await seal<Record<string, unknown>>(originalObject);
64
+
65
+ // Tamper with the ciphertext
66
+ const tamperedCiphertext =
67
+ ciphertext.slice(0, -1) + (ciphertext.at(-1) === 'A' ? 'B' : 'A');
68
+
69
+ // Expect decryption to fail
70
+ await expect(
71
+ unseal<Record<string, unknown>>({
72
+ ciphertext: tamperedCiphertext,
73
+ key,
74
+ }),
75
+ ).rejects.toThrowError();
76
+ }),
77
+ );
78
+ });
79
+ });
@@ -0,0 +1,174 @@
1
+ import type { ComponentRequest, TransactionOptions, UUID } from '../types';
2
+ import { seal, unseal } from './encryption';
3
+
4
+ export type TransactionType = 'withdraw' | 'deposit';
5
+ export interface ConnectionData<T extends ComponentRequest> {
6
+ tx: T;
7
+ authToken?: string;
8
+ txOptions?: TransactionOptions;
9
+ }
10
+
11
+ export interface ConnectionMetadata {
12
+ nodeUrl?: string;
13
+ participants: string[];
14
+ transactionType: TransactionType;
15
+ }
16
+ export interface ConnectionResponse<T extends ComponentRequest> {
17
+ id: UUID;
18
+ version: number;
19
+ metadata: ConnectionMetadata;
20
+ data: ConnectionData<T>;
21
+ key: string;
22
+ }
23
+
24
+ /**
25
+ * Manages encrypted connections using Cloudflare Durable Objects
26
+ */
27
+ export class ConnectionManager {
28
+ private endpoint: string;
29
+
30
+ constructor(endpoint: string) {
31
+ this.endpoint = endpoint;
32
+ }
33
+
34
+ /**
35
+ * Creates a new encrypted connection
36
+ * @param data The component request data to encrypt and store
37
+ * @param participants Array of participant identifiers
38
+ * @returns Promise resolving to connection details including ID, version, and encryption key
39
+ */
40
+ async create<T extends ComponentRequest>(
41
+ data: ConnectionData<T>,
42
+ metadata: ConnectionMetadata,
43
+ ): Promise<ConnectionResponse<T>> {
44
+ // Encrypt the data
45
+ const sealed = await seal(data);
46
+
47
+ // Prepare the request body
48
+ const body = {
49
+ metadata,
50
+ sealed: sealed.ciphertext,
51
+ };
52
+
53
+ // Create the connection
54
+ const response = await fetch(this.endpoint, {
55
+ method: 'POST',
56
+ headers: {
57
+ 'Content-Type': 'application/json',
58
+ },
59
+ body: JSON.stringify(body),
60
+ });
61
+
62
+ if (!response.ok) {
63
+ throw new Error(`Failed to create connection: ${await response.text()}`);
64
+ }
65
+
66
+ const result = await response.json();
67
+
68
+ return {
69
+ id: result.id,
70
+ version: result.version,
71
+ metadata: metadata,
72
+ data: data,
73
+ key: sealed.key,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Updates an existing connection with new encrypted data
79
+ * @param id Connection ID
80
+ * @param data New data to encrypt and store
81
+ * @param version Current version number
82
+ * @returns Promise resolving to updated connection details including new encryption key
83
+ */
84
+ async update<T extends ComponentRequest>(
85
+ id: UUID,
86
+ data: ConnectionData<T>,
87
+ version: number,
88
+ ): Promise<ConnectionResponse<T>> {
89
+ // Encrypt the new data
90
+ const sealed = await seal(data);
91
+
92
+ // Prepare the request body
93
+ const body = {
94
+ sealed: sealed.ciphertext,
95
+ version,
96
+ };
97
+
98
+ // Update the connection
99
+ const response = await fetch(`${this.endpoint}/${id}`, {
100
+ method: 'PATCH',
101
+ headers: {
102
+ 'Content-Type': 'application/json',
103
+ },
104
+ body: JSON.stringify(body),
105
+ });
106
+
107
+ if (!response.ok) {
108
+ throw new Error(`Failed to update connection: ${await response.text()}`);
109
+ }
110
+
111
+ const result = await response.json();
112
+
113
+ return {
114
+ id: result.id,
115
+ metadata: result.metadata,
116
+ version: result.version,
117
+ data: data,
118
+ key: sealed.key,
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Retrieves and decrypts connection data
124
+ * @param id Connection ID
125
+ * @param key Encryption key from previous create/update operation
126
+ * @returns Promise resolving to connection details including decrypted data
127
+ */
128
+ async get<T extends ComponentRequest>(
129
+ id: UUID,
130
+ key: string,
131
+ ): Promise<ConnectionResponse<T>> {
132
+ // Get the connection data
133
+ const response = await fetch(`${this.endpoint}/${id}`, {
134
+ method: 'GET',
135
+ });
136
+
137
+ if (!response.ok) {
138
+ throw new Error(`Failed to get connection: ${await response.text()}`);
139
+ }
140
+
141
+ const result = await response.json();
142
+
143
+ // Get the latest sealed data
144
+ const latestSealed = result.sealed[result.sealed.length - 1];
145
+
146
+ // Decrypt the data
147
+ const data = await unseal<ConnectionData<T>>({
148
+ ciphertext: latestSealed,
149
+ key,
150
+ });
151
+
152
+ return {
153
+ id: result.id,
154
+ version: result.version,
155
+ metadata: result.metadata,
156
+ data,
157
+ key,
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Closes a connection
163
+ * @param id Connection ID
164
+ */
165
+ async close(id: UUID): Promise<void> {
166
+ const response = await fetch(`${this.endpoint}/${id}`, {
167
+ method: 'DELETE',
168
+ });
169
+
170
+ if (!response.ok) {
171
+ throw new Error(`Failed to close connection: ${await response.text()}`);
172
+ }
173
+ }
174
+ }