@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.
- package/README.md +30 -0
- package/dist/cjs/notabene.cjs +1 -1
- package/dist/cjs/notabene.d.ts +92 -8
- package/dist/cjs/package.json +1 -1
- package/dist/esm/notabene.d.ts +92 -8
- package/dist/esm/notabene.js +282 -137
- package/dist/esm/package.json +1 -1
- package/dist/notabene.d.ts +92 -8
- package/dist/notabene.js +282 -137
- package/docs/README.md +30 -0
- package/docs/_media/TransactionOptions.md +6 -6
- package/docs/ivms/types/enumerations/PayloadVersionCode.md +2 -2
- package/docs/ivms/types/interfaces/PayloadMetadata.md +2 -2
- package/docs/ivms/types/type-aliases/Address.md +1 -1
- package/docs/ivms/types/type-aliases/AddressTypeCode.md +1 -1
- package/docs/ivms/types/type-aliases/Beneficiary.md +1 -1
- package/docs/ivms/types/type-aliases/BeneficiaryVASP.md +1 -1
- package/docs/ivms/types/type-aliases/DateAndPlaceOfBirth.md +1 -1
- package/docs/ivms/types/type-aliases/ISOCountryCode.md +1 -1
- package/docs/ivms/types/type-aliases/ISODate.md +1 -1
- package/docs/ivms/types/type-aliases/IVMS101.md +1 -1
- package/docs/ivms/types/type-aliases/IntermediaryVASP.md +1 -1
- package/docs/ivms/types/type-aliases/LegalPerson.md +1 -1
- package/docs/ivms/types/type-aliases/LegalPersonName.md +1 -1
- package/docs/ivms/types/type-aliases/LegalPersonNameID.md +1 -1
- package/docs/ivms/types/type-aliases/LegalPersonNameTypeCode.md +1 -1
- package/docs/ivms/types/type-aliases/LocalLegalPersonNameID.md +1 -1
- package/docs/ivms/types/type-aliases/LocalNaturalPersonNameID.md +1 -1
- package/docs/ivms/types/type-aliases/NationalIdentification.md +1 -1
- package/docs/ivms/types/type-aliases/NationalIdentifierTypeCode.md +1 -1
- package/docs/ivms/types/type-aliases/NaturalPerson.md +1 -1
- package/docs/ivms/types/type-aliases/NaturalPersonName.md +1 -1
- package/docs/ivms/types/type-aliases/NaturalPersonNameID.md +1 -1
- package/docs/ivms/types/type-aliases/NaturalPersonNameTypeCode.md +1 -1
- package/docs/ivms/types/type-aliases/OriginatingVASP.md +1 -1
- package/docs/ivms/types/type-aliases/Originator.md +1 -1
- package/docs/ivms/types/type-aliases/Person.md +1 -1
- package/docs/ivms/types/type-aliases/TransferPath.md +1 -1
- package/docs/ivms/types/type-aliases/TransliterationMethodCode.md +1 -1
- package/docs/notabene/README.md +5 -0
- package/docs/notabene/classes/ConnectionManager.md +165 -0
- package/docs/notabene/classes/EmbeddedComponent.md +17 -17
- package/docs/notabene/classes/default.md +7 -7
- package/docs/notabene/functions/decodeFragmentToObject.md +1 -1
- package/docs/notabene/interfaces/ConnectionData.md +41 -0
- package/docs/notabene/interfaces/ConnectionMetadata.md +37 -0
- package/docs/notabene/interfaces/ConnectionResponse.md +61 -0
- package/docs/notabene/interfaces/NotabeneConfig.md +5 -5
- package/docs/notabene/type-aliases/MessageCallback.md +1 -1
- package/docs/notabene/type-aliases/TransactionType.md +13 -0
- package/docs/types/README.md +1 -0
- package/docs/types/enumerations/AgentType.md +2 -2
- package/docs/types/enumerations/CMType.md +7 -7
- package/docs/types/enumerations/ErrorIdentifierCode.md +4 -4
- package/docs/types/enumerations/HMType.md +2 -2
- package/docs/types/enumerations/PersonType.md +3 -3
- package/docs/types/enumerations/ProofStatus.md +4 -4
- package/docs/types/enumerations/ProofTypes.md +16 -16
- package/docs/types/enumerations/Status.md +5 -5
- package/docs/types/enumerations/VASPSearchControl.md +2 -2
- package/docs/types/enumerations/ValidationSections.md +4 -4
- package/docs/types/interfaces/Agent.md +6 -6
- package/docs/types/interfaces/CallbackOptions.md +2 -2
- package/docs/types/interfaces/ComponentRequest.md +2 -2
- package/docs/types/interfaces/ComponentResponse.md +4 -4
- package/docs/types/interfaces/ConnectionRecord.md +7 -7
- package/docs/types/interfaces/ConnectionRequest.md +3 -3
- package/docs/types/interfaces/Counterparty.md +10 -10
- package/docs/types/interfaces/DeclarationProof.md +6 -6
- package/docs/types/interfaces/Deposit.md +11 -11
- package/docs/types/interfaces/DepositRequest.md +7 -7
- package/docs/types/interfaces/DepositRequestOptions.md +1 -1
- package/docs/types/interfaces/DepositTransaction.md +9 -9
- package/docs/types/interfaces/LegalPerson.md +13 -13
- package/docs/types/interfaces/MicroTransferProof.md +9 -9
- package/docs/types/interfaces/NaturalPerson.md +13 -13
- package/docs/types/interfaces/OwnershipProof.md +4 -4
- package/docs/types/interfaces/RefreshSource.md +2 -2
- package/docs/types/interfaces/Refreshable.md +1 -1
- package/docs/types/interfaces/ScreenshotProof.md +5 -5
- package/docs/types/interfaces/SignatureProof.md +9 -9
- package/docs/types/interfaces/ThresholdOptions.md +3 -3
- package/docs/types/interfaces/Transaction.md +8 -8
- package/docs/types/interfaces/TransactionOptions.md +6 -6
- package/docs/types/interfaces/TransactionResponse.md +9 -9
- package/docs/types/interfaces/VASP.md +9 -9
- package/docs/types/interfaces/Wallet.md +8 -8
- package/docs/types/interfaces/Withdrawal.md +10 -10
- package/docs/types/type-aliases/BlockchainAddress.md +1 -1
- package/docs/types/type-aliases/CAIP10.md +1 -1
- package/docs/types/type-aliases/CAIP19.md +1 -1
- package/docs/types/type-aliases/CAIP2.md +1 -1
- package/docs/types/type-aliases/CAIP220.md +1 -1
- package/docs/types/type-aliases/Cancel.md +1 -1
- package/docs/types/type-aliases/Completed.md +1 -1
- package/docs/types/type-aliases/ComponentMessage.md +1 -1
- package/docs/types/type-aliases/ConnectionOptions.md +1 -1
- package/docs/types/type-aliases/CryptoCredential.md +1 -1
- package/docs/types/type-aliases/DID.md +1 -1
- package/docs/types/type-aliases/DTI.md +1 -1
- package/docs/types/type-aliases/Destination.md +1 -1
- package/docs/types/type-aliases/Error.md +1 -1
- package/docs/types/type-aliases/FieldOptions.md +1 -1
- package/docs/types/type-aliases/FieldTypes.md +1 -1
- package/docs/types/type-aliases/HostMessage.md +1 -1
- package/docs/types/type-aliases/ISOCurrency.md +1 -1
- package/docs/types/type-aliases/InvalidValue.md +1 -1
- package/docs/types/type-aliases/LEI.md +1 -1
- package/docs/types/type-aliases/LegalPersonFieldName.md +1 -1
- package/docs/types/type-aliases/LegalPersonFields.md +3 -3
- package/docs/types/type-aliases/NationalIdentifierTypeFieldOptions.md +21 -0
- package/docs/types/type-aliases/NaturalPersonFieldName.md +1 -1
- package/docs/types/type-aliases/NaturalPersonFields.md +3 -3
- package/docs/types/type-aliases/NotabeneAsset.md +1 -1
- package/docs/types/type-aliases/Ready.md +1 -1
- package/docs/types/type-aliases/ResizeRequest.md +1 -1
- package/docs/types/type-aliases/Source.md +1 -1
- package/docs/types/type-aliases/Theme.md +1 -1
- package/docs/types/type-aliases/TransactionAsset.md +1 -1
- package/docs/types/type-aliases/TravelAddress.md +1 -1
- package/docs/types/type-aliases/URI.md +1 -1
- package/docs/types/type-aliases/UUID.md +1 -1
- package/docs/types/type-aliases/UpdateValue.md +1 -1
- package/docs/types/type-aliases/V1Asset.md +1 -1
- package/docs/types/type-aliases/V1Transaction.md +1 -1
- package/docs/types/type-aliases/VASPOptions.md +1 -1
- package/docs/types/type-aliases/ValidationError.md +1 -1
- package/package.json +1 -1
- package/src/notabene.ts +7 -0
- package/src/types.ts +35 -7
- package/src/utils/__tests__/connections.test.ts +284 -0
- package/src/utils/__tests__/encryption.test.ts +79 -0
- package/src/utils/connections.ts +174 -0
- 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:
|
|
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:
|
|
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.
|
|
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
|
|
396
|
+
* Field properties for national identifier type selection
|
|
396
397
|
* @public
|
|
397
398
|
*/
|
|
398
|
-
export type
|
|
399
|
-
[
|
|
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
|
|
460
|
+
* Field properties for legal person fields
|
|
442
461
|
* @public
|
|
443
462
|
*/
|
|
444
|
-
export type LegalPersonFields = {
|
|
445
|
-
|
|
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
|
+
}
|