ncc-06-js 0.2.3 → 0.3.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/DOCS.md CHANGED
@@ -43,28 +43,29 @@ Utilities for locator payload construction and evaluation.
43
43
 
44
44
  ## NCC-06 helpers
45
45
 
46
- ### `choosePreferredEndpoint(endpoints, { torPreferred?, expectedK? })`
47
- - Applies NCC-06 policy: prefer `wss://` with matching `k`, favor onion if `torPreferred`, fall back to any `ws://`.
46
+ ### `choosePreferredEndpoint(endpoints, { torPreferred?, expectedK?, allowedProtocols? })`
47
+ - Applies NCC-06 policy: favors onion if `torPreferred`, filters by `allowedProtocols` (default: `['wss', 'ws']`), and validates `k` fingerprints for secure protocols (`wss`, `https`, `tls`, etc).
48
48
  - Returns `{ endpoint?: NormalizedEndpoint, reason?: string, expected?, actual? }`.
49
49
 
50
50
  ### `resolveServiceEndpoint(options)`
51
51
  - Orchestrates resolution by querying bootstrap relays, preferring NCC-05 locators, and falling back to NCC-02 records.
52
- - **Options** include `bootstrapRelays`, `servicePubkey`, `serviceId`, `locatorId`, `expectedK`, `locatorSecretKey`, `torPreferred`, timeouts, and override hooks.
53
- - **Returns** `{ endpoint, source, serviceEvent, locatorPayload, selection }`.
52
+ - **Options** include `bootstrapRelays`, `servicePubkey`, `serviceId`, `locatorId`, `expectedK`, `locatorSecretKey`, `torPreferred`, `allowedProtocols`, timeouts, and override hooks.
53
+ - **Returns** `{ endpoint, source, serviceRecord, locatorPayload, selection }`.
54
54
 
55
55
  ## Sidecar config helpers
56
56
 
57
57
  ### `buildSidecarConfig(options)`
58
- - Mirrors the example sidecar setup and derives `ncc02ExpectedKey`, `publishRelays`, and `torControl` from operator intent so you do not need to duplicate those scripts.
58
+ - Mirrors the example sidecar setup and derives `ncc02ExpectedKey`, `publishRelays`, and `torControl` from operator intent.
59
+ - Supports `serviceUrl` and `serviceMode` as aliases for `relayUrl` and `relayMode`.
59
60
 
60
61
  ### `getRelayMode(config)`
61
- - Returns the normalized `"public"` or `"private"` mode that drives whether the sidecar publishes NCC-05 locators.
62
+ - Returns the normalized `"public"` or `"private"` mode based on `relayMode` or `serviceMode`.
62
63
 
63
64
  ### `setRelayMode(config, mode)`
64
- - Normalizes and writes `"public"` or `"private"` back into the config object so you can persistively toggle the relay’s exposure level.
65
+ - Normalizes and writes `"public"` or `"private"` back into the config object (setting both `relayMode` and `serviceMode`).
65
66
 
66
67
  ### `buildClientConfig(options)`
67
- - Rehydrates the minimal client config that the example resolver expects; dedupes publication relays, enforces `serviceIdentityUri`, and carries the `expectedK` for NCC-02 pinning.
68
+ - Rehydrates the minimal client config that the example resolver expects. Supports `serviceUrl` as alias for `relayUrl`.
68
69
 
69
70
  ## Key and TLS helpers
70
71
 
@@ -78,7 +79,7 @@ Utilities for locator payload construction and evaluation.
78
79
  - Reads a config block (`k.mode`) and returns the expected `k` string for static/generate/tls modes; used by the sidecar.
79
80
 
80
81
  ### Key helpers
81
- - `generateKeypair()`, `toNpub()`, `fromNsec()` wrap `nostr-tools` key utilities for convenience.
82
+ - `generateKeypair()`, `toNpub()`, `fromNpub()`, `toNsec()`, `fromNsec()` wrap `nostr-tools` key utilities for convenience.
82
83
  - `ensureSelfSignedCert(options)` generates a self-signed cert for local `wss://` endpoints.
83
84
 
84
85
  ## Endpoint helpers
package/README.md CHANGED
@@ -6,14 +6,13 @@ Reusable helpers extracted from the NCC-06 example relay, sidecar, and client im
6
6
 
7
7
  - **NCC-02 builders & validators** (`buildNcc02ServiceRecord`, `parseNcc02Tags`, `validateNcc02`) that manage the `d`, `u`, `k`, and `exp` tags a service record must expose.
8
8
  - **NCC-05 helpers** (`buildLocatorPayload`, `normalizeLocatorEndpoints`, `validateLocatorFreshness`) that assemble locator payloads, parse stored JSON, and enforce TTL/`updated_at` freshness rules.
9
- - **Deterministic NCC-06 resolution** via `choosePreferredEndpoint` and `resolveServiceEndpoint`, which query bootstrap relays, prefer fresh NCC-05 locators, verify `k` fingerprints for `wss://`, and fall back to NCC-02 `u` values.
10
- - **External endpoint helpers** (`buildExternalEndpoints`, `detectGlobalIPv6`, `getPublicIPv4`) so sidecars can declare onion/IPv6/IPv4 reachability in a reproducible order without making the relay probe the network.
11
- - **Scheduling helpers** (`scheduleWithJitter`) for applying bounded jitter to recurring NCC-02/NCC-05 timers without ever publishing outside the declared window.
12
- - **TLS/key utilities** (`ensureSelfSignedCert`, `generateKeypair`, `toNpub`, `fromNsec`, `generateExpectedK`, `validateExpectedKFormat`) that mirror the key and fingerprint management used by the example sidecar.
13
- - **Lightweight protocol helpers** (`parseNostrMessage`, `serializeNostrMessage`, `createReqMessage`) for downstream code that wants to reuse the same framing logic as the example client.
14
- - **Sidecar config helpers** (`buildSidecarConfig`, `buildClientConfig`) so you can reuse the same config generation logic the example sidecar/client rely on without copying the scripts.
15
- - **TypeScript definitions** (`index.d.ts`) that describe every exported helper, making it easier to import from TypeScript consumers.
16
- - **Relay mode helpers** (`getRelayMode`, `setRelayMode`) so you can portrait whether your relay is *public* (publishes NCC-05 locators) or *private* (only maintains an NCC-02 record without advertising endpoints).
9
+ - **Deterministic NCC-06 resolution** via `choosePreferredEndpoint` and `resolveServiceEndpoint`. It queries bootstrap relays, prefers fresh NCC-05 locators, verifies `k` fingerprints for any secure protocol (`wss://`, `https://`, etc.), and falls back to NCC-02 `u` values.
10
+ - **Service-Agnostic Helpers:** While originally built for relays, all helpers support generic `serviceUrl`, `serviceMode`, and custom `allowedProtocols`.
11
+ - **External endpoint helpers** (`buildExternalEndpoints`, `detectGlobalIPv6`, `getPublicIPv4`) so services can declare onion/IPv6/IPv4 reachability in a reproducible order.
12
+ ...
13
+ - **Sidecar config helpers** (`buildSidecarConfig`, `buildClientConfig`) so you can reuse the same config generation logic. Supports `serviceUrl` and `serviceMode` aliases for broader application.
14
+ ...
15
+ - **Relay & Service mode helpers** (`getRelayMode`, `setRelayMode`) so you can control whether your service is *public* (publishes NCC-05 locators) or *private`.
17
16
 
18
17
  ## Usage
19
18
 
@@ -23,36 +22,75 @@ Install directly from the repository (example workspace):
23
22
  npm install ../ncc-06-js
24
23
  ```
25
24
 
26
- Then import the helpers you need:
25
+ ### Resolving an HTTP API Service
27
26
 
28
27
  ```js
29
- import {
30
- resolveServiceEndpoint,
31
- buildExternalEndpoints,
32
- generateExpectedK
33
- } from 'ncc-06-js';
28
+ import { resolveServiceEndpoint } from 'ncc-06-js';
34
29
 
35
- const endpoints = await buildExternalEndpoints({
36
- ipv4: { enabled: true, protocol: 'wss', address: '1.2.3.4', port: 7447 },
37
- wsPort: 7000,
38
- wssPort: 7447,
39
- ncc02ExpectedKey: 'TESTKEY:relay-local-dev-1',
40
- ensureOnionService
30
+ const resolution = await resolveServiceEndpoint({
31
+ bootstrapRelays: ['wss://relay.damus.io'],
32
+ servicePubkey: '...',
33
+ serviceId: 'my-api',
34
+ locatorId: 'api-locator',
35
+ expectedK: '...', // SPKI fingerprint for HTTPS pinning
36
+ allowedProtocols: ['https', 'http'] // Override default [wss, ws]
41
37
  });
42
38
 
39
+ console.log('Resolved API endpoint:', resolution.endpoint);
40
+ ```
41
+
42
+ ### Resolving a Tor Service via Npub
43
+
44
+ ```js
45
+ import { resolveServiceEndpoint, fromNpub } from 'ncc-06-js';
46
+
47
+ const servicePubkey = fromNpub('npub1...');
48
+
43
49
  const resolution = await resolveServiceEndpoint({
44
- bootstrapRelays: ['ws://127.0.0.1:7000'],
45
- servicePubkey: '...',
50
+ bootstrapRelays: ['wss://relay.damus.io'],
51
+ servicePubkey,
46
52
  serviceId: 'relay',
47
53
  locatorId: 'relay-locator',
48
- expectedK: 'TESTKEY:relay-local-dev-1',
49
- locatorSecretKey: '...'
54
+ torPreferred: true // Prefer .onion endpoints if available
50
55
  });
51
56
 
52
- console.log('Resolved endpoint:', resolution.endpoint);
57
+ console.log('Resolved Onion Endpoint:', resolution.endpoint);
58
+ ```
59
+
60
+ ### Configuring an Onion Service Sidecar
61
+
62
+ ```js
63
+ import { buildExternalEndpoints, buildSidecarConfig } from 'ncc-06-js';
64
+
65
+ // 1. Build endpoints (detects Onion, IPv6, IPv4)
66
+ const endpoints = await buildExternalEndpoints({
67
+ tor: { enabled: true },
68
+ ensureOnionService: async () => ({ address: 'abcdef...', servicePort: 80 })
69
+ });
70
+
71
+ // 2. Build config
72
+ const config = buildSidecarConfig({
73
+ secretKey: '...',
74
+ serviceUrl: 'ws://abcdef....onion', // Primary identity URL
75
+ externalEndpoints: endpoints,
76
+ serviceMode: 'public'
77
+ });
78
+ ```
79
+
80
+ ### Building a Service Config
81
+
82
+ ```js
83
+ import { buildSidecarConfig } from 'ncc-06-js';
84
+
85
+ const config = buildSidecarConfig({
86
+ secretKey: '...',
87
+ serviceUrl: 'https://api.example.com',
88
+ serviceId: 'my-api',
89
+ serviceMode: 'public'
90
+ });
53
91
  ```
54
92
 
55
- The package exposes modular helpers so you can keep using your own transport stack while reusing the deterministic NCC-06 behaviour that now powers the `ncc06-client` harness.
93
+ The package exposes modular helpers so you can keep using your own transport stack while reusing deterministic NCC-06 behaviour.
56
94
 
57
95
  ## Trust model
58
96
 
package/index.d.ts CHANGED
@@ -34,7 +34,7 @@ declare module 'ncc-06-js' {
34
34
  type?: string;
35
35
  uri?: string;
36
36
  value?: string;
37
- protocol?: 'ws' | 'wss';
37
+ protocol?: string;
38
38
  family?: 'ipv4' | 'ipv6' | 'onion' | string;
39
39
  priority?: number;
40
40
  prio?: number;
@@ -65,6 +65,7 @@ declare module 'ncc-06-js' {
65
65
  export interface SelectorOptions {
66
66
  torPreferred?: boolean;
67
67
  expectedK?: string;
68
+ allowedProtocols?: string[];
68
69
  }
69
70
  export interface SelectorResult {
70
71
  endpoint: LocatorEndpoint | null;
@@ -78,6 +79,15 @@ declare module 'ncc-06-js' {
78
79
  ): SelectorResult;
79
80
  export { normalizeLocatorEndpoints };
80
81
 
82
+ export interface ResolvedService {
83
+ endpoint?: string;
84
+ fingerprint?: string;
85
+ expiry: number;
86
+ attestations: any[];
87
+ eventId: string;
88
+ pubkey: string;
89
+ }
90
+
81
91
  export interface ResolverOptions {
82
92
  bootstrapRelays: string[];
83
93
  servicePubkey: string;
@@ -88,11 +98,8 @@ declare module 'ncc-06-js' {
88
98
  locatorSecretKey?: string;
89
99
  ncc05TimeoutMs?: number;
90
100
  publicationRelayTimeoutMs?: number;
91
- queryRelayEvents?: (
92
- relays: string[],
93
- filter: Record<string, unknown>,
94
- options?: { timeoutMs?: number }
95
- ) => Promise<NostrEvent[]>;
101
+ pool?: any;
102
+ ncc02Resolver?: any;
96
103
  resolveLocator?: (options: {
97
104
  bootstrapRelays: string[];
98
105
  servicePubkey: string;
@@ -100,7 +107,6 @@ declare module 'ncc-06-js' {
100
107
  locatorSecretKey?: string;
101
108
  timeout?: number;
102
109
  }) => Promise<LocatorPayload | null>;
103
- throttle?: unknown;
104
110
  now?: number;
105
111
  }
106
112
  export interface ResolverSelection {
@@ -113,7 +119,7 @@ declare module 'ncc-06-js' {
113
119
  endpoint: string | null;
114
120
  source: 'locator' | 'ncc02' | null;
115
121
  locatorPayload: LocatorPayload | null;
116
- serviceEvent: NostrEvent;
122
+ serviceRecord: ResolvedService;
117
123
  selection: ResolverSelection;
118
124
  }
119
125
  export function resolveServiceEndpoint(options: ResolverOptions): Promise<ResolverResult>;
@@ -179,14 +185,14 @@ declare module 'ncc-06-js' {
179
185
  };
180
186
  ipv4?: {
181
187
  enabled?: boolean;
182
- protocol?: 'ws' | 'wss';
188
+ protocol?: string;
183
189
  port?: number;
184
190
  address?: string;
185
191
  publicSources?: string[];
186
192
  };
187
193
  ipv6?: {
188
194
  enabled?: boolean;
189
- protocol?: 'ws' | 'wss';
195
+ protocol?: string;
190
196
  port?: number;
191
197
  };
192
198
  wsPort?: number;
@@ -198,76 +204,59 @@ declare module 'ncc-06-js' {
198
204
  export function buildExternalEndpoints(options?: ExternalEndpointOptions): Promise<LocatorEndpoint[]>;
199
205
  export function detectGlobalIPv6(): string | null;
200
206
  export function getPublicIPv4(options?: { sources?: string[] }): Promise<string | null>;
207
+ export function normalizeRelayUrl(url: string): string;
208
+ export function normalizeRelays(relays: string[]): string[];
201
209
 
202
210
  export interface SidecarConfigOptions {
203
- serviceSk: string;
204
- servicePk: string;
205
- serviceNpub: string;
206
- relayUrl: string;
211
+ secretKey: string;
212
+ serviceUrl?: string;
213
+ relayUrl?: string;
207
214
  serviceId?: string;
208
215
  locatorId?: string;
209
216
  publicationRelays?: string[];
210
217
  publishRelays?: string[];
211
- ncc02ExpSeconds?: number;
212
- ncc05TtlSeconds?: number;
213
- torControl?: Record<string, unknown>;
214
- externalEndpoints?: Record<string, unknown>;
215
- k?: KConfig;
216
- baseDir?: string;
218
+ persistPath?: string;
219
+ certPath?: string;
217
220
  relayMode?: 'public' | 'private';
218
- ncc02ExpectedKeySource?: string;
221
+ serviceMode?: 'public' | 'private';
219
222
  }
220
223
  export interface SidecarConfig {
221
- serviceSk: string;
222
- servicePk: string;
223
- serviceNpub: string;
224
+ secretKey: string;
225
+ serviceUrl: string;
224
226
  relayUrl: string;
225
227
  serviceId: string;
226
228
  locatorId: string;
227
229
  publicationRelays: string[];
228
230
  publishRelays: string[];
229
- ncc02ExpSeconds: number;
230
- ncc05TtlSeconds: number;
231
- ncc02ExpectedKey: string;
232
- ncc02ExpectedKeySource: string;
233
- externalEndpoints: Record<string, unknown>;
234
- torControl: Record<string, unknown>;
235
- k: KConfig;
231
+ persistPath?: string;
232
+ certPath?: string;
236
233
  relayMode: 'public' | 'private';
234
+ serviceMode: 'public' | 'private';
237
235
  }
238
236
  export function buildSidecarConfig(options: SidecarConfigOptions): SidecarConfig;
239
- export function getRelayMode(config?: { relayMode?: string }): 'public' | 'private';
237
+ export function getRelayMode(config?: { relayMode?: string, serviceMode?: string }): 'public' | 'private';
240
238
  export function setRelayMode(config?: Record<string, unknown>, mode: 'public' | 'private'): Record<string, unknown>;
241
239
 
242
240
  export interface ClientConfigOptions {
243
- relayUrl: string;
244
- servicePubkey?: string;
245
- serviceNpub?: string;
246
241
  serviceIdentityUri?: string;
247
- locatorSecretKey?: string;
248
- locatorFriendPubkey?: string;
242
+ serviceNpub?: string;
243
+ servicePubkey?: string;
244
+ serviceUrl?: string;
245
+ relayUrl?: string;
249
246
  publicationRelays?: string[];
250
- staleFallbackSeconds?: number;
251
- torPreferred?: boolean;
252
- ncc05TimeoutMs?: number;
253
247
  serviceId?: string;
254
248
  locatorId?: string;
255
- expectedK?: string;
249
+ ncc02ExpectedKey?: string;
256
250
  }
257
251
  export interface ClientConfig {
258
- relayUrl: string;
259
252
  serviceIdentityUri: string;
260
253
  servicePubkey: string;
261
- serviceNpub: string;
254
+ serviceUrl: string;
255
+ relayUrl: string;
262
256
  publicationRelays: string[];
263
- staleFallbackSeconds: number;
264
- torPreferred: boolean;
265
- ncc05TimeoutMs: number;
266
- locatorSecretKey?: string;
267
- locatorFriendPubkey?: string;
268
- serviceId?: string;
269
- locatorId?: string;
257
+ serviceId: string;
258
+ locatorId: string;
270
259
  ncc02ExpectedKey?: string;
271
260
  }
272
261
  export function buildClientConfig(options: ClientConfigOptions): ClientConfig;
273
- }
262
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ncc-06-js",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "description": "Reusable NCC-06 discovery helpers extracted from the example relay, sidecar, and client.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -10,15 +10,15 @@
10
10
  "lint": "eslint . --ext .js"
11
11
  },
12
12
  "dependencies": {
13
- "ncc-02-js": "^0.2.3",
14
- "ncc-05-js": "^1.1.9",
13
+ "ncc-02-js": "^0.3.0",
14
+ "ncc-05-js": "^1.1.14",
15
15
  "nostr-tools": "^2.19.4",
16
16
  "selfsigned": "^5.4.0",
17
17
  "ws": "^8.18.3"
18
18
  },
19
19
  "devDependencies": {
20
- "@types/node": "^20.7.2",
21
- "eslint": "^8.54.0",
22
- "typescript": "^5.5.3"
20
+ "@types/node": "^25.0.3",
21
+ "eslint": "^9.39.2",
22
+ "typescript": "^5.9.3"
23
23
  }
24
24
  }
@@ -132,3 +132,25 @@ export async function getPublicIPv4({ sources = ['https://api.ipify.org?format=j
132
132
  }
133
133
  return null;
134
134
  }
135
+
136
+ export function normalizeRelayUrl(url) {
137
+ if (!url) return '';
138
+ let normalized = url.trim();
139
+ if (normalized.endsWith('/')) {
140
+ normalized = normalized.slice(0, -1);
141
+ }
142
+ if (!normalized.includes('://')) {
143
+ normalized = `wss://${normalized}`;
144
+ }
145
+ return normalized;
146
+ }
147
+
148
+ export function normalizeRelays(relays) {
149
+ if (!Array.isArray(relays)) return [];
150
+ const normalized = relays
151
+ .filter(Boolean)
152
+ .map(normalizeRelayUrl);
153
+ return [...new Set(normalized)];
154
+ }
155
+
156
+
package/src/ncc02.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { finalizeEvent, getPublicKey, validateEvent, verifyEvent } from 'nostr-tools/pure';
2
+ import { NCC02Builder } from 'ncc-02-js';
2
3
 
3
4
  const DEFAULT_KIND = 30059;
4
5
 
@@ -19,22 +20,31 @@ export function buildNcc02ServiceRecord({
19
20
  if (!secretKey) {
20
21
  throw new Error('secretKey is required to build NCC-02 records');
21
22
  }
22
- const timestamp = createdAt ?? Math.floor(Date.now() / 1000);
23
- const expiresAt = timestamp + Number(expirySeconds);
24
- const tags = [
25
- ['d', serviceId],
26
- ['u', endpoint],
27
- ['k', fingerprint],
28
- ['exp', expiresAt.toString()]
29
- ];
30
- const event = {
31
- kind,
32
- pubkey: getPublicKey(secretKey),
33
- created_at: timestamp,
34
- tags,
35
- content: ''
36
- };
37
- return finalizeEvent(event, secretKey);
23
+
24
+ const builder = new NCC02Builder(secretKey);
25
+ // NCC02Builder expects days.
26
+ const expiryDays = expirySeconds / (24 * 60 * 60);
27
+
28
+ const event = builder.createServiceRecord({
29
+ serviceId,
30
+ endpoint,
31
+ fingerprint,
32
+ expiryDays
33
+ });
34
+
35
+ // If createdAt or kind override is needed, we must re-sign.
36
+ if (createdAt || kind !== DEFAULT_KIND) {
37
+ const template = {
38
+ ...event,
39
+ created_at: createdAt ?? event.created_at,
40
+ kind: kind ?? event.kind,
41
+ id: undefined,
42
+ sig: undefined
43
+ };
44
+ return finalizeEvent(template, secretKey);
45
+ }
46
+
47
+ return event;
38
48
  }
39
49
 
40
50
  /**
@@ -70,4 +80,4 @@ export function validateNcc02(event, { expectedAuthor, expectedD, now, allowExpi
70
80
  return false;
71
81
  }
72
82
  return true;
73
- }
83
+ }
package/src/resolver.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import WebSocket from 'ws';
2
2
  import { NCC05Resolver } from 'ncc-05-js';
3
- import { parseNostrMessage, serializeNostrMessage, createReqMessage } from './protocol.js';
4
- import { validateNcc02, parseNcc02Tags } from './ncc02.js';
5
- import { normalizeLocatorEndpoints, validateLocatorFreshness } from './ncc05.js';
3
+ import { NCC02Resolver } from 'ncc-02-js';
4
+ import { validateLocatorFreshness, normalizeLocatorEndpoints } from './ncc05.js';
6
5
  import { choosePreferredEndpoint } from './selector.js';
7
6
 
8
7
  const DEFAULT_RELAY_TIMEOUT_MS = 5000;
@@ -21,7 +20,8 @@ export async function resolveServiceEndpoint(options = {}) {
21
20
  locatorSecretKey,
22
21
  ncc05TimeoutMs = 5000,
23
22
  publicationRelayTimeoutMs = DEFAULT_RELAY_TIMEOUT_MS,
24
- queryRelayEvents,
23
+ pool, // Allow passing a shared pool
24
+ ncc02Resolver, // Injection for testing
25
25
  resolveLocator,
26
26
  now
27
27
  } = options;
@@ -40,22 +40,25 @@ export async function resolveServiceEndpoint(options = {}) {
40
40
  }
41
41
 
42
42
  const timestamp = now ?? Math.floor(Date.now() / 1000);
43
- const fetchEvents = queryRelayEvents ?? defaultQueryRelayEvents;
44
43
  const locatorResolver = resolveLocator ?? defaultResolveLocator;
45
44
 
46
- const filter = {
47
- kinds: [30059],
48
- authors: [servicePubkey],
49
- '#d': [serviceId],
50
- limit: 10
51
- };
52
-
53
- const events = await fetchEvents(bootstrapRelays, filter, { timeoutMs: publicationRelayTimeoutMs });
54
- const serviceEvent = pickBestServiceRecord(events, timestamp, serviceId);
55
- if (!serviceEvent) {
56
- throw new Error('No valid NCC-02 service record available');
45
+ // 1. Resolve NCC-02 Service Record using the library
46
+ let serviceRecord;
47
+ try {
48
+ const resolver = ncc02Resolver || new NCC02Resolver(bootstrapRelays, { pool });
49
+ serviceRecord = await resolver.resolve(servicePubkey, serviceId, {
50
+ // We can pass options if needed, e.g. minLevel
51
+ });
52
+ } catch (err) {
53
+ // Map library errors or rethrow?
54
+ // The library throws generic Error or NCC02Error.
55
+ // We can just let it propagate or wrap.
56
+ // Existing code threw "No valid NCC-02 service record available".
57
+ // We'll let the library error bubble up as it provides more detail.
58
+ throw err;
57
59
  }
58
-
60
+
61
+ // 2. Resolve NCC-05 Locator
59
62
  const locatorPayload = await locatorResolver({
60
63
  bootstrapRelays,
61
64
  servicePubkey,
@@ -64,8 +67,9 @@ export async function resolveServiceEndpoint(options = {}) {
64
67
  timeout: ncc05TimeoutMs
65
68
  });
66
69
 
70
+ // 3. Determine Endpoint
67
71
  const selection = determineEndpoint({
68
- serviceEvent,
72
+ serviceRecord,
69
73
  locatorPayload,
70
74
  expectedK,
71
75
  torPreferred,
@@ -76,15 +80,20 @@ export async function resolveServiceEndpoint(options = {}) {
76
80
  endpoint: selection.endpoint,
77
81
  source: selection.source,
78
82
  locatorPayload,
79
- serviceEvent,
83
+ serviceRecord,
80
84
  selection
81
85
  };
82
86
  }
83
87
 
84
- function determineEndpoint({ serviceEvent, locatorPayload, expectedK, torPreferred, now }) {
85
- const tags = parseNcc02Tags(serviceEvent);
86
- const ncc02Url = tags.u;
87
- const isFreshService = !tags.exp || now <= Number(tags.exp);
88
+ function determineEndpoint({ serviceRecord, locatorPayload, expectedK, torPreferred, now }) {
89
+ // serviceRecord is { endpoint, fingerprint, expiry, attestations, ... }
90
+ // It is already validated (signature, expiry).
91
+
92
+ const ncc02Url = serviceRecord.endpoint;
93
+ const k = serviceRecord.fingerprint;
94
+ // expiry check was done by resolver, but we check if we need to?
95
+ // NCC02Resolver throws if expired. So we can assume it's fresh.
96
+
88
97
  const result = {
89
98
  endpoint: null,
90
99
  source: null,
@@ -110,20 +119,22 @@ function determineEndpoint({ serviceEvent, locatorPayload, expectedK, torPreferr
110
119
  result.evidence = selection;
111
120
  }
112
121
 
113
- if (ncc02Url && isFreshService) {
114
- if (ncc02Url.startsWith('wss://') && expectedK && tags.k && tags.k !== expectedK) {
122
+ if (ncc02Url) {
123
+ const isSecure = ncc02Url.match(/^(wss|https|tls|tcps):\/\//) || (ncc02Url.includes('://') && ncc02Url.split(':')[0].endsWith('s'));
124
+
125
+ if (isSecure && expectedK && k && k !== expectedK) {
115
126
  return {
116
127
  endpoint: null,
117
128
  source: 'ncc02',
118
129
  reason: 'k-mismatch',
119
- evidence: { expected: expectedK, actual: tags.k }
130
+ evidence: { expected: expectedK, actual: k }
120
131
  };
121
132
  }
122
133
  return {
123
134
  endpoint: ncc02Url,
124
135
  source: 'ncc02',
125
136
  reason: 'fallback',
126
- evidence: { tags }
137
+ evidence: { endpoint: ncc02Url, fingerprint: k }
127
138
  };
128
139
  }
129
140
 
@@ -131,91 +142,6 @@ function determineEndpoint({ serviceEvent, locatorPayload, expectedK, torPreferr
131
142
  return result;
132
143
  }
133
144
 
134
- function pickBestServiceRecord(events, now, expectedServiceId) {
135
- const candidates = [];
136
- for (const event of events) {
137
- if (!validateNcc02(event, { now, expectedD: expectedServiceId })) {
138
- continue;
139
- }
140
- candidates.push(event);
141
- }
142
- return candidates.sort((a, b) => {
143
- if (b.created_at !== a.created_at) {
144
- return b.created_at - a.created_at;
145
- }
146
- return b.id.localeCompare(a.id);
147
- })[0] || null;
148
- }
149
-
150
- async function defaultQueryRelayEvents(relays, filter, options = {}) {
151
- const queries = relays.map(relay => queryRelayForEvents(relay, filter, options.timeoutMs));
152
- const settled = await Promise.allSettled(queries);
153
- return settled.reduce((acc, item) => {
154
- if (item.status === 'fulfilled') {
155
- return acc.concat(item.value);
156
- }
157
- return acc;
158
- }, []);
159
- }
160
-
161
- function queryRelayForEvents(relayUrl, filter, timeoutMs = DEFAULT_RELAY_TIMEOUT_MS) {
162
- return new Promise((resolve, reject) => {
163
- const ws = new WebSocket(relayUrl);
164
- const events = [];
165
- const subId = `ncc06-${Math.random().toString(16).slice(2, 8)}`;
166
- let settled = false;
167
- const timer = setTimeout(() => {
168
- if (!settled) {
169
- settled = true;
170
- ws.close();
171
- resolve(events);
172
- }
173
- }, timeoutMs);
174
-
175
- ws.onopen = () => {
176
- ws.send(serializeNostrMessage(createReqMessage(subId, filter)));
177
- };
178
-
179
- ws.onmessage = raw => {
180
- const message = parseNostrMessage(raw.data.toString());
181
- if (!message) {
182
- return;
183
- }
184
- const [type, ...payload] = message;
185
- if (type === 'EVENT') {
186
- const [receivedSubId, event] = payload;
187
- if (receivedSubId === subId) {
188
- events.push(event);
189
- }
190
- } else if (type === 'EOSE') {
191
- const [receivedSubId] = payload;
192
- if (receivedSubId === subId && !settled) {
193
- settled = true;
194
- clearTimeout(timer);
195
- ws.close();
196
- resolve(events);
197
- }
198
- }
199
- };
200
-
201
- ws.onerror = err => {
202
- if (!settled) {
203
- settled = true;
204
- clearTimeout(timer);
205
- reject(err);
206
- }
207
- };
208
-
209
- ws.onclose = () => {
210
- if (!settled) {
211
- settled = true;
212
- clearTimeout(timer);
213
- resolve(events);
214
- }
215
- };
216
- });
217
- }
218
-
219
145
  async function defaultResolveLocator({
220
146
  bootstrapRelays,
221
147
  servicePubkey,
@@ -236,3 +162,4 @@ async function defaultResolveLocator({
236
162
  resolver.close();
237
163
  }
238
164
  }
165
+
package/src/selector.js CHANGED
@@ -9,41 +9,60 @@ function findByFamily(list, family) {
9
9
  return list.find(ep => ep.family === family);
10
10
  }
11
11
 
12
+ function isSecureProtocol(protocol) {
13
+ return ['wss', 'https', 'tls', 'tcps'].includes(protocol) || (protocol && protocol.endsWith('s') && protocol !== 'ws');
14
+ }
15
+
12
16
  /**
13
17
  * Choose which endpoint to connect to based on NCC-06 policy.
14
18
  * - prefers onion when torPreferred.
15
- * - prefers WSS endpoints with matching k.
19
+ * - filters by allowedProtocols (default: wss, ws).
20
+ * - validates 'k' for secure protocols.
16
21
  */
17
22
  export function choosePreferredEndpoint(endpoints = [], options = {}) {
18
- const { torPreferred = false, expectedK } = options;
23
+ const {
24
+ torPreferred = false,
25
+ expectedK,
26
+ allowedProtocols = ['wss', 'ws']
27
+ } = options;
28
+
19
29
  const normalized = endpoints.length ? endpoints : [];
20
- const wssEndpoints = normalized.filter(ep => ep.protocol === 'wss');
21
- const wsEndpoints = normalized.filter(ep => ep.protocol === 'ws');
30
+
31
+ // Filter by allowed protocols
32
+ const candidates = normalized.filter(ep => allowedProtocols.includes(ep.protocol));
33
+
34
+ let selection = null;
22
35
 
23
- let candidate = null;
36
+ // Tor Preference: Look for onion in candidates
24
37
  if (torPreferred) {
25
- candidate = findByFamily(wssEndpoints, 'onion') || findByFamily(wsEndpoints, 'onion');
38
+ selection = findByFamily(candidates, 'onion');
26
39
  }
27
- if (!candidate) {
28
- candidate = pickByPriority(wssEndpoints) || pickByPriority(wsEndpoints);
40
+
41
+ // Priority Fallback
42
+ if (!selection) {
43
+ selection = pickByPriority(candidates);
29
44
  }
30
- if (!candidate) {
45
+
46
+ if (!selection) {
31
47
  return { endpoint: null, reason: 'no-endpoint' };
32
48
  }
33
- if (candidate.protocol === 'wss') {
34
- if (!candidate.k) {
49
+
50
+ // Security Validation
51
+ if (isSecureProtocol(selection.protocol)) {
52
+ if (!selection.k) {
35
53
  return { endpoint: null, reason: 'missing-k' };
36
54
  }
37
- if (expectedK && candidate.k !== expectedK) {
55
+ if (expectedK && selection.k !== expectedK) {
38
56
  return {
39
57
  endpoint: null,
40
58
  reason: 'k-mismatch',
41
59
  expected: expectedK,
42
- actual: candidate.k
60
+ actual: selection.k
43
61
  };
44
62
  }
45
63
  }
46
- return { endpoint: candidate };
64
+
65
+ return { endpoint: selection };
47
66
  }
48
67
 
49
68
  export { normalizeLocatorEndpoints };
@@ -11,6 +11,8 @@ const DEFAULT_TOR_CONTROL = {
11
11
  timeout: 5000
12
12
  };
13
13
 
14
+ import { normalizeRelayUrl, normalizeRelays } from './external-endpoints.js';
15
+
14
16
  const RELAY_MODE_PUBLIC = 'public';
15
17
  const RELAY_MODE_PRIVATE = 'private';
16
18
 
@@ -18,136 +20,112 @@ function normalizeRelayMode(mode) {
18
20
  if (!mode) {
19
21
  return RELAY_MODE_PUBLIC;
20
22
  }
21
- const value = String(mode).toLowerCase();
23
+ const value = mode.toLowerCase();
22
24
  if (value !== RELAY_MODE_PUBLIC && value !== RELAY_MODE_PRIVATE) {
23
- throw new Error(`relayMode must be "${RELAY_MODE_PUBLIC}" or "${RELAY_MODE_PRIVATE}"`);
25
+ throw new Error(`relayMode (or serviceMode) must be "${RELAY_MODE_PUBLIC}" or "${RELAY_MODE_PRIVATE}"`);
24
26
  }
25
27
  return value;
26
28
  }
27
29
 
28
- function uniqueList(items) {
29
- const seen = new Set();
30
- return items.filter(item => {
31
- if (!item) return false;
32
- const normalized = item.trim();
33
- if (seen.has(normalized)) {
34
- return false;
35
- }
36
- seen.add(normalized);
37
- return true;
38
- });
30
+ function uniqueList(arr) {
31
+ return [...new Set(arr.filter(Boolean))];
39
32
  }
40
33
 
41
34
  export function getRelayMode(config = {}) {
42
- return normalizeRelayMode(config.relayMode);
35
+ return normalizeRelayMode(config.relayMode || config.serviceMode);
43
36
  }
44
37
 
45
38
  export function setRelayMode(config = {}, mode) {
46
39
  const normalized = normalizeRelayMode(mode);
47
- return { ...config, relayMode: normalized };
40
+ return { ...config, relayMode: normalized, serviceMode: normalized };
48
41
  }
49
42
 
50
43
  /**
51
- * Build a deployable sidecar config object that mirrors the example's defaults.
44
+ * Build configuration for the NCC-06 Sidecar.
52
45
  */
53
46
  export function buildSidecarConfig({
54
- serviceSk,
55
- servicePk,
56
- serviceNpub,
47
+ secretKey,
48
+ serviceUrl,
57
49
  relayUrl,
58
50
  serviceId = 'relay',
59
51
  locatorId = 'relay-locator',
60
52
  publicationRelays = [],
61
53
  publishRelays,
62
- ncc02ExpSeconds = 14 * 24 * 60 * 60,
63
- ncc05TtlSeconds = DEFAULT_TTL_SECONDS,
64
- torControl = DEFAULT_TOR_CONTROL,
65
- externalEndpoints = {},
66
- k = {},
67
- baseDir = process.cwd(),
54
+ persistPath,
55
+ certPath,
68
56
  relayMode,
69
- ncc02ExpectedKeySource
70
- } = {}) {
71
- if (!serviceSk || !servicePk || !serviceNpub) {
72
- throw new Error('service keypair must be provided');
57
+ serviceMode
58
+ }) {
59
+ if (!secretKey) {
60
+ throw new Error('secretKey is required');
73
61
  }
74
- if (!relayUrl) {
75
- throw new Error('relayUrl is required');
62
+
63
+ const primaryUrl = serviceUrl || relayUrl;
64
+
65
+ if (!primaryUrl) {
66
+ throw new Error('serviceUrl (or relayUrl) is required');
76
67
  }
77
68
 
78
- const expectedKey = getExpectedK({ k, externalEndpoints }, { baseDir });
79
- const normalizedPublicationRelays = uniqueList([relayUrl, ...publicationRelays]);
69
+ const normalizedPrimaryUrl = normalizeRelayUrl(primaryUrl);
70
+ const normalizedPublicationRelays = uniqueList([normalizedPrimaryUrl, ...publicationRelays]);
80
71
  const normalizedPublishRelays = uniqueList([
81
- relayUrl,
72
+ normalizedPrimaryUrl,
82
73
  ...(publishRelays ?? normalizedPublicationRelays)
83
74
  ]);
84
- const keySource = ncc02ExpectedKeySource ?? k.mode ?? 'auto';
85
- const normalizedRelayMode = normalizeRelayMode(relayMode);
75
+
76
+ const mode = relayMode || serviceMode;
77
+ const normalizedMode = normalizeRelayMode(mode);
86
78
 
87
79
  return {
88
- serviceSk,
89
- servicePk,
90
- serviceNpub,
91
- relayUrl,
80
+ secretKey,
81
+ serviceUrl: normalizedPrimaryUrl,
82
+ relayUrl: normalizedPrimaryUrl, // Backward compatibility
92
83
  serviceId,
93
84
  locatorId,
94
85
  publicationRelays: normalizedPublicationRelays,
95
86
  publishRelays: normalizedPublishRelays,
96
- ncc02ExpSeconds,
97
- ncc05TtlSeconds,
98
- ncc02ExpectedKey: expectedKey,
99
- ncc02ExpectedKeySource: keySource,
100
- externalEndpoints,
101
- torControl,
102
- k,
103
- relayMode: normalizedRelayMode
87
+ persistPath,
88
+ certPath,
89
+ relayMode: normalizedMode,
90
+ serviceMode: normalizedMode // Alias
104
91
  };
105
92
  }
106
93
 
107
94
  /**
108
- * Build a client config that matches the NCC-06 example expectations.
95
+ * Build configuration for the NCC-06 Client.
109
96
  */
110
97
  export function buildClientConfig({
111
- relayUrl,
112
- servicePubkey,
113
- serviceNpub,
114
98
  serviceIdentityUri,
115
- locatorSecretKey,
116
- locatorFriendPubkey,
99
+ serviceNpub, // Deprecated, but supported
100
+ servicePubkey,
101
+ serviceUrl,
102
+ relayUrl,
117
103
  publicationRelays = [],
118
- staleFallbackSeconds = 600,
119
- torPreferred = false,
120
- ncc05TimeoutMs = 5000,
121
- serviceId,
122
- locatorId,
123
- expectedK
124
- } = {}) {
125
- if (!relayUrl) {
126
- throw new Error('relayUrl is required');
127
- }
128
- const identityUri =
129
- serviceIdentityUri || (serviceNpub ? `wss://${serviceNpub}` : undefined);
130
- if (!identityUri) {
131
- throw new Error('serviceIdentityUri or serviceNpub is required');
104
+ serviceId = 'relay',
105
+ locatorId = 'relay-locator',
106
+ ncc02ExpectedKey
107
+ }) {
108
+ if (!serviceIdentityUri && !serviceNpub) {
109
+ throw new Error('serviceIdentityUri (or serviceNpub) is required');
132
110
  }
133
- if (!servicePubkey) {
134
- throw new Error('servicePubkey is required');
111
+
112
+ const primaryUrl = serviceUrl || relayUrl;
113
+
114
+ if (!primaryUrl) {
115
+ throw new Error('serviceUrl (or relayUrl) is required');
135
116
  }
136
- const publicationList = uniqueList([relayUrl, ...publicationRelays]);
117
+
118
+ const normalizedPrimaryUrl = normalizeRelayUrl(primaryUrl);
119
+ const publicationList = uniqueList([normalizedPrimaryUrl, ...publicationRelays]);
137
120
 
138
121
  return {
139
- relayUrl,
140
- serviceIdentityUri: identityUri,
122
+ serviceIdentityUri: serviceIdentityUri || (serviceNpub ? `wss://${serviceNpub}` : null),
141
123
  servicePubkey,
142
- serviceNpub: serviceNpub ?? '',
124
+ serviceUrl: normalizedPrimaryUrl,
125
+ relayUrl: normalizedPrimaryUrl, // Backward compatibility
143
126
  publicationRelays: publicationList,
144
- staleFallbackSeconds,
145
- torPreferred,
146
- ncc05TimeoutMs,
147
- locatorSecretKey,
148
- locatorFriendPubkey,
149
127
  serviceId,
150
128
  locatorId,
151
- ncc02ExpectedKey: expectedK
129
+ ncc02ExpectedKey
152
130
  };
153
131
  }
@@ -1,31 +1,23 @@
1
1
  import { test } from 'node:test';
2
2
  import { strict as assert } from 'assert';
3
3
  import { resolveServiceEndpoint } from '../src/resolver.js';
4
- import { buildNcc02ServiceRecord } from '../src/ncc02.js';
5
4
  import { buildLocatorPayload } from '../src/ncc05.js';
6
5
  import { generateKeypair } from '../src/keys.js';
7
6
 
8
7
  const SERVICE_ID = 'relay';
9
8
  const LOCATOR_ID = 'relay-locator';
10
9
 
11
- function buildServiceEvent({ secretKey, serviceId, endpoint, fingerprint }) {
12
- return buildNcc02ServiceRecord({
13
- secretKey,
14
- serviceId,
15
- endpoint,
16
- fingerprint,
17
- expirySeconds: 60
18
- });
19
- }
20
-
21
10
  test('resolver prefers NCC-05 endpoint when k matches', async () => {
22
- const { secretKey, publicKey } = generateKeypair();
23
- const serviceEvent = buildServiceEvent({
24
- secretKey,
25
- serviceId: SERVICE_ID,
11
+ const { publicKey } = generateKeypair();
12
+ const mockServiceRecord = {
26
13
  endpoint: 'wss://fallback',
27
- fingerprint: 'TESTKEY:match'
28
- });
14
+ fingerprint: 'TESTKEY:match',
15
+ expiry: Math.floor(Date.now() / 1000) + 60,
16
+ attestations: [],
17
+ eventId: 'mock-id',
18
+ pubkey: publicKey
19
+ };
20
+
29
21
  const locatorPayload = buildLocatorPayload({
30
22
  ttl: 60,
31
23
  endpoints: [
@@ -39,7 +31,7 @@ test('resolver prefers NCC-05 endpoint when k matches', async () => {
39
31
  serviceId: SERVICE_ID,
40
32
  locatorId: LOCATOR_ID,
41
33
  expectedK: 'TESTKEY:match',
42
- queryRelayEvents: async () => [serviceEvent],
34
+ ncc02Resolver: { resolve: async () => mockServiceRecord },
43
35
  resolveLocator: async () => locatorPayload
44
36
  });
45
37
 
@@ -48,13 +40,16 @@ test('resolver prefers NCC-05 endpoint when k matches', async () => {
48
40
  });
49
41
 
50
42
  test('resolver falls back to NCC-02 when locator k mismatches', async () => {
51
- const { secretKey, publicKey } = generateKeypair();
52
- const serviceEvent = buildServiceEvent({
53
- secretKey,
54
- serviceId: SERVICE_ID,
43
+ const { publicKey } = generateKeypair();
44
+ const mockServiceRecord = {
55
45
  endpoint: 'wss://fallback',
56
- fingerprint: 'TESTKEY:match'
57
- });
46
+ fingerprint: 'TESTKEY:match',
47
+ expiry: Math.floor(Date.now() / 1000) + 60,
48
+ attestations: [],
49
+ eventId: 'mock-id',
50
+ pubkey: publicKey
51
+ };
52
+
58
53
  const locatorPayload = buildLocatorPayload({
59
54
  ttl: 60,
60
55
  endpoints: [
@@ -68,11 +63,11 @@ test('resolver falls back to NCC-02 when locator k mismatches', async () => {
68
63
  serviceId: SERVICE_ID,
69
64
  locatorId: LOCATOR_ID,
70
65
  expectedK: 'TESTKEY:match',
71
- queryRelayEvents: async () => [serviceEvent],
66
+ ncc02Resolver: { resolve: async () => mockServiceRecord },
72
67
  resolveLocator: async () => locatorPayload
73
68
  });
74
69
 
75
70
  assert.equal(result.endpoint, 'wss://fallback');
76
71
  assert.equal(result.source, 'ncc02');
77
72
  assert.equal(result.selection.reason, 'fallback');
78
- });
73
+ });
@@ -21,7 +21,32 @@ test('rejects mismatched k', () => {
21
21
 
22
22
  test('returns missing k reason', () => {
23
23
  const result = choosePreferredEndpoint([
24
- { url: 'wss://no-k', protocol: 'wss', priority: 1 }
24
+ { protocol: 'wss', url: 'wss://secure', k: null }
25
25
  ]);
26
+ assert.equal(result.endpoint, null);
26
27
  assert.equal(result.reason, 'missing-k');
27
28
  });
29
+
30
+ test('supports custom allowedProtocols and validates https k', () => {
31
+ const endpoints = [
32
+ { protocol: 'https', url: 'https://secure', k: 'key', priority: 1 },
33
+ { protocol: 'http', url: 'http://insecure', priority: 2 }
34
+ ];
35
+ const result = choosePreferredEndpoint(endpoints, {
36
+ allowedProtocols: ['https', 'http'],
37
+ expectedK: 'key'
38
+ });
39
+ assert.equal(result.endpoint.url, 'https://secure');
40
+ });
41
+
42
+ test('rejects https with mismatched k', () => {
43
+ const endpoints = [
44
+ { protocol: 'https', url: 'https://secure', k: 'wrong', priority: 1 }
45
+ ];
46
+ const result = choosePreferredEndpoint(endpoints, {
47
+ allowedProtocols: ['https'],
48
+ expectedK: 'key'
49
+ });
50
+ assert.equal(result.endpoint, null);
51
+ assert.equal(result.reason, 'k-mismatch');
52
+ });
@@ -8,22 +8,26 @@ import {
8
8
  } from '../src/sidecar-config.js';
9
9
 
10
10
  test('buildSidecarConfig constructs expected fields based on inputs', () => {
11
- const cfg = buildSidecarConfig({
12
- serviceSk: 'sk',
13
- servicePk: 'pk',
14
- serviceNpub: 'npub',
15
- relayUrl: 'ws://localhost:7000',
16
- k: { mode: 'static', value: 'TESTKEY:abc' },
17
- publicationRelays: ['ws://aux:7001'],
18
- externalEndpoints: {
19
- ipv4: { enabled: true, protocol: 'ws', address: '127.0.0.1', port: 7447 }
20
- }
11
+ const config = buildSidecarConfig({
12
+ secretKey: 'hex',
13
+ relayUrl: 'wss://test',
14
+ relayMode: 'private'
21
15
  });
16
+ assert.equal(config.relayUrl, 'wss://test');
17
+ assert.equal(config.serviceUrl, 'wss://test');
18
+ assert.equal(config.relayMode, 'private');
19
+ });
22
20
 
23
- assert.equal(cfg.serviceSk, 'sk');
24
- assert.equal(cfg.ncc02ExpectedKey, 'TESTKEY:abc');
25
- assert.deepEqual(cfg.publicationRelays[0], 'ws://localhost:7000');
26
- assert.ok(cfg.publishRelays.length >= 1);
21
+ test('buildSidecarConfig supports serviceUrl and serviceMode aliases', () => {
22
+ const config = buildSidecarConfig({
23
+ secretKey: 'hex',
24
+ serviceUrl: 'https://api.example.com',
25
+ serviceMode: 'private'
26
+ });
27
+ assert.equal(config.serviceUrl, 'https://api.example.com');
28
+ assert.equal(config.relayUrl, 'https://api.example.com');
29
+ assert.equal(config.serviceMode, 'private');
30
+ assert.equal(config.relayMode, 'private');
27
31
  });
28
32
 
29
33
  test('buildClientConfig enforces identity URI and relays', () => {
@@ -31,7 +35,7 @@ test('buildClientConfig enforces identity URI and relays', () => {
31
35
  relayUrl: 'ws://relay',
32
36
  servicePubkey: 'pk',
33
37
  serviceNpub: 'npub',
34
- expectedK: 'TESTKEY:abc',
38
+ ncc02ExpectedKey: 'TESTKEY:abc',
35
39
  publicationRelays: ['ws://relay', 'ws://aux']
36
40
  });
37
41