Set clocks (#284)

* add clock functions

* remove bind
This commit is contained in:
Felicio Mununga 2022-06-30 15:11:13 +02:00 committed by GitHub
parent 0677fedc0e
commit 2f3ac73e5e
No known key found for this signature in database
GPG Key ID: 0EB8D75C775AB6F1
6 changed files with 85 additions and 7 deletions

View File

@ -10,6 +10,7 @@ import {
} from '../protos/chat-message' } from '../protos/chat-message'
import { EmojiReaction } from '../protos/emoji-reaction' import { EmojiReaction } from '../protos/emoji-reaction'
import { generateKeyFromPassword } from '../utils/generate-key-from-password' import { generateKeyFromPassword } from '../utils/generate-key-from-password'
import { getNextClock } from '../utils/get-next-clock'
import { idToContentTopic } from '../utils/id-to-content-topic' import { idToContentTopic } from '../utils/id-to-content-topic'
import { getReactions } from './community/get-reactions' import { getReactions } from './community/get-reactions'
@ -36,6 +37,7 @@ type FetchedMessage = { messageId: string; timestamp?: Date }
export class Chat { export class Chat {
private readonly client: Client private readonly client: Client
#clock: bigint
public readonly uuid: string public readonly uuid: string
public readonly id: string public readonly id: string
@ -72,6 +74,7 @@ export class Chat {
this.symmetricKey = options.symmetricKey this.symmetricKey = options.symmetricKey
this.description = options.description this.description = options.description
this.#clock = BigInt(Date.now())
this.chatCallbacks = new Set() this.chatCallbacks = new Set()
this.#messages = new Map() this.#messages = new Map()
this.#editTextEvents = new Map() this.#editTextEvents = new Map()
@ -243,7 +246,7 @@ export class Chat {
this.emitChange(description) this.emitChange(description)
} }
public handleNewMessage = (newMessage: ChatMessage, timestamp?: Date) => { public handleNewMessage = (newMessage: ChatMessage, timestamp: Date) => {
// fetching in progress // fetching in progress
if (this.#fetchingMessages) { if (this.#fetchingMessages) {
this.#oldestFetchedMessage = this.getOldestFetchedMessage( this.#oldestFetchedMessage = this.getOldestFetchedMessage(
@ -432,7 +435,7 @@ export class Chat {
// TODO: protos does not support optional fields :-( // TODO: protos does not support optional fields :-(
const payload = ChatMessageProto.encode({ const payload = ChatMessageProto.encode({
clock: BigInt(Date.now()), clock: this.setClock(this.#clock),
timestamp: BigInt(Date.now()), timestamp: BigInt(Date.now()),
text, text,
responseTo: responseTo ?? '', responseTo: responseTo ?? '',
@ -465,7 +468,7 @@ export class Chat {
public sendImageMessage = async (image: ImageMessage) => { public sendImageMessage = async (image: ImageMessage) => {
const payload = ChatMessageProto.encode({ const payload = ChatMessageProto.encode({
clock: BigInt(Date.now()), clock: this.setClock(this.#clock),
timestamp: BigInt(Date.now()), timestamp: BigInt(Date.now()),
text: '', text: '',
responseTo: '', responseTo: '',
@ -516,7 +519,7 @@ export class Chat {
} }
const payload = EditMessage.encode({ const payload = EditMessage.encode({
clock: BigInt(Date.now()), clock: this.setClock(this.#clock),
text, text,
messageId, messageId,
chatId: this.id, chatId: this.id,
@ -550,7 +553,7 @@ export class Chat {
} }
const payload = DeleteMessage.encode({ const payload = DeleteMessage.encode({
clock: BigInt(Date.now()), clock: this.setClock(this.#clock),
messageId, messageId,
chatId: this.id, chatId: this.id,
grant: new Uint8Array([]), grant: new Uint8Array([]),
@ -584,7 +587,7 @@ export class Chat {
) )
const payload = EmojiReaction.encode({ const payload = EmojiReaction.encode({
clock: BigInt(Date.now()), clock: this.setClock(this.#clock),
chatId: this.id, chatId: this.id,
messageType: 'COMMUNITY_CHAT' as MessageType, messageType: 'COMMUNITY_CHAT' as MessageType,
messageId, messageId,
@ -636,4 +639,10 @@ export class Chat {
return message return message
} }
public setClock = (currentClock?: bigint): bigint => {
this.#clock = getNextClock(currentClock)
return this.#clock
}
} }

View File

@ -7,6 +7,7 @@ import { CommunityRequestToJoin } from '../../protos/communities'
import { MessageType } from '../../protos/enums' import { MessageType } from '../../protos/enums'
import { compressPublicKey } from '../../utils/compress-public-key' import { compressPublicKey } from '../../utils/compress-public-key'
import { generateKeyFromPassword } from '../../utils/generate-key-from-password' import { generateKeyFromPassword } from '../../utils/generate-key-from-password'
import { getNextClock } from '../../utils/get-next-clock'
import { idToContentTopic } from '../../utils/id-to-content-topic' import { idToContentTopic } from '../../utils/id-to-content-topic'
import { Chat } from '../chat' import { Chat } from '../chat'
import { Member } from '../member' import { Member } from '../member'
@ -19,6 +20,7 @@ import type { Client } from '../client'
export class Community { export class Community {
private client: Client private client: Client
#clock: bigint
/** Compressed. */ /** Compressed. */
public publicKey: string public publicKey: string
@ -36,6 +38,7 @@ export class Community {
this.publicKey = publicKey this.publicKey = publicKey
this.id = publicKey.replace(/^0[xX]/, '') this.id = publicKey.replace(/^0[xX]/, '')
this.#clock = BigInt(Date.now())
this.chats = new Map() this.chats = new Map()
this.#members = new Map() this.#members = new Map()
this.#callbacks = new Set() this.#callbacks = new Set()
@ -252,7 +255,7 @@ export class Community {
public requestToJoin = async (chatId = '') => { public requestToJoin = async (chatId = '') => {
const payload = CommunityRequestToJoin.encode({ const payload = CommunityRequestToJoin.encode({
clock: BigInt(Date.now()), clock: this.setClock(this.#clock),
chatId, chatId,
communityId: hexToBytes(this.id), communityId: hexToBytes(this.id),
ensName: '', ensName: '',
@ -276,4 +279,10 @@ export class Community {
public isMember = (signerPublicKey: string): boolean => { public isMember = (signerPublicKey: string): boolean => {
return this.getMember(signerPublicKey) !== undefined return this.getMember(signerPublicKey) !== undefined
} }
public setClock = (currentClock?: bigint): bigint => {
this.#clock = getNextClock(currentClock)
return this.#clock
}
} }

View File

@ -11,6 +11,7 @@ import {
import { EmojiReaction } from '../../protos/emoji-reaction' import { EmojiReaction } from '../../protos/emoji-reaction'
import { PinMessage } from '../../protos/pin-message' import { PinMessage } from '../../protos/pin-message'
import { ProtocolMessage } from '../../protos/protocol-message' import { ProtocolMessage } from '../../protos/protocol-message'
import { isClockValid } from '../../utils/is-clock-valid'
import { payloadToId } from '../../utils/payload-to-id' import { payloadToId } from '../../utils/payload-to-id'
import { recoverPublicKey } from '../../utils/recover-public-key' import { recoverPublicKey } from '../../utils/recover-public-key'
import { getChatUuid } from './get-chat-uuid' import { getChatUuid } from './get-chat-uuid'
@ -27,6 +28,7 @@ export function handleWakuMessage(
community: Community community: Community
): void { ): void {
// decode (layers) // decode (layers)
// validate
if (!wakuMessage.payload) { if (!wakuMessage.payload) {
return return
} }
@ -35,6 +37,10 @@ export function handleWakuMessage(
return return
} }
if (!wakuMessage.timestamp) {
return
}
let messageToDecode = wakuMessage.payload let messageToDecode = wakuMessage.payload
let decodedProtocol let decodedProtocol
try { try {
@ -78,12 +84,18 @@ export function handleWakuMessage(
// decode // decode
const decodedPayload = CommunityDescription.decode(messageToDecode) const decodedPayload = CommunityDescription.decode(messageToDecode)
// validate
if (!isClockValid(BigInt(decodedPayload.clock), messageTimestamp)) {
return
}
if (!community.isOwner(signerPublicKey)) { if (!community.isOwner(signerPublicKey)) {
return return
} }
// handle (state and callback) // handle (state and callback)
community.handleDescription(decodedPayload) community.handleDescription(decodedPayload)
community.setClock(BigInt(decodedPayload.clock))
break break
} }
@ -92,6 +104,10 @@ export function handleWakuMessage(
// decode // decode
const decodedPayload = ChatMessage.decode(messageToDecode) const decodedPayload = ChatMessage.decode(messageToDecode)
if (!isClockValid(BigInt(decodedPayload.clock), messageTimestamp)) {
return
}
switch (decodedPayload.messageType) { switch (decodedPayload.messageType) {
case MessageType.COMMUNITY_CHAT: { case MessageType.COMMUNITY_CHAT: {
if (!community.isMember(signerPublicKey)) { if (!community.isMember(signerPublicKey)) {
@ -114,6 +130,7 @@ export function handleWakuMessage(
// handle // handle
chat.handleNewMessage(chatMessage, messageTimestamp) chat.handleNewMessage(chatMessage, messageTimestamp)
chat.setClock(decodedPayload.clock)
break break
} }
@ -129,6 +146,10 @@ export function handleWakuMessage(
case ApplicationMetadataMessage.Type.TYPE_EDIT_MESSAGE: { case ApplicationMetadataMessage.Type.TYPE_EDIT_MESSAGE: {
const decodedPayload = EditMessage.decode(messageToDecode) const decodedPayload = EditMessage.decode(messageToDecode)
if (!isClockValid(BigInt(decodedPayload.clock), messageTimestamp)) {
return
}
switch (decodedPayload.messageType) { switch (decodedPayload.messageType) {
case MessageType.COMMUNITY_CHAT: { case MessageType.COMMUNITY_CHAT: {
if (!community.isMember(signerPublicKey)) { if (!community.isMember(signerPublicKey)) {
@ -149,6 +170,7 @@ export function handleWakuMessage(
decodedPayload.clock, decodedPayload.clock,
signerPublicKey signerPublicKey
) )
chat.setClock(decodedPayload.clock)
break break
} }
@ -164,6 +186,10 @@ export function handleWakuMessage(
case ApplicationMetadataMessage.Type.TYPE_DELETE_MESSAGE: { case ApplicationMetadataMessage.Type.TYPE_DELETE_MESSAGE: {
const decodedPayload = DeleteMessage.decode(messageToDecode) const decodedPayload = DeleteMessage.decode(messageToDecode)
if (!isClockValid(BigInt(decodedPayload.clock), messageTimestamp)) {
return
}
switch (decodedPayload.messageType) { switch (decodedPayload.messageType) {
case MessageType.COMMUNITY_CHAT: { case MessageType.COMMUNITY_CHAT: {
if (!community.isMember(signerPublicKey)) { if (!community.isMember(signerPublicKey)) {
@ -183,6 +209,7 @@ export function handleWakuMessage(
decodedPayload.clock, decodedPayload.clock,
signerPublicKey signerPublicKey
) )
chat.setClock(decodedPayload.clock)
break break
} }
@ -198,6 +225,10 @@ export function handleWakuMessage(
case ApplicationMetadataMessage.Type.TYPE_PIN_MESSAGE: { case ApplicationMetadataMessage.Type.TYPE_PIN_MESSAGE: {
const decodedPayload = PinMessage.decode(messageToDecode) const decodedPayload = PinMessage.decode(messageToDecode)
if (!isClockValid(BigInt(decodedPayload.clock), messageTimestamp)) {
return
}
switch (decodedPayload.messageType) { switch (decodedPayload.messageType) {
case MessageType.COMMUNITY_CHAT: { case MessageType.COMMUNITY_CHAT: {
if (!community.isMember(signerPublicKey)) { if (!community.isMember(signerPublicKey)) {
@ -217,6 +248,7 @@ export function handleWakuMessage(
decodedPayload.clock, decodedPayload.clock,
decodedPayload.pinned decodedPayload.pinned
) )
chat.setClock(decodedPayload.clock)
break break
} }
@ -232,6 +264,10 @@ export function handleWakuMessage(
case ApplicationMetadataMessage.Type.TYPE_EMOJI_REACTION: { case ApplicationMetadataMessage.Type.TYPE_EMOJI_REACTION: {
const decodedPayload = EmojiReaction.decode(messageToDecode) const decodedPayload = EmojiReaction.decode(messageToDecode)
if (!isClockValid(BigInt(decodedPayload.clock), messageTimestamp)) {
return
}
switch (decodedPayload.messageType) { switch (decodedPayload.messageType) {
case MessageType.COMMUNITY_CHAT: { case MessageType.COMMUNITY_CHAT: {
if (!community.isMember(signerPublicKey)) { if (!community.isMember(signerPublicKey)) {
@ -252,6 +288,7 @@ export function handleWakuMessage(
decodedPayload.clock, decodedPayload.clock,
signerPublicKey signerPublicKey
) )
chat.setClock(decodedPayload.clock)
break break
} }

View File

@ -114,6 +114,7 @@ export function communityPermissions_AccessToJSON(
} }
export interface CommunityDescription { export interface CommunityDescription {
// fixme?: bigint
clock: number clock: number
members: { [key: string]: CommunityMember } members: { [key: string]: CommunityMember }
permissions: CommunityPermissions | undefined permissions: CommunityPermissions | undefined

View File

@ -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
}

View File

@ -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
}