diff --git a/packages/react-chat-example/src/index.tsx b/packages/react-chat-example/src/index.tsx index 4bcde45e..3227de4f 100644 --- a/packages/react-chat-example/src/index.tsx +++ b/packages/react-chat-example/src/index.tsx @@ -1,4 +1,4 @@ -import { lightTheme, ReactChat } from "@dappconnect/react-chat"; +import { darkTheme, lightTheme, ReactChat } from "@dappconnect/react-chat"; import React, { useRef, useState } from "react"; import ReactDOM from "react-dom"; import styled from "styled-components"; @@ -32,6 +32,8 @@ function DragDiv() { const moved = useRef(false); const setting = useRef(""); + const [theme, setTheme] = useState(true); + const onMouseMove = (e: MouseEvent) => { if (setting.current === "position") { e.preventDefault(); @@ -54,33 +56,42 @@ function DragDiv() { }; return ( - - { - setting.current = "position"; - document.addEventListener("mousemove", onMouseMove); - document.addEventListener("mouseup", onMouseUp); + <> + + + { - setting.current = "size"; + setting.current = "position"; document.addEventListener("mousemove", onMouseMove); document.addEventListener("mouseup", onMouseUp); }} - > - )} - + /> + + + + {showChat && ( + { + setting.current = "size"; + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseup", onMouseUp); + }} + > + )} + + ); } diff --git a/packages/react-chat/src/components/Chat.tsx b/packages/react-chat/src/components/Chat.tsx index 3ee414ef..c5b9513d 100644 --- a/packages/react-chat/src/components/Chat.tsx +++ b/packages/react-chat/src/components/Chat.tsx @@ -49,6 +49,7 @@ export function Chat({ loadPrevDay, loadingMessages, community, + contacts, } = useMessenger(activeChannel?.id ?? "", communityKey, identity); const [isModalVisible, setIsModalVisible] = useState(false); @@ -68,14 +69,7 @@ export function Chat({ description: community.description.identity?.description ?? "", }; } else { - return { - id: 1, - name: "", - icon: "", - members: 0, - membersList: [], - description: "", - }; + return undefined; } }, [community]); @@ -101,7 +95,7 @@ export function Chat({ {showChannels && !narrow && ( - {messenger ? ( + {community && communityData ? ( ) : ( @@ -117,6 +111,8 @@ export function Chat({ )} {showMembers && !narrow && ( )} - setIsModalVisible(false)} - icon={communityData.icon} - name={communityData.name} - subtitle="Public Community" - description={communityData.description} - publicKey={communityKey} - /> + {communityData && ( + setIsModalVisible(false)} + icon={communityData.icon} + name={communityData.name} + subtitle="Public Community" + description={communityData.description} + publicKey={communityKey} + /> + )} ); } diff --git a/packages/react-chat/src/components/Chat/ChatBody.tsx b/packages/react-chat/src/components/Chat/ChatBody.tsx index 0979675b..b56d4888 100644 --- a/packages/react-chat/src/components/Chat/ChatBody.tsx +++ b/packages/react-chat/src/components/Chat/ChatBody.tsx @@ -1,10 +1,12 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { Identity } from "status-communities/dist/cjs"; import styled from "styled-components"; import { useNarrow } from "../../contexts/narrowProvider"; import { ChannelData } from "../../models/ChannelData"; import { ChatMessage } from "../../models/ChatMessage"; import { CommunityData } from "../../models/CommunityData"; +import { Contact } from "../../models/Contact"; import { Metadata } from "../../models/Metadata"; import { Theme } from "../../styles/themes"; import { Channel } from "../Channels/Channel"; @@ -21,9 +23,11 @@ import { ChatInput } from "./ChatInput"; import { ChatMessages } from "./ChatMessages"; interface ChatBodyProps { + identity: Identity; + contacts: Contact[]; theme: Theme; channel: ChannelData; - community: CommunityData; + community: CommunityData | undefined; messenger: any; messages: ChatMessage[]; sendMessage: (text: string, image?: Uint8Array) => void; @@ -44,6 +48,8 @@ interface ChatBodyProps { } export function ChatBody({ + identity, + contacts, theme, channel, community, @@ -95,7 +101,7 @@ export function ChatBody({ } > - {messenger ? ( + {messenger && community ? ( <> {(showCommunity || narrow) && ( @@ -125,14 +131,14 @@ export function ChatBody({ > - {!messenger && } + {!community && } - {messenger ? ( + {messenger && community ? ( <> {!showChannelsList && !showMembersList && ( <> {messages.length > 0 ? ( - messenger ? ( + messenger && community ? ( (undefined); const [showSizeLimit, setShowSizeLimit] = useState(false); + useEffect(() => { window.addEventListener("click", () => setShowEmoji(false)); return () => { @@ -127,18 +128,17 @@ export function ChatInput({ theme, addMessage }: ChatInputProps) { /> - {image && ( - - + + {image && ( setImageUint(undefined)} /> - - )} - + )} + + { @@ -160,6 +160,12 @@ export function ChatInput({ theme, addMessage }: ChatInputProps) { ); } +const InputWrapper = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + const View = styled.div` display: flex; align-items: center; @@ -187,28 +193,7 @@ const InputButtons = styled.div` } `; -const ImagePreviewWrapper = styled.div` - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 82px; - z-index: 1; -`; - -const ImagePreviewOverlay = styled.div` - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: #eef2f5; - border-radius: 16px 16px 4px 16px; - opacity: 0.9; -`; - const ImagePreview = styled.img` - position: relative; width: 64px; height: 64px; border-radius: 16px 16px 4px 16px; diff --git a/packages/react-chat/src/components/Members/Members.tsx b/packages/react-chat/src/components/Members/Members.tsx index fcdff0d2..bfd28c3b 100644 --- a/packages/react-chat/src/components/Members/Members.tsx +++ b/packages/react-chat/src/components/Members/Members.tsx @@ -1,18 +1,21 @@ import React from "react"; +import { Identity } from "status-communities/dist/cjs"; import styled from "styled-components"; -import { CommunityData } from "../../models/CommunityData"; +import { Contact } from "../../models/Contact"; import { MembersList } from "./MembersList"; interface MembersProps { - community: CommunityData; + identity: Identity; + contacts: Contact[]; setShowChannels: (val: boolean) => void; setMembersList: any; } export function Members({ - community, + identity, + contacts, setShowChannels, setMembersList, }: MembersProps) { @@ -20,7 +23,8 @@ export function Members({ Members diff --git a/packages/react-chat/src/components/Members/MembersList.tsx b/packages/react-chat/src/components/Members/MembersList.tsx index f9c4a43e..5888d92c 100644 --- a/packages/react-chat/src/components/Members/MembersList.tsx +++ b/packages/react-chat/src/components/Members/MembersList.tsx @@ -1,20 +1,24 @@ import React from "react"; +import { Identity, utils } from "status-communities/dist/cjs"; +import { bufToHex } from "status-communities/dist/cjs/utils"; import styled from "styled-components"; -import { CommunityData } from "../../models/CommunityData"; +import { Contact } from "../../models/Contact"; import { UserIcon } from "../Icons/UserIcon"; import { Member, MemberData, MemberIcon } from "./Member"; interface MembersListProps { - community: CommunityData; + identity: Identity; + contacts: Contact[]; setShowChannels: (val: boolean) => void; setShowMembers?: (val: boolean) => void; setMembersList: any; } export function MembersList({ - community, + identity, + contacts, setShowChannels, setShowMembers, setMembersList, @@ -27,18 +31,19 @@ export function MembersList({ - Guest564732 + {utils.bufToHex(identity.publicKey)} Online - {community.membersList - .filter(() => false) - .map((member) => ( + {contacts + .filter((e) => e.id != bufToHex(identity.publicKey)) + .filter((e) => e.online) + .map((contact) => ( Offline - {community.membersList.map((member) => ( - - ))} + {contacts + .filter((e) => e.id != bufToHex(identity.publicKey)) + .filter((e) => !e.online) + .map((contact) => ( + + ))} ); diff --git a/packages/react-chat/src/components/NarrowMode/NarrowMembers.tsx b/packages/react-chat/src/components/NarrowMode/NarrowMembers.tsx index dbe07daa..4e494f31 100644 --- a/packages/react-chat/src/components/NarrowMode/NarrowMembers.tsx +++ b/packages/react-chat/src/components/NarrowMode/NarrowMembers.tsx @@ -1,20 +1,26 @@ import React from "react"; +import { Identity } from "status-communities/dist/cjs"; import styled from "styled-components"; import { CommunityData } from "../../models/CommunityData"; +import { Contact } from "../../models/Contact"; import { MembersList } from "../Members/MembersList"; import { NarrowTopbar } from "./NarrowTopbar"; interface NarrowMembersProps { + identity: Identity; community: CommunityData; + contacts: Contact[]; setShowChannels: (val: boolean) => void; setShowMembersList: (val: boolean) => void; setMembersList: any; } export function NarrowMembers({ + identity, community, + contacts, setShowChannels, setShowMembersList, setMembersList, @@ -23,7 +29,8 @@ export function NarrowMembers({ ( {} ); @@ -17,6 +20,9 @@ export function useMessages(chatId: string) { (msg: ApplicationMetadataMessage, id: string, date: Date) => { const newMessage = ChatMessage.fromMetadataMessage(msg, date); if (newMessage) { + if (contacts) { + contacts.addContact(newMessage.sender); + } setMessages((prev) => { return { ...prev, @@ -31,7 +37,7 @@ export function useMessages(chatId: string) { incNotification(id); } }, - [] + [contacts] ); const activeMessages = useMemo( diff --git a/packages/react-chat/src/hooks/messenger/useMessenger.ts b/packages/react-chat/src/hooks/messenger/useMessenger.ts index 3458be74..09d3a2d5 100644 --- a/packages/react-chat/src/hooks/messenger/useMessenger.ts +++ b/packages/react-chat/src/hooks/messenger/useMessenger.ts @@ -1,8 +1,16 @@ // import { StoreCodec } from "js-waku"; -import { useCallback, useEffect, useState } from "react"; -import { Community, Identity, Messenger } from "status-communities/dist/cjs"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { + Community, + Contacts, + Identity, + Messenger, + utils, +} from "status-communities/dist/cjs"; -import { createCommunityMessenger } from "../../utils/createCommunityMessenger"; +import { Contact } from "../../models/Contact"; +import { createCommunity } from "../../utils/createCommunity"; +import { createMessenger } from "../../utils/createMessenger"; import { useLoadPrevDay } from "./useLoadPrevDay"; import { useMessages } from "./useMessages"; @@ -13,27 +21,65 @@ export function useMessenger( identity: Identity ) { const [messenger, setMessenger] = useState(undefined); + + const [internalContacts, setInternalContacts] = useState<{ + [id: string]: number; + }>({}); + + const contactsClass = useMemo(() => { + if (messenger) { + const newContacts = new Contacts( + identity, + messenger.waku, + (id, clock) => { + setInternalContacts((prev) => { + return { ...prev, [id]: clock }; + }); + } + ); + newContacts.addContact(utils.bufToHex(identity.publicKey)); + return newContacts; + } + }, [messenger]); + + const contacts = useMemo(() => { + const now = Date.now(); + const newContacts: Contact[] = []; + Object.entries(internalContacts).forEach(([id, clock]) => { + newContacts.push({ + id, + online: clock > now - 301000, + }); + }); + return newContacts; + }, [internalContacts]); + const { addMessage, clearNotifications, notifications, messages } = - useMessages(chatId); + useMessages(chatId, contactsClass); const [community, setCommunity] = useState(undefined); const { loadPrevDay, loadingMessages } = useLoadPrevDay(chatId, messenger); useEffect(() => { - createCommunityMessenger(communityKey, addMessage, identity).then( - (result) => { - setCommunity(result.community); - setMessenger(result.messenger); - } - ); + createMessenger(identity).then((e) => { + setMessenger(e); + }); }, []); + useEffect(() => { + if (messenger && contactsClass) { + createCommunity(communityKey, addMessage, messenger).then((e) => { + setCommunity(e); + }); + } + }, [messenger, communityKey, addMessage, contactsClass]); + useEffect(() => { if (messenger && community?.chats) { Array.from(community?.chats.values()).forEach(({ id }) => loadPrevDay(id) ); } - }, [messenger]); + }, [messenger, community]); const sendMessage = useCallback( async (messageText?: string, image?: Uint8Array) => { @@ -63,5 +109,6 @@ export function useMessenger( loadPrevDay, loadingMessages, community, + contacts, }; } diff --git a/packages/react-chat/src/hooks/useRefBreak.ts b/packages/react-chat/src/hooks/useRefBreak.ts index 494a6481..23db5a51 100644 --- a/packages/react-chat/src/hooks/useRefBreak.ts +++ b/packages/react-chat/src/hooks/useRefBreak.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; export function useRefBreak(dimension: number, sizeThreshold: number) { - const [widthBreak, setWidthBreak] = useState(false); + const [widthBreak, setWidthBreak] = useState(dimension < sizeThreshold); useEffect(() => { const checkDimensions = () => { diff --git a/packages/react-chat/src/models/ChatMessage.ts b/packages/react-chat/src/models/ChatMessage.ts index 91e29fb0..e202812c 100644 --- a/packages/react-chat/src/models/ChatMessage.ts +++ b/packages/react-chat/src/models/ChatMessage.ts @@ -1,4 +1,4 @@ -import { ApplicationMetadataMessage } from "status-communities/dist/cjs"; +import { ApplicationMetadataMessage, utils } from "status-communities/dist/cjs"; import { uintToImgUrl } from "../utils"; @@ -29,10 +29,7 @@ export class ChatMessage { if (msg.chatMessage?.image) { image = uintToImgUrl(msg.chatMessage?.image.payload); } - const sender = msg.signer.reduce( - (p: string, c: number): string => p + c.toString(16), - "0x" - ); + const sender = utils.bufToHex(msg.signer); return new ChatMessage(content, date, sender, image); } else { return undefined; diff --git a/packages/react-chat/src/models/Contact.ts b/packages/react-chat/src/models/Contact.ts new file mode 100644 index 00000000..0677d8d0 --- /dev/null +++ b/packages/react-chat/src/models/Contact.ts @@ -0,0 +1,4 @@ +export type Contact = { + id: string; + online: boolean; +}; diff --git a/packages/react-chat/src/utils/createCommunity.ts b/packages/react-chat/src/utils/createCommunity.ts new file mode 100644 index 00000000..7d7e20b2 --- /dev/null +++ b/packages/react-chat/src/utils/createCommunity.ts @@ -0,0 +1,23 @@ +import { Community, Messenger } from "status-communities/dist/cjs"; +import { ApplicationMetadataMessage } from "status-communities/dist/cjs"; + +export async function createCommunity( + communityKey: string, + addMessage: (msg: ApplicationMetadataMessage, id: string, date: Date) => void, + messenger: Messenger +) { + const community = await Community.instantiateCommunity( + communityKey, + messenger.waku + ); + await Promise.all( + Array.from(community.chats.values()).map(async (chat) => { + await messenger.joinChat(chat); + messenger.addObserver( + (msg, date) => addMessage(msg, chat.id, date), + chat.id + ); + }) + ); + return community; +} diff --git a/packages/react-chat/src/utils/createCommunityMessenger.ts b/packages/react-chat/src/utils/createCommunityMessenger.ts deleted file mode 100644 index bda50eef..00000000 --- a/packages/react-chat/src/utils/createCommunityMessenger.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { StoreCodec } from "js-waku"; -import { Community, Identity, Messenger } from "status-communities/dist/cjs"; -import { ApplicationMetadataMessage } from "status-communities/dist/cjs"; - -const WAKU_OPTIONS = { - libp2p: { - config: { - pubsub: { - enabled: true, - emitSelf: true, - }, - }, - }, -}; - -export async function createCommunityMessenger( - communityKey: string, - addMessage: (msg: ApplicationMetadataMessage, id: string, date: Date) => void, - identity: Identity -) { - const messenger = await Messenger.create(identity, WAKU_OPTIONS); - await new Promise((resolve) => { - messenger.waku.libp2p.peerStore.on("change:protocols", ({ protocols }) => { - if (protocols.includes(StoreCodec)) { - resolve(""); - } - }); - }); - const community = await Community.instantiateCommunity( - communityKey, - messenger.waku - ); - await Promise.all( - Array.from(community.chats.values()).map(async (chat) => { - await messenger.joinChat(chat); - messenger.addObserver( - (msg, date) => addMessage(msg, chat.id, date), - chat.id - ); - }) - ); - - return { messenger, community, identity }; -} diff --git a/packages/react-chat/src/utils/createMessenger.ts b/packages/react-chat/src/utils/createMessenger.ts new file mode 100644 index 00000000..f9fba769 --- /dev/null +++ b/packages/react-chat/src/utils/createMessenger.ts @@ -0,0 +1,26 @@ +import { StoreCodec } from "js-waku"; +import { Identity, Messenger } from "status-communities/dist/cjs"; + +const WAKU_OPTIONS = { + libp2p: { + config: { + pubsub: { + enabled: true, + emitSelf: true, + }, + }, + }, +}; + +export async function createMessenger(identity: Identity) { + const messenger = await Messenger.create(identity, WAKU_OPTIONS); + await new Promise((resolve) => { + messenger.waku.libp2p.peerStore.on("change:protocols", ({ protocols }) => { + if (protocols.includes(StoreCodec)) { + resolve(""); + } + }); + }); + + return messenger; +} diff --git a/packages/status-communities/proto/communities/v1/status_update.proto b/packages/status-communities/proto/communities/v1/status_update.proto new file mode 100644 index 00000000..759b390e --- /dev/null +++ b/packages/status-communities/proto/communities/v1/status_update.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package communities.v1; + +/* Specs: +:AUTOMATIC + To Send - "AUTOMATIC" status ping every 5 minutes + Display - Online for up to 5 minutes from the last clock, after that Offline +:ALWAYS_ONLINE + To Send - "ALWAYS_ONLINE" status ping every 5 minutes + Display - Online for up to 2 weeks from the last clock, after that Offline +:INACTIVE + To Send - A single "INACTIVE" status ping + Display - Offline forever +Note: Only send pings if the user interacted with the app in the last x minutes. */ +message StatusUpdate { + + uint64 clock = 1; + + StatusType status_type = 2; + + string custom_text = 3; + + enum StatusType { + UNKNOWN_STATUS_TYPE = 0; + AUTOMATIC = 1; + DO_NOT_DISTURB = 2; + ALWAYS_ONLINE = 3; + INACTIVE = 4; + }; + + } \ No newline at end of file diff --git a/packages/status-communities/src/contacts.ts b/packages/status-communities/src/contacts.ts new file mode 100644 index 00000000..7e7e5536 --- /dev/null +++ b/packages/status-communities/src/contacts.ts @@ -0,0 +1,63 @@ +import { Waku, WakuMessage } from "js-waku"; + +import { idToContactCodeTopic } from "./contentTopic"; +import { Identity } from "./identity"; +import { StatusUpdate_StatusType } from "./proto/communities/v1/status_update"; +import { bufToHex } from "./utils"; +import { StatusUpdate } from "./wire/status_update"; + +export class Contacts { + waku: Waku; + identity: Identity; + private callback: (id: string, clock: number) => void; + private contacts: string[] = []; + + public constructor( + identity: Identity, + waku: Waku, + callback: (id: string, clock: number) => void + ) { + this.waku = waku; + this.identity = identity; + this.callback = callback; + this.startBroadcast(); + } + + public addContact(id: string): void { + if (!this.contacts.find((e) => id === e)) { + const now = new Date(); + const callback = (wakuMessage: WakuMessage): void => { + if (wakuMessage.payload) { + const msg = StatusUpdate.decode(wakuMessage.payload); + this.callback(id, msg.clock ?? 0); + } + }; + this.contacts.push(id); + this.callback(id, 0); + this.waku.store.queryHistory([idToContactCodeTopic(id)], { + callback: (msgs) => msgs.forEach((e) => callback(e)), + timeFilter: { + startTime: new Date(now.getTime() - 400000), + endTime: now, + }, + }); + this.waku.relay.addObserver(callback, [idToContactCodeTopic(id)]); + } + } + + private startBroadcast(): void { + const send = async (): Promise => { + const statusUpdate = StatusUpdate.create( + StatusUpdate_StatusType.AUTOMATIC, + "" + ); + const msg = await WakuMessage.fromBytes( + statusUpdate.encode(), + idToContactCodeTopic(bufToHex(this.identity.publicKey)) + ); + this.waku.relay.send(msg); + }; + send(); + setInterval(send, 300000); + } +} diff --git a/packages/status-communities/src/contentTopic.ts b/packages/status-communities/src/contentTopic.ts index 0c78777f..48c1dc22 100644 --- a/packages/status-communities/src/contentTopic.ts +++ b/packages/status-communities/src/contentTopic.ts @@ -16,3 +16,7 @@ export function idToContentTopic(id: string): string { return "/waku/1/" + "0x" + topic.toString("hex") + "/rfc26"; } + +export function idToContactCodeTopic(id: string): string { + return idToContentTopic(id + "-contact-code"); +} diff --git a/packages/status-communities/src/index.ts b/packages/status-communities/src/index.ts index 4a3027b8..c12bddab 100644 --- a/packages/status-communities/src/index.ts +++ b/packages/status-communities/src/index.ts @@ -1,6 +1,7 @@ export { Identity } from "./identity"; export { Messenger } from "./messenger"; export { Community } from "./community"; +export { Contacts } from "./contacts"; export { Chat } from "./chat"; export * as utils from "./utils"; export { ApplicationMetadataMessage } from "./wire/application_metadata_message"; diff --git a/packages/status-communities/src/proto/communities/v1/status_update.ts b/packages/status-communities/src/proto/communities/v1/status_update.ts new file mode 100644 index 00000000..31e9f122 --- /dev/null +++ b/packages/status-communities/src/proto/communities/v1/status_update.ts @@ -0,0 +1,212 @@ +/* eslint-disable */ +import Long from "long"; +import _m0 from "protobufjs/minimal"; + +export const protobufPackage = "communities.v1"; + +/** + * Specs: + * :AUTOMATIC + * To Send - "AUTOMATIC" status ping every 5 minutes + * Display - Online for up to 5 minutes from the last clock, after that Offline + * :ALWAYS_ONLINE + * To Send - "ALWAYS_ONLINE" status ping every 5 minutes + * Display - Online for up to 2 weeks from the last clock, after that Offline + * :INACTIVE + * To Send - A single "INACTIVE" status ping + * Display - Offline forever + * Note: Only send pings if the user interacted with the app in the last x minutes. + */ +export interface StatusUpdate { + clock: number; + statusType: StatusUpdate_StatusType; + customText: string; +} + +export enum StatusUpdate_StatusType { + UNKNOWN_STATUS_TYPE = 0, + AUTOMATIC = 1, + DO_NOT_DISTURB = 2, + ALWAYS_ONLINE = 3, + INACTIVE = 4, + UNRECOGNIZED = -1, +} + +export function statusUpdate_StatusTypeFromJSON( + object: any +): StatusUpdate_StatusType { + switch (object) { + case 0: + case "UNKNOWN_STATUS_TYPE": + return StatusUpdate_StatusType.UNKNOWN_STATUS_TYPE; + case 1: + case "AUTOMATIC": + return StatusUpdate_StatusType.AUTOMATIC; + case 2: + case "DO_NOT_DISTURB": + return StatusUpdate_StatusType.DO_NOT_DISTURB; + case 3: + case "ALWAYS_ONLINE": + return StatusUpdate_StatusType.ALWAYS_ONLINE; + case 4: + case "INACTIVE": + return StatusUpdate_StatusType.INACTIVE; + case -1: + case "UNRECOGNIZED": + default: + return StatusUpdate_StatusType.UNRECOGNIZED; + } +} + +export function statusUpdate_StatusTypeToJSON( + object: StatusUpdate_StatusType +): string { + switch (object) { + case StatusUpdate_StatusType.UNKNOWN_STATUS_TYPE: + return "UNKNOWN_STATUS_TYPE"; + case StatusUpdate_StatusType.AUTOMATIC: + return "AUTOMATIC"; + case StatusUpdate_StatusType.DO_NOT_DISTURB: + return "DO_NOT_DISTURB"; + case StatusUpdate_StatusType.ALWAYS_ONLINE: + return "ALWAYS_ONLINE"; + case StatusUpdate_StatusType.INACTIVE: + return "INACTIVE"; + default: + return "UNKNOWN"; + } +} + +const baseStatusUpdate: object = { clock: 0, statusType: 0, customText: "" }; + +export const StatusUpdate = { + encode( + message: StatusUpdate, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.clock !== 0) { + writer.uint32(8).uint64(message.clock); + } + if (message.statusType !== 0) { + writer.uint32(16).int32(message.statusType); + } + if (message.customText !== "") { + writer.uint32(26).string(message.customText); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): StatusUpdate { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseStatusUpdate } as StatusUpdate; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.clock = longToNumber(reader.uint64() as Long); + break; + case 2: + message.statusType = reader.int32() as any; + break; + case 3: + message.customText = reader.string(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): StatusUpdate { + const message = { ...baseStatusUpdate } as StatusUpdate; + if (object.clock !== undefined && object.clock !== null) { + message.clock = Number(object.clock); + } else { + message.clock = 0; + } + if (object.statusType !== undefined && object.statusType !== null) { + message.statusType = statusUpdate_StatusTypeFromJSON(object.statusType); + } else { + message.statusType = 0; + } + if (object.customText !== undefined && object.customText !== null) { + message.customText = String(object.customText); + } else { + message.customText = ""; + } + return message; + }, + + toJSON(message: StatusUpdate): unknown { + const obj: any = {}; + message.clock !== undefined && (obj.clock = message.clock); + message.statusType !== undefined && + (obj.statusType = statusUpdate_StatusTypeToJSON(message.statusType)); + message.customText !== undefined && (obj.customText = message.customText); + return obj; + }, + + fromPartial(object: DeepPartial): StatusUpdate { + const message = { ...baseStatusUpdate } as StatusUpdate; + if (object.clock !== undefined && object.clock !== null) { + message.clock = object.clock; + } else { + message.clock = 0; + } + if (object.statusType !== undefined && object.statusType !== null) { + message.statusType = object.statusType; + } else { + message.statusType = 0; + } + if (object.customText !== undefined && object.customText !== null) { + message.customText = object.customText; + } else { + message.customText = ""; + } + return message; + }, +}; + +declare var self: any | undefined; +declare var window: any | undefined; +declare var global: any | undefined; +var globalThis: any = (() => { + if (typeof globalThis !== "undefined") return globalThis; + if (typeof self !== "undefined") return self; + if (typeof window !== "undefined") return window; + if (typeof global !== "undefined") return global; + throw "Unable to locate global object"; +})(); + +type Builtin = + | Date + | Function + | Uint8Array + | string + | number + | boolean + | undefined; +export type DeepPartial = T extends Builtin + ? T + : T extends Array + ? Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +function longToNumber(long: Long): number { + if (long.gt(Number.MAX_SAFE_INTEGER)) { + throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER"); + } + return long.toNumber(); +} + +if (_m0.util.Long !== Long) { + _m0.util.Long = Long as any; + _m0.configure(); +} diff --git a/packages/status-communities/src/wire/status_update.ts b/packages/status-communities/src/wire/status_update.ts new file mode 100644 index 00000000..25d5e357 --- /dev/null +++ b/packages/status-communities/src/wire/status_update.ts @@ -0,0 +1,49 @@ +import { Reader } from "protobufjs"; + +import * as proto from "../proto/communities/v1/status_update"; + +export class StatusUpdate { + public constructor(public proto: proto.StatusUpdate) {} + + public static create( + statusType: proto.StatusUpdate_StatusType, + customText: string + ): StatusUpdate { + const clock = Date.now(); + + const proto = { + clock, + statusType, + customText, + }; + + return new StatusUpdate(proto); + } + + /** + * Decode the payload as CommunityChat message. + * + * @throws + */ + static decode(bytes: Uint8Array): StatusUpdate { + const protoBuf = proto.StatusUpdate.decode(Reader.create(bytes)); + + return new StatusUpdate(protoBuf); + } + + encode(): Uint8Array { + return proto.StatusUpdate.encode(this.proto).finish(); + } + + public get clock(): number | undefined { + return this.proto.clock; + } + + public get statusType(): proto.StatusUpdate_StatusType | undefined { + return this.proto.statusType; + } + + public get customText(): string | undefined { + return this.proto.customText; + } +}