mirror of
https://github.com/logos-messaging/logos-delivery-js.git
synced 2026-03-09 11:13:17 +00:00
* wip * feat: integrate sds-r with message channels * feat: add SDS-R events * fix: fixed buffer handling incoming and outgoing * fix: more buffer fixes * fix: remove some magic numbers * fix: buffer optimisation, backwards compatible senderId * fix: fix implementation guide, remove unrelated claude file * fix: further buffer optimisations * fix: linting errors * fix: suggestions from code review Co-authored-by: Sasha <118575614+weboko@users.noreply.github.com> Co-authored-by: fryorcraken <110212804+fryorcraken@users.noreply.github.com> * fix: remove implementation guide * fix: build errors, remove override, improve buffer * fix: consistent use of MessageId and ParticipantId * fix: switch to conditionally constructed from conditionally executed --------- Co-authored-by: fryorcraken <commits@fryorcraken.xyz> Co-authored-by: Sasha <118575614+weboko@users.noreply.github.com> Co-authored-by: fryorcraken <110212804+fryorcraken@users.noreply.github.com>
250 lines
6.7 KiB
TypeScript
250 lines
6.7 KiB
TypeScript
import { proto_sds_message } from "@waku/proto";
|
|
import { Logger } from "@waku/utils";
|
|
|
|
export type MessageId = string;
|
|
export type HistoryEntry = proto_sds_message.HistoryEntry;
|
|
export type ChannelId = string;
|
|
export type ParticipantId = string;
|
|
|
|
const log = new Logger("sds:message");
|
|
|
|
export class Message implements proto_sds_message.SdsMessage {
|
|
public constructor(
|
|
public messageId: MessageId,
|
|
public channelId: string,
|
|
public senderId: ParticipantId,
|
|
public causalHistory: proto_sds_message.HistoryEntry[],
|
|
public lamportTimestamp?: bigint | undefined,
|
|
public bloomFilter?: Uint8Array<ArrayBufferLike> | undefined,
|
|
public content?: Uint8Array<ArrayBufferLike> | undefined,
|
|
public repairRequest: proto_sds_message.HistoryEntry[] = [],
|
|
/**
|
|
* Not encoded, set after it is sent, used to include in follow-up messages
|
|
*/
|
|
public retrievalHint?: Uint8Array | undefined
|
|
) {}
|
|
|
|
public encode(): Uint8Array {
|
|
return proto_sds_message.SdsMessage.encode(this);
|
|
}
|
|
|
|
public static decode(
|
|
data: Uint8Array
|
|
): undefined | ContentMessage | SyncMessage | EphemeralMessage {
|
|
try {
|
|
const {
|
|
messageId,
|
|
channelId,
|
|
senderId,
|
|
causalHistory,
|
|
lamportTimestamp,
|
|
bloomFilter,
|
|
content,
|
|
repairRequest
|
|
} = proto_sds_message.SdsMessage.decode(data);
|
|
|
|
if (testContentMessage({ lamportTimestamp, content })) {
|
|
return new ContentMessage(
|
|
messageId,
|
|
channelId,
|
|
senderId,
|
|
causalHistory,
|
|
lamportTimestamp!,
|
|
bloomFilter,
|
|
content!,
|
|
repairRequest
|
|
);
|
|
}
|
|
|
|
if (testEphemeralMessage({ lamportTimestamp, content })) {
|
|
return new EphemeralMessage(
|
|
messageId,
|
|
channelId,
|
|
senderId,
|
|
causalHistory,
|
|
undefined,
|
|
bloomFilter,
|
|
content!,
|
|
repairRequest
|
|
);
|
|
}
|
|
|
|
if (testSyncMessage({ lamportTimestamp, content })) {
|
|
return new SyncMessage(
|
|
messageId,
|
|
channelId,
|
|
senderId,
|
|
causalHistory,
|
|
lamportTimestamp!,
|
|
bloomFilter,
|
|
undefined,
|
|
repairRequest
|
|
);
|
|
}
|
|
log.error(
|
|
"message received was of unknown type",
|
|
lamportTimestamp,
|
|
content
|
|
);
|
|
} catch (err) {
|
|
log.error("failed to decode sds message", err);
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
export class SyncMessage extends Message {
|
|
public constructor(
|
|
public messageId: MessageId,
|
|
public channelId: string,
|
|
public senderId: ParticipantId,
|
|
public causalHistory: proto_sds_message.HistoryEntry[],
|
|
public lamportTimestamp: bigint,
|
|
public bloomFilter: Uint8Array<ArrayBufferLike> | undefined,
|
|
public content: undefined,
|
|
public repairRequest: proto_sds_message.HistoryEntry[] = [],
|
|
/**
|
|
* Not encoded, set after it is sent, used to include in follow-up messages
|
|
*/
|
|
public retrievalHint?: Uint8Array | undefined
|
|
) {
|
|
super(
|
|
messageId,
|
|
channelId,
|
|
senderId,
|
|
causalHistory,
|
|
lamportTimestamp,
|
|
bloomFilter,
|
|
content,
|
|
repairRequest,
|
|
retrievalHint
|
|
);
|
|
}
|
|
}
|
|
|
|
function testSyncMessage(message: {
|
|
lamportTimestamp?: bigint;
|
|
content?: Uint8Array;
|
|
}): boolean {
|
|
return Boolean(
|
|
"lamportTimestamp" in message &&
|
|
typeof message.lamportTimestamp === "bigint" &&
|
|
(message.content === undefined || message.content.length === 0)
|
|
);
|
|
}
|
|
|
|
export function isSyncMessage(
|
|
message: Message | ContentMessage | SyncMessage | EphemeralMessage
|
|
): message is SyncMessage {
|
|
return testSyncMessage(message);
|
|
}
|
|
|
|
export class EphemeralMessage extends Message {
|
|
public constructor(
|
|
public messageId: MessageId,
|
|
public channelId: string,
|
|
public senderId: ParticipantId,
|
|
public causalHistory: proto_sds_message.HistoryEntry[],
|
|
public lamportTimestamp: undefined,
|
|
public bloomFilter: Uint8Array<ArrayBufferLike> | undefined,
|
|
public content: Uint8Array<ArrayBufferLike>,
|
|
public repairRequest: proto_sds_message.HistoryEntry[] = [],
|
|
/**
|
|
* Not encoded, set after it is sent, used to include in follow-up messages
|
|
*/
|
|
public retrievalHint?: Uint8Array | undefined
|
|
) {
|
|
if (!content || !content.length) {
|
|
throw Error("Ephemeral Message must have content");
|
|
}
|
|
super(
|
|
messageId,
|
|
channelId,
|
|
senderId,
|
|
causalHistory,
|
|
lamportTimestamp,
|
|
bloomFilter,
|
|
content,
|
|
repairRequest,
|
|
retrievalHint
|
|
);
|
|
}
|
|
}
|
|
|
|
export function isEphemeralMessage(
|
|
message: Message | ContentMessage | SyncMessage | EphemeralMessage
|
|
): message is EphemeralMessage {
|
|
return testEphemeralMessage(message);
|
|
}
|
|
|
|
function testEphemeralMessage(message: {
|
|
lamportTimestamp?: bigint;
|
|
content?: Uint8Array;
|
|
}): boolean {
|
|
return Boolean(
|
|
message.lamportTimestamp === undefined &&
|
|
"content" in message &&
|
|
message.content &&
|
|
message.content.length
|
|
);
|
|
}
|
|
|
|
export class ContentMessage extends Message {
|
|
public constructor(
|
|
public messageId: MessageId,
|
|
public channelId: string,
|
|
public senderId: ParticipantId,
|
|
public causalHistory: proto_sds_message.HistoryEntry[],
|
|
public lamportTimestamp: bigint,
|
|
public bloomFilter: Uint8Array<ArrayBufferLike> | undefined,
|
|
public content: Uint8Array<ArrayBufferLike>,
|
|
public repairRequest: proto_sds_message.HistoryEntry[] = [],
|
|
/**
|
|
* Not encoded, set after it is sent, used to include in follow-up messages
|
|
*/
|
|
public retrievalHint?: Uint8Array | undefined
|
|
) {
|
|
if (!content.length) {
|
|
throw Error("Content Message must have content");
|
|
}
|
|
super(
|
|
messageId,
|
|
channelId,
|
|
senderId,
|
|
causalHistory,
|
|
lamportTimestamp,
|
|
bloomFilter,
|
|
content,
|
|
repairRequest,
|
|
retrievalHint
|
|
);
|
|
}
|
|
|
|
// `valueOf` is used by comparison operands such as `<`
|
|
public valueOf(): string {
|
|
// Create a sortable string representation that matches the compare logic
|
|
// Pad lamportTimestamp to ensure proper lexicographic ordering
|
|
// Use 16 digits to handle up to Number.MAX_SAFE_INTEGER (9007199254740991)
|
|
const paddedTimestamp = this.lamportTimestamp.toString().padStart(16, "0");
|
|
return `${paddedTimestamp}_${this.messageId}`;
|
|
}
|
|
}
|
|
|
|
export function isContentMessage(
|
|
message: Message | ContentMessage
|
|
): message is ContentMessage {
|
|
return testContentMessage(message);
|
|
}
|
|
|
|
function testContentMessage(message: {
|
|
lamportTimestamp?: bigint;
|
|
content?: Uint8Array;
|
|
}): message is { lamportTimestamp: bigint; content: Uint8Array } {
|
|
return Boolean(
|
|
"lamportTimestamp" in message &&
|
|
typeof message.lamportTimestamp === "bigint" &&
|
|
message.content &&
|
|
message.content.length
|
|
);
|
|
}
|