@notabene/javascript-sdk 2.19.1 → 2.20.0

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/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.19.1",
13
+ "version": "2.20.0",
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
@@ -102,6 +102,7 @@ export type {
102
102
  export type { Invoice } from './types';
103
103
  export {
104
104
  ConnectionManager,
105
+ decryptCounterpartyAssistPayload,
105
106
  getRefreshResult,
106
107
  type ConnectionData,
107
108
  type ConnectionMetadata,
package/src/types.ts CHANGED
@@ -584,6 +584,12 @@ export interface Transaction extends ComponentRequest {
584
584
  export interface RefreshSource {
585
585
  url: URI;
586
586
  key: string;
587
+ /**
588
+ * The connection id (also embedded in `url`). Exposed directly so consumers can
589
+ * correlate a Counterparty Assist webhook event (whose `payload.id` is this id)
590
+ * without parsing it out of the url.
591
+ */
592
+ id: string;
587
593
  }
588
594
 
589
595
  export interface Refreshable {
@@ -881,6 +887,17 @@ export type CounterpartyAssistConfig =
881
887
  counterpartyTypes: PersonType[];
882
888
  /** @remarks Requires a transactionId to be passed in. */
883
889
  identityVerification?: IdentityVerificationConfig;
890
+ /**
891
+ * Optional URL that receives an `EC.counterpartyAssist*` webhook when the
892
+ * counterparty completes or closes their part. MVP-only per-link override;
893
+ * superseded by Portal-managed subscriptions post-migration.
894
+ *
895
+ * Transmitted and stored as plaintext, so do not embed secrets (e.g. `?token=`)
896
+ * in this URL. Payloads carry the encrypted `sealed` blob (a successful decrypt
897
+ * is itself an authenticity check); signed delivery is provided by the future
898
+ * Svix-backed pipeline.
899
+ */
900
+ webhookUrl?: string;
884
901
  };
885
902
 
886
903
  /**
@@ -4,6 +4,7 @@ import type { DID, RefreshSource } from '../../types';
4
4
  import {
5
5
  ConnectionManager,
6
6
  type ConnectionStatus,
7
+ decryptCounterpartyAssistPayload,
7
8
  getRefreshResult,
8
9
  type TransactionType,
9
10
  } from '../connections';
@@ -53,6 +54,7 @@ const arbConnectionMetadata = fc.record({
53
54
  participants: fc.array(fc.string(), { minLength: 1 }), // At least one participant
54
55
  nodeUrl: fc.webUrl(),
55
56
  transactionType: fc.constantFrom<TransactionType>('withdraw', 'deposit'),
57
+ webhookUrl: fc.option(fc.webUrl(), { nil: undefined }),
56
58
  });
57
59
 
58
60
  describe('ConnectionManager', () => {
@@ -317,6 +319,7 @@ describe('ConnectionManager', () => {
317
319
  const refreshSource: RefreshSource = {
318
320
  url: 'https://test-endpoint.com',
319
321
  key: sealed.key,
322
+ id: 'test-id',
320
323
  };
321
324
 
322
325
  // Common metadata for all tests
@@ -385,6 +388,7 @@ describe('ConnectionManager', () => {
385
388
  const completedRefreshSource: RefreshSource = {
386
389
  url: 'https://test-endpoint.com',
387
390
  key: completedSealed.key,
391
+ id: 'test-id',
388
392
  };
389
393
 
390
394
  const mockResponse = createMockResponse(
@@ -435,3 +439,28 @@ describe('ConnectionManager', () => {
435
439
  });
436
440
  });
437
441
  });
442
+
443
+ describe('decryptCounterpartyAssistPayload', () => {
444
+ it('round-trips a sealed payload back to the original object', async () => {
445
+ const original = { result: { ref: 'abc' }, tx: { amount: '10' } };
446
+ const sealed = await seal(original);
447
+
448
+ const decrypted = await decryptCounterpartyAssistPayload<typeof original>({
449
+ sealed: sealed.ciphertext,
450
+ key: sealed.key,
451
+ });
452
+
453
+ expect(decrypted).toEqual(original);
454
+ });
455
+
456
+ it('throws when the key is wrong', async () => {
457
+ const sealed = await seal({ a: 1 });
458
+ const other = await seal({ b: 2 });
459
+ await expect(
460
+ decryptCounterpartyAssistPayload({
461
+ sealed: sealed.ciphertext,
462
+ key: other.key,
463
+ }),
464
+ ).rejects.toThrow();
465
+ });
466
+ });
@@ -24,6 +24,14 @@ export interface ConnectionMetadata {
24
24
  readonly participants: readonly string[];
25
25
  readonly transactionType: TransactionType;
26
26
  readonly locale?: string;
27
+ /**
28
+ * Delivery target for Counterparty Assist completion/closure webhooks.
29
+ * Sent and stored as plaintext so the connection can route the event, so do not
30
+ * embed secrets (e.g. `?token=`) in this URL. Webhook payloads carry the encrypted
31
+ * `sealed` blob, whose successful decryption is itself an authenticity check;
32
+ * signed delivery is provided by the future Svix-backed pipeline.
33
+ */
34
+ readonly webhookUrl?: string;
27
35
  }
28
36
 
29
37
  export interface ConnectionResponse<T extends ComponentRequest> {
@@ -61,7 +69,7 @@ export type ConnectionResult<T extends ComponentRequest> =
61
69
  * @returns Promise resolving to connection result with decrypted data
62
70
  */
63
71
  export async function getRefreshResult<T extends ComponentRequest>(
64
- refreshSource: RefreshSource,
72
+ refreshSource: Pick<RefreshSource, 'url' | 'key'>,
65
73
  ): Promise<ConnectionResult<T>> {
66
74
  const response = await fetch(refreshSource.url, {
67
75
  method: 'GET',
@@ -109,6 +117,22 @@ export async function getRefreshResult<T extends ComponentRequest>(
109
117
  };
110
118
  }
111
119
 
120
+ /**
121
+ * Decrypt a Counterparty Assist webhook payload with the key the VASP captured
122
+ * at connection creation. Single call, no network fetch (the webhook already
123
+ * carried the ciphertext).
124
+ * @public
125
+ */
126
+ export async function decryptCounterpartyAssistPayload<T>({
127
+ sealed,
128
+ key,
129
+ }: {
130
+ sealed: string;
131
+ key: string;
132
+ }): Promise<T> {
133
+ return unseal<T>({ ciphertext: sealed, key });
134
+ }
135
+
112
136
  /**
113
137
  * Manages encrypted connections using Cloudflare Durable Objects
114
138
  */