4
1
mirror of https://github.com/Qortal/qortal-ui.git synced 2025-02-11 09:45:52 +00:00

Merge pull request #335 from AlphaX-Qortal/master

updates and additions
This commit is contained in:
AlphaX 2025-01-22 19:30:00 +01:00 committed by GitHub
commit aa902563dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 3381 additions and 1830 deletions

View File

@ -26,8 +26,8 @@ Easiest way to install the lastest required packages on Linux is via nvm.
``` source ~/.profile ``` (For Debian based distro) <br/>
``` source ~/.bashrc ``` (For Fedora / CentOS) <br/>
``` nvm ls-remote ``` (Fetch list of available versions) <br/>
``` nvm install v18.20.3 ``` (Latest LTS: Hydrogen supported by Electron V31) <br/>
``` npm --location=global install npm@10.8.1 ``` <br/>
``` nvm install v20.18.1 ``` (Latest LTS: Iron supported by Electron V34) <br/>
``` npm --location=global install npm@11.0.0 ``` <br/>
Adding via binary package mirror will only work if you have set the package path. You can do a node or java build via ports instead by downloading ports with portsnap fetch method.

View File

@ -1,6 +1,7 @@
import { Sha256 } from 'asmcrypto.js'
import Base58 from './api/deps/Base58'
import Base64 from './api/deps/Base64'
import Base64Message from './api/deps/Base64Message'
import { base58PublicKeyToAddress } from './api/wallet/base58PublicKeyToAddress'
import { validateAddress } from './api/wallet/validateAddress'
import { decryptChatMessage, decryptChatMessageBase64 } from './api/transactions/chat/decryptChatMessage'
@ -9,6 +10,7 @@ import _ from 'lodash'
window.Sha256 = Sha256
window.Base58 = Base58
window.Base64 = Base64
window.Base64Message = Base64Message
window._ = _
window.base58PublicKeyToAddress = base58PublicKeyToAddress
window.validateAddress = validateAddress

View File

@ -0,0 +1,82 @@
import {
uint8ArrayToBase64,
base64ToUint8Array,
uint8ArrayToObject,
decryptSingle
} from '../../../plugins/plugins/core/components/GroupEncryption.js'
const Base64Message = {}
Base64Message.decode = function (string, keys, ref) {
const binaryString = atob(string)
const binaryLength = binaryString.length
const bytes = new Uint8Array(binaryLength)
for (let i = 0; i < binaryLength; i++) {
bytes[i] = binaryString.charCodeAt(i)
}
const decoder = new TextDecoder()
const decodedString = decoder.decode(bytes)
if (decodedString.includes("messageText") || decodedString === "4001") {
if (decodedString === "4001") {
const firstString = 'First group key created.'
const hubString = '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + firstString + '"}]}]},"images":[""],"repliedTo":"","version":3}'
return hubString
} else {
return decodedString
}
} else {
let repliedToStr = ''
let messageStr = ''
let hubString = ''
const res = decryptSingle(string, keys, false)
const decryptToUnit8Array = base64ToUint8Array(res)
const responseData = uint8ArrayToObject(decryptToUnit8Array)
if (responseData.type === "notification") {
const messageStrRaw = responseData.data.message
messageStr = messageStrRaw.trim()
}
if (ref !== "noref") {
if (responseData.type === "reaction") {
repliedToStr = ref
messageStr = responseData.content
}
}
if (responseData.hasOwnProperty('message') && typeof responseData['message'] === 'string' && responseData['message'].length) {
const messageStrRaw = responseData.message
const messageJoin1 = messageStrRaw.split('"').join('<upvote>')
const messageReplace1 = messageJoin1.replace('<p>', '')
const messageReplace2 = messageReplace1.replace('</p>', '')
const messageTrim = messageReplace2.trim()
const messageJoin2 = messageTrim.split('<br><br>').join('"},{"type":"hardBreak"},{"type":"hardBreak"},{"type":"text","text":"')
const messageJoin3 = messageJoin2.split('<br>').join('"},{"type":"hardBreak"},{"type":"text","text":"')
messageStr = messageJoin3
}
if (responseData.repliedTo) {
repliedToStr = responseData.repliedTo
}
if (responseData.type === "edit") {
hubString = '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + messageStr + '"}]}]},"images":[""],"repliedTo":"' + repliedToStr + '","version":3,"isEdited":true}'
} else if (responseData.type === "reaction") {
hubString = '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + messageStr + '"}]}]},"images":[""],"repliedTo":"' + repliedToStr + '","version":3,"isReaction":true}'
} else {
hubString = '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + messageStr + '"}]}]},"images":[""],"repliedTo":"' + repliedToStr + '","version":3}'
}
const preparedString = hubString.split('<upvote>').join('\\"')
const finalString = preparedString.replace(/<\/?[^>]+(>|$)/g, '')
return finalString
}
}
export default Base64Message

3932
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "qortal-ui",
"version": "4.5.5",
"version": "4.6.0",
"description": "Qortal Project - decentralize the world - Data storage, communications, web hosting, decentralized trading, complete infrastructure for the future blockchain-based Internet",
"keywords": [
"QORT",
@ -30,13 +30,14 @@
"publish": "electron-builder -p always"
},
"dependencies": {
"@hapi/hapi": "21.3.10",
"@hapi/hapi": "21.3.12",
"@hapi/inert": "7.1.0",
"@lit-labs/motion": "1.0.6",
"@popperjs/core": "2.11.8",
"@tiptap/core": "2.0.4",
"@tiptap/extension-highlight": "2.0.4",
"@tiptap/extension-image": "2.0.4",
"@tiptap/extension-mention": "2.0.0",
"@tiptap/extension-placeholder": "2.0.4",
"@tiptap/extension-underline": "2.0.4",
"@tiptap/html": "2.0.4",
@ -49,32 +50,32 @@
"crypto-js": "4.2.0",
"driver.js": "1.3.1",
"electron-dl": "3.5.2",
"electron-log": "5.1.5",
"electron-log": "5.2.4",
"electron-store": "8.2.0",
"electron-updater": "6.2.1",
"electron-updater": "6.3.9",
"emoji-picker-js": "https://github.com/Qortal/emoji-picker-js",
"extract-zip": "2.0.1",
"jssha": "3.3.1",
"localforage": "1.10.0",
"lodash": "4.17.21",
"os-locale": "5.0.0",
"prosemirror-commands": "1.5.2",
"prosemirror-commands": "1.6.2",
"prosemirror-dropcursor": "1.8.1",
"prosemirror-gapcursor": "1.3.2",
"prosemirror-history": "1.4.0",
"prosemirror-history": "1.4.1",
"prosemirror-keymap": "1.2.2",
"prosemirror-model": "1.21.3",
"prosemirror-schema-list": "1.4.0",
"prosemirror-model": "1.24.1",
"prosemirror-schema-list": "1.5.0",
"prosemirror-state": "1.4.3",
"prosemirror-transform": "1.9.0",
"prosemirror-view": "1.33.8",
"prosemirror-transform": "1.10.2",
"prosemirror-view": "1.37.2",
"sass": "1.77.6",
"short-unique-id": "5.2.0",
"xhr2": "0.2.1"
},
"devDependencies": {
"@babel/core": "7.24.7",
"@electron/packager": "18.3.3",
"@babel/core": "7.26.0",
"@electron/packager": "18.3.6",
"@material/mwc-button": "0.27.0",
"@material/mwc-checkbox": "0.27.0",
"@material/mwc-dialog": "0.27.0",
@ -111,11 +112,11 @@
"@polymer/paper-toast": "3.0.1",
"@polymer/paper-tooltip": "3.0.1",
"@qortal/rollup-plugin-web-worker-loader": "1.6.5",
"@rollup/plugin-alias": "5.1.0",
"@rollup/plugin-alias": "5.1.1",
"@rollup/plugin-babel": "6.0.4",
"@rollup/plugin-commonjs": "26.0.1",
"@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-replace": "5.0.7",
"@rollup/plugin-commonjs": "28.0.2",
"@rollup/plugin-node-resolve": "16.0.0",
"@rollup/plugin-replace": "6.0.2",
"@rollup/plugin-terser": "0.4.4",
"@vaadin/avatar": "24.2.9",
"@vaadin/button": "24.2.9",
@ -125,10 +126,10 @@
"@vaadin/tabs": "24.2.9",
"@vaadin/tabsheet": "24.2.9",
"@vaadin/tooltip": "24.2.9",
"@zip.js/zip.js": "2.7.45",
"axios": "1.7.2",
"electron": "31.1.0",
"electron-builder": "24.13.3",
"@zip.js/zip.js": "2.7.54",
"axios": "1.7.9",
"electron": "34.0.0",
"electron-builder": "25.1.8",
"epml": "0.3.3",
"file-saver": "2.0.5",
"highcharts": "11.1.0",
@ -139,13 +140,13 @@
"pwa-helpers": "0.9.1",
"redux": "5.0.1",
"redux-thunk": "3.1.0",
"rollup": "4.18.0",
"rollup": "4.31.0",
"rollup-plugin-node-globals": "1.4.0",
"rollup-plugin-progress": "1.1.2",
"rollup-plugin-scss": "3.0.0",
"shelljs": "0.8.5"
},
"engines": {
"node": ">=18.20.3"
"node": ">=20.18.1"
}
}

View File

@ -15,6 +15,7 @@ import StarterKit from '@tiptap/starter-kit'
import Underline from '@tiptap/extension-underline'
import Placeholder from '@tiptap/extension-placeholder'
import Highlight from '@tiptap/extension-highlight'
import Mention from '@tiptap/extension-mention'
import WebWorker from 'web-worker:./computePowWorker.js'
import WebWorkerFile from 'web-worker:./computePowWorkerFile.js'
import WebWorkerSortMessages from 'web-worker:./webworkerSortMessages.js'
@ -134,7 +135,8 @@ class ChatPage extends LitElement {
messageQueue: { type: Array },
isInProcessQueue: { type: Boolean },
loggedInUserName: { type: String },
loggedInUserAddress: { type: String }
loggedInUserAddress: { type: String },
secretKeysWorker: { type: Object }
}
}
@ -241,6 +243,7 @@ class ChatPage extends LitElement {
this.isInProcessQueue = false
this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode()
this.secretKeysWorker = {}
}
render() {
@ -342,7 +345,7 @@ class ChatPage extends LitElement {
}
${+this.repliedToMessageObj.version > 1 ?
html`
<span style="color: var(--black);">${unsafeHTML(generateHTML(this.repliedToMessageObj.message, [StarterKit, Underline, Highlight]))}</span>
<span style="color: var(--black);">${unsafeHTML(generateHTML(this.repliedToMessageObj.message, [StarterKit, Underline, Highlight, Mention]))}</span>
`
: ''
}
@ -359,7 +362,7 @@ class ChatPage extends LitElement {
<vaadin-icon class="reply-icon" icon="vaadin:pencil" slot="icon"></vaadin-icon>
<div class="repliedTo-message">
<p class="senderName">${translate("chatpage.cchange25")}</p>
<span style="color: var(--black);">${unsafeHTML(generateHTML(this.editedMessageObj.message, [StarterKit, Underline, Highlight]))}</span>
<span style="color: var(--black);">${unsafeHTML(generateHTML(this.editedMessageObj.message, [StarterKit, Underline, Highlight, Mention]))}</span>
</div>
<vaadin-icon class="close-icon" icon="vaadin:close-big" slot="icon" @click=${() => this.closeEditMessageContainer()}></vaadin-icon>
</div>
@ -815,16 +818,21 @@ class ChatPage extends LitElement {
}
window.addEventListener('storage', () => {
this.secretKeysWorker = {}
const checkLanguage = localStorage.getItem('qortalLanguage')
const checkTheme = localStorage.getItem('qortalTheme')
const arraySecretKeysWorkerNew = JSON.parse(localStorage.getItem("symKeysCurrent") || "[]")
this.userLanguage = checkLanguage
this.secretKeysWorker = arraySecretKeysWorkerNew[0]
if (checkTheme === 'dark') {
this.theme = 'dark'
} else {
this.theme = 'light'
}
document.querySelector('html').setAttribute('theme', this.theme)
})
@ -906,7 +914,6 @@ class ChatPage extends LitElement {
addToQueue(outSideMsg, messageQueue) {
// Push the new message object to the queue
this.messageQueue = [...messageQueue, { ...outSideMsg, timestamp: Date.now() }]
// Start processing the queue only if the message we just added is the only one in the queue
@ -921,7 +928,9 @@ class ChatPage extends LitElement {
async processQueue() {
if (this.messageQueue.length === 0) return
const currentMessage = this.messageQueue[0]
try {
const res = await this.sendMessage(currentMessage)
@ -932,8 +941,7 @@ class ChatPage extends LitElement {
}
if (this.messageQueue.length > 0) {
setTimeout(() => this.processQueue(), 2000) // Wait for 10 seconds before retrying
// setTimeout(() => this.processQueue(), 0) // Process the next message immediately
setTimeout(() => this.processQueue(), 2000) // Wait for 2 seconds before retrying
}
} catch (error) {
console.error("Failed to send message:", error)
@ -944,6 +952,7 @@ class ChatPage extends LitElement {
async getLastestMessages() {
try {
let getInitialMessages = []
if (this.isReceipient) {
getInitialMessages = await parentEpml.request('apiCall', {
type: 'api',
@ -955,6 +964,7 @@ class ChatPage extends LitElement {
url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64`
})
}
this.processMessages(getInitialMessages, true, false)
} catch (error) { /* empty */ }
}
@ -991,6 +1001,7 @@ class ChatPage extends LitElement {
return memberItem
})
const membersWithName = await Promise.all(getMembersWithName)
this.groupMembers = [...this.groupMembers, ...membersWithName]
this.pageNumber = this.pageNumber + 1
} catch (error) { /* empty */ }
@ -998,6 +1009,7 @@ class ChatPage extends LitElement {
async connectedCallback() {
super.connectedCallback()
await this.initUpdate()
if (!this.webWorker) {
@ -1023,9 +1035,7 @@ class ChatPage extends LitElement {
const elementChatGifId = this.shadowRoot.getElementById('chatGifId').shadowRoot.getElementById('newGifChat')
const elementChatAttachmentId = this.shadowRoot.getElementById('chatAttachmentId').shadowRoot.getElementById('newAttachmentChat')
const elementChatFileId = this.shadowRoot.getElementById('chatFileId').shadowRoot.getElementById('newFileChat')
const placeholderString = get('chatpage.cchange114')
const clipboardTextParser = (text, context, plain) => {
const splitLines = text.replace().split(/(?:\r\n?|\n)/)
const nodesLines = []
@ -1061,6 +1071,7 @@ class ChatPage extends LitElement {
StarterKit,
Underline,
Highlight,
Mention,
Placeholder.configure({
placeholder: `${placeholderString}`
}),
@ -1099,6 +1110,7 @@ class ChatPage extends LitElement {
StarterKit,
Underline,
Highlight,
Mention,
Placeholder.configure({
placeholder: `${placeholderString}`
}),
@ -1134,6 +1146,7 @@ class ChatPage extends LitElement {
StarterKit,
Underline,
Highlight,
Mention,
Placeholder.configure({
placeholder: `${placeholderString}`
}),
@ -1169,6 +1182,7 @@ class ChatPage extends LitElement {
StarterKit,
Underline,
Highlight,
Mention,
Placeholder.configure({
placeholder: `${placeholderString}`
}),
@ -1204,6 +1218,7 @@ class ChatPage extends LitElement {
StarterKit,
Underline,
Highlight,
Mention,
Placeholder.configure({
placeholder: `${placeholderString}`
}),
@ -1379,23 +1394,30 @@ class ChatPage extends LitElement {
if (findMessage) {
findMessage.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
const findElement = findMessage.shadowRoot.querySelector('.message-parent')
if (findElement) {
findElement.classList.add('blink-bg')
setTimeout(() => {
findElement.classList.remove('blink-bg')
}, 2000)
}
const chatScrollerElement = this.shadowRoot.querySelector('chat-scroller')
if (chatScrollerElement && chatScrollerElement.disableFetching) {
chatScrollerElement.disableFetching = false
}
return
}
const findOriginalMessage = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById(clickedOnMessage.signature)
if (findOriginalMessage) {
const messageClientRect = findOriginalMessage.getBoundingClientRect()
this.isLoadingGoToRepliedMessage = {
...this.isLoadingGoToRepliedMessage,
loading: true,
@ -1414,15 +1436,19 @@ class ChatPage extends LitElement {
)
|| marginElements.find((item) => item.messageObj.signature === message.originalSignature)
|| marginElements.find((item) => item.messageObj.originalSignature === message.originalSignature)
if (findMessage2) {
findMessage2.scrollIntoView({ block: 'center' })
}
if (findMessage2) {
this.isLoadingGoToRepliedMessage = {
...this.isLoadingGoToRepliedMessage,
loading: false
}
const findElement = findMessage2.shadowRoot.querySelector('.message-parent')
if (findElement) {
findElement.classList.add('blink-bg')
setTimeout(() => {
@ -1438,11 +1464,14 @@ class ChatPage extends LitElement {
return
}
this.isLoadingGoToRepliedMessage = {
...this.isLoadingGoToRepliedMessage,
loading: false
}
let errorMsg = get("chatpage.cchange66")
parentEpml.request('showSnackBar', `${errorMsg}`)
}
}
@ -1581,6 +1610,10 @@ class ChatPage extends LitElement {
}
async initUpdate() {
this.secretKeysWorker = {}
let arraySecretKeysWorkerInit = JSON.parse(localStorage.getItem("symKeysCurrent") || "[]")
this.secretKeysWorker = arraySecretKeysWorkerInit[0]
if (this.webSocket) {
this.webSocket.close(1000, 'switch chat')
this.webSocket = ''
@ -1784,7 +1817,7 @@ class ChatPage extends LitElement {
}
}
let userName = ''
let userName = 'this.chatId'
if (this.isReceipient) {
userName = await getName(this._chatId)
@ -1873,7 +1906,9 @@ class ChatPage extends LitElement {
type: "api",
url: `/chat/message/${messageToGoTo.originalSignature || messageToGoTo.signature}?encoding=BASE64`,
})
if (!findMsg) return null
if (this.isReceipient) {
const getInitialMessagesBefore = await parentEpml.request('apiCall', {
type: 'api',
@ -1886,7 +1921,7 @@ class ChatPage extends LitElement {
const getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter]
let decodeMsgs = []
await new Promise((res, rej) => {
this.webWorkerDecodeMessages.postMessage({ messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey })
this.webWorkerDecodeMessages.postMessage({ messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey, secretKeys: this.secretKeysWorker })
this.webWorkerDecodeMessages.onmessage = e => {
decodeMsgs = e.data
@ -1904,6 +1939,7 @@ class ChatPage extends LitElement {
isReceipient: this.isReceipient,
decodeMessageFunc: this.decodeMessage,
_publicKey: this._publicKey,
symKeys: this.secretKeysWorker,
addToUpdateMessageHashmap: this.addToUpdateMessageHashmap
}))
@ -1949,7 +1985,7 @@ class ChatPage extends LitElement {
let decodeMsgs = []
await new Promise((res, rej) => {
this.webWorkerDecodeMessages.postMessage({ messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey })
this.webWorkerDecodeMessages.postMessage({ messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey, secretKeys: this.secretKeysWorker })
this.webWorkerDecodeMessages.onmessage = e => {
decodeMsgs = e.data
@ -1967,6 +2003,7 @@ class ChatPage extends LitElement {
isReceipient: this.isReceipient,
decodeMessageFunc: this.decodeMessage,
_publicKey: this._publicKey,
symKeys: this.secretKeysWorker,
addToUpdateMessageHashmap: this.addToUpdateMessageHashmap
}))
@ -1988,6 +2025,7 @@ class ChatPage extends LitElement {
type: 'api',
url: `/chat/messages/count?after=${lastMsg.timestamp}&txGroupId=${Number(this._chatId)}&limit=20&reverse=false`
})
this.messagesRendered = {
messages: list,
type: 'inBetween',
@ -2008,6 +2046,7 @@ class ChatPage extends LitElement {
}
return
}
if (this.isReceipient) {
const getInitialMessages = await parentEpml.request('apiCall', {
type: 'api',
@ -2015,7 +2054,7 @@ class ChatPage extends LitElement {
})
let decodeMsgs = []
await new Promise((res, rej) => {
this.webWorkerDecodeMessages.postMessage({ messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey })
this.webWorkerDecodeMessages.postMessage({ messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey, secretKeys: this.secretKeysWorker })
this.webWorkerDecodeMessages.onmessage = e => {
decodeMsgs = e.data
@ -2033,6 +2072,7 @@ class ChatPage extends LitElement {
isReceipient: this.isReceipient,
decodeMessageFunc: this.decodeMessage,
_publicKey: this._publicKey,
symKeys: this.secretKeysWorker,
addToUpdateMessageHashmap: this.addToUpdateMessageHashmap
}))
@ -2062,7 +2102,7 @@ class ChatPage extends LitElement {
let decodeMsgs = []
await new Promise((res, rej) => {
this.webWorkerDecodeMessages.postMessage({ messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey })
this.webWorkerDecodeMessages.postMessage({ messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey, secretKeys: this.secretKeysWorker })
this.webWorkerDecodeMessages.onmessage = e => {
decodeMsgs = e.data
@ -2080,6 +2120,7 @@ class ChatPage extends LitElement {
isReceipient: this.isReceipient,
decodeMessageFunc: this.decodeMessage,
_publicKey: this._publicKey,
symKeys: this.secretKeysWorker,
addToUpdateMessageHashmap: this.addToUpdateMessageHashmap
}))
@ -2109,6 +2150,7 @@ class ChatPage extends LitElement {
}
return
}
const timestamp = scrollElement.messageObj.timestamp
if (this.isReceipient) {
@ -2119,7 +2161,7 @@ class ChatPage extends LitElement {
let decodeMsgs = []
await new Promise((res, rej) => {
this.webWorkerDecodeMessages.postMessage({ messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey })
this.webWorkerDecodeMessages.postMessage({ messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey, secretKeys: this.secretKeysWorker })
this.webWorkerDecodeMessages.onmessage = e => {
decodeMsgs = e.data
@ -2137,6 +2179,7 @@ class ChatPage extends LitElement {
isReceipient: this.isReceipient,
decodeMessageFunc: this.decodeMessage,
_publicKey: this._publicKey,
symKeys: this.secretKeysWorker,
addToUpdateMessageHashmap: this.addToUpdateMessageHashmap
}))
@ -2172,7 +2215,7 @@ class ChatPage extends LitElement {
})
let decodeMsgs = []
await new Promise((res, rej) => {
this.webWorkerDecodeMessages.postMessage({ messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey })
this.webWorkerDecodeMessages.postMessage({ messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey, secretKeys: this.secretKeysWorker })
this.webWorkerDecodeMessages.onmessage = e => {
decodeMsgs = e.data
@ -2190,6 +2233,7 @@ class ChatPage extends LitElement {
isReceipient: this.isReceipient,
decodeMessageFunc: this.decodeMessage,
_publicKey: this._publicKey,
symKeys: this.secretKeysWorker,
addToUpdateMessageHashmap: this.addToUpdateMessageHashmap
}))
@ -2219,9 +2263,10 @@ class ChatPage extends LitElement {
}
async addToUpdateMessageHashmap(array) {
const withoutHubReactions = array.filter(({decodedMessage}) => !decodedMessage.includes('isReaction'))
const newObj = {}
array.forEach((item) => {
withoutHubReactions.forEach((item) => {
const signature = item.originalSignature || item.signature
newObj[signature] = item
})
@ -2262,6 +2307,7 @@ class ChatPage extends LitElement {
async processMessages(messages, isInitial, isUnread, count) {
const isReceipient = this.chatId.includes('direct')
let decodedMessages = []
if (!this.webWorkerDecodeMessages) {
@ -2273,7 +2319,7 @@ class ChatPage extends LitElement {
}
await new Promise((res, rej) => {
this.webWorkerDecodeMessages.postMessage({ messages: messages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey })
this.webWorkerDecodeMessages.postMessage({ messages: messages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey, secretKeys: this.secretKeysWorker })
this.webWorkerDecodeMessages.onmessage = e => {
decodedMessages = e.data
@ -2295,6 +2341,7 @@ class ChatPage extends LitElement {
isReceipient: isReceipient,
decodeMessageFunc: this.decodeMessage,
_publicKey: this._publicKey,
symKeys: this.secretKeysWorker,
addToUpdateMessageHashmap: this.addToUpdateMessageHashmap
}))
} catch (error) {
@ -2346,6 +2393,7 @@ class ChatPage extends LitElement {
isReceipient: isReceipient,
decodeMessageFunc: this.decodeMessage,
_publicKey: this._publicKey,
symKeys: this.secretKeysWorker,
isNotInitial: true,
addToUpdateMessageHashmap: this.addToUpdateMessageHashmap
}))
@ -2374,6 +2422,11 @@ class ChatPage extends LitElement {
this.requestUpdate()
}
closeRepliedToContainer() {
this.repliedToMessageObj = null
this.requestUpdate()
}
// set edited message in chat editor
setEditedMessageObj(messageObj) {
this.editor.commands.focus('end')
@ -2388,11 +2441,6 @@ class ChatPage extends LitElement {
this.editor.commands.setContent('')
}
closeRepliedToContainer() {
this.repliedToMessageObj = null
this.requestUpdate()
}
/**
* New Message Template implementation, takes in a message object.
* @param { Object } messageObj
@ -2456,7 +2504,7 @@ class ChatPage extends LitElement {
* @param {Object} encodedMessageObj
*
*/
decodeMessage(encodedMessageObj, isReceipient, _publicKey) {
decodeMessage(encodedMessageObj, isReceipient, _publicKey, symKeys) {
let isReceipientVar
let _publicKeyVar
@ -2476,14 +2524,23 @@ class ChatPage extends LitElement {
let decodedMessage = window.parent.decryptChatMessageBase64(encodedMessageObj.data, window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey, _publicKeyVar.key, encodedMessageObj.reference)
decodedMessageObj = { ...encodedMessageObj, decodedMessage }
} else if (encodedMessageObj.isEncrypted === false && encodedMessageObj.data) {
let decodedMessage = window.parent.Base64.decode(encodedMessageObj.data)
let decodedMessage = window.parent.Base64Message.decode(encodedMessageObj.data, symKeys)
decodedMessageObj = { ...encodedMessageObj, decodedMessage }
} else {
decodedMessageObj = { ...encodedMessageObj, decodedMessage: "Cannot Decrypt Message!" }
}
} else {
// group chat
let decodedMessage = window.parent.Base64.decode(encodedMessageObj.data)
let decodedMessage
const noRef = "noref"
if (encodedMessageObj.chatReference) {
decodedMessage = window.parent.Base64Message.decode(encodedMessageObj.data, symKeys, encodedMessageObj.chatReference)
} else {
decodedMessage = window.parent.Base64Message.decode(encodedMessageObj.data, symKeys, noRef)
}
decodedMessageObj = { ...encodedMessageObj, decodedMessage }
}

View File

@ -9,6 +9,7 @@ import { chatStyles } from './plugins-css'
import isElectron from 'is-electron'
import axios from 'axios'
import Highlight from '@tiptap/extension-highlight'
import Mention from '@tiptap/extension-mention'
import ShortUniqueId from 'short-unique-id'
import StarterKit from '@tiptap/starter-kit'
import Underline from '@tiptap/extension-underline'
@ -1086,7 +1087,7 @@ class MessageTemplate extends LitElement {
const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage)
if (+parsedMessageObj.version > 1 && parsedMessageObj.messageText) {
messageVersion2 = generateHTML(parsedMessageObj.messageText, [StarterKit, Underline, Highlight])
messageVersion2 = generateHTML(parsedMessageObj.messageText, [StarterKit, Underline, Highlight, Mention])
messageVersion2WithLink = processText(messageVersion2)
}
@ -1256,7 +1257,7 @@ class MessageTemplate extends LitElement {
if (repliedToData && repliedToData.decodedMessage && repliedToData.decodedMessage.messageText) {
try {
repliedToMessageText = generateHTML(repliedToData.decodedMessage.messageText, [StarterKit, Underline, Highlight])
repliedToMessageText = generateHTML(repliedToData.decodedMessage.messageText, [StarterKit, Underline, Highlight, Mention])
} catch (error) { /* empty */ }
}

View File

@ -0,0 +1,600 @@
import Base58 from '../../../../crypto/api/deps/Base58'
import ed2curve from '../../../../crypto/api/deps/ed2curve'
import nacl from '../../../../crypto/api/deps/nacl-fast'
export function base64ToUint8Array(base64) {
const binaryString = atob(base64)
const len = binaryString.length
const bytes = new Uint8Array(len)
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i)
}
return bytes
}
export function uint8ArrayToBase64(uint8Array) {
const length = uint8Array.length
let binaryString = ''
const chunkSize = 1024 * 1024 // Process 1MB at a time
for (let i = 0; i < length; i += chunkSize) {
const chunkEnd = Math.min(i + chunkSize, length)
const chunk = uint8Array.subarray(i, chunkEnd)
binaryString += Array.from(chunk, byte => String.fromCharCode(byte)).join('')
}
return btoa(binaryString)
}
export function objectToBase64(obj) {
// Step 1: Convert the object to a JSON string
const jsonString = JSON.stringify(obj)
// Step 2: Create a Blob from the JSON string
const blob = new Blob([jsonString], { type: 'application/json' })
// Step 3: Create a FileReader to read the Blob as a base64-encoded string
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onloadend = () => {
if (typeof reader.result === 'string') {
// Remove 'data:application/json;base64,' prefix
const base64 = reader.result.replace(
'data:application/json;base64,',
''
)
resolve(base64)
} else {
reject(new Error('Failed to read the Blob as a base64-encoded string'))
}
}
reader.onerror = () => {
reject(reader.error)
}
reader.readAsDataURL(blob)
})
}
export function uint8ArrayToObject(uint8Array) {
// Decode the byte array using TextDecoder
const decoder = new TextDecoder()
const jsonString = decoder.decode(uint8Array)
// Convert the JSON string back into an object
return JSON.parse(jsonString)
}
export function validateSecretKey(obj) {
// Check if the input is an object
if (typeof obj !== "object" || obj === null) {
return false
}
// Iterate over each key in the object
for (let key in obj) {
// Ensure the key is a string representation of a positive integer
if (!/^\d+$/.test(key)) {
return false
}
// Get the corresponding value for the key
const value = obj[key]
// Check that value is an object and not null
if (typeof value !== "object" || value === null) {
return false
}
// Check for messageKey
if (!value.hasOwnProperty("messageKey")) {
return false
}
// Ensure messageKey and nonce are non-empty strings
if (typeof value.messageKey !== "string" || value.messageKey.trim() === "") {
return false
}
}
// If all checks passed, return true
return true
}
// Function to create a symmetric key and nonce
export const createSymmetricKeyAndNonce = () => {
const messageKey = new Uint8Array(32) // 32 bytes for the symmetric key
crypto.getRandomValues(messageKey)
return { messageKey: uint8ArrayToBase64(messageKey)}
}
export const encryptDataGroup = (data64, publicKeys, privateKey, userPublicKey, customSymmetricKey) => {
let combinedPublicKeys = [...publicKeys, userPublicKey]
const decodedPrivateKey = Base58.decode(privateKey)
const publicKeysDuplicateFree = [...new Set(combinedPublicKeys)]
const Uint8ArrayData = base64ToUint8Array(data64)
if (!(Uint8ArrayData instanceof Uint8Array)) {
throw new Error("The Uint8ArrayData you've submitted is invalid")
}
try {
// Generate a random symmetric key for the message.
let messageKey
if(customSymmetricKey){
messageKey = base64ToUint8Array(customSymmetricKey)
} else {
messageKey = new Uint8Array(32)
crypto.getRandomValues(messageKey)
}
if(!messageKey) throw new Error('Cannot create symmetric key')
const nonce = new Uint8Array(24)
crypto.getRandomValues(nonce)
// Encrypt the data with the symmetric key.
const encryptedData = nacl.secretbox(Uint8ArrayData, nonce, messageKey)
// Generate a keyNonce outside of the loop.
const keyNonce = new Uint8Array(24)
crypto.getRandomValues(keyNonce)
// Encrypt the symmetric key for each recipient.
let encryptedKeys = []
publicKeysDuplicateFree.forEach((recipientPublicKey) => {
const publicKeyUnit8Array = Base58.decode(recipientPublicKey)
const convertedPrivateKey = ed2curve.convertSecretKey(decodedPrivateKey)
const convertedPublicKey = ed2curve.convertPublicKey(publicKeyUnit8Array)
const sharedSecret = new Uint8Array(32)
// the length of the sharedSecret will be 32 + 16
// When you're encrypting data using nacl.secretbox, it's adding an authentication tag to the result, which is 16 bytes long. This tag is used for verifying the integrity and authenticity of the data when it is decrypted
nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey)
// Encrypt the symmetric key with the shared secret.
const encryptedKey = nacl.secretbox(messageKey, keyNonce, sharedSecret)
encryptedKeys.push(encryptedKey)
})
const str = "qortalGroupEncryptedData"
const strEncoder = new TextEncoder()
const strUint8Array = strEncoder.encode(str)
// Convert sender's public key to Uint8Array and add to the message
const senderPublicKeyUint8Array = Base58.decode(userPublicKey)
// Combine all data into a single Uint8Array.
// Calculate size of combinedData
let combinedDataSize = strUint8Array.length + nonce.length + keyNonce.length + senderPublicKeyUint8Array.length + encryptedData.length + 4
let encryptedKeysSize = 0
encryptedKeys.forEach((key) => {
encryptedKeysSize += key.length
})
combinedDataSize += encryptedKeysSize
let combinedData = new Uint8Array(combinedDataSize)
combinedData.set(strUint8Array)
combinedData.set(nonce, strUint8Array.length)
combinedData.set(keyNonce, strUint8Array.length + nonce.length)
combinedData.set(senderPublicKeyUint8Array, strUint8Array.length + nonce.length + keyNonce.length)
combinedData.set(encryptedData, strUint8Array.length + nonce.length + keyNonce.length + senderPublicKeyUint8Array.length)
// Initialize offset for encryptedKeys
let encryptedKeysOffset = strUint8Array.length + nonce.length + keyNonce.length + senderPublicKeyUint8Array.length + encryptedData.length
encryptedKeys.forEach((key) => {
combinedData.set(key, encryptedKeysOffset)
encryptedKeysOffset += key.length
})
const countArray = new Uint8Array(new Uint32Array([publicKeysDuplicateFree.length]).buffer)
combinedData.set(countArray, combinedData.length - 4)
return uint8ArrayToBase64(combinedData)
} catch (error) {
console.log('error', error)
throw new Error("Error in encrypting data")
}
}
export const encryptSingle = async (data64, secretKeyObject, typeNumber = 2) => {
// Find the highest key in the secretKeyObject
const highestKey = Math.max(...Object.keys(secretKeyObject).filter(item => !isNaN(+item)).map(Number))
const highestKeyObject = secretKeyObject[highestKey]
// Convert data and keys from base64
const Uint8ArrayData = base64ToUint8Array(data64)
const messageKey = base64ToUint8Array(highestKeyObject.messageKey)
if (!(Uint8ArrayData instanceof Uint8Array)) {
throw new Error("The Uint8ArrayData you've submitted is invalid")
}
let nonce, encryptedData, encryptedDataBase64, finalEncryptedData
// Convert type number to a fixed length of 3 digits
const typeNumberStr = typeNumber.toString().padStart(3, '0')
if (highestKeyObject.nonce) {
// Old format: Use the nonce from secretKeyObject
nonce = base64ToUint8Array(highestKeyObject.nonce)
// Encrypt the data with the existing nonce and message key
encryptedData = nacl.secretbox(Uint8ArrayData, nonce, messageKey)
encryptedDataBase64 = uint8ArrayToBase64(encryptedData)
// Concatenate the highest key, type number, and encrypted data (old format)
const highestKeyStr = highestKey.toString().padStart(10, '0') // Fixed length of 10 digits
finalEncryptedData = btoa(highestKeyStr + encryptedDataBase64)
} else {
// New format: Generate a random nonce and embed it in the message
nonce = new Uint8Array(24) // 24 bytes for the nonce
crypto.getRandomValues(nonce)
// Encrypt the data with the new nonce and message key
encryptedData = nacl.secretbox(Uint8ArrayData, nonce, messageKey)
encryptedDataBase64 = uint8ArrayToBase64(encryptedData)
// Convert the nonce to base64
const nonceBase64 = uint8ArrayToBase64(nonce)
// Concatenate the highest key, type number, nonce, and encrypted data (new format)
const highestKeyStr = highestKey.toString().padStart(10, '0') // Fixed length of 10 digits
const highestKeyBytes = new TextEncoder().encode(highestKeyStr.padStart(10, '0'))
const typeNumberBytes = new TextEncoder().encode(typeNumberStr.padStart(3, '0'))
// Step 3: Concatenate all binary
const combinedBinary = new Uint8Array(
highestKeyBytes.length + typeNumberBytes.length + nonce.length + encryptedData.length
)
// finalEncryptedData = btoa(highestKeyStr) + btoa(typeNumberStr) + nonceBase64 + encryptedDataBase64
combinedBinary.set(highestKeyBytes, 0)
combinedBinary.set(typeNumberBytes, highestKeyBytes.length)
combinedBinary.set(nonce, highestKeyBytes.length + typeNumberBytes.length)
combinedBinary.set(encryptedData, highestKeyBytes.length + typeNumberBytes.length + nonce.length)
// Step 4: Base64 encode once
finalEncryptedData = uint8ArrayToBase64(combinedBinary)
}
return finalEncryptedData
}
export const decodeBase64ForUIChatMessages = (messages) => {
let msgs = []
for(const msg of messages) {
try {
const decoded = atob(msg.data)
const parseDecoded =JSON.parse(decodeURIComponent(escape(decoded)))
msgs.push({
...msg,
...parseDecoded
})
} catch (error) {
}
}
return msgs
}
export function decryptSingle(data64, secretKeyObject, skipDecodeBase64) {
// First, decode the base64-encoded input (if skipDecodeBase64 is not set)
const decodedData = skipDecodeBase64 ? data64 : atob(data64)
// Then, decode it again for the specific format (if double encoding is used)
const decodeForNumber = atob(decodedData)
// Extract the key (assuming it's always the first 10 characters)
const keyStr = decodeForNumber.slice(0, 10)
// Convert the key string back to a number
const highestKey = parseInt(keyStr, 10)
// Check if we have a valid secret key for the extracted highestKey
if (!secretKeyObject[highestKey]) {
throw new Error('Cannot find correct secretKey')
}
const secretKeyEntry = secretKeyObject[highestKey]
let typeNumberStr, nonceBase64, encryptedDataBase64
// Determine if typeNumber exists by checking if the next 3 characters after keyStr are digits
const possibleTypeNumberStr = decodeForNumber.slice(10, 13)
const hasTypeNumber = /^\d{3}$/.test(possibleTypeNumberStr) // Check if next 3 characters are digits
if (secretKeyEntry.nonce) {
// Old format: nonce is present in the secretKeyObject, so no type number exists
nonceBase64 = secretKeyEntry.nonce
encryptedDataBase64 = decodeForNumber.slice(10) // The remaining part is the encrypted data
} else {
if (hasTypeNumber) {
// const typeNumberStr = new TextDecoder().decode(typeNumberBytes)
if(decodeForNumber.slice(10, 13) !== '001'){
const decodedBinary = base64ToUint8Array(decodedData)
const highestKeyBytes = decodedBinary.slice(0, 10) // if ASCII digits only
const highestKeyStr = new TextDecoder().decode(highestKeyBytes)
const nonce = decodedBinary.slice(13, 13 + 24)
const encryptedData = decodedBinary.slice(13 + 24)
const highestKey = parseInt(highestKeyStr, 10)
const messageKey = base64ToUint8Array(secretKeyObject[+highestKey].messageKey)
const decryptedBytes = nacl.secretbox.open(encryptedData, nonce, messageKey)
// Check if decryption was successful
if (!decryptedBytes) {
throw new Error("Decryption failed")
}
// Convert the decrypted Uint8Array back to a Base64 string
return uint8ArrayToBase64(decryptedBytes)
}
// New format: Extract type number and nonce
typeNumberStr = possibleTypeNumberStr // Extract type number
nonceBase64 = decodeForNumber.slice(13, 45) // Extract nonce (next 32 characters after type number)
encryptedDataBase64 = decodeForNumber.slice(45) // The remaining part is the encrypted data
} else {
// Old format without type number (nonce is embedded in the message, first 32 characters after keyStr)
nonceBase64 = decodeForNumber.slice(10, 42) // First 32 characters for the nonce
encryptedDataBase64 = decodeForNumber.slice(42) // The remaining part is the encrypted data
}
}
// Convert Base64 strings to Uint8Array
const Uint8ArrayData = base64ToUint8Array(encryptedDataBase64)
const nonce = base64ToUint8Array(nonceBase64)
const messageKey = base64ToUint8Array(secretKeyEntry.messageKey)
if (!(Uint8ArrayData instanceof Uint8Array)) {
throw new Error("The Uint8ArrayData you've submitted is invalid")
}
// Decrypt the data using the nonce and messageKey
const decryptedData = nacl.secretbox.open(Uint8ArrayData, nonce, messageKey)
// Check if decryption was successful
if (!decryptedData) {
throw new Error("Decryption failed")
}
// Convert the decrypted Uint8Array back to a Base64 string
return uint8ArrayToBase64(decryptedData)
}
export const decryptGroupEncryptionWithSharingKey = async (data64EncryptedData, key) => {
const allCombined = base64ToUint8Array(data64EncryptedData)
const str = "qortalGroupEncryptedData"
const strEncoder = new TextEncoder()
const strUint8Array = strEncoder.encode(str)
// Extract the nonce
const nonceStartPosition = strUint8Array.length
const nonceEndPosition = nonceStartPosition + 24 // Nonce is 24 bytes
const nonce = allCombined.slice(nonceStartPosition, nonceEndPosition)
// Extract the shared keyNonce
const keyNonceStartPosition = nonceEndPosition
const keyNonceEndPosition = keyNonceStartPosition + 24 // Nonce is 24 bytes
const keyNonce = allCombined.slice(keyNonceStartPosition, keyNonceEndPosition)
// Extract the sender's public key
const senderPublicKeyStartPosition = keyNonceEndPosition
const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 // Public keys are 32 bytes
// Calculate count first
const countStartPosition = allCombined.length - 4 // 4 bytes before the end, since count is stored in Uint32 (4 bytes)
const countArray = allCombined.slice(countStartPosition, countStartPosition + 4)
const count = new Uint32Array(countArray.buffer)[0]
// Then use count to calculate encryptedData
const encryptedDataStartPosition = senderPublicKeyEndPosition // start position of encryptedData
const encryptedDataEndPosition = allCombined.length - ((count * (32 + 16)) + 4)
const encryptedData = allCombined.slice(encryptedDataStartPosition, encryptedDataEndPosition)
const symmetricKey = base64ToUint8Array(key)
// Decrypt the data using the nonce and messageKey
const decryptedData = nacl.secretbox.open(encryptedData, nonce, symmetricKey)
// Check if decryption was successful
if (!decryptedData) {
throw new Error("Decryption failed")
}
// Convert the decrypted Uint8Array back to a Base64 string
return uint8ArrayToBase64(decryptedData)
}
export function decryptGroupDataQortalRequest(data64EncryptedData, privateKey) {
const allCombined = base64ToUint8Array(data64EncryptedData)
const str = "qortalGroupEncryptedData"
const strEncoder = new TextEncoder()
const strUint8Array = strEncoder.encode(str)
// Extract the nonce
const nonceStartPosition = strUint8Array.length
const nonceEndPosition = nonceStartPosition + 24 // Nonce is 24 bytes
const nonce = allCombined.slice(nonceStartPosition, nonceEndPosition)
// Extract the shared keyNonce
const keyNonceStartPosition = nonceEndPosition
const keyNonceEndPosition = keyNonceStartPosition + 24 // Nonce is 24 bytes
const keyNonce = allCombined.slice(keyNonceStartPosition, keyNonceEndPosition)
// Extract the sender's public key
const senderPublicKeyStartPosition = keyNonceEndPosition
const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 // Public keys are 32 bytes
const senderPublicKey = allCombined.slice(senderPublicKeyStartPosition, senderPublicKeyEndPosition)
// Calculate count first
const countStartPosition = allCombined.length - 4 // 4 bytes before the end, since count is stored in Uint32 (4 bytes)
const countArray = allCombined.slice(countStartPosition, countStartPosition + 4)
const count = new Uint32Array(countArray.buffer)[0]
// Then use count to calculate encryptedData
const encryptedDataStartPosition = senderPublicKeyEndPosition // start position of encryptedData
const encryptedDataEndPosition = allCombined.length - ((count * (32 + 16)) + 4)
const encryptedData = allCombined.slice(encryptedDataStartPosition, encryptedDataEndPosition)
// Extract the encrypted keys
// 32+16 = 48
const combinedKeys = allCombined.slice(encryptedDataEndPosition, encryptedDataEndPosition + (count * 48))
if (!privateKey) {
throw new Error("Unable to retrieve keys")
}
const decodedPrivateKey = Base58.decode(privateKey)
const convertedPrivateKey = ed2curve.convertSecretKey(decodedPrivateKey)
const convertedSenderPublicKey = ed2curve.convertPublicKey(senderPublicKey)
const sharedSecret = new Uint8Array(32)
nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedSenderPublicKey)
for (let i = 0; i < count; i++) {
const encryptedKey = combinedKeys.slice(i * 48, (i + 1) * 48)
// Decrypt the symmetric key.
const decryptedKey = nacl.secretbox.open(encryptedKey, keyNonce, sharedSecret)
// If decryption was successful, decryptedKey will not be null.
if (decryptedKey) {
// Decrypt the data using the symmetric key.
const decryptedData = nacl.secretbox.open(encryptedData, nonce, decryptedKey)
// If decryption was successful, decryptedData will not be null.
if (decryptedData) {
return decryptedData
}
}
}
throw new Error("Unable to decrypt data")
}
export function decryptGroupData(data64EncryptedData, privateKey) {
const allCombined = base64ToUint8Array(data64EncryptedData)
const str = "qortalGroupEncryptedData"
const strEncoder = new TextEncoder()
const strUint8Array = strEncoder.encode(str)
// Extract the nonce
const nonceStartPosition = strUint8Array.length
const nonceEndPosition = nonceStartPosition + 24 // Nonce is 24 bytes
const nonce = allCombined.slice(nonceStartPosition, nonceEndPosition)
// Extract the shared keyNonce
const keyNonceStartPosition = nonceEndPosition
const keyNonceEndPosition = keyNonceStartPosition + 24 // Nonce is 24 bytes
const keyNonce = allCombined.slice(keyNonceStartPosition, keyNonceEndPosition)
// Extract the sender's public key
const senderPublicKeyStartPosition = keyNonceEndPosition
const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 // Public keys are 32 bytes
const senderPublicKey = allCombined.slice(senderPublicKeyStartPosition, senderPublicKeyEndPosition)
// Calculate count first
const countStartPosition = allCombined.length - 4 // 4 bytes before the end, since count is stored in Uint32 (4 bytes)
const countArray = allCombined.slice(countStartPosition, countStartPosition + 4)
const count = new Uint32Array(countArray.buffer)[0]
// Then use count to calculate encryptedData
const encryptedDataStartPosition = senderPublicKeyEndPosition // start position of encryptedData
const encryptedDataEndPosition = allCombined.length - ((count * (32 + 16)) + 4)
const encryptedData = allCombined.slice(encryptedDataStartPosition, encryptedDataEndPosition)
// Extract the encrypted keys
// 32+16 = 48
const combinedKeys = allCombined.slice(encryptedDataEndPosition, encryptedDataEndPosition + (count * 48))
if (!privateKey) {
throw new Error("Unable to retrieve keys")
}
const decodedPrivateKey = Base58.decode(privateKey)
const convertedPrivateKey = ed2curve.convertSecretKey(decodedPrivateKey)
const convertedSenderPublicKey = ed2curve.convertPublicKey(senderPublicKey)
const sharedSecret = new Uint8Array(32)
nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedSenderPublicKey)
for (let i = 0; i < count; i++) {
const encryptedKey = combinedKeys.slice(i * 48, (i + 1) * 48)
// Decrypt the symmetric key.
const decryptedKey = nacl.secretbox.open(encryptedKey, keyNonce, sharedSecret)
// If decryption was successful, decryptedKey will not be null.
if (decryptedKey) {
// Decrypt the data using the symmetric key.
const decryptedData = nacl.secretbox.open(encryptedData, nonce, decryptedKey)
// If decryption was successful, decryptedData will not be null.
if (decryptedData) {
return {decryptedData, count}
}
}
}
throw new Error("Unable to decrypt data")
}
export function uint8ArrayStartsWith(uint8Array, string) {
const stringEncoder = new TextEncoder()
const stringUint8Array = stringEncoder.encode(string)
if (uint8Array.length < stringUint8Array.length) {
return false
}
for (let i = 0; i < stringUint8Array.length; i++) {
if (uint8Array[i] !== stringUint8Array[i]) {
return false
}
}
return true
}
export function decryptDeprecatedSingle(uint8Array, publicKey, privateKey) {
const combinedData = uint8Array
const str = "qortalEncryptedData"
const strEncoder = new TextEncoder()
const strUint8Array = strEncoder.encode(str)
const strData = combinedData.slice(0, strUint8Array.length)
const nonce = combinedData.slice(strUint8Array.length, strUint8Array.length + 24)
const _encryptedData = combinedData.slice(strUint8Array.length + 24)
const _publicKey = window.parent.Base58.decode(publicKey)
if (!privateKey || !_publicKey) {
throw new Error("Unable to retrieve keys")
}
const convertedPrivateKey = ed2curve.convertSecretKey(privateKey)
const convertedPublicKey = ed2curve.convertPublicKey(_publicKey)
const sharedSecret = new Uint8Array(32)
nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey)
const _chatEncryptionSeed = new window.parent.Sha256().process(sharedSecret).finish().result
const _decryptedData = nacl.secretbox.open(_encryptedData, nonce, _chatEncryptionSeed)
if (!_decryptedData) {
throw new Error("Unable to decrypt")
}
return uint8ArrayToBase64(_decryptedData)
}

View File

@ -1,4 +1,11 @@
import { Sha256 } from 'asmcrypto.js'
import {
uint8ArrayToBase64,
base64ToUint8Array,
uint8ArrayToObject,
decryptSingle
} from './GroupEncryption.js'
const nacl = {}
@ -2741,7 +2748,8 @@ self.addEventListener('message', async (e) => {
eachMessage,
e.data.isReceipient,
e.data._publicKey,
e.data.privateKey
e.data.privateKey,
e.data.secretKeys
)
})
postMessage(decodeMsgs)
@ -2754,7 +2762,8 @@ self.addEventListener('message', async (e) => {
}
})
const decode = (string) => {
const decode = (string, keys, ref) => {
const binaryString = atob(string)
const binaryLength = binaryString.length
const bytes = new Uint8Array(binaryLength)
@ -2765,8 +2774,66 @@ const decode = (string) => {
const decoder = new TextDecoder()
const decodedString = decoder.decode(bytes)
if (decodedString.includes("messageText") || decodedString === "4001") {
if (decodedString === "4001") {
const firstString = 'First group key created.'
const hubString = '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + firstString + '"}]}]},"images":[""],"repliedTo":"","version":3}'
return hubString
} else {
return decodedString
}
} else {
let repliedToStr = ''
let messageStr = ''
let hubString = ''
const res = decryptSingle(string, keys, false)
const decryptToUnit8Array = base64ToUint8Array(res)
const responseData = uint8ArrayToObject(decryptToUnit8Array)
if (responseData.type === "notification") {
const messageStrRaw = responseData.data.message
messageStr = messageStrRaw.trim()
}
if (ref !== "noref") {
if (responseData.type === "reaction") {
repliedToStr = ref
messageStr = responseData.content
}
}
if (responseData.hasOwnProperty('message') && typeof responseData['message'] === 'string' && responseData['message'].length) {
const messageStrRaw = responseData.message
const messageJoin1 = messageStrRaw.split('"').join('<upvote>')
const messageReplace1 = messageJoin1.replace('<p>', '')
const messageReplace2 = messageReplace1.replace('</p>', '')
const messageTrim = messageReplace2.trim()
const messageJoin2 = messageTrim.split('<br><br>').join('"},{"type":"hardBreak"},{"type":"hardBreak"},{"type":"text","text":"')
const messageJoin3 = messageJoin2.split('<br>').join('"},{"type":"hardBreak"},{"type":"text","text":"')
messageStr = messageJoin3
}
if (responseData.repliedTo) {
repliedToStr = responseData.repliedTo
}
if (responseData.type === "edit") {
hubString = '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + messageStr + '"}]}]},"images":[""],"repliedTo":"' + repliedToStr + '","version":3,"isEdited":true}'
} else if (responseData.type === "reaction") {
hubString = '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + messageStr + '"}]}]},"images":[""],"repliedTo":"' + repliedToStr + '","version":3,"isReaction":true}'
} else {
hubString = '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + messageStr + '"}]}]},"images":[""],"repliedTo":"' + repliedToStr + '","version":3}'
}
const preparedString = hubString.split('<upvote>').join('\\"')
const finalString = preparedString.replace(/<\/?[^>]+(>|$)/g, '')
return finalString
}
}
export const decryptChatMessageBase64 = (encryptedMessage, privateKey, recipientPublicKey, lastReference) => {
let _encryptedMessage = atob(encryptedMessage)
@ -2822,7 +2889,7 @@ export const decryptChatMessageBase64 = (encryptedMessage, privateKey, recipient
return new TextDecoder('utf-8').decode(_decryptedMessage)
}
const decodeMessage = (encodedMessageObj, isReceipient, _publicKey, privateKey) => {
const decodeMessage = (encodedMessageObj, isReceipient, _publicKey, privateKey, secretKeys) => {
let isReceipientVar
let _publicKeyVar
@ -2847,7 +2914,7 @@ const decodeMessage = (encodedMessageObj, isReceipient, _publicKey, privateKey)
)
decodedMessageObj = { ...encodedMessageObj, decodedMessage }
} else if (encodedMessageObj.isEncrypted === false && encodedMessageObj.data) {
let decodedMessage = decode(encodedMessageObj.data)
let decodedMessage = decode(encodedMessageObj.data, secretKeys)
decodedMessageObj = { ...encodedMessageObj, decodedMessage }
} else {
decodedMessageObj = {
@ -2857,7 +2924,16 @@ const decodeMessage = (encodedMessageObj, isReceipient, _publicKey, privateKey)
}
} else {
// group chat
let decodedMessage = decode(encodedMessageObj.data)
let decodedMessage
const noRef = "noref"
if (encodedMessageObj.chatReference) {
decodedMessage = decode(encodedMessageObj.data, secretKeys, encodedMessageObj.chatReference)
} else {
decodedMessage = decode(encodedMessageObj.data, secretKeys, noRef)
}
decodedMessageObj = { ...encodedMessageObj, decodedMessage }
}

View File

@ -5,6 +5,15 @@ import { Epml } from '../../../epml'
import { generateHTML } from '@tiptap/core'
import { roundToNearestDecimal } from '../../utils/functions'
import { groupManagementStyles } from '../components/plugins-css'
import {
decryptGroupData,
uint8ArrayToBase64,
base64ToUint8Array,
uint8ArrayToObject,
validateSecretKey,
decryptSingle
} from '../components/GroupEncryption'
import Base58 from '../../../../crypto/api/deps/Base58'
import isElectron from 'is-electron'
import Highlight from '@tiptap/extension-highlight'
import ShortUniqueId from 'short-unique-id'
@ -56,6 +65,7 @@ class GroupManagement extends LitElement {
manageGroupObj: { type: Object },
joinGroupObj: { type: Object },
leaveGroupObj: { type: Object },
secretKeys: { type: Object },
btnDisable: { type: Boolean },
isLoading: { type: Boolean },
createGroupFee: { type: Number },
@ -130,6 +140,7 @@ class GroupManagement extends LitElement {
this.manageGroupObj = {}
this.joinGroupObj = {}
this.leaveGroupObj = {}
this.secretKeys = {}
this.recipientPublicKey = ''
this.btnDisable = false
this.isLoading = false
@ -754,36 +765,61 @@ class GroupManagement extends LitElement {
}
const getGroupInvites = async () => {
let timerGroupInvites
let invitedGroupInfo = []
let myGroupInvites = []
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
let timerGroupInvites
let myInvitesObj = []
let myArrObj = []
this.myGroupInvites = []
this.myGroupIdArr = []
this.myInvites = []
this.myInvitesFilter = []
this.myJoinedGroups = []
this.myOpenInvites = []
await parentEpml.request('apiCall', {
url: `/groups/invites/${this.selectedAddress.address}`
}).then(res => {
this.myGroupInvites = res
url: `/transactions/search?txType=GROUP_INVITE&address=${this.selectedAddress.address}&confirmationStatus=CONFIRMED&limit=0&reverse=false`
}).then(response => {
this.myInvites = response
})
if (this.isEmptyArray(this.myGroupInvites) === true) {
this.myInvitesFilter = this.myInvites.filter(elm => {
return elm.invitee === this.selectedAddress.address
})
this.myJoinedGroups = await getJoinedGroups()
this.myOpenInvites = this.myInvitesFilter.filter(myOpenGroup => {
let value = this.myJoinedGroups.some(myJoinedGroup => myOpenGroup.groupId === myJoinedGroup.groupId)
return !value
})
if (this.isEmptyArray(this.myOpenInvites) === true) {
clearTimeout(timerGroupInvites)
timerGroupInvites = setTimeout(getGroupInvites, 300000)
} else {
const currentTime = Date.now()
this.myGroupInvites.forEach(a => {
if (a.expiry > currentTime) {
let callTheNewInviteUrl = `${nodeUrl}/groups/${a.groupId}`
fetch(callTheNewInviteUrl).then(res => {
this.myOpenInvites.forEach(a => {
let expiry = a.timestamp + a.timeToLive
if (expiry > currentTime || a.timeToLive === 0) {
let invitedGroupInfoUrl = `${nodeUrl}/groups/${a.groupId}`
fetch(invitedGroupInfoUrl).then(res => {
return res.json()
}).then(jsonRes => {
myArrObj.push(jsonRes)
if (myArrObj.length) {
myArrObj.forEach(b => {
const infoObjToAdd = {
invitedGroupInfo.push(jsonRes)
if (invitedGroupInfo.length) {
let newExpiry
if (a.timeToLive === 0) {
newExpiry = 4070912471000
} else {
newExpiry = expiry
}
invitedGroupInfo.forEach(b => {
const groupInfoObj = {
invitee: a.invitee,
groupId: b.groupId,
owner: b.owner,
@ -792,12 +828,12 @@ class GroupManagement extends LitElement {
created: b.created,
isOpen: b.isOpen,
memberCount: b.memberCount,
expiry: a.expiry
expiry: newExpiry
}
myInvitesObj.push(infoObjToAdd)
myGroupInvites.push(groupInfoObj)
})
}
this.groupInvites = myInvitesObj
this.groupInvites = myGroupInvites
})
}
})
@ -2820,9 +2856,89 @@ class GroupManagement extends LitElement {
this.chatInfoId = groupObj.groupId
this.chatInfoMembers = groupObj.memberCount
this.shadowRoot.getElementById('downloadProgressDialog').open()
await this.getSymKeyFile(groupObj.groupId)
await this.getChatContent(groupObj.groupId)
}
async getSymKeyFile(groupId) {
this.secretKeys = {}
this.groupAdmins = {}
let data
let supArray = []
let gAdmin = ''
let gAddress = ''
const symIdentifier = 'symmetric-qchat-group-' + groupId
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const getNameUrl = `${nodeUrl}/arbitrary/resources?service=DOCUMENT_PRIVATE&identifier=${symIdentifier}&limit=1&reverse=true`
const getAdminUrl = `${nodeUrl}/groups/members/${groupId}?onlyAdmins=true&limit=20`
supArray = await fetch(getNameUrl).then(response => {
return response.json()
})
if (this.isEmptyArray(supArray) || groupId === 0) {
console.log("No Symetric Key")
} else {
supArray.map(item => {
gAdmin = item.name
})
const addressUrl = `${nodeUrl}/names/${gAdmin}`
let addressObject = await fetch(addressUrl).then(response => {
return response.json()
})
gAddress = addressObject.owner
let adminRes = await fetch(getAdminUrl).then(response => {
return response.json()
})
this.groupAdmins = adminRes.members
const adminExists = (adminAddress) => {
return this.groupAdmins.some(function(checkAdmin) {
return checkAdmin.member === adminAddress
})
}
if (adminExists(gAddress)) {
const dataUrl = `${nodeUrl}/arbitrary/DOCUMENT_PRIVATE/${gAdmin}/${symIdentifier}?encoding=base64&rebuild=true&async=true`
const res = await fetch(dataUrl)
data = await res.text()
const decryptedKey = await this.decryptGroupEncryption(data)
const dataint8Array = base64ToUint8Array(decryptedKey.data)
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array)
if (!validateSecretKey(decryptedKeyToObject)) {
throw new Error("SecretKey is not valid")
}
this.secretKeys = decryptedKeyToObject
}
}
}
async decryptGroupEncryption(data) {
try {
const privateKey = Base58.encode(window.parent.reduxStore.getState().app.wallet._addresses[0].keyPair.privateKey)
const encryptedData = decryptGroupData(data, privateKey)
return {
data: uint8ArrayToBase64(encryptedData.decryptedData),
count: encryptedData.count
}
} catch (error) {
console.log("Error:", error.message)
}
}
async openPreviewGeneral() {
this.chatInfoName = 'Qortal General Chat'
this.chatInfoId = 0
@ -2866,8 +2982,57 @@ class GroupManagement extends LitElement {
const decoder = new TextDecoder()
const decodedString = decoder.decode(bytes)
if (decodedString.includes("messageText") || decodedString === "4001") {
if (decodedString === "4001") {
const firstString = 'First group key created.'
const hubString = '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + firstString + '"}]}]},"images":[""],"repliedTo":"","version":3}'
return hubString
} else {
return decodedString
}
} else {
let repliedToStr = ''
let messageStr = ''
let hubString = ''
const res = decryptSingle(string, this.secretKeys, false)
const decryptToUnit8Array = base64ToUint8Array(res)
const responseData = uint8ArrayToObject(decryptToUnit8Array)
if (responseData.type === "notification") {
const messageStrRaw = responseData.data.message
messageStr = messageStrRaw.trim()
}
if (responseData.hasOwnProperty('message') && typeof responseData['message'] === 'string' && responseData['message'].length) {
const messageStrRaw = responseData.message
const messageJoin1 = messageStrRaw.split('"').join('<upvote>')
const messageReplace1 = messageJoin1.replace('<p>', '')
const messageReplace2 = messageReplace1.replace('</p>', '')
const messageTrim = messageReplace2.trim()
const messageJoin2 = messageTrim.split('<br><br>').join('"},{"type":"hardBreak"},{"type":"hardBreak"},{"type":"text","text":"')
const messageJoin3 = messageJoin2.split('<br>').join('"},{"type":"hardBreak"},{"type":"text","text":"')
messageStr = messageJoin3
}
if (responseData.repliedTo) {
repliedToStr = responseData.repliedTo
}
if (responseData.type === "edit") {
hubString = '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + messageStr + '"}]}]},"images":[""],"repliedTo":"' + repliedToStr + '","version":3,"isEdited":true}'
} else {
hubString = '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + messageStr + '"}]}]},"images":[""],"repliedTo":"' + repliedToStr + '","version":3}'
}
const preparedString = hubString.split('<upvote>').join('\\"')
const finalString = preparedString.replace(/<\/?[^>]+(>|$)/g, '')
return finalString
}
}
async getChatContent(involved) {
let chatArray = []

View File

@ -480,7 +480,8 @@ class NodeManagement extends LitElement {
updateMintingAccounts() {
this.mintingAccounts = []
parentEpml.request('apiCall', {
url: `/admin/mintingaccounts`
url: `/admin/mintingaccounts?apiKey=${this.getApiKey()}`,
method: 'GET'
}).then((res) => {
this.mintingAccounts = res
})

View File

@ -5,12 +5,21 @@ import { passiveSupport } from 'passive-events-support/src/utils'
import { Editor, Extension } from '@tiptap/core'
import { supportCountryFlagEmojis } from '../components/ChatEmojiFlags'
import { qchatStyles } from '../components/plugins-css'
import {
decryptGroupData,
uint8ArrayToBase64,
base64ToUint8Array,
uint8ArrayToObject,
validateSecretKey
} from '../components/GroupEncryption'
import Base58 from '../../../../crypto/api/deps/Base58'
import isElectron from 'is-electron'
import WebWorker from 'web-worker:./computePowWorker'
import StarterKit from '@tiptap/starter-kit'
import Underline from '@tiptap/extension-underline'
import Placeholder from '@tiptap/extension-placeholder'
import Highlight from '@tiptap/extension-highlight'
import Mention from '@tiptap/extension-mention'
import ShortUniqueId from 'short-unique-id'
import snackbar from '../components/snackbar'
import '../components/ChatWelcomePage'
@ -429,8 +438,116 @@ class Chat extends LitElement {
}
async setActiveChatHeadUrl(url) {
await this.getSymKeyFile(url)
}
async getSymKeyFile(url) {
this.secretKeys = {}
this.groupAdmins = {}
let data
let supArray = []
let gAdmin = ''
let gAddress = ''
let currentGroupId = url.substring(6)
let symIdentifier = 'symmetric-qchat-group-' + currentGroupId
let locateString = "Downloading and decrypt keys ! Please wait..."
let keysToOld = "Wait until an admin re-encrypts the keys. Only unencrypted messages will be displayed."
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const getNameUrl = `${nodeUrl}/arbitrary/resources?service=DOCUMENT_PRIVATE&identifier=${symIdentifier}&limit=1&reverse=true`
const getAdminUrl = `${nodeUrl}/groups/members/${currentGroupId}?onlyAdmins=true&limit=20`
if (localStorage.getItem("symKeysCurrent") === null) {
localStorage.setItem("symKeysCurrent", "")
}
supArray = await fetch(getNameUrl).then(response => {
return response.json()
})
if (this.isEmptyArray(supArray) || currentGroupId === 0) {
this.activeChatHeadUrl = url
this.requestUpdate()
} else {
parentEpml.request('showSnackBar', `${locateString}`)
supArray.map(item => {
gAdmin = item.name
})
const addressUrl = `${nodeUrl}/names/${gAdmin}`
let addressObject = await fetch(addressUrl).then(response => {
return response.json()
})
gAddress = addressObject.owner
let adminRes = await fetch(getAdminUrl).then(response => {
return response.json()
})
this.groupAdmins = adminRes.members
const adminExists = (adminAddress) => {
return this.groupAdmins.some(function(checkAdmin) {
return checkAdmin.member === adminAddress
})
}
if (adminExists(gAddress)) {
const dataUrl = `${nodeUrl}/arbitrary/DOCUMENT_PRIVATE/${gAdmin}/${symIdentifier}?encoding=base64&rebuild=true&async=true`
const res = await fetch(dataUrl)
data = await res.text()
const decryptedKey = await this.decryptGroupEncryption(data)
if (decryptedKey === undefined) {
parentEpml.request('showSnackBar', `${keysToOld}`)
this.activeChatHeadUrl = url
this.requestUpdate()
} else {
const dataint8Array = base64ToUint8Array(decryptedKey.data)
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array)
if (!validateSecretKey(decryptedKeyToObject)) {
throw new Error("SecretKey is not valid")
}
localStorage.removeItem("symKeysCurrent")
localStorage.setItem("symKeysCurrent", "")
let oldSymIdentifier = JSON.parse(localStorage.getItem("symKeysCurrent") || "[]")
oldSymIdentifier.push(decryptedKeyToObject)
localStorage.setItem("symKeysCurrent", JSON.stringify(oldSymIdentifier))
let arraySecretKeys = JSON.parse(localStorage.getItem("symKeysCurrent") || "[]")
this.secretKeys = arraySecretKeys[0]
this.activeChatHeadUrl = url
this.requestUpdate()
}
} else {
this.activeChatHeadUrl = url
this.requestUpdate()
}
}
}
async decryptGroupEncryption(data) {
try {
const privateKey = Base58.encode(window.parent.reduxStore.getState().app.wallet._addresses[0].keyPair.privateKey)
const encryptedData = decryptGroupData(data, privateKey)
return {
data: uint8ArrayToBase64(encryptedData.decryptedData),
count: encryptedData.count
}
} catch (error) {
console.log("Error:", error.message)
}
}
resetChatEditor() {
@ -461,6 +578,7 @@ class Chat extends LitElement {
StarterKit,
Underline,
Highlight,
Mention,
Placeholder.configure({
placeholder: 'Write something …'
}),

View File

@ -56,7 +56,7 @@ export const getUserNameFromAddress = async (address) => {
}
}
export const replaceMessagesEdited = async ({ decodedMessages, parentEpml, isReceipient, decodeMessageFunc, _publicKey, addToUpdateMessageHashmap }) => {
export const replaceMessagesEdited = async ({ decodedMessages, parentEpml, isReceipient, decodeMessageFunc, _publicKey, symKeys, addToUpdateMessageHashmap }) => {
const MAX_CONCURRENT_REQUESTS = 5 // Maximum number of concurrent requests
const executeWithConcurrencyLimit = async (array, asyncFn) => {
@ -82,7 +82,6 @@ export const replaceMessagesEdited = async ({ decodedMessages, parentEpml, isRec
const findUpdatedMessage = async (msg) => {
let msgItem = { ...msg }
try {
let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}`
if (!isReceipient) {
@ -96,7 +95,7 @@ export const replaceMessagesEdited = async ({ decodedMessages, parentEpml, isRec
})
if (Array.isArray(newMsgResponse) && newMsgResponse.length > 0) {
const decodeResponseItem = decodeMessageFunc(newMsgResponse[0], isReceipient, _publicKey)
const decodeResponseItem = decodeMessageFunc(newMsgResponse[0], isReceipient, _publicKey, symKeys)
delete decodeResponseItem.timestamp
@ -143,8 +142,8 @@ export const replaceMessagesEdited = async ({ decodedMessages, parentEpml, isRec
})
if (originalReplyMessage && Array.isArray(replyResponse) && replyResponse.length !== 0) {
const decodeOriginalReply = decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey)
const decodeUpdatedReply = decodeMessageFunc(replyResponse[0], isReceipient, _publicKey)
const decodeOriginalReply = decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey, symKeys)
const decodeUpdatedReply = decodeMessageFunc(replyResponse[0], isReceipient, _publicKey, symKeys)
msgItem.repliedToData = {
...decodeUpdatedReply,
@ -152,7 +151,7 @@ export const replaceMessagesEdited = async ({ decodedMessages, parentEpml, isRec
sender: decodeOriginalReply.sender
}
} else if (originalReplyMessage) {
msgItem.repliedToData = decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey)
msgItem.repliedToData = decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey, symKeys)
}
}
@ -165,9 +164,10 @@ export const replaceMessagesEdited = async ({ decodedMessages, parentEpml, isRec
}
const sortedMessages = decodedMessages.sort((a, b) => b.timestamp - a.timestamp)
const withoutHubReactions = sortedMessages.filter(({decodedMessage}) => !decodedMessage.includes('isReaction'))
// Execute the functions with concurrency limit
const updatedMessages = await executeWithConcurrencyLimit(sortedMessages, findUpdatedMessage)
const updatedMessages = await executeWithConcurrencyLimit(withoutHubReactions, findUpdatedMessage)
addToUpdateMessageHashmap(updatedMessages)
return updatedMessages