diff --git a/qortal-ui-core/language/us.json b/qortal-ui-core/language/us.json index 37e80f84..38f36797 100644 --- a/qortal-ui-core/language/us.json +++ b/qortal-ui-core/language/us.json @@ -488,7 +488,8 @@ "cchange26": "File size exceeds 5 MB", "cchange27": "A registered name is required to send images", "cchange28": "This file is not an image", - "cchange29": "Cancel" + "cchange29": "Maximum message size is 1000 bytes", + "cchange30": "Cancel" }, "welcomepage": { "wcchange1": "Welcome to Q-Chat", diff --git a/qortal-ui-plugins/build-config.js b/qortal-ui-plugins/build-config.js index 2949988b..35506107 100644 --- a/qortal-ui-plugins/build-config.js +++ b/qortal-ui-plugins/build-config.js @@ -8,6 +8,8 @@ const commonjs = require('@rollup/plugin-commonjs'); const alias = require('@rollup/plugin-alias'); const { terser } = require('rollup-plugin-terser'); const babel = require('@rollup/plugin-babel'); +const webWorkerLoader = require('rollup-plugin-web-worker-loader'); + const aliases = {}; @@ -40,6 +42,7 @@ const generateRollupConfig = (inputFile, outputFile) => { commonjs(), globals(), progress(), + webWorkerLoader(), babel.babel({ babelHelpers: 'bundled', exclude: 'node_modules/**', diff --git a/qortal-ui-plugins/package.json b/qortal-ui-plugins/package.json index c81ac3c8..b1694c27 100644 --- a/qortal-ui-plugins/package.json +++ b/qortal-ui-plugins/package.json @@ -19,6 +19,7 @@ "dependencies": { "@material/mwc-list": "0.27.0", "@material/mwc-select": "0.27.0", + "asmcrypto.js": "2.3.2", "compressorjs": "^1.1.1", "emoji-picker-js": "https://github.com/Qortal/emoji-picker-js", "localforage": "^1.10.0", @@ -60,7 +61,8 @@ "rollup": "2.79.1", "rollup-plugin-node-globals": "1.4.0", "rollup-plugin-progress": "1.1.2", - "rollup-plugin-terser": "7.0.2" + "rollup-plugin-terser": "7.0.2", + "rollup-plugin-web-worker-loader": "^1.6.1" }, "engines": { "node": ">=16.15.0" diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index 7e12adac..7d2a1a6e 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -21,7 +21,10 @@ import '@material/mwc-dialog' import '@material/mwc-icon' import { replaceMessagesEdited } from '../../utils/replace-messages-edited.js'; import { publishData } from '../../utils/publish-image.js'; +import WebWorker from 'web-worker:./computePowWorker.js'; +import WebWorkerImage from 'web-worker:./computePowWorkerImage.js'; +// hello const messagesCache = localForage.createInstance({ name: "messages-cache", }); @@ -516,7 +519,7 @@ class ChatPage extends LitElement { this.imageFile = null }} > - ${translate("chatpage.cchange29")} + ${translate("chatpage.cchange30")} = 256) { + } else if (this.chatMessageSize >= 1000) { this.isLoading = false; this.chatEditor.enable(); - let err1string = get("chatpage.cchange24"); + let err1string = get("chatpage.cchange29"); parentEpml.request('showSnackBar', `${err1string}`); } else if (this.repliedToMessageObj) { let chatReference = this.repliedToMessageObj.reference @@ -1688,23 +1699,33 @@ class ChatPage extends LitElement { }; const _computePow = async (chatBytes) => { - const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; }); - const chatBytesArray = new Uint8Array(_chatBytesArray); - const chatBytesHash = new window.parent.Sha256().process(chatBytesArray).finish().result; - const hashPtr = window.parent.sbrk(32, window.parent.heap); - const hashAry = new Uint8Array(window.parent.memory.buffer, hashPtr, 32); - hashAry.set(chatBytesHash); - const difficulty = this.balance === 0 ? 12 : 8; - const workBufferLength = 8 * 1024 * 1024; - const workBufferPtr = window.parent.sbrk(workBufferLength, window.parent.heap); - let nonce = window.parent.computePow(hashPtr, workBufferPtr, workBufferLength, difficulty); + const path = window.parent.location.origin + '/memory-pow/memory-pow.wasm.full' + const worker = new WebWorker(); + let nonce = null + let chatBytesArray = null + await new Promise((res, rej) => { + console.log({chatBytes}) + worker.postMessage({chatBytes, path, difficulty}); + + worker.onmessage = e => { + + + worker.terminate() + chatBytesArray = e.data.chatBytesArray + nonce = e.data.nonce + res() + + } + }) let _response = await parentEpml.request('sign_chat', { nonce: this.selectedAddress.nonce, chatBytesArray: chatBytesArray, chatNonce: nonce }); + + getSendChatResponse(_response); }; @@ -1951,30 +1972,34 @@ class ChatPage extends LitElement { if (e.type === 'paste') { e.preventDefault(); const item_list = await navigator.clipboard.read(); - console.log({item_list}) let image_type; // we will feed this later const item = item_list.find( item => // choose the one item holding our image - item.types.some( type => { // does this item have our type + item.types.some( type => { if (type.startsWith( 'image/')) { - image_type = type; // store which kind of image type it is + image_type = type; return true; } }) ); - const blob = item && await item.getType( image_type ); + if(item){ + const blob = item && await item.getType( image_type ); var file = new File([blob], "name", { type: image_type }); editorConfig.insertImage(file) - navigator.clipboard.readText().then(clipboardText => { - let escapedText = editorConfig.escape(clipboardText); - editor.insertText(escapedText); - }).catch(err => { - // Fallback if everything fails... - let textData = (e.originalEvent || e).clipboardData.getData('text/plain'); - editor.insertText(textData); - }) + } else { + navigator.clipboard.readText().then(clipboardText => { + let escapedText = editorConfig.escape(clipboardText); + editor.insertText(escapedText); + }).catch(err => { + // Fallback if everything fails... + let textData = (e.originalEvent || e).clipboardData.getData('text/plain'); + editor.insertText(textData); + }) + } + + return false; } diff --git a/qortal-ui-plugins/plugins/core/components/computePowWorker.js b/qortal-ui-plugins/plugins/core/components/computePowWorker.js new file mode 100644 index 00000000..96db8beb --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/computePowWorker.js @@ -0,0 +1,83 @@ +import { Sha256 } from 'asmcrypto.js' + + +function sbrk(size, heap){ + let brk = 512 * 1024 // stack top + let old = brk + brk += size + + if (brk > heap.length) + throw new Error('heap exhausted') + + return old +} + + + +self.addEventListener('message', async e => { + console.log({data: e.data}) + const response = await computePow(e.data.chatBytes, e.data.path, e.data.difficulty) + postMessage(response) + +}) + + +const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }) +const heap = new Uint8Array(memory.buffer) + + + +const computePow = async (chatBytes, path, difficulty) => { + + let response = null + + await new Promise((resolve, reject)=> { + + const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; }); + const chatBytesArray = new Uint8Array(_chatBytesArray); + const chatBytesHash = new Sha256().process(chatBytesArray).finish().result; + const hashPtr = sbrk(32, heap); + const hashAry = new Uint8Array(memory.buffer, hashPtr, 32); + hashAry.set(chatBytesHash); + + + const workBufferLength = 8 * 1024 * 1024; + const workBufferPtr = sbrk(workBufferLength, heap); + + + + const importObject = { + env: { + memory: memory + }, + }; + + function loadWebAssembly(filename, imports) { + // Fetch the file and compile it + return fetch(filename) + .then(response => response.arrayBuffer()) + .then(buffer => WebAssembly.compile(buffer)) + .then(module => { + + // Create the instance. + return new WebAssembly.Instance(module, importObject); + }); +} + + +loadWebAssembly(path) + .then(wasmModule => { + response = { + nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty), + chatBytesArray + } + + resolve() + + }); + + + }) + + return response +} \ No newline at end of file diff --git a/qortal-ui-plugins/plugins/core/components/computePowWorkerImage.js b/qortal-ui-plugins/plugins/core/components/computePowWorkerImage.js new file mode 100644 index 00000000..e008361b --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/computePowWorkerImage.js @@ -0,0 +1,93 @@ +import { Sha256 } from 'asmcrypto.js' + + + +function sbrk(size, heap){ + let brk = 512 * 1024 // stack top + let old = brk + brk += size + + if (brk > heap.length) + throw new Error('heap exhausted') + + return old +} + + + + +self.addEventListener('message', async e => { + console.log({data: e.data}) + const response = await computePow(e.data.convertedBytes, e.data.path) + postMessage(response) + +}) + + +const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }) +const heap = new Uint8Array(memory.buffer) + + + +const computePow = async (convertedBytes, path) => { + + + let response = null + + await new Promise((resolve, reject)=> { + + const _convertedBytesArray = Object.keys(convertedBytes).map( + function (key) { + return convertedBytes[key] + } +) +const convertedBytesArray = new Uint8Array(_convertedBytesArray) +const convertedBytesHash = new Sha256() + .process(convertedBytesArray) + .finish().result +const hashPtr = sbrk(32, heap) +const hashAry = new Uint8Array( + memory.buffer, + hashPtr, + 32 +) + +hashAry.set(convertedBytesHash) +const difficulty = 14 +const workBufferLength = 8 * 1024 * 1024 +const workBufferPtr = sbrk( + workBufferLength, + heap +) + + const importObject = { + env: { + memory: memory + }, + }; + + function loadWebAssembly(filename, imports) { + return fetch(filename) + .then(response => response.arrayBuffer()) + .then(buffer => WebAssembly.compile(buffer)) + .then(module => { + return new WebAssembly.Instance(module, importObject); + }); +} + +console.log({path}) +loadWebAssembly(path) + .then(wasmModule => { + response = { + nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty), + + } + resolve() + + }); + + + }) + + return response +} \ No newline at end of file diff --git a/qortal-ui-plugins/plugins/utils/publish-image.js b/qortal-ui-plugins/plugins/utils/publish-image.js index 362b65ef..05594d1c 100644 --- a/qortal-ui-plugins/plugins/utils/publish-image.js +++ b/qortal-ui-plugins/plugins/utils/publish-image.js @@ -14,9 +14,9 @@ export const publishData = async ({ service, identifier, parentEpml, - metaData, uploadType, selectedAddress, + worker }) => { const validateName = async (receiverName) => { let nameRes = await parentEpml.request("apiCall", { @@ -47,35 +47,23 @@ export const publishData = async ({ const convertedBytes = window.parent.Base58.decode(convertedBytesBase58) - const _convertedBytesArray = Object.keys(convertedBytes).map( - function (key) { - return convertedBytes[key] - } - ) - const convertedBytesArray = new Uint8Array(_convertedBytesArray) - const convertedBytesHash = new window.parent.Sha256() - .process(convertedBytesArray) - .finish().result - const hashPtr = window.parent.sbrk(32, window.parent.heap) - const hashAry = new Uint8Array( - window.parent.memory.buffer, - hashPtr, - 32 - ) - - hashAry.set(convertedBytesHash) - const difficulty = 14 - const workBufferLength = 8 * 1024 * 1024 - const workBufferPtr = window.parent.sbrk( - workBufferLength, - window.parent.heap - ) - let nonce = window.parent.computePow( - hashPtr, - workBufferPtr, - workBufferLength, - difficulty - ) + let nonce = null + const computPath =window.parent.location.origin + '/memory-pow/memory-pow.wasm.full' + await new Promise((res, rej) => { + + worker.postMessage({convertedBytes, path: computPath}); + + worker.onmessage = e => { + + worker.terminate() + + nonce = e.data.nonce + res() + + } + }) + + let response = await parentEpml.request("sign_arbitrary", { nonce: selectedAddress.nonce, arbitraryBytesBase58: transactionBytesBase58, @@ -131,18 +119,7 @@ export const publishData = async ({ postBody = Buffer.from(fileBuffer).toString("base64") } - // Optional metadata - - // let title = encodeURIComponent(metaData.title || "") - // let description = encodeURIComponent(metaData.description || "") - // let category = encodeURIComponent(metaData.category || "") - // let tag1 = encodeURIComponent(metaData.tag1 || "") - // let tag2 = encodeURIComponent(metaData.tag2 || "") - // let tag3 = encodeURIComponent(metaData.tag3 || "") - // let tag4 = encodeURIComponent(metaData.tag4 || "") - // let tag5 = encodeURIComponent(metaData.tag5 || "") - - // let metadataQueryString = `title=${title}&description=${description}&category=${category}&tags=${tag1}&tags=${tag2}&tags=${tag3}&tags=${tag4}&tags=${tag5}` + let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}?apiKey=${getApiKey()}` if (identifier != null && identifier.trim().length > 0) { @@ -157,5 +134,10 @@ export const publishData = async ({ return uploadDataRes } } - await validate() + try { + await validate() + } catch (error) { + throw new Error(error.message) + } + }