ncc-06-js 0.2.0 → 0.2.2
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/.eslintrc.json +19 -0
- package/DOCS.md +12 -0
- package/README.md +2 -0
- package/index.d.ts +269 -0
- package/package.json +6 -4
- package/src/external-endpoints.js +1 -1
- package/src/index.js +1 -0
- package/src/resolver.js +1 -1
- package/src/sidecar-config.js +127 -0
- package/test/external-endpoints.test.js +1 -1
- package/test/k.test.js +1 -1
- package/test/sidecar-config.test.js +36 -0
- package/tsconfig.json +18 -0
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"node": true,
|
|
4
|
+
"es2020": true
|
|
5
|
+
},
|
|
6
|
+
"extends": "eslint:recommended",
|
|
7
|
+
"parserOptions": {
|
|
8
|
+
"ecmaVersion": 2020,
|
|
9
|
+
"sourceType": "module"
|
|
10
|
+
},
|
|
11
|
+
"rules": {
|
|
12
|
+
"no-unused-vars": [
|
|
13
|
+
"warn",
|
|
14
|
+
{
|
|
15
|
+
"argsIgnorePattern": "^_"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
package/DOCS.md
CHANGED
|
@@ -52,6 +52,14 @@ Utilities for locator payload construction and evaluation.
|
|
|
52
52
|
- **Options** include `bootstrapRelays`, `servicePubkey`, `serviceId`, `locatorId`, `expectedK`, `locatorSecretKey`, `torPreferred`, timeouts, and override hooks.
|
|
53
53
|
- **Returns** `{ endpoint, source, serviceEvent, locatorPayload, selection }`.
|
|
54
54
|
|
|
55
|
+
## Sidecar config helpers
|
|
56
|
+
|
|
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.
|
|
59
|
+
|
|
60
|
+
### `buildClientConfig(options)`
|
|
61
|
+
- Rehydrates the minimal client config that the example resolver expects; dedupes publication relays, enforces `serviceIdentityUri`, and carries the `expectedK` for NCC-02 pinning.
|
|
62
|
+
|
|
55
63
|
## Key and TLS helpers
|
|
56
64
|
|
|
57
65
|
### `generateExpectedK`, `validateExpectedKFormat`
|
|
@@ -108,3 +116,7 @@ const delay = scheduleWithJitter(60000); // use this for republish timers
|
|
|
108
116
|
```
|
|
109
117
|
|
|
110
118
|
The helpers are intentionally small, focused on NCC-06 policy, and rely on the calling code for transport/threading so they can be reused inside Node scripts, CLI tools, or downstream SDKs.
|
|
119
|
+
|
|
120
|
+
## TypeScript definitions
|
|
121
|
+
|
|
122
|
+
`ncc-06-js` ships an `index.d.ts` file that mirrors the helpers documented above so TypeScript consumers receive accurate typings.
|
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ Reusable helpers extracted from the NCC-06 example relay, sidecar, and client im
|
|
|
11
11
|
- **Scheduling helpers** (`scheduleWithJitter`) for applying bounded jitter to recurring NCC-02/NCC-05 timers without ever publishing outside the declared window.
|
|
12
12
|
- **TLS/key utilities** (`ensureSelfSignedCert`, `generateKeypair`, `toNpub`, `fromNsec`, `generateExpectedK`, `validateExpectedKFormat`) that mirror the key and fingerprint management used by the example sidecar.
|
|
13
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.
|
|
14
16
|
|
|
15
17
|
## Usage
|
|
16
18
|
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
declare module 'ncc-06-js' {
|
|
2
|
+
export interface NostrEvent {
|
|
3
|
+
id: string;
|
|
4
|
+
pubkey: string;
|
|
5
|
+
created_at: number;
|
|
6
|
+
kind: number;
|
|
7
|
+
tags: string[][];
|
|
8
|
+
content: string;
|
|
9
|
+
sig: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface BuildNcc02Options {
|
|
13
|
+
secretKey: string;
|
|
14
|
+
serviceId: string;
|
|
15
|
+
endpoint: string;
|
|
16
|
+
fingerprint?: string | null;
|
|
17
|
+
expirySeconds?: number;
|
|
18
|
+
kind?: number;
|
|
19
|
+
createdAt?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildNcc02ServiceRecord(options: BuildNcc02Options): NostrEvent;
|
|
23
|
+
export function parseNcc02Tags(event: NostrEvent): Record<string, string | undefined>;
|
|
24
|
+
export interface ValidateNcc02Options {
|
|
25
|
+
expectedAuthor?: string;
|
|
26
|
+
expectedD?: string;
|
|
27
|
+
now?: number;
|
|
28
|
+
allowExpired?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export function validateNcc02(event: NostrEvent, options?: ValidateNcc02Options): boolean;
|
|
31
|
+
|
|
32
|
+
export interface LocatorEndpoint {
|
|
33
|
+
url: string;
|
|
34
|
+
type?: string;
|
|
35
|
+
uri?: string;
|
|
36
|
+
value?: string;
|
|
37
|
+
protocol?: 'ws' | 'wss';
|
|
38
|
+
family?: 'ipv4' | 'ipv6' | 'onion' | string;
|
|
39
|
+
priority?: number;
|
|
40
|
+
prio?: number;
|
|
41
|
+
k?: string;
|
|
42
|
+
fingerprint?: string;
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface LocatorPayload {
|
|
47
|
+
ttl: number;
|
|
48
|
+
updated_at: number;
|
|
49
|
+
endpoints: LocatorEndpoint[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const DEFAULT_TTL_SECONDS: number;
|
|
53
|
+
export function buildLocatorPayload(options?: {
|
|
54
|
+
endpoints?: LocatorEndpoint[];
|
|
55
|
+
ttl?: number;
|
|
56
|
+
updatedAt?: number;
|
|
57
|
+
}): LocatorPayload;
|
|
58
|
+
export function parseLocatorPayload(content: string | null | undefined): LocatorPayload | null;
|
|
59
|
+
export function validateLocatorFreshness(
|
|
60
|
+
payload: LocatorPayload | null | undefined,
|
|
61
|
+
options?: { now?: number; allowStale?: boolean }
|
|
62
|
+
): boolean;
|
|
63
|
+
export function normalizeLocatorEndpoints(endpoints?: LocatorEndpoint[]): LocatorEndpoint[];
|
|
64
|
+
|
|
65
|
+
export interface SelectorOptions {
|
|
66
|
+
torPreferred?: boolean;
|
|
67
|
+
expectedK?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface SelectorResult {
|
|
70
|
+
endpoint: LocatorEndpoint | null;
|
|
71
|
+
reason?: string;
|
|
72
|
+
expected?: string;
|
|
73
|
+
actual?: string;
|
|
74
|
+
}
|
|
75
|
+
export function choosePreferredEndpoint(
|
|
76
|
+
endpoints?: LocatorEndpoint[],
|
|
77
|
+
options?: SelectorOptions
|
|
78
|
+
): SelectorResult;
|
|
79
|
+
export { normalizeLocatorEndpoints };
|
|
80
|
+
|
|
81
|
+
export interface ResolverOptions {
|
|
82
|
+
bootstrapRelays: string[];
|
|
83
|
+
servicePubkey: string;
|
|
84
|
+
serviceId: string;
|
|
85
|
+
locatorId: string;
|
|
86
|
+
expectedK?: string;
|
|
87
|
+
torPreferred?: boolean;
|
|
88
|
+
locatorSecretKey?: string;
|
|
89
|
+
ncc05TimeoutMs?: number;
|
|
90
|
+
publicationRelayTimeoutMs?: number;
|
|
91
|
+
queryRelayEvents?: (
|
|
92
|
+
relays: string[],
|
|
93
|
+
filter: Record<string, unknown>,
|
|
94
|
+
options?: { timeoutMs?: number }
|
|
95
|
+
) => Promise<NostrEvent[]>;
|
|
96
|
+
resolveLocator?: (options: {
|
|
97
|
+
bootstrapRelays: string[];
|
|
98
|
+
servicePubkey: string;
|
|
99
|
+
locatorId: string;
|
|
100
|
+
locatorSecretKey?: string;
|
|
101
|
+
timeout?: number;
|
|
102
|
+
}) => Promise<LocatorPayload | null>;
|
|
103
|
+
throttle?: unknown;
|
|
104
|
+
now?: number;
|
|
105
|
+
}
|
|
106
|
+
export interface ResolverSelection {
|
|
107
|
+
endpoint: string | null;
|
|
108
|
+
source: 'locator' | 'ncc02' | null;
|
|
109
|
+
reason: string;
|
|
110
|
+
evidence?: Record<string, unknown>;
|
|
111
|
+
}
|
|
112
|
+
export interface ResolverResult {
|
|
113
|
+
endpoint: string | null;
|
|
114
|
+
source: 'locator' | 'ncc02' | null;
|
|
115
|
+
locatorPayload: LocatorPayload | null;
|
|
116
|
+
serviceEvent: NostrEvent;
|
|
117
|
+
selection: ResolverSelection;
|
|
118
|
+
}
|
|
119
|
+
export function resolveServiceEndpoint(options: ResolverOptions): Promise<ResolverResult>;
|
|
120
|
+
|
|
121
|
+
export interface Protocol {
|
|
122
|
+
parseNostrMessage(messageString: string): unknown[] | null;
|
|
123
|
+
serializeNostrMessage(messageArray: unknown[]): string;
|
|
124
|
+
createReqMessage(subId: string, ...filters: unknown[]): unknown[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface Keypair {
|
|
128
|
+
secretKey: string;
|
|
129
|
+
publicKey: string;
|
|
130
|
+
npub: string;
|
|
131
|
+
nsec: string;
|
|
132
|
+
}
|
|
133
|
+
export function generateKeypair(): Keypair;
|
|
134
|
+
export function toNpub(pubkey: string): string;
|
|
135
|
+
export function fromNpub(npub: string): string;
|
|
136
|
+
export function toNsec(secretKey: string): string;
|
|
137
|
+
export function fromNsec(nsec: string): string;
|
|
138
|
+
|
|
139
|
+
export type KMode = 'tls_spki' | 'static' | 'generate';
|
|
140
|
+
export interface KConfig {
|
|
141
|
+
mode?: KMode;
|
|
142
|
+
value?: string;
|
|
143
|
+
certPath?: string;
|
|
144
|
+
persistPath?: string;
|
|
145
|
+
externalEndpoints?: Record<string, unknown>;
|
|
146
|
+
}
|
|
147
|
+
export function generateExpectedK(options?: {
|
|
148
|
+
prefix?: string;
|
|
149
|
+
label?: string;
|
|
150
|
+
suffix?: string;
|
|
151
|
+
}): string;
|
|
152
|
+
export function validateExpectedKFormat(k: string): boolean;
|
|
153
|
+
export function computeKFromCertPem(pem: string): string;
|
|
154
|
+
export function getExpectedK(cfg?: { k?: KConfig; externalEndpoints?: Record<string, unknown> }, options?: {
|
|
155
|
+
baseDir?: string;
|
|
156
|
+
}): string;
|
|
157
|
+
|
|
158
|
+
export interface JitterResult {
|
|
159
|
+
baseMs: number;
|
|
160
|
+
jitterRatio?: number;
|
|
161
|
+
}
|
|
162
|
+
export function scheduleWithJitter(baseMs: number, jitterRatio?: number): number;
|
|
163
|
+
|
|
164
|
+
export interface SelfSignedCertificate {
|
|
165
|
+
keyPath: string;
|
|
166
|
+
certPath: string;
|
|
167
|
+
}
|
|
168
|
+
export interface EnsureCertOptions {
|
|
169
|
+
targetDir?: string;
|
|
170
|
+
keyFileName?: string;
|
|
171
|
+
certFileName?: string;
|
|
172
|
+
altNames?: string[];
|
|
173
|
+
}
|
|
174
|
+
export function ensureSelfSignedCert(options?: EnsureCertOptions): Promise<SelfSignedCertificate>;
|
|
175
|
+
|
|
176
|
+
export interface ExternalEndpointOptions {
|
|
177
|
+
tor?: {
|
|
178
|
+
enabled?: boolean;
|
|
179
|
+
};
|
|
180
|
+
ipv4?: {
|
|
181
|
+
enabled?: boolean;
|
|
182
|
+
protocol?: 'ws' | 'wss';
|
|
183
|
+
port?: number;
|
|
184
|
+
address?: string;
|
|
185
|
+
publicSources?: string[];
|
|
186
|
+
};
|
|
187
|
+
ipv6?: {
|
|
188
|
+
enabled?: boolean;
|
|
189
|
+
protocol?: 'ws' | 'wss';
|
|
190
|
+
port?: number;
|
|
191
|
+
};
|
|
192
|
+
wsPort?: number;
|
|
193
|
+
wssPort?: number;
|
|
194
|
+
ncc02ExpectedKey?: string;
|
|
195
|
+
ensureOnionService?: () => Promise<{ address: string; servicePort: number } | null>;
|
|
196
|
+
publicIpv4Sources?: string[];
|
|
197
|
+
}
|
|
198
|
+
export function buildExternalEndpoints(options?: ExternalEndpointOptions): Promise<LocatorEndpoint[]>;
|
|
199
|
+
export function detectGlobalIPv6(): string | null;
|
|
200
|
+
export function getPublicIPv4(options?: { sources?: string[] }): Promise<string | null>;
|
|
201
|
+
|
|
202
|
+
export interface SidecarConfigOptions {
|
|
203
|
+
serviceSk: string;
|
|
204
|
+
servicePk: string;
|
|
205
|
+
serviceNpub: string;
|
|
206
|
+
relayUrl: string;
|
|
207
|
+
serviceId?: string;
|
|
208
|
+
locatorId?: string;
|
|
209
|
+
publicationRelays?: string[];
|
|
210
|
+
publishRelays?: string[];
|
|
211
|
+
ncc02ExpSeconds?: number;
|
|
212
|
+
ncc05TtlSeconds?: number;
|
|
213
|
+
torControl?: Record<string, unknown>;
|
|
214
|
+
externalEndpoints?: Record<string, unknown>;
|
|
215
|
+
k?: KConfig;
|
|
216
|
+
baseDir?: string;
|
|
217
|
+
ncc02ExpectedKeySource?: string;
|
|
218
|
+
}
|
|
219
|
+
export interface SidecarConfig {
|
|
220
|
+
serviceSk: string;
|
|
221
|
+
servicePk: string;
|
|
222
|
+
serviceNpub: string;
|
|
223
|
+
relayUrl: string;
|
|
224
|
+
serviceId: string;
|
|
225
|
+
locatorId: string;
|
|
226
|
+
publicationRelays: string[];
|
|
227
|
+
publishRelays: string[];
|
|
228
|
+
ncc02ExpSeconds: number;
|
|
229
|
+
ncc05TtlSeconds: number;
|
|
230
|
+
ncc02ExpectedKey: string;
|
|
231
|
+
ncc02ExpectedKeySource: string;
|
|
232
|
+
externalEndpoints: Record<string, unknown>;
|
|
233
|
+
torControl: Record<string, unknown>;
|
|
234
|
+
k: KConfig;
|
|
235
|
+
}
|
|
236
|
+
export function buildSidecarConfig(options: SidecarConfigOptions): SidecarConfig;
|
|
237
|
+
|
|
238
|
+
export interface ClientConfigOptions {
|
|
239
|
+
relayUrl: string;
|
|
240
|
+
servicePubkey?: string;
|
|
241
|
+
serviceNpub?: string;
|
|
242
|
+
serviceIdentityUri?: string;
|
|
243
|
+
locatorSecretKey?: string;
|
|
244
|
+
locatorFriendPubkey?: string;
|
|
245
|
+
publicationRelays?: string[];
|
|
246
|
+
staleFallbackSeconds?: number;
|
|
247
|
+
torPreferred?: boolean;
|
|
248
|
+
ncc05TimeoutMs?: number;
|
|
249
|
+
serviceId?: string;
|
|
250
|
+
locatorId?: string;
|
|
251
|
+
expectedK?: string;
|
|
252
|
+
}
|
|
253
|
+
export interface ClientConfig {
|
|
254
|
+
relayUrl: string;
|
|
255
|
+
serviceIdentityUri: string;
|
|
256
|
+
servicePubkey: string;
|
|
257
|
+
serviceNpub: string;
|
|
258
|
+
publicationRelays: string[];
|
|
259
|
+
staleFallbackSeconds: number;
|
|
260
|
+
torPreferred: boolean;
|
|
261
|
+
ncc05TimeoutMs: number;
|
|
262
|
+
locatorSecretKey?: string;
|
|
263
|
+
locatorFriendPubkey?: string;
|
|
264
|
+
serviceId?: string;
|
|
265
|
+
locatorId?: string;
|
|
266
|
+
ncc02ExpectedKey?: string;
|
|
267
|
+
}
|
|
268
|
+
export function buildClientConfig(options: ClientConfigOptions): ClientConfig;
|
|
269
|
+
}
|
package/package.json
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ncc-06-js",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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",
|
|
7
|
+
"types": "index.d.ts",
|
|
7
8
|
"scripts": {
|
|
8
9
|
"test": "node --test",
|
|
9
10
|
"lint": "eslint . --ext .js"
|
|
10
11
|
},
|
|
11
12
|
"dependencies": {
|
|
12
|
-
"ncc-02-js": "^0.2.
|
|
13
|
-
"ncc-05": "^1.1.
|
|
13
|
+
"ncc-02-js": "^0.2.3",
|
|
14
|
+
"ncc-05-js": "^1.1.9",
|
|
14
15
|
"nostr-tools": "^2.19.4",
|
|
15
16
|
"selfsigned": "^5.4.0",
|
|
16
17
|
"ws": "^8.18.3"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
20
|
"@types/node": "^20.7.2",
|
|
20
|
-
"eslint": "^8.54.0"
|
|
21
|
+
"eslint": "^8.54.0",
|
|
22
|
+
"typescript": "^5.5.3"
|
|
21
23
|
}
|
|
22
24
|
}
|
|
@@ -76,7 +76,7 @@ export async function buildExternalEndpoints({
|
|
|
76
76
|
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
77
77
|
return a.index - b.index;
|
|
78
78
|
})
|
|
79
|
-
.map(({ index, createdAt, ...endpoint }) => endpoint);
|
|
79
|
+
.map(({ index: _index, createdAt: _createdAt, ...endpoint }) => endpoint);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
package/src/index.js
CHANGED
package/src/resolver.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import WebSocket from 'ws';
|
|
2
|
-
import { NCC05Resolver } from 'ncc-05';
|
|
2
|
+
import { NCC05Resolver } from 'ncc-05-js';
|
|
3
3
|
import { parseNostrMessage, serializeNostrMessage, createReqMessage } from './protocol.js';
|
|
4
4
|
import { validateNcc02, parseNcc02Tags } from './ncc02.js';
|
|
5
5
|
import { normalizeLocatorEndpoints, validateLocatorFreshness } from './ncc05.js';
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { DEFAULT_TTL_SECONDS } from './ncc05.js';
|
|
2
|
+
import { getExpectedK } from './k.js';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_TOR_CONTROL = {
|
|
5
|
+
enabled: false,
|
|
6
|
+
host: '127.0.0.1',
|
|
7
|
+
port: 9051,
|
|
8
|
+
password: '',
|
|
9
|
+
servicePort: 80,
|
|
10
|
+
serviceFile: './onion-service.json',
|
|
11
|
+
timeout: 5000
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function uniqueList(items) {
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
return items.filter(item => {
|
|
17
|
+
if (!item) return false;
|
|
18
|
+
const normalized = item.trim();
|
|
19
|
+
if (seen.has(normalized)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
seen.add(normalized);
|
|
23
|
+
return true;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build a deployable sidecar config object that mirrors the example's defaults.
|
|
29
|
+
*/
|
|
30
|
+
export function buildSidecarConfig({
|
|
31
|
+
serviceSk,
|
|
32
|
+
servicePk,
|
|
33
|
+
serviceNpub,
|
|
34
|
+
relayUrl,
|
|
35
|
+
serviceId = 'relay',
|
|
36
|
+
locatorId = 'relay-locator',
|
|
37
|
+
publicationRelays = [],
|
|
38
|
+
publishRelays,
|
|
39
|
+
ncc02ExpSeconds = 14 * 24 * 60 * 60,
|
|
40
|
+
ncc05TtlSeconds = DEFAULT_TTL_SECONDS,
|
|
41
|
+
torControl = DEFAULT_TOR_CONTROL,
|
|
42
|
+
externalEndpoints = {},
|
|
43
|
+
k = {},
|
|
44
|
+
baseDir = process.cwd(),
|
|
45
|
+
ncc02ExpectedKeySource
|
|
46
|
+
} = {}) {
|
|
47
|
+
if (!serviceSk || !servicePk || !serviceNpub) {
|
|
48
|
+
throw new Error('service keypair must be provided');
|
|
49
|
+
}
|
|
50
|
+
if (!relayUrl) {
|
|
51
|
+
throw new Error('relayUrl is required');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const expectedKey = getExpectedK({ k, externalEndpoints }, { baseDir });
|
|
55
|
+
const normalizedPublicationRelays = uniqueList([relayUrl, ...publicationRelays]);
|
|
56
|
+
const normalizedPublishRelays = uniqueList([
|
|
57
|
+
relayUrl,
|
|
58
|
+
...(publishRelays ?? normalizedPublicationRelays)
|
|
59
|
+
]);
|
|
60
|
+
const keySource = ncc02ExpectedKeySource ?? k.mode ?? 'auto';
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
serviceSk,
|
|
64
|
+
servicePk,
|
|
65
|
+
serviceNpub,
|
|
66
|
+
relayUrl,
|
|
67
|
+
serviceId,
|
|
68
|
+
locatorId,
|
|
69
|
+
publicationRelays: normalizedPublicationRelays,
|
|
70
|
+
publishRelays: normalizedPublishRelays,
|
|
71
|
+
ncc02ExpSeconds,
|
|
72
|
+
ncc05TtlSeconds,
|
|
73
|
+
ncc02ExpectedKey: expectedKey,
|
|
74
|
+
ncc02ExpectedKeySource: keySource,
|
|
75
|
+
externalEndpoints,
|
|
76
|
+
torControl,
|
|
77
|
+
k
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Build a client config that matches the NCC-06 example expectations.
|
|
83
|
+
*/
|
|
84
|
+
export function buildClientConfig({
|
|
85
|
+
relayUrl,
|
|
86
|
+
servicePubkey,
|
|
87
|
+
serviceNpub,
|
|
88
|
+
serviceIdentityUri,
|
|
89
|
+
locatorSecretKey,
|
|
90
|
+
locatorFriendPubkey,
|
|
91
|
+
publicationRelays = [],
|
|
92
|
+
staleFallbackSeconds = 600,
|
|
93
|
+
torPreferred = false,
|
|
94
|
+
ncc05TimeoutMs = 5000,
|
|
95
|
+
serviceId,
|
|
96
|
+
locatorId,
|
|
97
|
+
expectedK
|
|
98
|
+
} = {}) {
|
|
99
|
+
if (!relayUrl) {
|
|
100
|
+
throw new Error('relayUrl is required');
|
|
101
|
+
}
|
|
102
|
+
const identityUri =
|
|
103
|
+
serviceIdentityUri || (serviceNpub ? `wss://${serviceNpub}` : undefined);
|
|
104
|
+
if (!identityUri) {
|
|
105
|
+
throw new Error('serviceIdentityUri or serviceNpub is required');
|
|
106
|
+
}
|
|
107
|
+
if (!servicePubkey) {
|
|
108
|
+
throw new Error('servicePubkey is required');
|
|
109
|
+
}
|
|
110
|
+
const publicationList = uniqueList([relayUrl, ...publicationRelays]);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
relayUrl,
|
|
114
|
+
serviceIdentityUri: identityUri,
|
|
115
|
+
servicePubkey,
|
|
116
|
+
serviceNpub: serviceNpub ?? '',
|
|
117
|
+
publicationRelays: publicationList,
|
|
118
|
+
staleFallbackSeconds,
|
|
119
|
+
torPreferred,
|
|
120
|
+
ncc05TimeoutMs,
|
|
121
|
+
locatorSecretKey,
|
|
122
|
+
locatorFriendPubkey,
|
|
123
|
+
serviceId,
|
|
124
|
+
locatorId,
|
|
125
|
+
ncc02ExpectedKey: expectedK
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -46,7 +46,7 @@ test('detectGlobalIPv6 filters private addresses', () => {
|
|
|
46
46
|
|
|
47
47
|
test('getPublicIPv4 returns from first reachable source', async () => {
|
|
48
48
|
const original = global.fetch;
|
|
49
|
-
global.fetch = async (
|
|
49
|
+
global.fetch = async (_url) => ({
|
|
50
50
|
ok: true,
|
|
51
51
|
text: async () => '{"ip":"5.6.7.8"}'
|
|
52
52
|
});
|
package/test/k.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { strict as assert } from 'assert';
|
|
2
2
|
import { test } from 'node:test';
|
|
3
3
|
import { computeKFromCertPem, getExpectedK } from '../src/k.js';
|
|
4
|
-
import { readFileSync,
|
|
4
|
+
import { readFileSync, rmSync, mkdirSync } from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import os from 'os';
|
|
7
7
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import { strict as assert } from 'assert';
|
|
3
|
+
import { buildSidecarConfig, buildClientConfig } from '../src/sidecar-config.js';
|
|
4
|
+
|
|
5
|
+
test('buildSidecarConfig constructs expected fields based on inputs', () => {
|
|
6
|
+
const cfg = buildSidecarConfig({
|
|
7
|
+
serviceSk: 'sk',
|
|
8
|
+
servicePk: 'pk',
|
|
9
|
+
serviceNpub: 'npub',
|
|
10
|
+
relayUrl: 'ws://localhost:7000',
|
|
11
|
+
k: { mode: 'static', value: 'TESTKEY:abc' },
|
|
12
|
+
publicationRelays: ['ws://aux:7001'],
|
|
13
|
+
externalEndpoints: {
|
|
14
|
+
ipv4: { enabled: true, protocol: 'ws', address: '127.0.0.1', port: 7447 }
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
assert.equal(cfg.serviceSk, 'sk');
|
|
19
|
+
assert.equal(cfg.ncc02ExpectedKey, 'TESTKEY:abc');
|
|
20
|
+
assert.deepEqual(cfg.publicationRelays[0], 'ws://localhost:7000');
|
|
21
|
+
assert.ok(cfg.publishRelays.length >= 1);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('buildClientConfig enforces identity URI and relays', () => {
|
|
25
|
+
const client = buildClientConfig({
|
|
26
|
+
relayUrl: 'ws://relay',
|
|
27
|
+
servicePubkey: 'pk',
|
|
28
|
+
serviceNpub: 'npub',
|
|
29
|
+
expectedK: 'TESTKEY:abc',
|
|
30
|
+
publicationRelays: ['ws://relay', 'ws://aux']
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
assert.equal(client.serviceIdentityUri, 'wss://npub');
|
|
34
|
+
assert.equal(client.publicationRelays.length, 2);
|
|
35
|
+
assert.equal(client.ncc02ExpectedKey, 'TESTKEY:abc');
|
|
36
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ES2020",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"allowSyntheticDefaultImports": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"checkJs": false,
|
|
13
|
+
"allowJs": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"types": ["node"]
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*.js", "test/**/*.js"]
|
|
18
|
+
}
|