@waku/sds 0.0.4-f911bf8.0 → 0.0.5-2ed5ddc.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.
@@ -1,20 +1,18 @@
1
1
  import { TypedEventEmitter } from "@libp2p/interface";
2
2
  import { sha256 } from "@noble/hashes/sha256";
3
3
  import { bytesToHex } from "@noble/hashes/utils";
4
- import { proto_sds_message } from "@waku/proto";
4
+ import { Logger } from "@waku/utils";
5
5
 
6
- import { DefaultBloomFilter } from "./bloom.js";
6
+ import { DefaultBloomFilter } from "../bloom_filter/bloom.js";
7
7
 
8
- export enum MessageChannelEvent {
9
- MessageDelivered = "messageDelivered"
10
- }
11
- type MessageChannelEvents = {
12
- [MessageChannelEvent.MessageDelivered]: CustomEvent<string>;
13
- };
14
-
15
- export type Message = proto_sds_message.SdsMessage;
16
- export type HistoryEntry = proto_sds_message.HistoryEntry;
17
- export type ChannelId = string;
8
+ import { Command, Handlers, ParamsByAction, Task } from "./command_queue.js";
9
+ import {
10
+ ChannelId,
11
+ HistoryEntry,
12
+ Message,
13
+ MessageChannelEvent,
14
+ MessageChannelEvents
15
+ } from "./events.js";
18
16
 
19
17
  export const DEFAULT_BLOOM_FILTER_OPTIONS = {
20
18
  capacity: 10000,
@@ -24,27 +22,46 @@ export const DEFAULT_BLOOM_FILTER_OPTIONS = {
24
22
  const DEFAULT_CAUSAL_HISTORY_SIZE = 2;
25
23
  const DEFAULT_RECEIVED_MESSAGE_TIMEOUT = 1000 * 60 * 5; // 5 minutes
26
24
 
25
+ const log = new Logger("sds:message-channel");
26
+
27
27
  interface MessageChannelOptions {
28
28
  causalHistorySize?: number;
29
29
  receivedMessageTimeoutEnabled?: boolean;
30
30
  receivedMessageTimeout?: number;
31
- deliveredMessageCallback?: (messageId: string) => void;
32
31
  }
33
32
 
34
33
  export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
34
+ public readonly channelId: ChannelId;
35
35
  private lamportTimestamp: number;
36
36
  private filter: DefaultBloomFilter;
37
37
  private outgoingBuffer: Message[];
38
38
  private acknowledgements: Map<string, number>;
39
39
  private incomingBuffer: Message[];
40
40
  private localHistory: { timestamp: number; historyEntry: HistoryEntry }[];
41
- private channelId: ChannelId;
42
41
  private causalHistorySize: number;
43
42
  private acknowledgementCount: number;
44
43
  private timeReceived: Map<string, number>;
45
44
  private receivedMessageTimeoutEnabled: boolean;
46
45
  private receivedMessageTimeout: number;
47
- private deliveredMessageCallback?: (messageId: string) => void;
46
+
47
+ private tasks: Task[] = [];
48
+ private handlers: Handlers = {
49
+ [Command.Send]: async (
50
+ params: ParamsByAction[Command.Send]
51
+ ): Promise<void> => {
52
+ await this._sendMessage(params.payload, params.callback);
53
+ },
54
+ [Command.Receive]: async (
55
+ params: ParamsByAction[Command.Receive]
56
+ ): Promise<void> => {
57
+ this._receiveMessage(params.message);
58
+ },
59
+ [Command.SendEphemeral]: async (
60
+ params: ParamsByAction[Command.SendEphemeral]
61
+ ): Promise<void> => {
62
+ await this._sendEphemeralMessage(params.payload, params.callback);
63
+ }
64
+ };
48
65
 
49
66
  public constructor(
50
67
  channelId: ChannelId,
@@ -66,7 +83,6 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
66
83
  options.receivedMessageTimeoutEnabled ?? false;
67
84
  this.receivedMessageTimeout =
68
85
  options.receivedMessageTimeout ?? DEFAULT_RECEIVED_MESSAGE_TIMEOUT;
69
- this.deliveredMessageCallback = options.deliveredMessageCallback;
70
86
  }
71
87
 
72
88
  public static getMessageId(payload: Uint8Array): string {
@@ -74,20 +90,59 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
74
90
  }
75
91
 
76
92
  /**
77
- * Send a message to the SDS channel.
93
+ * Processes all queued tasks sequentially to ensure proper message ordering.
78
94
  *
79
- * Increments the lamport timestamp, constructs a `Message` object
80
- * with the given payload, and adds it to the outgoing buffer.
95
+ * This method should be called periodically by the library consumer to execute
96
+ * queued send/receive operations in the correct sequence.
81
97
  *
82
- * If the callback is successful, the message is also added to
83
- * the bloom filter and message history. In the context of
84
- * Waku, this likely means the message was published via
85
- * light push or relay.
98
+ * @example
99
+ * ```typescript
100
+ * const channel = new MessageChannel("my-channel");
86
101
  *
87
- * See https://rfc.vac.dev/vac/raw/sds/#send-message
102
+ * // Queue some operations
103
+ * await channel.sendMessage(payload, callback);
104
+ * channel.receiveMessage(incomingMessage);
88
105
  *
89
- * @param payload - The payload to send.
90
- * @param callback - A callback function that returns a boolean indicating whether the message was sent successfully.
106
+ * // Process all queued operations
107
+ * await channel.processTasks();
108
+ * ```
109
+ *
110
+ * @throws Will emit a 'taskError' event if any task fails, but continues processing remaining tasks
111
+ */
112
+ public async processTasks(): Promise<void> {
113
+ while (this.tasks.length > 0) {
114
+ const item = this.tasks.shift();
115
+ if (!item) {
116
+ continue;
117
+ }
118
+
119
+ await this.executeTask(item);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Queues a message to be sent on this channel.
125
+ *
126
+ * The message will be processed sequentially when processTasks() is called.
127
+ * This ensures proper lamport timestamp ordering and causal history tracking.
128
+ *
129
+ * @param payload - The message content as a byte array
130
+ * @param callback - Optional callback function called after the message is processed
131
+ * @returns Promise that resolves when the message is queued (not sent)
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * const channel = new MessageChannel("chat-room");
136
+ * const message = new TextEncoder().encode("Hello, world!");
137
+ *
138
+ * await channel.sendMessage(message, async (processedMessage) => {
139
+ * console.log("Message processed:", processedMessage.messageId);
140
+ * return { success: true };
141
+ * });
142
+ *
143
+ * // Actually send the message
144
+ * await channel.processTasks();
145
+ * ```
91
146
  */
92
147
  public async sendMessage(
93
148
  payload: Uint8Array,
@@ -96,36 +151,13 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
96
151
  retrievalHint?: Uint8Array;
97
152
  }>
98
153
  ): Promise<void> {
99
- this.lamportTimestamp++;
100
-
101
- const messageId = MessageChannel.getMessageId(payload);
102
-
103
- const message: Message = {
104
- messageId,
105
- channelId: this.channelId,
106
- lamportTimestamp: this.lamportTimestamp,
107
- causalHistory: this.localHistory
108
- .slice(-this.causalHistorySize)
109
- .map(({ historyEntry }) => historyEntry),
110
- bloomFilter: this.filter.toBytes(),
111
- content: payload
112
- };
113
-
114
- this.outgoingBuffer.push(message);
115
-
116
- if (callback) {
117
- const { success, retrievalHint } = await callback(message);
118
- if (success) {
119
- this.filter.insert(messageId);
120
- this.localHistory.push({
121
- timestamp: this.lamportTimestamp,
122
- historyEntry: {
123
- messageId,
124
- retrievalHint
125
- }
126
- });
154
+ this.tasks.push({
155
+ command: Command.Send,
156
+ params: {
157
+ payload,
158
+ callback
127
159
  }
128
- }
160
+ });
129
161
  }
130
162
 
131
163
  /**
@@ -141,72 +173,58 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
141
173
  * @param payload - The payload to send.
142
174
  * @param callback - A callback function that returns a boolean indicating whether the message was sent successfully.
143
175
  */
144
- public sendEphemeralMessage(
176
+ public async sendEphemeralMessage(
145
177
  payload: Uint8Array,
146
- callback?: (message: Message) => boolean
147
- ): void {
148
- const message: Message = {
149
- messageId: MessageChannel.getMessageId(payload),
150
- channelId: this.channelId,
151
- content: payload,
152
- lamportTimestamp: undefined,
153
- causalHistory: [],
154
- bloomFilter: undefined
155
- };
156
-
157
- if (callback) {
158
- callback(message);
159
- }
178
+ callback?: (message: Message) => Promise<boolean>
179
+ ): Promise<void> {
180
+ this.tasks.push({
181
+ command: Command.SendEphemeral,
182
+ params: {
183
+ payload,
184
+ callback
185
+ }
186
+ });
160
187
  }
188
+
161
189
  /**
162
- * Process a received SDS message for this channel.
190
+ * Queues a received message for processing.
163
191
  *
164
- * Review the acknowledgement status of messages in the outgoing buffer
165
- * by inspecting the received message's bloom filter and causal history.
166
- * Add the received message to the bloom filter.
167
- * If the local history contains every message in the received message's
168
- * causal history, deliver the message. Otherwise, add the message to the
169
- * incoming buffer.
192
+ * The message will be processed when processTasks() is called, ensuring
193
+ * proper dependency resolution and causal ordering.
170
194
  *
171
- * See https://rfc.vac.dev/vac/raw/sds/#receive-message
195
+ * @param message - The message to receive and process
172
196
  *
173
- * @param message - The received SDS message.
197
+ * @example
198
+ * ```typescript
199
+ * const channel = new MessageChannel("chat-room");
200
+ *
201
+ * // Receive a message from the network
202
+ * channel.receiveMessage(incomingMessage);
203
+ *
204
+ * // Process the received message
205
+ * await channel.processTasks();
206
+ * ```
174
207
  */
175
208
  public receiveMessage(message: Message): void {
176
- if (!message.lamportTimestamp) {
177
- // Messages with no timestamp are ephemeral messages and should be delivered immediately
178
- this.deliverMessage(message);
179
- return;
180
- }
181
- // review ack status
182
- this.reviewAckStatus(message);
183
- // add to bloom filter (skip for messages with empty content)
184
- if (message.content?.length && message.content.length > 0) {
185
- this.filter.insert(message.messageId);
186
- }
187
- // verify causal history
188
- const dependenciesMet = message.causalHistory.every((historyEntry) =>
189
- this.localHistory.some(
190
- ({ historyEntry: { messageId } }) =>
191
- messageId === historyEntry.messageId
192
- )
193
- );
194
- if (!dependenciesMet) {
195
- this.incomingBuffer.push(message);
196
- this.timeReceived.set(message.messageId, Date.now());
197
- } else {
198
- this.deliverMessage(message);
199
- }
209
+ this.tasks.push({
210
+ command: Command.Receive,
211
+ params: {
212
+ message
213
+ }
214
+ });
200
215
  }
201
216
 
202
- // https://rfc.vac.dev/vac/raw/sds/#periodic-incoming-buffer-sweep
217
+ /**
218
+ * Processes messages in the incoming buffer, delivering those with satisfied dependencies.
219
+ *
220
+ * @returns Array of history entries for messages still missing dependencies
221
+ */
203
222
  public sweepIncomingBuffer(): HistoryEntry[] {
204
223
  const { buffer, missing } = this.incomingBuffer.reduce<{
205
224
  buffer: Message[];
206
- missing: HistoryEntry[];
225
+ missing: Set<HistoryEntry>;
207
226
  }>(
208
227
  ({ buffer, missing }, message) => {
209
- // Check each message for missing dependencies
210
228
  const missingDependencies = message.causalHistory.filter(
211
229
  (messageHistoryEntry) =>
212
230
  !this.localHistory.some(
@@ -215,9 +233,13 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
215
233
  )
216
234
  );
217
235
  if (missingDependencies.length === 0) {
218
- // Any message with no missing dependencies is delivered
219
- // and removed from the buffer (implicitly by not adding it to the new incoming buffer)
220
236
  this.deliverMessage(message);
237
+ this.safeSendEvent(MessageChannelEvent.MessageDelivered, {
238
+ detail: {
239
+ messageId: message.messageId,
240
+ sentOrReceived: "received"
241
+ }
242
+ });
221
243
  return { buffer, missing };
222
244
  }
223
245
 
@@ -232,18 +254,23 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
232
254
  return { buffer, missing };
233
255
  }
234
256
  }
235
- // Any message with missing dependencies stays in the buffer
236
- // and the missing message IDs are returned for processing.
257
+ missingDependencies.forEach((dependency) => {
258
+ missing.add(dependency);
259
+ });
237
260
  return {
238
261
  buffer: buffer.concat(message),
239
- missing: missing.concat(missingDependencies)
262
+ missing
240
263
  };
241
264
  },
242
- { buffer: new Array<Message>(), missing: new Array<HistoryEntry>() }
265
+ { buffer: new Array<Message>(), missing: new Set<HistoryEntry>() }
243
266
  );
244
- // Update the incoming buffer to only include messages with no missing dependencies
245
267
  this.incomingBuffer = buffer;
246
- return missing;
268
+
269
+ this.safeSendEvent(MessageChannelEvent.MissedMessages, {
270
+ detail: Array.from(missing)
271
+ });
272
+
273
+ return Array.from(missing);
247
274
  }
248
275
 
249
276
  // https://rfc.vac.dev/vac/raw/sds/#periodic-outgoing-buffer-sweep
@@ -251,7 +278,6 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
251
278
  unacknowledged: Message[];
252
279
  possiblyAcknowledged: Message[];
253
280
  } {
254
- // Partition all messages in the outgoing buffer into unacknowledged and possibly acknowledged messages
255
281
  return this.outgoingBuffer.reduce<{
256
282
  unacknowledged: Message[];
257
283
  possiblyAcknowledged: Message[];
@@ -285,7 +311,7 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
285
311
  *
286
312
  * @param callback - A callback function that returns a boolean indicating whether the message was sent successfully.
287
313
  */
288
- public sendSyncMessage(
314
+ public async sendSyncMessage(
289
315
  callback?: (message: Message) => Promise<boolean>
290
316
  ): Promise<boolean> {
291
317
  this.lamportTimestamp++;
@@ -304,15 +330,165 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
304
330
  };
305
331
 
306
332
  if (callback) {
307
- return callback(message);
333
+ try {
334
+ await callback(message);
335
+ this.safeSendEvent(MessageChannelEvent.SyncSent, {
336
+ detail: message
337
+ });
338
+ return true;
339
+ } catch (error) {
340
+ log.error("Callback execution failed in sendSyncMessage:", error);
341
+ throw error;
342
+ }
343
+ }
344
+ return false;
345
+ }
346
+
347
+ private _receiveMessage(message: Message): void {
348
+ const isDuplicate =
349
+ message.content &&
350
+ message.content.length > 0 &&
351
+ this.timeReceived.has(message.messageId);
352
+
353
+ if (isDuplicate) {
354
+ return;
355
+ }
356
+
357
+ if (!message.lamportTimestamp) {
358
+ this.deliverMessage(message);
359
+ return;
360
+ }
361
+ if (message.content?.length === 0) {
362
+ this.safeSendEvent(MessageChannelEvent.SyncReceived, {
363
+ detail: message
364
+ });
365
+ } else {
366
+ this.safeSendEvent(MessageChannelEvent.MessageReceived, {
367
+ detail: message
368
+ });
369
+ }
370
+ this.reviewAckStatus(message);
371
+ if (message.content?.length && message.content.length > 0) {
372
+ this.filter.insert(message.messageId);
373
+ }
374
+ const dependenciesMet = message.causalHistory.every((historyEntry) =>
375
+ this.localHistory.some(
376
+ ({ historyEntry: { messageId } }) =>
377
+ messageId === historyEntry.messageId
378
+ )
379
+ );
380
+ if (!dependenciesMet) {
381
+ this.incomingBuffer.push(message);
382
+ this.timeReceived.set(message.messageId, Date.now());
383
+ } else {
384
+ this.deliverMessage(message);
385
+ this.safeSendEvent(MessageChannelEvent.MessageDelivered, {
386
+ detail: {
387
+ messageId: message.messageId,
388
+ sentOrReceived: "received"
389
+ }
390
+ });
391
+ }
392
+ }
393
+
394
+ private async executeTask<A extends Command>(item: Task<A>): Promise<void> {
395
+ try {
396
+ const handler = this.handlers[item.command];
397
+ await handler(item.params as ParamsByAction[A]);
398
+ } catch (error) {
399
+ log.error(`Task execution failed for command ${item.command}:`, error);
400
+ this.dispatchEvent(
401
+ new CustomEvent("taskError", {
402
+ detail: { command: item.command, error, params: item.params }
403
+ })
404
+ );
405
+ }
406
+ }
407
+
408
+ private safeSendEvent<T extends MessageChannelEvent>(
409
+ event: T,
410
+ eventInit?: CustomEventInit
411
+ ): void {
412
+ try {
413
+ this.dispatchEvent(new CustomEvent(event, eventInit));
414
+ } catch (error) {
415
+ log.error(`Failed to dispatch event ${event}:`, error);
416
+ }
417
+ }
418
+
419
+ private async _sendMessage(
420
+ payload: Uint8Array,
421
+ callback?: (message: Message) => Promise<{
422
+ success: boolean;
423
+ retrievalHint?: Uint8Array;
424
+ }>
425
+ ): Promise<void> {
426
+ this.lamportTimestamp++;
427
+
428
+ const messageId = MessageChannel.getMessageId(payload);
429
+
430
+ const message: Message = {
431
+ messageId,
432
+ channelId: this.channelId,
433
+ lamportTimestamp: this.lamportTimestamp,
434
+ causalHistory: this.localHistory
435
+ .slice(-this.causalHistorySize)
436
+ .map(({ historyEntry }) => historyEntry),
437
+ bloomFilter: this.filter.toBytes(),
438
+ content: payload
439
+ };
440
+
441
+ this.outgoingBuffer.push(message);
442
+
443
+ if (callback) {
444
+ try {
445
+ const { success, retrievalHint } = await callback(message);
446
+ if (success) {
447
+ this.filter.insert(messageId);
448
+ this.localHistory.push({
449
+ timestamp: this.lamportTimestamp,
450
+ historyEntry: {
451
+ messageId,
452
+ retrievalHint
453
+ }
454
+ });
455
+ this.timeReceived.set(messageId, Date.now());
456
+ this.safeSendEvent(MessageChannelEvent.MessageSent, {
457
+ detail: message
458
+ });
459
+ }
460
+ } catch (error) {
461
+ log.error("Callback execution failed in _sendMessage:", error);
462
+ throw error;
463
+ }
464
+ }
465
+ }
466
+
467
+ private async _sendEphemeralMessage(
468
+ payload: Uint8Array,
469
+ callback?: (message: Message) => Promise<boolean>
470
+ ): Promise<void> {
471
+ const message: Message = {
472
+ messageId: MessageChannel.getMessageId(payload),
473
+ channelId: this.channelId,
474
+ content: payload,
475
+ lamportTimestamp: undefined,
476
+ causalHistory: [],
477
+ bloomFilter: undefined
478
+ };
479
+
480
+ if (callback) {
481
+ try {
482
+ await callback(message);
483
+ } catch (error) {
484
+ log.error("Callback execution failed in _sendEphemeralMessage:", error);
485
+ throw error;
486
+ }
308
487
  }
309
- return Promise.resolve(false);
310
488
  }
311
489
 
312
490
  // See https://rfc.vac.dev/vac/raw/sds/#deliver-message
313
491
  private deliverMessage(message: Message, retrievalHint?: Uint8Array): void {
314
- this.notifyDeliveredMessage(message.messageId);
315
-
316
492
  const messageLamportTimestamp = message.lamportTimestamp ?? 0;
317
493
  if (messageLamportTimestamp > this.lamportTimestamp) {
318
494
  this.lamportTimestamp = messageLamportTimestamp;
@@ -352,17 +528,23 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
352
528
  // to determine the acknowledgement status of messages in the outgoing buffer.
353
529
  // See https://rfc.vac.dev/vac/raw/sds/#review-ack-status
354
530
  private reviewAckStatus(receivedMessage: Message): void {
355
- // the participant MUST mark all messages in the received causal_history as acknowledged.
356
531
  receivedMessage.causalHistory.forEach(({ messageId }) => {
357
532
  this.outgoingBuffer = this.outgoingBuffer.filter(
358
- ({ messageId: outgoingMessageId }) => outgoingMessageId !== messageId
533
+ ({ messageId: outgoingMessageId }) => {
534
+ if (outgoingMessageId !== messageId) {
535
+ return true;
536
+ }
537
+ this.safeSendEvent(MessageChannelEvent.MessageAcknowledged, {
538
+ detail: messageId
539
+ });
540
+ return false;
541
+ }
359
542
  );
360
543
  this.acknowledgements.delete(messageId);
361
544
  if (!this.filter.lookup(messageId)) {
362
545
  this.filter.insert(messageId);
363
546
  }
364
547
  });
365
- // the participant MUST mark all messages included in the bloom_filter as possibly acknowledged
366
548
  if (!receivedMessage.bloomFilter) {
367
549
  return;
368
550
  }
@@ -380,6 +562,12 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
380
562
  const count = (this.acknowledgements.get(message.messageId) ?? 0) + 1;
381
563
  if (count < this.acknowledgementCount) {
382
564
  this.acknowledgements.set(message.messageId, count);
565
+ this.safeSendEvent(MessageChannelEvent.PartialAcknowledgement, {
566
+ detail: {
567
+ messageId: message.messageId,
568
+ count
569
+ }
570
+ });
383
571
  return true;
384
572
  }
385
573
  this.acknowledgements.delete(message.messageId);
@@ -391,15 +579,4 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
391
579
  private getAcknowledgementCount(): number {
392
580
  return 2;
393
581
  }
394
-
395
- private notifyDeliveredMessage(messageId: string): void {
396
- if (this.deliveredMessageCallback) {
397
- this.deliveredMessageCallback(messageId);
398
- }
399
- this.dispatchEvent(
400
- new CustomEvent(MessageChannelEvent.MessageDelivered, {
401
- detail: messageId
402
- })
403
- );
404
- }
405
582
  }
package/dist/bloom.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"bloom.js","sourceRoot":"","sources":["../src/bloom.ts"],"names":[],"mappings":"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"}