applesauce-relay 0.0.0-next-20251231062646 → 0.0.0-next-20260116173453

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/dist/relay.d.ts CHANGED
@@ -25,6 +25,12 @@ export type RelayOptions = {
25
25
  publishTimeout?: number;
26
26
  /** How long to keep the connection alive after nothing is subscribed (default 30s) */
27
27
  keepAlive?: number;
28
+ /** Enable/disable ping functionality (default false) */
29
+ enablePing?: boolean;
30
+ /** How often to send pings in milliseconds (default 29000) */
31
+ pingFrequency?: number;
32
+ /** How long to wait for EOSE response in milliseconds (default 20000) */
33
+ pingTimeout?: number;
28
34
  /** Default retry config for subscription() method */
29
35
  subscriptionRetry?: RetryConfig;
30
36
  /** Default retry config for request() method */
@@ -66,6 +72,8 @@ export declare class Relay implements IRelay {
66
72
  * @note Subscribing to this will not connect to the relay
67
73
  */
68
74
  notice$: Observable<string>;
75
+ /** Timestamp of the last message received from the relay */
76
+ private lastMessageReceivedAt;
69
77
  /** An observable that emits the NIP-11 information document for the relay */
70
78
  information$: Observable<RelayInformation | null>;
71
79
  protected _nip11: RelayInformation | null;
@@ -96,6 +104,12 @@ export declare class Relay implements IRelay {
96
104
  publishTimeout: number;
97
105
  /** How long to keep the connection alive after nothing is subscribed (default 30s) */
98
106
  keepAlive: number;
107
+ /** Enable/disable ping functionality (default false) */
108
+ enablePing: boolean;
109
+ /** How often to send pings in milliseconds (default 29000) */
110
+ pingFrequency: number;
111
+ /** How long to wait for EOSE response in milliseconds (default 20000) */
112
+ pingTimeout: number;
99
113
  /** Default retry config for subscription() method */
100
114
  protected subscriptionReconnect: RetryConfig;
101
115
  /** Default retry config for request() method */
package/dist/relay.js CHANGED
@@ -24,6 +24,11 @@ export var SyncDirection;
24
24
  /** An error that is thrown when a REQ is closed from the relay side */
25
25
  export class ReqCloseError extends Error {
26
26
  }
27
+ /** A dummy filter that will return empty results */
28
+ const PING_FILTER = {
29
+ ids: ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
30
+ limit: 0,
31
+ };
27
32
  export class Relay {
28
33
  url;
29
34
  log = logger.extend("Relay");
@@ -58,6 +63,8 @@ export class Relay {
58
63
  * @note Subscribing to this will not connect to the relay
59
64
  */
60
65
  notice$;
66
+ /** Timestamp of the last message received from the relay */
67
+ lastMessageReceivedAt = 0;
61
68
  /** An observable that emits the NIP-11 information document for the relay */
62
69
  information$;
63
70
  _nip11 = null;
@@ -103,6 +110,12 @@ export class Relay {
103
110
  publishTimeout = 30_000;
104
111
  /** How long to keep the connection alive after nothing is subscribed (default 30s) */
105
112
  keepAlive = 30_000;
113
+ /** Enable/disable ping functionality (default false) */
114
+ enablePing = false;
115
+ /** How often to send pings in milliseconds (default 29000) */
116
+ pingFrequency = 29_000;
117
+ /** How long to wait for EOSE response in milliseconds (default 20000) */
118
+ pingTimeout = 20_000;
106
119
  /** Default retry config for subscription() method */
107
120
  subscriptionReconnect;
108
121
  /** Default retry config for request() method */
@@ -142,6 +155,12 @@ export class Relay {
142
155
  this.publishTimeout = opts.publishTimeout;
143
156
  if (opts?.keepAlive !== undefined)
144
157
  this.keepAlive = opts.keepAlive;
158
+ if (opts?.enablePing !== undefined)
159
+ this.enablePing = opts.enablePing;
160
+ if (opts?.pingFrequency !== undefined)
161
+ this.pingFrequency = opts.pingFrequency;
162
+ if (opts?.pingTimeout !== undefined)
163
+ this.pingTimeout = opts.pingTimeout;
145
164
  // Set retry configs
146
165
  this.subscriptionReconnect = { ...DEFAULT_RETRY_CONFIG, ...(opts?.subscriptionRetry ?? {}) };
147
166
  this.requestReconnect = { ...DEFAULT_RETRY_CONFIG, ...(opts?.requestRetry ?? {}) };
@@ -228,7 +247,12 @@ export class Relay {
228
247
  this.challenge$.next(challenge);
229
248
  }));
230
249
  const allMessagesSubject = new Subject();
231
- const listenForAllMessages = this.socket.pipe(tap((message) => allMessagesSubject.next(message)));
250
+ const listenForAllMessages = this.socket.pipe(tap((message) => {
251
+ // Update the last message received at timestamp
252
+ this.lastMessageReceivedAt = Date.now();
253
+ // Pass to the message subject
254
+ allMessagesSubject.next(message);
255
+ }));
232
256
  // Create passive observables for messages and notices
233
257
  this.message$ = allMessagesSubject.asObservable();
234
258
  this.notice$ = this.message$.pipe(
@@ -236,12 +260,44 @@ export class Relay {
236
260
  filter((m) => Array.isArray(m) && m[0] === "NOTICE"),
237
261
  // pick the string out of the message
238
262
  map((m) => m[1]));
263
+ // Create ping health check observable
264
+ const pingHealthCheck = this.connected$.pipe(
265
+ // Switch based on connection state
266
+ switchMap((connected) => {
267
+ // Only run when connected and ping is enabled
268
+ if (!connected || !this.enablePing)
269
+ return NEVER;
270
+ // Start timer that emits periodically
271
+ return timer(this.pingFrequency, this.pingFrequency).pipe(
272
+ // For each ping, create a dummy REQ and wait for EOSE
273
+ mergeMap(() => {
274
+ // Skip ping if we have received a message in the last pingFrequency milliseconds
275
+ if (Date.now() - this.lastMessageReceivedAt < this.pingFrequency)
276
+ return NEVER;
277
+ // Send a ping request
278
+ this.send(["REQ", "ping:" + nanoid(), PING_FILTER]);
279
+ // Wait for the EOSE response
280
+ return this.message$.pipe(
281
+ // Complete after first message received
282
+ take(1),
283
+ // Add timeout to detect unresponsive connections
284
+ timeout({
285
+ first: this.pingTimeout,
286
+ with: () => {
287
+ console.warn(`Relay connection has become unresponsive: ${this.url}`);
288
+ return NEVER;
289
+ },
290
+ }));
291
+ }));
292
+ }),
293
+ // Catch errors to prevent breaking the watchTower
294
+ catchError(() => NEVER));
239
295
  // Merge all watchers
240
296
  this.watchTower = this.ready$.pipe(switchMap((ready) => {
241
297
  if (!ready)
242
298
  return NEVER;
243
299
  // Only start the watch tower if the relay is ready
244
- return merge(listenForAllMessages, listenForNotice, ListenForChallenge, this.information$).pipe(
300
+ return merge(listenForAllMessages, listenForNotice, ListenForChallenge, this.information$, pingHealthCheck).pipe(
245
301
  // Never emit any values
246
302
  ignoreElements(),
247
303
  // Start the reconnect timer if the connection has an error
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-relay",
3
- "version": "0.0.0-next-20251231062646",
3
+ "version": "0.0.0-next-20260116173453",
4
4
  "description": "nostr relay communication framework built on rxjs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -57,14 +57,14 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "@noble/hashes": "^1.7.1",
60
- "applesauce-core": "0.0.0-next-20251231062646",
60
+ "applesauce-core": "0.0.0-next-20260116173453",
61
61
  "nanoid": "^5.0.9",
62
62
  "nostr-tools": "~2.19",
63
63
  "rxjs": "^7.8.1"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@hirez_io/observer-spy": "^2.2.0",
67
- "applesauce-signers": "0.0.0-next-20251231062646",
67
+ "applesauce-signers": "^5.0.0",
68
68
  "rimraf": "^6.0.1",
69
69
  "typescript": "^5.7.3",
70
70
  "vitest": "^4.0.15",