Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/expose-operational-relay-urls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@contextvm/sdk': patch
---

`NostrClientTransport` now exposes `getOperationalRelayUrls()`, returning the current handler's relay URLs: configured URLs before `start()`, and the final set after relay resolution (server-identity hints, kind-10002 discovery, fallback probe) once `start()` resolves. Callers can read the resolved set once and persist it, so future sessions construct a plain configured client without re-paying discovery.
2 changes: 1 addition & 1 deletion docs/contextvm-docs
Submodule contextvm-docs updated from 20d01e to a26867
41 changes: 41 additions & 0 deletions src/transport/nostr-client-transport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,47 @@ describe('NostrClientTransport instance shape', () => {
]);
});

test('getOperationalRelayUrls returns configured URLs before start and resolved hints after', async () => {
const configuredUrl = 'wss://configured.example.com';
const relayHintUrl = 'wss://relay.example.com';
const transport = new NostrClientTransport({
serverPubkey: nip19.nprofileEncode({
pubkey: 'b'.repeat(64),
relays: [relayHintUrl],
}),
signer: new PrivateKeySigner('a'.repeat(64)),
relayHandler: [configuredUrl],
});

expect(transport.getOperationalRelayUrls()).toEqual([configuredUrl]);

await transport['resolveOperationalRelayHandler']();

// Configured URLs win: resolution must not swap a configured handler.
expect(transport.getOperationalRelayUrls()).toEqual([configuredUrl]);
});

test('getOperationalRelayUrls returns resolved URLs after resolution swaps the handler', async () => {
const relayHintUrl = 'wss://relay.example.com';
const transport = new NostrClientTransport({
serverPubkey: nip19.nprofileEncode({
pubkey: 'b'.repeat(64),
relays: [relayHintUrl],
}),
signer: new PrivateKeySigner('a'.repeat(64)),
relayHandler: [],
});

expect(transport.getOperationalRelayUrls()).toEqual([]);

await transport['resolveOperationalRelayHandler']();

expect(transport.getOperationalRelayUrls()).toEqual([relayHintUrl]);
// Second resolution is a no-op: the set is stable after the first one.
await transport['resolveOperationalRelayHandler']();
expect(transport.getOperationalRelayUrls()).toEqual([relayHintUrl]);
});

test('uses bootstrap discovery relays by default when none are provided', () => {
const transport = new NostrClientTransport({
serverPubkey: 'b'.repeat(64),
Expand Down
11 changes: 11 additions & 0 deletions src/transport/nostr-client-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,17 @@ export class NostrClientTransport
await this.inboundCoordinator.processIncomingEvent(event);
}

/**
* Gets the transport's current operational relay URLs.
*
* Returns configured URLs before `start()`; once `start()` resolves, the
* final set after relay resolution (hints, kind-10002 discovery, fallback
* probe) — stable for the transport's lifetime.
*/
public getOperationalRelayUrls(): string[] {
return this.relayHandler.getRelayUrls?.() ?? [];
}

/**
* Gets the server's initialize event if received.
* @returns The server initialize event or undefined
Expand Down
Loading