From 2f3ac73e5e5ab3fca40c8699e1c7c2048eb58964 Mon Sep 17 00:00:00 2001 From: Felicio Mununga Date: Thu, 30 Jun 2022 15:11:13 +0200 Subject: [PATCH] Set clocks (#284) * add clock functions * remove bind --- packages/status-js/src/client/chat.ts | 21 ++++++++--- .../src/client/community/community.ts | 11 +++++- .../client/community/handle-waku-message.ts | 37 +++++++++++++++++++ .../src/proto/communities/v1/communities.ts | 1 + .../status-js/src/utils/get-next-clock.ts | 6 +++ .../status-js/src/utils/is-clock-valid.ts | 16 ++++++++ 6 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 packages/status-js/src/utils/get-next-clock.ts create mode 100644 packages/status-js/src/utils/is-clock-valid.ts diff --git a/packages/status-js/src/client/chat.ts b/packages/status-js/src/client/chat.ts index edb0a800..36fc3971 100644 --- a/packages/status-js/src/client/chat.ts +++ b/packages/status-js/src/client/chat.ts @@ -10,6 +10,7 @@ import { } from '../protos/chat-message' import { EmojiReaction } from '../protos/emoji-reaction' import { generateKeyFromPassword } from '../utils/generate-key-from-password' +import { getNextClock } from '../utils/get-next-clock' import { idToContentTopic } from '../utils/id-to-content-topic' import { getReactions } from './community/get-reactions' @@ -36,6 +37,7 @@ type FetchedMessage = { messageId: string; timestamp?: Date } export class Chat { private readonly client: Client + #clock: bigint public readonly uuid: string public readonly id: string @@ -72,6 +74,7 @@ export class Chat { this.symmetricKey = options.symmetricKey this.description = options.description + this.#clock = BigInt(Date.now()) this.chatCallbacks = new Set() this.#messages = new Map() this.#editTextEvents = new Map() @@ -243,7 +246,7 @@ export class Chat { this.emitChange(description) } - public handleNewMessage = (newMessage: ChatMessage, timestamp?: Date) => { + public handleNewMessage = (newMessage: ChatMessage, timestamp: Date) => { // fetching in progress if (this.#fetchingMessages) { this.#oldestFetchedMessage = this.getOldestFetchedMessage( @@ -432,7 +435,7 @@ export class Chat { // TODO: protos does not support optional fields :-( const payload = ChatMessageProto.encode({ - clock: BigInt(Date.now()), + clock: this.setClock(this.#clock), timestamp: BigInt(Date.now()), text, responseTo: responseTo ?? '', @@ -465,7 +468,7 @@ export class Chat { public sendImageMessage = async (image: ImageMessage) => { const payload = ChatMessageProto.encode({ - clock: BigInt(Date.now()), + clock: this.setClock(this.#clock), timestamp: BigInt(Date.now()), text: '', responseTo: '', @@ -516,7 +519,7 @@ export class Chat { } const payload = EditMessage.encode({ - clock: BigInt(Date.now()), + clock: this.setClock(this.#clock), text, messageId, chatId: this.id, @@ -550,7 +553,7 @@ export class Chat { } const payload = DeleteMessage.encode({ - clock: BigInt(Date.now()), + clock: this.setClock(this.#clock), messageId, chatId: this.id, grant: new Uint8Array([]), @@ -584,7 +587,7 @@ export class Chat { ) const payload = EmojiReaction.encode({ - clock: BigInt(Date.now()), + clock: this.setClock(this.#clock), chatId: this.id, messageType: 'COMMUNITY_CHAT' as MessageType, messageId, @@ -636,4 +639,10 @@ export class Chat { return message } + + public setClock = (currentClock?: bigint): bigint => { + this.#clock = getNextClock(currentClock) + + return this.#clock + } } diff --git a/packages/status-js/src/client/community/community.ts b/packages/status-js/src/client/community/community.ts index 0cccdfcc..40c5426e 100644 --- a/packages/status-js/src/client/community/community.ts +++ b/packages/status-js/src/client/community/community.ts @@ -7,6 +7,7 @@ import { CommunityRequestToJoin } from '../../protos/communities' import { MessageType } from '../../protos/enums' import { compressPublicKey } from '../../utils/compress-public-key' import { generateKeyFromPassword } from '../../utils/generate-key-from-password' +import { getNextClock } from '../../utils/get-next-clock' import { idToContentTopic } from '../../utils/id-to-content-topic' import { Chat } from '../chat' import { Member } from '../member' @@ -19,6 +20,7 @@ import type { Client } from '../client' export class Community { private client: Client + #clock: bigint /** Compressed. */ public publicKey: string @@ -36,6 +38,7 @@ export class Community { this.publicKey = publicKey this.id = publicKey.replace(/^0[xX]/, '') + this.#clock = BigInt(Date.now()) this.chats = new Map() this.#members = new Map() this.#callbacks = new Set() @@ -252,7 +255,7 @@ export class Community { public requestToJoin = async (chatId = '') => { const payload = CommunityRequestToJoin.encode({ - clock: BigInt(Date.now()), + clock: this.setClock(this.#clock), chatId, communityId: hexToBytes(this.id), ensName: '', @@ -276,4 +279,10 @@ export class Community { public isMember = (signerPublicKey: string): boolean => { return this.getMember(signerPublicKey) !== undefined } + + public setClock = (currentClock?: bigint): bigint => { + this.#clock = getNextClock(currentClock) + + return this.#clock + } } diff --git a/packages/status-js/src/client/community/handle-waku-message.ts b/packages/status-js/src/client/community/handle-waku-message.ts index 9068413c..37562ab7 100644 --- a/packages/status-js/src/client/community/handle-waku-message.ts +++ b/packages/status-js/src/client/community/handle-waku-message.ts @@ -11,6 +11,7 @@ import { import { EmojiReaction } from '../../protos/emoji-reaction' import { PinMessage } from '../../protos/pin-message' import { ProtocolMessage } from '../../protos/protocol-message' +import { isClockValid } from '../../utils/is-clock-valid' import { payloadToId } from '../../utils/payload-to-id' import { recoverPublicKey } from '../../utils/recover-public-key' import { getChatUuid } from './get-chat-uuid' @@ -27,6 +28,7 @@ export function handleWakuMessage( community: Community ): void { // decode (layers) + // validate if (!wakuMessage.payload) { return } @@ -35,6 +37,10 @@ export function handleWakuMessage( return } + if (!wakuMessage.timestamp) { + return + } + let messageToDecode = wakuMessage.payload let decodedProtocol try { @@ -78,12 +84,18 @@ export function handleWakuMessage( // decode const decodedPayload = CommunityDescription.decode(messageToDecode) + // validate + if (!isClockValid(BigInt(decodedPayload.clock), messageTimestamp)) { + return + } + if (!community.isOwner(signerPublicKey)) { return } // handle (state and callback) community.handleDescription(decodedPayload) + community.setClock(BigInt(decodedPayload.clock)) break } @@ -92,6 +104,10 @@ export function handleWakuMessage( // decode const decodedPayload = ChatMessage.decode(messageToDecode) + if (!isClockValid(BigInt(decodedPayload.clock), messageTimestamp)) { + return + } + switch (decodedPayload.messageType) { case MessageType.COMMUNITY_CHAT: { if (!community.isMember(signerPublicKey)) { @@ -114,6 +130,7 @@ export function handleWakuMessage( // handle chat.handleNewMessage(chatMessage, messageTimestamp) + chat.setClock(decodedPayload.clock) break } @@ -129,6 +146,10 @@ export function handleWakuMessage( case ApplicationMetadataMessage.Type.TYPE_EDIT_MESSAGE: { const decodedPayload = EditMessage.decode(messageToDecode) + if (!isClockValid(BigInt(decodedPayload.clock), messageTimestamp)) { + return + } + switch (decodedPayload.messageType) { case MessageType.COMMUNITY_CHAT: { if (!community.isMember(signerPublicKey)) { @@ -149,6 +170,7 @@ export function handleWakuMessage( decodedPayload.clock, signerPublicKey ) + chat.setClock(decodedPayload.clock) break } @@ -164,6 +186,10 @@ export function handleWakuMessage( case ApplicationMetadataMessage.Type.TYPE_DELETE_MESSAGE: { const decodedPayload = DeleteMessage.decode(messageToDecode) + if (!isClockValid(BigInt(decodedPayload.clock), messageTimestamp)) { + return + } + switch (decodedPayload.messageType) { case MessageType.COMMUNITY_CHAT: { if (!community.isMember(signerPublicKey)) { @@ -183,6 +209,7 @@ export function handleWakuMessage( decodedPayload.clock, signerPublicKey ) + chat.setClock(decodedPayload.clock) break } @@ -198,6 +225,10 @@ export function handleWakuMessage( case ApplicationMetadataMessage.Type.TYPE_PIN_MESSAGE: { const decodedPayload = PinMessage.decode(messageToDecode) + if (!isClockValid(BigInt(decodedPayload.clock), messageTimestamp)) { + return + } + switch (decodedPayload.messageType) { case MessageType.COMMUNITY_CHAT: { if (!community.isMember(signerPublicKey)) { @@ -217,6 +248,7 @@ export function handleWakuMessage( decodedPayload.clock, decodedPayload.pinned ) + chat.setClock(decodedPayload.clock) break } @@ -232,6 +264,10 @@ export function handleWakuMessage( case ApplicationMetadataMessage.Type.TYPE_EMOJI_REACTION: { const decodedPayload = EmojiReaction.decode(messageToDecode) + if (!isClockValid(BigInt(decodedPayload.clock), messageTimestamp)) { + return + } + switch (decodedPayload.messageType) { case MessageType.COMMUNITY_CHAT: { if (!community.isMember(signerPublicKey)) { @@ -252,6 +288,7 @@ export function handleWakuMessage( decodedPayload.clock, signerPublicKey ) + chat.setClock(decodedPayload.clock) break } diff --git a/packages/status-js/src/proto/communities/v1/communities.ts b/packages/status-js/src/proto/communities/v1/communities.ts index bcb8f5eb..6f29c9ad 100644 --- a/packages/status-js/src/proto/communities/v1/communities.ts +++ b/packages/status-js/src/proto/communities/v1/communities.ts @@ -114,6 +114,7 @@ export function communityPermissions_AccessToJSON( } export interface CommunityDescription { + // fixme?: bigint clock: number members: { [key: string]: CommunityMember } permissions: CommunityPermissions | undefined diff --git a/packages/status-js/src/utils/get-next-clock.ts b/packages/status-js/src/utils/get-next-clock.ts new file mode 100644 index 00000000..392cebbc --- /dev/null +++ b/packages/status-js/src/utils/get-next-clock.ts @@ -0,0 +1,6 @@ +export const getNextClock = (currentClock = 0n): bigint => { + const now = BigInt(Date.now()) // timestamp + const nextClock = currentClock < now ? now : currentClock + 1n + + return nextClock +} diff --git a/packages/status-js/src/utils/is-clock-valid.ts b/packages/status-js/src/utils/is-clock-valid.ts new file mode 100644 index 00000000..b2739d65 --- /dev/null +++ b/packages/status-js/src/utils/is-clock-valid.ts @@ -0,0 +1,16 @@ +const MAX_OFFSET = BigInt(120 * 1000) + +export function isClockValid( + messageClock: bigint, + messageTimestamp: Date +): boolean { + if (messageClock <= 0) { + return false + } + + if (messageClock > BigInt(messageTimestamp.getTime()) + MAX_OFFSET) { + return false + } + + return true +}