Introduce online status broadcast (#99)

This commit is contained in:
Szymon Szlachtowicz 2021-10-28 09:47:14 +02:00 committed by GitHub
parent f957aa76cf
commit 7c3f256e61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 610 additions and 168 deletions

View File

@ -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,6 +56,14 @@ function DragDiv() {
};
return (
<>
<button
onClick={() => {
setTheme(!theme);
}}
>
Change theme
</button>
<Drag style={{ left: x, top: y, width: width, height: height }} ref={ref}>
<Bubble
onMouseDown={() => {
@ -64,7 +74,7 @@ function DragDiv() {
/>
<FloatingDiv className={showChat ? "" : "hide"}>
<ReactChat
theme={lightTheme}
theme={theme ? lightTheme : darkTheme}
communityKey={
"0x0262c65c881f5a9f79343a26faaa02aad3af7c533d9445fb1939ed11b8bf4d2abd"
}
@ -81,6 +91,7 @@ function DragDiv() {
></SizeSet>
)}
</Drag>
</>
);
}

View File

@ -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({
<ChatWrapper>
{showChannels && !narrow && (
<ChannelsWrapper>
{messenger ? (
{community && communityData ? (
<StyledCommunity onClick={showModal} community={communityData} />
) : (
<CommunitySkeleton />
@ -117,6 +111,8 @@ export function Chat({
</ChannelsWrapper>
)}
<ChatBody
identity={identity}
contacts={contacts}
theme={theme}
channel={activeChannel}
messenger={messenger}
@ -140,11 +136,13 @@ export function Chat({
/>
{showMembers && !narrow && (
<Members
community={communityData}
identity={identity}
contacts={contacts}
setShowChannels={setShowChannels}
setMembersList={setMembersList}
/>
)}
{communityData && (
<CommunityModal
isVisible={isModalVisible}
onClose={() => setIsModalVisible(false)}
@ -154,6 +152,7 @@ export function Chat({
description={communityData.description}
publicKey={communityKey}
/>
)}
</ChatWrapper>
);
}

View File

@ -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({
}
>
<ChannelWrapper className={className}>
{messenger ? (
{messenger && community ? (
<>
{(showCommunity || narrow) && (
<CommunityWrap className={className}>
@ -125,14 +131,14 @@ export function ChatBody({
>
<MembersIcon />
</MemberBtn>
{!messenger && <Loading />}
{!community && <Loading />}
</ChatTopbar>
{messenger ? (
{messenger && community ? (
<>
{!showChannelsList && !showMembersList && (
<>
{messages.length > 0 ? (
messenger ? (
messenger && community ? (
<ChatMessages
messages={messages}
loadPrevDay={loadPrevDay}
@ -163,6 +169,8 @@ export function ChatBody({
)}
{showMembersList && narrow && (
<NarrowMembers
identity={identity}
contacts={contacts}
community={community}
setShowChannels={setShowChannelsList}
setShowMembersList={setShowMembersList}

View File

@ -23,6 +23,7 @@ export function ChatInput({ theme, addMessage }: ChatInputProps) {
const [inputHeight, setInputHeight] = useState(40);
const [imageUint, setImageUint] = useState<undefined | Uint8Array>(undefined);
const [showSizeLimit, setShowSizeLimit] = useState(false);
useEffect(() => {
window.addEventListener("click", () => setShowEmoji(false));
return () => {
@ -127,11 +128,9 @@ export function ChatInput({ theme, addMessage }: ChatInputProps) {
/>
</AddPictureInputWrapper>
<Row style={{ height: `${inputHeight + (image ? 73 : 0)}px` }}>
<InputWrapper>
{image && (
<ImagePreviewWrapper>
<ImagePreviewOverlay />
<ImagePreview src={image} onClick={() => setImageUint(undefined)} />
</ImagePreviewWrapper>
)}
<Input
placeholder="Message"
@ -139,6 +138,7 @@ export function ChatInput({ theme, addMessage }: ChatInputProps) {
onChange={onInputChange}
onKeyPress={onInputKeyPress}
/>
</InputWrapper>
<InputButtons>
<ChatButton
onClick={(e) => {
@ -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;

View File

@ -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({
<MembersWrapper>
<MemberHeading>Members</MemberHeading>
<MembersList
community={community}
identity={identity}
contacts={contacts}
setShowChannels={setShowChannels}
setMembersList={setMembersList}
/>

View File

@ -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({
<MemberIcon>
<UserIcon memberView={true} />
</MemberIcon>
<MemberName>Guest564732</MemberName>
<MemberName>{utils.bufToHex(identity.publicKey)}</MemberName>
</MemberData>
</MemberCategory>
<MemberCategory>
<MemberCategoryName>Online</MemberCategoryName>
{community.membersList
.filter(() => false)
.map((member) => (
{contacts
.filter((e) => e.id != bufToHex(identity.publicKey))
.filter((e) => e.online)
.map((contact) => (
<Member
key={member}
member={member}
isOnline={false}
key={contact.id}
member={contact.id}
isOnline={contact.online}
setShowChannels={setShowChannels}
setShowMembers={setShowMembers}
setMembersList={setMembersList}
@ -47,11 +52,14 @@ export function MembersList({
</MemberCategory>
<MemberCategory>
<MemberCategoryName>Offline</MemberCategoryName>
{community.membersList.map((member) => (
{contacts
.filter((e) => e.id != bufToHex(identity.publicKey))
.filter((e) => !e.online)
.map((contact) => (
<Member
key={member}
member={member}
isOnline={false}
key={contact.id}
member={contact.id}
isOnline={contact.online}
setShowChannels={setShowChannels}
setShowMembers={setShowMembers}
setMembersList={setMembersList}

View File

@ -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({
<ListWrapper>
<NarrowTopbar list="Community members" community={community.name} />
<MembersList
community={community}
identity={identity}
contacts={contacts}
setShowChannels={setShowChannels}
setShowMembers={setShowMembersList}
setMembersList={setMembersList}

View File

@ -1,12 +1,15 @@
import { useCallback, useMemo, useState } from "react";
import { ApplicationMetadataMessage } from "status-communities/dist/cjs";
import {
ApplicationMetadataMessage,
Contacts,
} from "status-communities/dist/cjs";
import { ChatMessage } from "../../models/ChatMessage";
import { binarySetInsert } from "../../utils";
import { useNotifications } from "./useNotifications";
export function useMessages(chatId: string) {
export function useMessages(chatId: string, contacts?: Contacts) {
const [messages, setMessages] = useState<{ [chatId: string]: ChatMessage[] }>(
{}
);
@ -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(

View File

@ -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<Messenger | undefined>(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<Contact[]>(() => {
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<Community | undefined>(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,
};
}

View File

@ -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 = () => {

View File

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

View File

@ -0,0 +1,4 @@
export type Contact = {
id: string;
online: boolean;
};

View File

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

View File

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

View File

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

View File

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

View File

@ -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<void> => {
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);
}
}

View File

@ -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");
}

View File

@ -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";

View File

@ -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>): 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> = T extends Builtin
? T
: T extends Array<infer U>
? Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: T extends {}
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;
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();
}

View File

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