ncc-06-js 0.4.2 → 0.6.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/DOCS.md +2 -2
- package/eslint.config.js +31 -0
- package/index.d.ts +5 -2
- package/package.json +5 -3
- package/src/external-endpoints.js +16 -9
- package/src/ncc02.js +28 -30
- package/src/ncc05.js +1 -1
- package/src/protocol.js +1 -1
- package/src/resolver.js +43 -30
- package/src/sidecar-config.js +1 -14
- package/test/ncc02.test.js +9 -3
package/DOCS.md
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
These functions help build, parse, and verify NCC-02 service records (kind `30059`).
|
|
8
8
|
|
|
9
9
|
### `buildNcc02ServiceRecord(options)`
|
|
10
|
-
- **Purpose**: create a signed service record containing `d`, `u`, `k`, and
|
|
10
|
+
- **Purpose**: asynchronously create a signed service record containing `d`, `u`, `k`, `exp`, and optional privacy metadata.
|
|
11
11
|
- **Example**:
|
|
12
12
|
```js
|
|
13
|
-
const event = buildNcc02ServiceRecord({
|
|
13
|
+
const event = await buildNcc02ServiceRecord({
|
|
14
14
|
secretKey,
|
|
15
15
|
serviceId: 'relay',
|
|
16
16
|
endpoint: 'wss://127.0.0.1:7447',
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { fileURLToPath, URL } from 'node:url';
|
|
2
|
+
import { FlatCompat } from '@eslint/eslintrc';
|
|
3
|
+
import globals from 'globals';
|
|
4
|
+
import pkg from '@eslint/js';
|
|
5
|
+
|
|
6
|
+
const { configs } = pkg;
|
|
7
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
8
|
+
const compat = new FlatCompat({ baseDirectory: __dirname, recommendedConfig: configs.recommended });
|
|
9
|
+
const envGlobals = {
|
|
10
|
+
...globals.es2020,
|
|
11
|
+
...globals.node
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default [
|
|
15
|
+
...compat.extends('eslint:recommended'),
|
|
16
|
+
{
|
|
17
|
+
languageOptions: {
|
|
18
|
+
ecmaVersion: 2020,
|
|
19
|
+
sourceType: 'module',
|
|
20
|
+
globals: envGlobals
|
|
21
|
+
},
|
|
22
|
+
rules: {
|
|
23
|
+
'no-unused-vars': [
|
|
24
|
+
'warn',
|
|
25
|
+
{
|
|
26
|
+
argsIgnorePattern: '^_'
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
];
|
package/index.d.ts
CHANGED
|
@@ -17,9 +17,11 @@ declare module 'ncc-06-js' {
|
|
|
17
17
|
expirySeconds?: number;
|
|
18
18
|
kind?: number;
|
|
19
19
|
createdAt?: number;
|
|
20
|
+
isPrivate?: boolean;
|
|
21
|
+
privateRecipients?: string[];
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
export function buildNcc02ServiceRecord(options: BuildNcc02Options): NostrEvent
|
|
24
|
+
export function buildNcc02ServiceRecord(options: BuildNcc02Options): Promise<NostrEvent>;
|
|
23
25
|
export function parseNcc02Tags(event: NostrEvent): Record<string, string | undefined>;
|
|
24
26
|
export interface ValidateNcc02Options {
|
|
25
27
|
expectedAuthor?: string;
|
|
@@ -95,6 +97,7 @@ declare module 'ncc-06-js' {
|
|
|
95
97
|
locatorId: string;
|
|
96
98
|
expectedK?: string;
|
|
97
99
|
torPreferred?: boolean;
|
|
100
|
+
allowedProtocols?: string[];
|
|
98
101
|
locatorSecretKey?: string;
|
|
99
102
|
ncc05TimeoutMs?: number;
|
|
100
103
|
publicationRelayTimeoutMs?: number;
|
|
@@ -259,4 +262,4 @@ declare module 'ncc-06-js' {
|
|
|
259
262
|
ncc02ExpectedKey?: string;
|
|
260
263
|
}
|
|
261
264
|
export function buildClientConfig(options: ClientConfigOptions): ClientConfig;
|
|
262
|
-
}
|
|
265
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ncc-06-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Reusable NCC-06 discovery helpers for multimodal service identities.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -10,13 +10,15 @@
|
|
|
10
10
|
"lint": "eslint . --ext .js"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"ncc-02-js": "^0.
|
|
14
|
-
"ncc-05-js": "^1.
|
|
13
|
+
"ncc-02-js": "^0.5.0",
|
|
14
|
+
"ncc-05-js": "^1.2.0",
|
|
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
|
+
"@eslint/eslintrc": "^2.0.3",
|
|
21
|
+
"@eslint/js": "^9.39.2",
|
|
20
22
|
"@types/node": "^25.0.3",
|
|
21
23
|
"eslint": "^9.39.2",
|
|
22
24
|
"typescript": "^5.9.3"
|
|
@@ -3,6 +3,7 @@ import os from 'os';
|
|
|
3
3
|
const IPV4_PRIORITY = 10;
|
|
4
4
|
const IPV6_PRIORITY = 20;
|
|
5
5
|
const ONION_PRIORITY = 30;
|
|
6
|
+
const SECURE_PROTOCOLS = new Set(['wss', 'https', 'tls', 'tcps']);
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Build a list of external endpoints that the operator wants to publish.
|
|
@@ -43,22 +44,24 @@ export async function buildExternalEndpoints({
|
|
|
43
44
|
if (ipv6?.enabled) {
|
|
44
45
|
const address = detectGlobalIPv6();
|
|
45
46
|
if (address) {
|
|
46
|
-
const protocol = ipv6.protocol || 'ws';
|
|
47
|
-
const
|
|
47
|
+
const protocol = (ipv6.protocol || 'ws').toLowerCase();
|
|
48
|
+
const secure = isSecureProtocol(protocol);
|
|
49
|
+
const port = ipv6.port || (secure ? wssPort : wsPort);
|
|
48
50
|
const url = `${protocol}://[${address}]:${port}`;
|
|
49
51
|
addEndpoint({
|
|
50
52
|
url,
|
|
51
53
|
priority: IPV6_PRIORITY,
|
|
52
54
|
family: 'ipv6',
|
|
53
55
|
protocol,
|
|
54
|
-
k:
|
|
56
|
+
k: secure ? ncc02ExpectedKey : undefined
|
|
55
57
|
});
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
if (ipv4?.enabled) {
|
|
60
|
-
const protocol = ipv4.protocol || 'wss';
|
|
61
|
-
const
|
|
62
|
+
const protocol = (ipv4.protocol || 'wss').toLowerCase();
|
|
63
|
+
const secure = isSecureProtocol(protocol);
|
|
64
|
+
const port = ipv4.port || (secure ? wssPort : wsPort);
|
|
62
65
|
const address = ipv4.address || (await getPublicIPv4({ sources: ipv4.publicSources ?? publicIpv4Sources }));
|
|
63
66
|
if (address) {
|
|
64
67
|
addEndpoint({
|
|
@@ -66,7 +69,7 @@ export async function buildExternalEndpoints({
|
|
|
66
69
|
priority: IPV4_PRIORITY,
|
|
67
70
|
family: 'ipv4',
|
|
68
71
|
protocol,
|
|
69
|
-
k:
|
|
72
|
+
k: secure ? ncc02ExpectedKey : undefined
|
|
70
73
|
});
|
|
71
74
|
}
|
|
72
75
|
}
|
|
@@ -126,13 +129,19 @@ export async function getPublicIPv4({ sources = ['https://api.ipify.org?format=j
|
|
|
126
129
|
if (match) {
|
|
127
130
|
return match[0];
|
|
128
131
|
}
|
|
129
|
-
} catch
|
|
132
|
+
} catch {
|
|
130
133
|
continue;
|
|
131
134
|
}
|
|
132
135
|
}
|
|
133
136
|
return null;
|
|
134
137
|
}
|
|
135
138
|
|
|
139
|
+
function isSecureProtocol(protocol) {
|
|
140
|
+
if (!protocol) return false;
|
|
141
|
+
const normalized = protocol.toLowerCase();
|
|
142
|
+
return SECURE_PROTOCOLS.has(normalized) || (normalized.endsWith('s') && normalized !== 'ws');
|
|
143
|
+
}
|
|
144
|
+
|
|
136
145
|
export function normalizeRelayUrl(url) {
|
|
137
146
|
if (!url) return '';
|
|
138
147
|
let normalized = url.trim();
|
|
@@ -152,5 +161,3 @@ export function normalizeRelays(relays) {
|
|
|
152
161
|
.map(normalizeRelayUrl);
|
|
153
162
|
return [...new Set(normalized)];
|
|
154
163
|
}
|
|
155
|
-
|
|
156
|
-
|
package/src/ncc02.js
CHANGED
|
@@ -1,39 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NCC02Builder, verifyNCC02Event } from 'ncc-02-js';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const DEFAULT_EXPIRY_SECONDS = 14 * 24 * 60 * 60;
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
function ensureSecretKey(key) {
|
|
6
|
+
if (!key) {
|
|
7
|
+
throw new Error('secretKey is required');
|
|
8
|
+
}
|
|
9
|
+
return key;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function buildNcc02ServiceRecord({
|
|
9
13
|
secretKey,
|
|
10
14
|
serviceId,
|
|
11
15
|
endpoint,
|
|
12
16
|
fingerprint,
|
|
13
|
-
expirySeconds =
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
expirySeconds = DEFAULT_EXPIRY_SECONDS,
|
|
18
|
+
isPrivate = false,
|
|
19
|
+
privateRecipients
|
|
16
20
|
}) {
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
ensureSecretKey(secretKey);
|
|
22
|
+
if (!serviceId) {
|
|
23
|
+
throw new Error('serviceId is required');
|
|
19
24
|
}
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
kind,
|
|
31
|
-
pubkey: getPublicKey(secretKey),
|
|
32
|
-
created_at: timestamp,
|
|
33
|
-
tags,
|
|
34
|
-
content: ''
|
|
35
|
-
};
|
|
36
|
-
return finalizeEvent(event, secretKey);
|
|
25
|
+
const builder = new NCC02Builder(secretKey);
|
|
26
|
+
const expiryDays = Number(expirySeconds) / (24 * 60 * 60);
|
|
27
|
+
return builder.createServiceRecord({
|
|
28
|
+
serviceId,
|
|
29
|
+
endpoint,
|
|
30
|
+
fingerprint,
|
|
31
|
+
expiryDays,
|
|
32
|
+
isPrivate,
|
|
33
|
+
privateRecipients
|
|
34
|
+
});
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
export function parseNcc02Tags(event) {
|
|
@@ -44,10 +42,10 @@ export function parseNcc02Tags(event) {
|
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
export function validateNcc02(event, { expectedAuthor, expectedD, now, allowExpired = false } = {}) {
|
|
47
|
-
if (!event || event.kind !==
|
|
45
|
+
if (!event || event.kind !== 30059) {
|
|
48
46
|
return false;
|
|
49
47
|
}
|
|
50
|
-
if (!
|
|
48
|
+
if (!verifyNCC02Event(event)) {
|
|
51
49
|
return false;
|
|
52
50
|
}
|
|
53
51
|
const tags = parseNcc02Tags(event);
|
package/src/ncc05.js
CHANGED
package/src/protocol.js
CHANGED
package/src/resolver.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import WebSocket from 'ws';
|
|
2
1
|
import { SimplePool } from 'nostr-tools';
|
|
3
2
|
import { NCC05Resolver } from 'ncc-05-js';
|
|
3
|
+
import { parsePrivateFlag } from 'ncc-02-js';
|
|
4
4
|
import { validateNcc02, parseNcc02Tags } from './ncc02.js';
|
|
5
5
|
import { validateLocatorFreshness, normalizeLocatorEndpoints } from './ncc05.js';
|
|
6
6
|
import { choosePreferredEndpoint } from './selector.js';
|
|
@@ -18,6 +18,7 @@ export async function resolveServiceEndpoint(options = {}) {
|
|
|
18
18
|
locatorId,
|
|
19
19
|
expectedK,
|
|
20
20
|
torPreferred = false,
|
|
21
|
+
allowedProtocols,
|
|
21
22
|
locatorSecretKey,
|
|
22
23
|
ncc05TimeoutMs = 5000,
|
|
23
24
|
publicationRelayTimeoutMs = DEFAULT_RELAY_TIMEOUT_MS,
|
|
@@ -46,37 +47,49 @@ export async function resolveServiceEndpoint(options = {}) {
|
|
|
46
47
|
|
|
47
48
|
let serviceRecord;
|
|
48
49
|
if (ncc02Resolver) {
|
|
49
|
-
|
|
50
|
+
try {
|
|
51
|
+
serviceRecord = await ncc02Resolver.resolve(servicePubkey, serviceId, {});
|
|
52
|
+
} catch (e) {
|
|
53
|
+
serviceRecord = await ncc02Resolver.resolveLatest(servicePubkey, {});
|
|
54
|
+
}
|
|
50
55
|
} else {
|
|
51
56
|
const poolToUse = pool || new SimplePool();
|
|
52
57
|
try {
|
|
53
|
-
const
|
|
58
|
+
const filter = {
|
|
54
59
|
kinds: [30059],
|
|
55
|
-
authors: [servicePubkey]
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// Sort and validate
|
|
68
|
-
const validEvents = events
|
|
69
|
-
.filter(e => validateNcc02(e, { expectedAuthor: servicePubkey, expectedD: serviceId, now: timestamp }))
|
|
60
|
+
authors: [servicePubkey]
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
console.log(`[NCC-Resolver] Querying relays for author ${servicePubkey}...`);
|
|
64
|
+
const events = await poolToUse.querySync(bootstrapRelays, filter);
|
|
65
|
+
|
|
66
|
+
let validEvents = (events || [])
|
|
67
|
+
.filter(e => {
|
|
68
|
+
const tags = parseNcc02Tags(e);
|
|
69
|
+
return tags.d === serviceId && validateNcc02(e, { expectedAuthor: servicePubkey, expectedD: serviceId, now: timestamp });
|
|
70
|
+
})
|
|
70
71
|
.sort((a, b) => b.created_at - a.created_at);
|
|
71
72
|
|
|
73
|
+
if (validEvents.length === 0) {
|
|
74
|
+
console.log(`[NCC-Resolver] No specific record for "${serviceId}", falling back to latest...`);
|
|
75
|
+
validEvents = (events || [])
|
|
76
|
+
.filter(e => validateNcc02(e, { expectedAuthor: servicePubkey, now: timestamp }))
|
|
77
|
+
.sort((a, b) => b.created_at - a.created_at);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log(`[NCC-Resolver] Found ${validEvents.length} candidate events.`);
|
|
81
|
+
|
|
72
82
|
if (validEvents[0]) {
|
|
73
83
|
const tags = parseNcc02Tags(validEvents[0]);
|
|
84
|
+
console.log(`[NCC-Resolver] Selected NCC-02 record: ${validEvents[0].id} (d=${tags.d})`);
|
|
85
|
+
console.log(`[NCC-Resolver] Raw tags:`, JSON.stringify(validEvents[0].tags));
|
|
74
86
|
serviceRecord = {
|
|
75
87
|
endpoint: tags.u,
|
|
76
88
|
fingerprint: tags.k,
|
|
77
89
|
expiry: Number(tags.exp),
|
|
78
90
|
eventId: validEvents[0].id,
|
|
79
|
-
pubkey: validEvents[0].pubkey
|
|
91
|
+
pubkey: validEvents[0].pubkey,
|
|
92
|
+
isPrivate: parsePrivateFlag(validEvents[0].tags) === true
|
|
80
93
|
};
|
|
81
94
|
}
|
|
82
95
|
} finally {
|
|
@@ -85,7 +98,7 @@ export async function resolveServiceEndpoint(options = {}) {
|
|
|
85
98
|
}
|
|
86
99
|
|
|
87
100
|
if (!serviceRecord) {
|
|
88
|
-
throw new Error(`No valid NCC-02 record found for ${
|
|
101
|
+
throw new Error(`No valid NCC-02 record found for ${servicePubkey}`);
|
|
89
102
|
}
|
|
90
103
|
|
|
91
104
|
// 2. Resolve NCC-05 Locator
|
|
@@ -103,6 +116,7 @@ export async function resolveServiceEndpoint(options = {}) {
|
|
|
103
116
|
locatorPayload,
|
|
104
117
|
expectedK,
|
|
105
118
|
torPreferred,
|
|
119
|
+
allowedProtocols,
|
|
106
120
|
now: timestamp
|
|
107
121
|
});
|
|
108
122
|
|
|
@@ -115,14 +129,9 @@ export async function resolveServiceEndpoint(options = {}) {
|
|
|
115
129
|
};
|
|
116
130
|
}
|
|
117
131
|
|
|
118
|
-
function determineEndpoint({ serviceRecord, locatorPayload, expectedK, torPreferred, now }) {
|
|
119
|
-
// serviceRecord is { endpoint, fingerprint, expiry, attestations, ... }
|
|
120
|
-
// It is already validated (signature, expiry).
|
|
121
|
-
|
|
132
|
+
function determineEndpoint({ serviceRecord, locatorPayload, expectedK, torPreferred, allowedProtocols, now }) {
|
|
122
133
|
const ncc02Url = serviceRecord.endpoint;
|
|
123
134
|
const k = serviceRecord.fingerprint;
|
|
124
|
-
// expiry check was done by resolver, but we check if we need to?
|
|
125
|
-
// NCC02Resolver throws if expired. So we can assume it's fresh.
|
|
126
135
|
|
|
127
136
|
const result = {
|
|
128
137
|
endpoint: null,
|
|
@@ -135,7 +144,8 @@ function determineEndpoint({ serviceRecord, locatorPayload, expectedK, torPrefer
|
|
|
135
144
|
const normalized = normalizeLocatorEndpoints(locatorPayload.endpoints || []);
|
|
136
145
|
const selection = choosePreferredEndpoint(normalized, {
|
|
137
146
|
torPreferred,
|
|
138
|
-
expectedK
|
|
147
|
+
expectedK,
|
|
148
|
+
allowedProtocols
|
|
139
149
|
});
|
|
140
150
|
if (selection.endpoint) {
|
|
141
151
|
return {
|
|
@@ -169,6 +179,10 @@ function determineEndpoint({ serviceRecord, locatorPayload, expectedK, torPrefer
|
|
|
169
179
|
}
|
|
170
180
|
|
|
171
181
|
result.reason = result.reason || 'no-endpoint';
|
|
182
|
+
if (serviceRecord.isPrivate && !locatorPayload) {
|
|
183
|
+
result.reason = 'private-no-decryption';
|
|
184
|
+
}
|
|
185
|
+
|
|
172
186
|
return result;
|
|
173
187
|
}
|
|
174
188
|
|
|
@@ -184,7 +198,7 @@ async function defaultResolveLocator({
|
|
|
184
198
|
timeout
|
|
185
199
|
});
|
|
186
200
|
try {
|
|
187
|
-
return await resolver.
|
|
201
|
+
return await resolver.resolveLatest(servicePubkey, locatorSecretKey, {
|
|
188
202
|
strict: false,
|
|
189
203
|
gossip: false
|
|
190
204
|
});
|
|
@@ -193,5 +207,4 @@ async function defaultResolveLocator({
|
|
|
193
207
|
resolver.close();
|
|
194
208
|
}
|
|
195
209
|
}
|
|
196
|
-
}
|
|
197
|
-
|
|
210
|
+
}
|
package/src/sidecar-config.js
CHANGED
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
import { normalizeRelayUrl, normalizeRelays } from './external-endpoints.js';
|
|
1
|
+
import { normalizeRelayUrl } from './external-endpoints.js';
|
|
15
2
|
|
|
16
3
|
const RELAY_MODE_PUBLIC = 'public';
|
|
17
4
|
const RELAY_MODE_PRIVATE = 'private';
|
package/test/ncc02.test.js
CHANGED
|
@@ -3,14 +3,17 @@ import { strict as assert } from 'assert';
|
|
|
3
3
|
import { buildNcc02ServiceRecord, parseNcc02Tags, validateNcc02 } from '../src/ncc02.js';
|
|
4
4
|
import { generateKeypair } from '../src/keys.js';
|
|
5
5
|
|
|
6
|
-
test('builds and validates NCC-02 service record', () => {
|
|
6
|
+
test('builds and validates NCC-02 service record', async () => {
|
|
7
7
|
const { secretKey, publicKey } = generateKeypair();
|
|
8
|
-
const
|
|
8
|
+
const recipients = ['cipher-text-1', 'cipher-text-2'];
|
|
9
|
+
const serviceEvent = await buildNcc02ServiceRecord({
|
|
9
10
|
secretKey,
|
|
10
11
|
serviceId: 'relay',
|
|
11
12
|
endpoint: 'wss://127.0.0.1:7447',
|
|
12
13
|
fingerprint: 'TESTKEY:resolver',
|
|
13
|
-
expirySeconds: 60
|
|
14
|
+
expirySeconds: 60,
|
|
15
|
+
isPrivate: true,
|
|
16
|
+
privateRecipients: recipients
|
|
14
17
|
});
|
|
15
18
|
|
|
16
19
|
assert.equal(serviceEvent.pubkey, publicKey);
|
|
@@ -18,5 +21,8 @@ test('builds and validates NCC-02 service record', () => {
|
|
|
18
21
|
assert.equal(tags.d, 'relay');
|
|
19
22
|
assert.equal(tags.u, 'wss://127.0.0.1:7447');
|
|
20
23
|
assert.equal(tags.k, 'TESTKEY:resolver');
|
|
24
|
+
assert.equal(tags.private, 'true');
|
|
25
|
+
const privateTagCount = serviceEvent.tags.filter(t => t[0] === 'privateRecipients').length;
|
|
26
|
+
assert.strictEqual(privateTagCount, recipients.length);
|
|
21
27
|
assert.ok(validateNcc02(serviceEvent, { expectedAuthor: publicKey, expectedD: 'relay' }));
|
|
22
28
|
});
|