const contract = props.contract || "chat.cali-portal-ws"; const encryptionUrl = props.encryptionUrl || "https://cali-encryption.euw3.staging.gcp.calimero.network/key"; const componentOwnerId = props.componentOwnerId || "calimero.testnet"; const ChatContainer = styled.div` background-color: #0e0e10; padding-top: 27px; padding-left: 4px; `; const PageContainer = styled.div` width: 100%; height: 100vh; background-color: #0e0e10; `; State.init({ bootstraping: true, loggedIn: false, organizationName: "", openCreateChannel: false, channelList: [], selectedChannel: -1, settingsId: -1, directMessagesOpen: true, usersList: [], selectedDM: context.accountId, channelDetailsOpen: false, addNewUserClick: false, channelUserList: [], chatMessages: [], message: "", name: "", inputId: 0, aboutSelected: true, channelMeta: null, functionLoader: false, img: null, key: null, }); const resetImage = () => State.update({ img: null }); const updateMemberList = () => Near.asyncCalimeroView(contract, "get_members").then((m) => { State.update({ usersList: m }); return m; }); const updateChannelMemberList = (id) => { const usedId = id || state.selectedChannel; if (usedId < 0) { return; } return Near.asyncCalimeroView( contract, "get_members", { group: state.channelList[usedId] }, undefined, true ).then((m) => State.update({ channelUserList: m })); }; const updateChannelList = () => Near.asyncCalimeroView( contract, "get_groups", { account: context.accountId }, undefined, true ).then((c) => State.update({ channelList: c })); if (state.selectedChannel >= 0) { Near.asyncCalimeroView( contract, "channel_info", { group: state.channelList[state.selectedChannel] }, undefined, true ).then((m) => State.update({ channelMeta: m })); } //HELPER FUNCTIONS function parseHexString(hexString) { const result = []; while (hexString.length >= 2) { result.push(parseInt(hexString.substring(0, 2), 16)); hexString = hexString.substring(2, hexString.length); } return result; } function toHexString(byteArray) { const result = ""; for (let byte of byteArray) { result += ("0" + (byte & 0xff).toString(16)).slice(-2); } return result; } function encrypt(text, key) { const nonce = Crypto.randomBytes(16); const cipher = Crypto.createCipheriv( "aes-256-cbc", parseHexString(key), nonce ); let encrypted = cipher.update(text, "utf8", "base64"); encrypted += cipher.final("base64"); return { text: encrypted, nonce: toHexString(nonce) }; } //CHANGE FUNCTIONS const onAddEmoji = (text, emoji) => { State.update({ message: text + emoji }); }; const onChannelSelected = (id) => { State.update({ selectedChannel: id }); updateChannelMemberList(id); }; const onChangeChannelDialog = (open) => { State.update({ openCreateChannel: open }); }; const onChangeChannelSettings = (id) => State.update({ settingsId: id }); const openChannelSettings = (id) => { State.update({ settingsId: id }); }; const onChangeOpenDMs = (open) => State.update({ directMessagesOpen: open }); const onChangeDMSelected = (id) => State.update({ selectedDM: id }); const channelDetailsClick = (open) => State.update({ channelDetailsOpen: open }); const onAddNewUser = (open) => State.update({ addNewUserClick: open }); const handleClosePopupUser = () => onAddNewUser(false); const onChangeMessage = ({ target }) => { State.update({ message: target.value }); }; const onChangeName = ({ target }) => { State.update({ name: target.value }); }; const updateInputId = (id) => { State.update({ inputId: id }); }; const sendMessage = () => { if (!state.message) { return; } let params = {}; if (state.selectedDM) { params = { account: state.selectedDM }; } else { params = { group: state.channelList[state.selectedChannel] }; } params.message = state.message; if (state.img) { params.message = params.message + `$?$https://ipfs.near.social/ipfs/${state.img.cid}`; resetImage(); } const encrypted = encrypt(params.message, state.key); params.message = encrypted.text; params.nonce = encrypted.nonce; params.timestamp = Date.now(); State.update({ message: "" }); updateInputId(Math.random().toString(36)); Near.fakCalimeroCall(contract, "send_message", params); }; const joinCurb = () => { Near.requestCalimeroFak(contract); }; const handleLeaveChannel = () => { const channel = state.channelList[state.selectedChannel]; State.update({ selectedChannel: -1, selectedDM: context.accountId }); Near.fakCalimeroCall(contract, "leave_group", { group: channel, account: context.accountId, }).then(() => { handleCloseSettingsPopup(); }); }; const handleCreateChannel = () => { State.update({ functionLoader: true }); Near.fakCalimeroCall(contract, "create_group", { group: { name: state.name }, }).then(() => { State.update({ functionLoader: false }); handleClosePopup(); }); }; const handleInviteUser = () => { State.update({ functionLoader: true }); Near.fakCalimeroCall(contract, "group_invite", { group: state.channelList[state.selectedChannel], account: state.name, }).then(() => { onAddNewUser(false); State.update({ functionLoader: false }); }); }; const handleCloseSettingsPopup = () => { swithTab(true); State.update({ channelDetailsOpen: false }); }; const handleClosePopup = () => onChangeChannelDialog(false); const swithTab = (selected) => State.update({ aboutSelected: selected }); const addMemberFromSettings = () => { State.update({ channelDetailsOpen: false }); onAddNewUser(true); }; const addMessageReaction = (params) => { Near.fakCalimeroCall(contract, "toggle_reaction", params); }; const openMemberList = () => { swithTab(false); State.update({ channelDetailsOpen: true }); }; const isMember = (accountId, members) => { return (members || state.usersList) .map((user) => user.id) .includes(accountId); }; const verifyKey = () => { Near.hasValidCalimeroFak(contract).then((result) => { State.update({ bootstraping: false, loggedIn: result }); Near.asyncCalimeroView(contract, "get_name").then((n) => State.update({ organizationName: n }) ); if (result) { updateMemberList().then((members) => { if (!isMember(context.accountId, members)) { Near.fakCalimeroCall(contract, "join"); } }); updateChannelList(); } }); }; if (state.bootstraping) { verifyKey(); } const ping = () => Near.fakCalimeroCall(contract, "ping"); if (state.loggedIn) { const keyBody = { from: context.accountId, }; if (state.channelList[state.selectedChannel]) { keyBody.group = state.channelList[state.selectedChannel]; } else { keyBody.to = state.selectedDM; } const cacheKey = { ...keyBody }; keyBody.nonce = toHexString(Crypto.randomBytes(16)); Calimero.sign( contract, Buffer.from(context.accountId + "|" + keyBody.nonce) ).then((signature) => { keyBody.signature = toHexString(signature.signature); const keyData = useCache( () => asyncFetch(encryptionUrl, { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: JSON.stringify(keyBody), }), cacheKey ); State.update({ key: keyData.body.key }); }); } updateMemberList(); updateChannelMemberList(); updateChannelList(); //useCache(ping, "ping", {subscribe: true}); return ( <PageContainer> {context.accountId ? ( <> {state.bootstraping ? ( <Widget src={`${componentOwnerId}/widget/Calimero.Curb.Popups.LoadingPopup`} props={{ componentOwnerId }} /> ) : ( <> {state.loggedIn && isMember(context.accountId) ? ( <> {state.openCreateChannel && ( <Widget src={`${componentOwnerId}/widget/Calimero.Curb.Popups.InputPopup`} props={{ componentOwnerId, title: "Create new Channel", placeholder: "# channel name", buttonText: "Create", handleClosePopup: handleClosePopup, onChange: onChangeName, handleClickEvent: handleCreateChannel, functionLoader: state.functionLoader, }} /> )} {state.addNewUserClick && ( <Widget src={`${componentOwnerId}/widget/Calimero.Curb.Popups.InputPopup`} props={{ componentOwnerId, title: `Invite user to ${ state.channelList[state.selectedChannel].name }`, placeholder: "account_id", buttonText: "Invite", handleClosePopup: handleClosePopupUser, onChange: onChangeName, handleClickEvent: handleInviteUser, functionLoader: state.functionLoader, }} /> )} {state.channelDetailsOpen && ( <> <Widget src={`${componentOwnerId}/widget/Calimero.Curb.Settings.DetailsContainer`} props={{ componentOwnerId, handleCloseSettingsPopup: handleCloseSettingsPopup, channelName: state.channelList[state.selectedChannel].name, selectedTab: state.aboutSelected, userCount: state.channelUserList.length, onSwitch: () => swithTab(!state.aboutSelected), dateCreated: state.channelMeta.createdAt, manager: state.channelMeta.createdBy, handleLeaveChannel: handleLeaveChannel, userList: state.channelUserList, addMember: () => addMemberFromSettings(), aboutSelected: state.aboutSelected, }} /> </> )} <Widget src={`${componentOwnerId}/widget/Calimero.Curb.Navbar.CurbNavbar`} props={{ componentOwnerId, organizationName: state.organizationName, channelSelected: state.selectedDM ? state.selectedDM : state.channelList[state.selectedChannel].name, channelDetailsClick: () => channelDetailsClick(!state.channelDetailsOpen), channelDetailsOpen: state.channelDetailsOpen, channelUserList: state.selectedDM ? [] : state.channelUserList, isDMSelected: !!state.selectedDM, onAddNewUser: () => onAddNewUser(!addNewUserClick), openMemberList: () => openMemberList(), }} /> <div className="d-flex"> <Widget src={`${componentOwnerId}/widget/Calimero.Curb.SideSelector.SideSelector`} props={{ componentOwnerId, onChangeChannelDialog: (open) => onChangeChannelDialog(open), onChannelSelected: (id) => onChannelSelected(id), onChangeChannelSettings: (id) => openChannelSettings(id), onChangeDMSelected: (id) => onChangeDMSelected(id), channelList: state.channelList, selectedChannel: state.selectedChannel, onChangeOpenDMs: (open) => onChangeOpenDMs(open), directMessagesOpen: state.directMessagesOpen, usersList: state.usersList, selectedDM: state.selectedDM, }} /> <ChatContainer> <Widget src={`${componentOwnerId}/widget/Calimero.Curb.Chat.ChatContainer`} props={{ componentOwnerId, key: parseHexString(state.key), messages: Near.calimeroView( contract, "get_messages", state.channelList[state.selectedChannel] ? { group: state.channelList[state.selectedChannel], } : { accounts: [ context.accountId, state.selectedDM, ], }, undefined, true ) || [], addMessageReaction: (params) => addMessageReaction(params), }} /> <Widget src={`${componentOwnerId}/widget/Calimero.Curb.Chat.MessageInput`} props={{ componentOwnerId, onChange: onChangeMessage, selectedChat: state.selectedDM ? state.selectedDM : "#" + state.channelList[state.selectedChannel].name, sendMessage: sendMessage, inputId: state.inputId, onAddEmoji: (emoji) => onAddEmoji(state.message, emoji), message: state.message, img: state.img, uploadComponent: ( <IpfsImageUpload image={state.img} className="btn btn-secondary" /> ), resetImage: resetImage, }} /> </ChatContainer> </div> </> ) : ( <> <Widget src={`${componentOwnerId}/widget/Calimero.Curb.Popups.JoinCurbPopup`} props={{ componentOwnerId, joinCurb, }} /> </> )} </> )} </> ) : ( <Widget src={`${componentOwnerId}/widget/Calimero.Curb.Popups.LoginPopup`} props={{ componentOwnerId }} /> )} </PageContainer> );