@notabene/javascript-sdk 2.14.2 → 2.15.0-next.1

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.14.2",
13
+ "version": "2.15.0-next.1",
14
14
  "source": "src/notabene.ts",
15
15
  "main": "dist/cjs/notabene.cjs",
16
16
  "module": "dist/esm/notabene.js",
@@ -1,5 +1,5 @@
1
1
  import { fc, test } from '@fast-check/vitest';
2
- import { describe } from 'vitest';
2
+ import { describe, expect, it } from 'vitest';
3
3
  import Notabene from '../notabene';
4
4
  import {
5
5
  abitraryCallbackOptions,
@@ -14,11 +14,13 @@ import {
14
14
  arbitraryWithdrawal,
15
15
  } from '../utils/arbitraries';
16
16
 
17
+ const arbitraryNodeUrl = () => fc.webUrl().map((u) => u.replace(/\/+$/, ''));
18
+
17
19
  describe('Notabene', () => {
18
20
  describe('defaults', () => {
19
21
  test.prop([
20
22
  fc.string({ minLength: 3 }),
21
- fc.webUrl(),
23
+ arbitraryNodeUrl(),
22
24
  arbitraryComponent(),
23
25
  fc.object(),
24
26
  ])(
@@ -32,7 +34,7 @@ describe('Notabene', () => {
32
34
  return (
33
35
  url.hash ===
34
36
  `#authToken=${encodeURIComponent(authToken)}&value=${encodeURIComponent(JSON.stringify(value))}` &&
35
- url.searchParams.get('nodeUrl') === nodeUrl &&
37
+ url.searchParams.get('nodeUrl') === nodeUrl.replace(/\/+$/, '') &&
36
38
  url.pathname === `/${component}`
37
39
  );
38
40
  },
@@ -42,7 +44,7 @@ describe('Notabene', () => {
42
44
  describe('locale', () => {
43
45
  test.prop([
44
46
  fc.string({ minLength: 3 }),
45
- fc.webUrl(),
47
+ arbitraryNodeUrl(),
46
48
  arbitraryComponent(),
47
49
  fc.object(),
48
50
  arbitraryLocale(),
@@ -84,7 +86,7 @@ describe('Notabene', () => {
84
86
  describe('with uxUrl', () => {
85
87
  test.prop([
86
88
  fc.string({ minLength: 3 }),
87
- fc.webUrl(),
89
+ arbitraryNodeUrl(),
88
90
  arbitraryComponent(),
89
91
  fc.webAuthority(),
90
92
  fc.object(),
@@ -113,7 +115,7 @@ describe('Notabene', () => {
113
115
  describe('with value and configuration', () => {
114
116
  test.prop([
115
117
  fc.string({ minLength: 3 }),
116
- fc.webUrl(),
118
+ arbitraryNodeUrl(),
117
119
  arbitraryComponent(),
118
120
  fc.object(),
119
121
  fc.object(),
@@ -140,7 +142,7 @@ describe('Notabene', () => {
140
142
  describe('with value and configuration', () => {
141
143
  test.prop([
142
144
  fc.string({ minLength: 3 }),
143
- fc.webUrl(),
145
+ arbitraryNodeUrl(),
144
146
  arbitraryComponent(),
145
147
  fc.object(),
146
148
  fc.object(),
@@ -170,7 +172,7 @@ describe('Notabene', () => {
170
172
  describe('createWithdrawalAssist', () => {
171
173
  test.prop([
172
174
  fc.string({ minLength: 3 }), // authToken
173
- fc.webUrl(), // nodeUrl
175
+ arbitraryNodeUrl(), // nodeUrl
174
176
  arbitraryWithdrawal(),
175
177
  arbitraryTransactionOptions(), // options
176
178
  abitraryCallbackOptions(), // callbacks
@@ -204,7 +206,7 @@ describe('Notabene', () => {
204
206
  describe('createDepositRequest', () => {
205
207
  test.prop([
206
208
  fc.string({ minLength: 3 }), // authToken
207
- fc.webUrl(), // nodeUrl
209
+ arbitraryNodeUrl(), // nodeUrl
208
210
  arbitraryDepositRequest(),
209
211
  arbitraryDepositRequestOptions(), // options
210
212
  abitraryCallbackOptions(), // callbacks
@@ -238,7 +240,7 @@ describe('Notabene', () => {
238
240
  describe('createConnectWallet', () => {
239
241
  test.prop([
240
242
  fc.string({ minLength: 3 }), // authToken
241
- fc.webUrl(), // nodeUrl
243
+ arbitraryNodeUrl(), // nodeUrl
242
244
  arbitraryConnectionRequest(),
243
245
  arbitraryConnectionOptions(), // options
244
246
  abitraryCallbackOptions(), // callbacks
@@ -268,10 +270,80 @@ describe('Notabene', () => {
268
270
  },
269
271
  );
270
272
  });
273
+ describe('nodeUrl trailing slash normalization', () => {
274
+ it('should strip a single trailing slash', () => {
275
+ const notabene = new Notabene({
276
+ nodeUrl: 'https://api.eu1.notabene.id/',
277
+ authToken: 'token',
278
+ });
279
+ const url = new URL(notabene.componentUrl('withdrawal-assist', {}));
280
+ expect(url.searchParams.get('nodeUrl')).toBe(
281
+ 'https://api.eu1.notabene.id',
282
+ );
283
+ });
284
+
285
+ it('should strip multiple trailing slashes', () => {
286
+ const notabene = new Notabene({
287
+ nodeUrl: 'https://api.eu1.notabene.id///',
288
+ authToken: 'token',
289
+ });
290
+ const url = new URL(notabene.componentUrl('withdrawal-assist', {}));
291
+ expect(url.searchParams.get('nodeUrl')).toBe(
292
+ 'https://api.eu1.notabene.id',
293
+ );
294
+ });
295
+
296
+ it('should leave a clean URL unchanged', () => {
297
+ const notabene = new Notabene({
298
+ nodeUrl: 'https://api.eu1.notabene.id',
299
+ authToken: 'token',
300
+ });
301
+ const url = new URL(notabene.componentUrl('withdrawal-assist', {}));
302
+ expect(url.searchParams.get('nodeUrl')).toBe(
303
+ 'https://api.eu1.notabene.id',
304
+ );
305
+ });
306
+
307
+ it('should trim whitespace', () => {
308
+ const notabene = new Notabene({
309
+ nodeUrl: ' https://api.eu1.notabene.id ',
310
+ authToken: 'token',
311
+ });
312
+ const url = new URL(notabene.componentUrl('withdrawal-assist', {}));
313
+ expect(url.searchParams.get('nodeUrl')).toBe(
314
+ 'https://api.eu1.notabene.id',
315
+ );
316
+ });
317
+
318
+ it('should throw for an invalid URL', () => {
319
+ expect(
320
+ () => new Notabene({ nodeUrl: 'not-a-url', authToken: 'token' }),
321
+ ).toThrow('Invalid nodeUrl');
322
+ });
323
+
324
+ it('should throw for a non-http protocol', () => {
325
+ expect(
326
+ () =>
327
+ new Notabene({ nodeUrl: 'ftp://example.com', authToken: 'token' }),
328
+ ).toThrow('must start with http:// or https://');
329
+ });
330
+
331
+ it('should preserve URL paths', () => {
332
+ const notabene = new Notabene({
333
+ nodeUrl: 'https://api.notabene.id/v2',
334
+ authToken: 'token',
335
+ });
336
+ const url = new URL(notabene.componentUrl('withdrawal-assist', {}));
337
+ expect(url.searchParams.get('nodeUrl')).toBe(
338
+ 'https://api.notabene.id/v2',
339
+ );
340
+ });
341
+ });
342
+
271
343
  describe('createDepositAssist', () => {
272
344
  test.prop([
273
345
  fc.string({ minLength: 3 }), // authToken
274
- fc.webUrl(), // nodeUrl
346
+ arbitraryNodeUrl(), // nodeUrl
275
347
  arbitraryDeposit(),
276
348
  arbitraryTransactionOptions(), // options
277
349
  abitraryCallbackOptions(), // callbacks
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  CMType,
3
3
  HMType,
4
- HostMessage,
5
- TransactionResponse,
6
- ValidationError,
4
+ type HostMessage,
5
+ type TransactionResponse,
6
+ type ValidationError,
7
7
  } from '../types';
8
8
  import {
9
- MessageCallback,
9
+ type MessageCallback,
10
10
  MessageEventManager,
11
11
  } from '../utils/MessageEventManager';
12
12
 
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  NaturalPerson,
3
3
  NaturalPersonName,
4
4
  NaturalPersonNameTypeCode,
package/src/notabene.ts CHANGED
@@ -2,6 +2,7 @@ import EmbeddedComponent from './components/EmbeddedComponent';
2
2
  import type {
3
3
  Account,
4
4
  Agent,
5
+ AgentSections,
5
6
  BlockchainAddress,
6
7
  CAIP10,
7
8
  CAIP19,
@@ -45,6 +46,7 @@ import type {
45
46
  RefreshSource,
46
47
  ResizeRequest,
47
48
  ScreenshotProof,
49
+ SectionOption,
48
50
  SignatureProof,
49
51
  SIWXInput,
50
52
  SolanaMetadata,
@@ -70,7 +72,6 @@ import {
70
72
  ErrorIdentifierCode,
71
73
  HMType,
72
74
  IdentityVerificationMethod,
73
- OAuthProvider,
74
75
  PersonType,
75
76
  ProofStatus,
76
77
  ProofTypes,
@@ -113,7 +114,6 @@ export {
113
114
  ErrorIdentifierCode,
114
115
  HMType,
115
116
  IdentityVerificationMethod,
116
- OAuthProvider,
117
117
  PersonType,
118
118
  ProofStatus,
119
119
  ProofTypes,
@@ -125,6 +125,7 @@ export {
125
125
  export type {
126
126
  Account,
127
127
  Agent,
128
+ AgentSections,
128
129
  BlockchainAddress,
129
130
  CAIP10,
130
131
  CAIP19,
@@ -169,6 +170,7 @@ export type {
169
170
  RefreshSource,
170
171
  ResizeRequest,
171
172
  ScreenshotProof,
173
+ SectionOption,
172
174
  SignatureProof,
173
175
  SIWXInput,
174
176
  SolanaMetadata,
@@ -228,6 +230,23 @@ export interface NotabeneConfig {
228
230
  *
229
231
  * @public
230
232
  */
233
+ /** Trim whitespace, validate, and strip trailing slashes from a URL. */
234
+ function normalizeUrl(url: string, field: string): string {
235
+ const normalized = url.trim().replace(/\/+$/, '');
236
+ let parsed: URL;
237
+ try {
238
+ parsed = new URL(normalized);
239
+ } catch {
240
+ throw new Error(`Invalid ${field}: "${url}" is not a valid URL`);
241
+ }
242
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
243
+ throw new Error(
244
+ `Invalid ${field}: "${url}" (must start with http:// or https://)`,
245
+ );
246
+ }
247
+ return normalized;
248
+ }
249
+
231
250
  export default class Notabene {
232
251
  private nodeUrl?: string;
233
252
  private authToken?: string;
@@ -241,8 +260,13 @@ export default class Notabene {
241
260
  * @param config - Configuration options for the Notabene SDK
242
261
  */
243
262
  constructor(config: NotabeneConfig) {
244
- this.uxUrl = config.uxUrl || 'https://connect.notabene.id';
245
- this.nodeUrl = config.nodeUrl;
263
+ this.uxUrl = normalizeUrl(
264
+ config.uxUrl || 'https://connect.notabene.id',
265
+ 'uxUrl',
266
+ );
267
+ this.nodeUrl = config.nodeUrl
268
+ ? normalizeUrl(config.nodeUrl, 'nodeUrl')
269
+ : undefined;
246
270
  this.authToken = config.authToken;
247
271
  this.theme = config.theme;
248
272
  this.locale = config.locale;
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { OriginatorV2 } from '../../ivms/v2Types';
2
+ import type { OriginatorV2 } from '../../ivms/v2Types';
3
3
  import type { Deposit, TransactionResponse, Withdrawal } from '../../types';
4
4
  import { PersonType } from '../../types';
5
5
  import { componentResponseToTxRequests, enrichConfig } from '../transformer';
@@ -7,8 +7,8 @@ import type {
7
7
  } from '@notabene/javascript-sdk/src/ivms/types';
8
8
  import type { Agent } from '@taprsvp/types';
9
9
  import {
10
- Deposit,
11
10
  PersonType,
11
+ type Deposit,
12
12
  type IVMS101,
13
13
  type V1Transaction,
14
14
  type Withdrawal,
@@ -1,8 +1,8 @@
1
1
  import { decodeJwt } from 'jose';
2
2
  import {
3
- Deposit,
4
- DID,
5
3
  PersonType,
4
+ type Deposit,
5
+ type DID,
6
6
  type OwnershipProof,
7
7
  type TransactionResponse,
8
8
  type Withdrawal,
@@ -4,12 +4,12 @@ import type {
4
4
  } from '@notabene/javascript-sdk/src/ivms/types';
5
5
  import { CAIP10Schema, type DID } from '@taprsvp/types';
6
6
  import type { NaturalPersonNameV2, PersonV2 } from '../ivms';
7
- import { Deposit, Withdrawal } from '../types';
7
+ import type { Deposit, Withdrawal } from '../types';
8
8
 
9
9
  export function isDeposit(
10
10
  transaction: Withdrawal | Deposit,
11
11
  ): transaction is Deposit {
12
- return 'source' in transaction && transaction.source !== undefined;
12
+ return (transaction as Deposit).source !== undefined;
13
13
  }
14
14
 
15
15
  export function isWithdrawal(
package/src/types.ts CHANGED
@@ -792,7 +792,6 @@ export type VASPOptions = {
792
792
  */
793
793
  export enum IdentityVerificationMethod {
794
794
  SMS = 'sms',
795
- EMAIL = 'email',
796
795
  }
797
796
 
798
797
  /**
@@ -808,14 +807,6 @@ export enum CodeVerificationStatus {
808
807
  UNREACHABLE = 'unreachable',
809
808
  }
810
809
 
811
- /**
812
- * Supported OAuth providers for authentication
813
- * @public
814
- */
815
- export enum OAuthProvider {
816
- COINBASE = 'coinbase',
817
- }
818
-
819
810
  /**
820
811
  * Configuration options for identity verification
821
812
  * @public
@@ -823,8 +814,6 @@ export enum OAuthProvider {
823
814
  export type IdentityVerificationConfig = {
824
815
  /** The required verification method. If not specified, none will be used */
825
816
  requiredMethod?: IdentityVerificationMethod;
826
- /** OAuth provider(s) to use for authentication. Can be a single provider or array of providers */
827
- oAuth?: OAuthProvider[] | OAuthProvider;
828
817
  };
829
818
 
830
819
  /**
@@ -839,6 +828,7 @@ export type CounterpartyAssistConfig =
839
828
  | boolean
840
829
  | {
841
830
  counterpartyTypes: PersonType[];
831
+ /** @remarks Requires a transactionId to be passed in. */
842
832
  identityVerification?: IdentityVerificationConfig;
843
833
  };
844
834
 
@@ -870,18 +860,68 @@ export interface ThresholdOptions {
870
860
  currency: ISOCurrency; // Currency of threshold
871
861
  proofTypes?: ProofTypes[]; // If left empty no proof will be required under threshold
872
862
  }
863
+ /**
864
+ * Options that can be placed in either the main or fallback sections of the agent selection step.
865
+ *
866
+ * @remarks
867
+ * Combines proof types from {@link ProofTypes} with additional UI-specific options:
868
+ * - `ProofTypes.SelfDeclaration` — ownership proof via self-declaration
869
+ * - `ProofTypes.Screenshot` — ownership proof via screenshot upload
870
+ * - `ProofTypes.MicroTransfer` — ownership proof via micro-transfer
871
+ * - `'signature'` — wallet connection for ownership proof via signature
872
+ * - `'manual-signing'` — ownership proof via manual message signing
873
+ * - `'add-vasp'` — manually add an unlisted exchange/VASP (hosted flow)
874
+ *
875
+ * Options are automatically filtered by flow (e.g. `add-vasp` is ignored in self-hosted).
876
+ * Including an option implicitly enables that capability.
877
+ *
878
+ * @public
879
+ */
880
+ export type SectionOption =
881
+ | ProofTypes.SelfDeclaration
882
+ | ProofTypes.Screenshot
883
+ | ProofTypes.MicroTransfer
884
+ | 'signature'
885
+ | 'manual-signing'
886
+ | 'add-vasp';
887
+
888
+ /**
889
+ * Explicit layout configuration for the agent selection step.
890
+ *
891
+ * @remarks
892
+ * Controls which options appear in the main selection area vs. behind the
893
+ * "Can't find what you're looking for?" fallback toggle.
894
+ *
895
+ * When the main section has no options (empty or all filtered out), fallback options
896
+ * are promoted and shown inline. When the main section has only one option, a dedicated
897
+ * single-option layout is used.
898
+ *
899
+ * @public
900
+ */
901
+ export interface AgentSections {
902
+ /** Options shown in the main selection area (always visible) */
903
+ main?: SectionOption[];
904
+ /** Options shown behind the "Can't find what you're looking for?" fallback toggle */
905
+ fallback?: SectionOption[];
906
+ }
907
+
873
908
  /**
874
909
  * Configuration options for Transaction components
875
910
  * @public
876
911
  */
877
912
  export interface TransactionOptions {
913
+ /**
914
+ * Explicit layout configuration for the agent selection step.
915
+ * When provided, takes precedence over legacy fields (`proofs.fallbacks`, `vasps.addUnknown`).
916
+ */
917
+ agentSections?: AgentSections;
878
918
  proofs?: {
879
919
  reuseProof?: boolean; // Defaults true
880
920
  microTransfer?: {
881
921
  destination: BlockchainAddress;
882
922
  amountSubunits: string;
883
923
  };
884
- fallbacks?: ProofTypes[];
924
+ fallbacks?: ProofTypes[]; // Legacy — replaced by agentSections.fallback
885
925
  deminimis?: ThresholdOptions;
886
926
  };
887
927
  jurisdiction?: string;
@@ -1,4 +1,4 @@
1
- import { ComponentMessage, HostMessage } from '../types';
1
+ import type { ComponentMessage, HostMessage } from '../types';
2
2
 
3
3
  /**
4
4
  * Callback function for handling component messages.
@@ -3,9 +3,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
  import type { DID, RefreshSource } from '../../types';
4
4
  import {
5
5
  ConnectionManager,
6
- ConnectionStatus,
6
+ type ConnectionStatus,
7
7
  getRefreshResult,
8
- TransactionType,
8
+ type TransactionType,
9
9
  } from '../connections';
10
10
  import { seal } from '../encryption';
11
11
 
@@ -1,6 +1,6 @@
1
1
  import * as fc from 'fast-check';
2
2
  import { describe, expect, it } from 'vitest';
3
- import { Agent, AgentType } from '../../types';
3
+ import { type Agent, AgentType } from '../../types';
4
4
  import { seal, unseal } from '../encryption';
5
5
 
6
6
  // Helper to validate that two objects are deeply equal