parent
6fc6410b32
commit
fef65d0fd2
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue