@waku/sds 0.0.4-f911bf8.0 → 0.0.4
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/CHANGELOG.md +31 -0
- package/bundle/index.js +6427 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/{bloom.js → bloom_filter/bloom.js} +2 -2
- package/dist/bloom_filter/bloom.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/message_channel/command_queue.d.ts +29 -0
- package/dist/message_channel/command_queue.js +7 -0
- package/dist/message_channel/command_queue.js.map +1 -0
- package/dist/message_channel/events.d.ts +32 -0
- package/dist/message_channel/events.js +19 -0
- package/dist/message_channel/events.js.map +1 -0
- package/dist/message_channel/index.d.ts +3 -0
- package/dist/message_channel/index.js +4 -0
- package/dist/message_channel/index.js.map +1 -0
- package/dist/message_channel/message_channel.d.ts +142 -0
- package/dist/{sds.js → message_channel/message_channel.js} +265 -111
- package/dist/message_channel/message_channel.js.map +1 -0
- package/package.json +91 -1
- package/src/{bloom.ts → bloom_filter/bloom.ts} +2 -2
- package/src/index.ts +15 -1
- package/src/message_channel/command_queue.ts +33 -0
- package/src/message_channel/events.ts +41 -0
- package/src/message_channel/index.ts +3 -0
- package/src/{sds.ts → message_channel/message_channel.ts} +311 -134
- package/dist/bloom.js.map +0 -1
- package/dist/sds.d.ts +0 -108
- package/dist/sds.js.map +0 -1
- /package/dist/{bloom.d.ts → bloom_filter/bloom.d.ts} +0 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
import type { Message } from "./events.js";
|
2
|
+
|
3
|
+
export enum Command {
|
4
|
+
Send = "send",
|
5
|
+
Receive = "receive",
|
6
|
+
SendEphemeral = "sendEphemeral"
|
7
|
+
}
|
8
|
+
|
9
|
+
export interface ParamsByAction {
|
10
|
+
[Command.Send]: {
|
11
|
+
payload: Uint8Array;
|
12
|
+
callback?: (message: Message) => Promise<{
|
13
|
+
success: boolean;
|
14
|
+
retrievalHint?: Uint8Array;
|
15
|
+
}>;
|
16
|
+
};
|
17
|
+
[Command.Receive]: {
|
18
|
+
message: Message;
|
19
|
+
};
|
20
|
+
[Command.SendEphemeral]: {
|
21
|
+
payload: Uint8Array;
|
22
|
+
callback?: (message: Message) => Promise<boolean>;
|
23
|
+
};
|
24
|
+
}
|
25
|
+
|
26
|
+
export type Task<A extends Command = Command> = {
|
27
|
+
command: A;
|
28
|
+
params: ParamsByAction[A];
|
29
|
+
};
|
30
|
+
|
31
|
+
export type Handlers = {
|
32
|
+
[A in Command]: (params: ParamsByAction[A]) => Promise<void>;
|
33
|
+
};
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import { proto_sds_message } from "@waku/proto";
|
2
|
+
|
3
|
+
export enum MessageChannelEvent {
|
4
|
+
MessageSent = "messageSent",
|
5
|
+
MessageDelivered = "messageDelivered",
|
6
|
+
MessageReceived = "messageReceived",
|
7
|
+
MessageAcknowledged = "messageAcknowledged",
|
8
|
+
PartialAcknowledgement = "partialAcknowledgement",
|
9
|
+
MissedMessages = "missedMessages",
|
10
|
+
SyncSent = "syncSent",
|
11
|
+
SyncReceived = "syncReceived"
|
12
|
+
}
|
13
|
+
|
14
|
+
export type Message = proto_sds_message.SdsMessage;
|
15
|
+
export type HistoryEntry = proto_sds_message.HistoryEntry;
|
16
|
+
export type ChannelId = string;
|
17
|
+
|
18
|
+
export function encodeMessage(message: Message): Uint8Array {
|
19
|
+
return proto_sds_message.SdsMessage.encode(message);
|
20
|
+
}
|
21
|
+
|
22
|
+
export function decodeMessage(data: Uint8Array): Message {
|
23
|
+
return proto_sds_message.SdsMessage.decode(data);
|
24
|
+
}
|
25
|
+
|
26
|
+
export type MessageChannelEvents = {
|
27
|
+
[MessageChannelEvent.MessageSent]: CustomEvent<Message>;
|
28
|
+
[MessageChannelEvent.MessageDelivered]: CustomEvent<{
|
29
|
+
messageId: string;
|
30
|
+
sentOrReceived: "sent" | "received";
|
31
|
+
}>;
|
32
|
+
[MessageChannelEvent.MessageReceived]: CustomEvent<Message>;
|
33
|
+
[MessageChannelEvent.MessageAcknowledged]: CustomEvent<string>;
|
34
|
+
[MessageChannelEvent.PartialAcknowledgement]: CustomEvent<{
|
35
|
+
messageId: string;
|
36
|
+
count: number;
|
37
|
+
}>;
|
38
|
+
[MessageChannelEvent.MissedMessages]: CustomEvent<HistoryEntry[]>;
|
39
|
+
[MessageChannelEvent.SyncSent]: CustomEvent<Message>;
|
40
|
+
[MessageChannelEvent.SyncReceived]: CustomEvent<Message>;
|
41
|
+
};
|
@@ -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 {
|
4
|
+
import { Logger } from "@waku/utils";
|
5
5
|
|
6
|
-
import { DefaultBloomFilter } from "
|
6
|
+
import { DefaultBloomFilter } from "../bloom_filter/bloom.js";
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
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
|
-
*
|
93
|
+
* Processes all queued tasks sequentially to ensure proper message ordering.
|
78
94
|
*
|
79
|
-
*
|
80
|
-
*
|
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
|
-
*
|
83
|
-
*
|
84
|
-
*
|
85
|
-
* light push or relay.
|
98
|
+
* @example
|
99
|
+
* ```typescript
|
100
|
+
* const channel = new MessageChannel("my-channel");
|
86
101
|
*
|
87
|
-
*
|
102
|
+
* // Queue some operations
|
103
|
+
* await channel.sendMessage(payload, callback);
|
104
|
+
* channel.receiveMessage(incomingMessage);
|
88
105
|
*
|
89
|
-
*
|
90
|
-
*
|
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.
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
-
*
|
190
|
+
* Queues a received message for processing.
|
163
191
|
*
|
164
|
-
*
|
165
|
-
*
|
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
|
-
*
|
195
|
+
* @param message - The message to receive and process
|
172
196
|
*
|
173
|
-
* @
|
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
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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
|
-
|
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
|
-
|
236
|
-
|
257
|
+
missingDependencies.forEach((dependency) => {
|
258
|
+
missing.add(dependency);
|
259
|
+
});
|
237
260
|
return {
|
238
261
|
buffer: buffer.concat(message),
|
239
|
-
missing
|
262
|
+
missing
|
240
263
|
};
|
241
264
|
},
|
242
|
-
{ buffer: new Array<Message>(), missing: new
|
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
|
-
|
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
|
-
|
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 }) =>
|
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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"}
|