From 454e83cdedb11720299c0a9fa8b0d5971d10db14 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 20 Oct 2023 03:07:36 +0300 Subject: [PATCH] save settings to qdn --- core/language/us.json | 5 + core/src/components/app-view.js | 3 +- .../friends-view/computePowWorkerFile.src.js | 92 +++++ .../components/friends-view/friends-feed.js | 34 +- .../components/friends-view/friends-view.js | 69 +++- .../friends-view/save-settings-qdn.js | 322 +++++++++++++++++- core/src/components/qort-theme-toggle.js | 3 +- core/src/components/show-plugin.js | 1 + core/src/components/theme-toggle.js | 1 + .../core/components/qdn-action-encryption.js | 41 +++ 10 files changed, 559 insertions(+), 12 deletions(-) create mode 100644 core/src/components/friends-view/computePowWorkerFile.src.js diff --git a/core/language/us.json b/core/language/us.json index c9ceae75..e3dcd6ee 100644 --- a/core/language/us.json +++ b/core/language/us.json @@ -1199,5 +1199,10 @@ "friend16": "Select the Q-Apps you want updates from, especially those related to your friends.", "friends17": "Friends", "friends18": "No items in your feed" + }, + "save": { + "saving1": "Unable to fetch saved settings", + "saving2": "Nothing to save", + "saving3": "Save unsaved changes" } } \ No newline at end of file diff --git a/core/src/components/app-view.js b/core/src/components/app-view.js index 55bcec24..bb103eca 100644 --- a/core/src/components/app-view.js +++ b/core/src/components/app-view.js @@ -560,10 +560,10 @@ class AppView extends connect(store)(LitElement) {
- +
@@ -738,6 +738,7 @@ class AppView extends connect(store)(LitElement) { await this.botArrrTradebook() window.addEventListener('storage', async () => { + console.log('testing') this.tradeBotBtcBook = JSON.parse(localStorage.getItem(this.botBtcWallet) || "[]") this.tradeBotLtcBook = JSON.parse(localStorage.getItem(this.botLtcWallet) || "[]") this.tradeBotDogeBook = JSON.parse(localStorage.getItem(this.botDogeWallet) || "[]") diff --git a/core/src/components/friends-view/computePowWorkerFile.src.js b/core/src/components/friends-view/computePowWorkerFile.src.js new file mode 100644 index 00000000..d9f5f662 --- /dev/null +++ b/core/src/components/friends-view/computePowWorkerFile.src.js @@ -0,0 +1,92 @@ +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 => { + 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); + }); +} + + +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/core/src/components/friends-view/friends-feed.js b/core/src/components/friends-view/friends-feed.js index a56463dc..10bfcd24 100644 --- a/core/src/components/friends-view/friends-feed.js +++ b/core/src/components/friends-view/friends-feed.js @@ -19,7 +19,8 @@ class FriendsFeed extends connect(store)(LitElement) { feed: {type: Array}, setHasNewFeed: {attribute:false}, isLoading: {type: Boolean}, - hasFetched: {type: Boolean} + hasFetched: {type: Boolean}, + mySelectedFeeds: {type: Array} }; } constructor(){ @@ -38,6 +39,7 @@ class FriendsFeed extends connect(store)(LitElement) { this.mySelectedFeeds = [] this.getSchemas = this.getSchemas.bind(this) this.hasFetched = false + this._updateFeeds = this._updateFeeds.bind(this) } @@ -66,6 +68,24 @@ class FriendsFeed extends connect(store)(LitElement) { return myNode; } + _updateFeeds(event) { + const detail = event.detail + console.log('detail2', detail) + this.mySelectedFeeds = detail + this.reFetchFeedData() + this.requestUpdate() + } + + connectedCallback() { + super.connectedCallback() + console.log('callback') + window.addEventListener('friends-my-selected-feeds-event', this._updateFeeds) } + + disconnectedCallback() { + window.removeEventListener('friends-my-selected-feeds-event', this._updateFeeds) + super.disconnectedCallback() + } + async getSchemas(){ this.mySelectedFeeds = JSON.parse(localStorage.getItem('friends-my-selected-feeds') || "[]") const schemas = this.mySelectedFeeds @@ -100,7 +120,7 @@ class FriendsFeed extends connect(store)(LitElement) { stop = false; } }; - interval = setInterval(getAnswer, 600000); + interval = setInterval(getAnswer, 900000); } @@ -144,9 +164,17 @@ class FriendsFeed extends connect(store)(LitElement) { try { - await this.getEndpoints() + await new Promise(()=> { + setTimeout((res) => { + res() + }, 5000); + }) + if(this.mySelectedFeeds.length === 0){ + await this.getEndpoints() this.loadAndMergeData(); + } + this.getFeedOnInterval() } catch (error) { diff --git a/core/src/components/friends-view/friends-view.js b/core/src/components/friends-view/friends-view.js index 119d8933..ab90fe32 100644 --- a/core/src/components/friends-view/friends-view.js +++ b/core/src/components/friends-view/friends-view.js @@ -70,6 +70,9 @@ class FriendsView extends connect(store)(LitElement) { this.isOpenAddFriendsModal = false this.editContent = null this.addToFriendList = this.addToFriendList.bind(this) + this._updateFriends = this._updateFriends.bind(this) + this._updateFeed = this._updateFeed.bind(this) + } getNodeUrl() { @@ -100,6 +103,35 @@ class FriendsView extends connect(store)(LitElement) { this.elementObserver(); this.mySelectedFeeds = JSON.parse(localStorage.getItem('friends-my-selected-feeds') || "[]") this.friendList = JSON.parse(localStorage.getItem('friends-my-friend-list') || "[]") + + + + + } + + _updateFriends(event) { + const detail = event.detail + this.friendList = detail + } + _updateFeed(event) { + console.log({event}) + const detail = event.detail + console.log({detail}) + this.mySelectedFeeds = detail + this.requestUpdate() + } + + connectedCallback() { + super.connectedCallback() + console.log('callback') + window.addEventListener('friends-my-friend-list-event', this._updateFriends) + window.addEventListener('friends-my-selected-feeds-event', this._updateFeed) + } + + disconnectedCallback() { + window.removeEventListener('friends-my-friend-list-event', this._updateFriends) + window.addEventListener('friends-my-selected-feeds-event', this._updateFeed) + super.disconnectedCallback() } elementObserver() { @@ -150,7 +182,7 @@ class FriendsView extends connect(store)(LitElement) { ]; } this.userFoundModalOpen = true; - } catch (error) { + } catch (error) { // let err4string = get("chatpage.cchange35"); // parentEpml.request('showSnackBar', `${err4string}`) } @@ -199,7 +231,7 @@ class FriendsView extends connect(store)(LitElement) { return ret } - addToFriendList(val, isRemove){ + async addToFriendList(val, isRemove){ const copyVal = {...val} delete copyVal.mySelectedFeeds if(isRemove){ @@ -222,6 +254,11 @@ class FriendsView extends connect(store)(LitElement) { this.myFollowName(copyVal.name) } this.setMySelectedFeeds(val.mySelectedFeeds) + await new Promise((res)=> { + setTimeout(()=> { + res() + },50) + }) this.userSelected = {}; this.shadowRoot.getElementById('sendTo').value = '' this.isLoading = false; @@ -234,9 +271,36 @@ class FriendsView extends connect(store)(LitElement) { } setMyFriends(friendList){ localStorage.setItem('friends-my-friend-list', JSON.stringify(friendList)); + const tempSettingsData= JSON.parse(localStorage.getItem('temp-settings-data') || "{}") + const newTemp = { + ...tempSettingsData, + userLists: { + data: [friendList], + timestamp: Date.now() + } + } + + localStorage.setItem('temp-settings-data', JSON.stringify(newTemp)); + this.dispatchEvent( + new CustomEvent('temp-settings-data-event', { + bubbles: true, + composed: true + }), + ); + } setMySelectedFeeds(mySelectedFeeds){ this.mySelectedFeeds = mySelectedFeeds + const tempSettingsData= JSON.parse(localStorage.getItem('temp-settings-data') || "{}") + const newTemp = { + ...tempSettingsData, + friendsFeed: { + data: mySelectedFeeds, + timestamp: Date.now() + } + } + + localStorage.setItem('temp-settings-data', JSON.stringify(newTemp)); localStorage.setItem('friends-my-selected-feeds', JSON.stringify(mySelectedFeeds)); } openEditFriend(val){ @@ -253,6 +317,7 @@ class FriendsView extends connect(store)(LitElement) { } render() { + console.log('rendered1') return html`
diff --git a/core/src/components/friends-view/save-settings-qdn.js b/core/src/components/friends-view/save-settings-qdn.js index b7b4516e..4f2531f8 100644 --- a/core/src/components/friends-view/save-settings-qdn.js +++ b/core/src/components/friends-view/save-settings-qdn.js @@ -1,10 +1,27 @@ import { LitElement, html, css } from 'lit'; import '@material/mwc-icon'; import './friends-side-panel.js'; -class SaveSettingsQdn extends LitElement { +import { connect } from 'pwa-helpers'; +import { store } from '../../store.js'; +import WebWorker from 'web-worker:./computePowWorkerFile.src.js' +import '@polymer/paper-spinner/paper-spinner-lite.js' +import '@vaadin/tooltip'; +import { + get +} from 'lit-translate'; +import { decryptGroupData, encryptDataGroup, objectToBase64, uint8ArrayToBase64, uint8ArrayToObject } from '../../../../plugins/plugins/core/components/qdn-action-encryption.js'; +import { publishData } from '../../../../plugins/plugins/utils/publish-image.js'; +import { parentEpml } from '../show-plugin.js'; + +class SaveSettingsQdn extends connect(store)(LitElement) { static get properties() { return { - isOpen: {type: Boolean} + isOpen: {type: Boolean}, + syncPercentage: {type: Number}, + settingsRawData: {type: Object}, + valuesToBeSavedOnQdn: {type: Object}, + resourceExists: {type: Boolean}, + isSaving: {type: Boolean} }; } @@ -12,6 +29,19 @@ class SaveSettingsQdn extends LitElement { constructor() { super(); this.isOpen = false + this.getGeneralSettingsQdn = this.getGeneralSettingsQdn.bind(this) + this._updateTempSettingsData = this._updateTempSettingsData.bind(this) + this.setValues = this.setValues.bind(this) + this.saveToQdn = this.saveToQdn.bind(this) + this.syncPercentage = 0 + this.hasRetrievedResource = false + this.hasAttemptedToFetchResource = false + this.resourceExists = undefined + this.settingsRawData = null + this.nodeUrl = this.getNodeUrl() + this.myNode = this.getMyNode() + this.valuesToBeSavedOnQdn = {} + this.isSaving = false } static styles = css` .header { @@ -40,15 +70,297 @@ class SaveSettingsQdn extends LitElement { transform: translateX(0); /* slide in to its original position */ } + .notActive { + opacity: 0.5; + cursor: default; + color: var(--black) + } + .active { + opacity: 1; + cursor: pointer; + color: green; + } `; +getNodeUrl(){ + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + + const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port + return nodeUrl +} +getMyNode(){ + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + + return myNode +} + +async getRawData (dataItem){ + const url = `${this.nodeUrl}/arbitrary/${dataItem.service}/${dataItem.name}/${dataItem.identifier}?encoding=base64`; + const res = await fetch(url) + const data = await res.text() + if(data.error) throw new Error('Cannot retrieve your data from qdn') + const decryptedData = decryptGroupData(data) + const decryptedDataToBase64 = uint8ArrayToObject(decryptedData) + return decryptedDataToBase64 +} + +async setValues(response, resource){ + this.settingsRawData = response + const rawDataTimestamp = resource.updated + + const tempSettingsData = JSON.parse(localStorage.getItem('temp-settings-data') || "{}") + if(tempSettingsData){ + + } + const userLists = response.userLists || [] + const friendsFeed = response.friendsFeed + + this.valuesToBeSavedOnQdn = {} + if(userLists.length > 0 && (!tempSettingsData.userLists || (tempSettingsData.userLists && (tempSettingsData.userLists.timestamp < rawDataTimestamp)))){ + const friendList = userLists[0] + localStorage.setItem('friends-my-friend-list', JSON.stringify(friendList)); + this.dispatchEvent( + new CustomEvent('friends-my-friend-list-event', { + bubbles: true, + composed: true, + detail: friendList, + }), + ); + } else if(tempSettingsData.userLists && tempSettingsData.userLists.timestamp > rawDataTimestamp){ + this.valuesToBeSavedOnQdn = { + ...this.valuesToBeSavedOnQdn, + userLists: { + data: tempSettingsData.userLists.data + } + } + } + + if(friendsFeed && (!tempSettingsData.friendsFeed || ( tempSettingsData.friendsFeed && (tempSettingsData.friendsFeed.timestamp < rawDataTimestamp)))){ + localStorage.setItem('friends-my-selected-feeds', JSON.stringify(friendsFeed)); + this.dispatchEvent( + new CustomEvent('friends-my-selected-feeds-event', { + bubbles: true, + composed: true, + detail: friendsFeed, + }), + ); + } else if(tempSettingsData.friendsFeed && tempSettingsData.friendsFeed.timestamp > rawDataTimestamp){ + this.valuesToBeSavedOnQdn = { + ...this.valuesToBeSavedOnQdn, + friendsFeed: { + data: tempSettingsData.friendsFeed.data + } + } + } +} + +async getGeneralSettingsQdn(){ + try { + this.hasAttemptedToFetchResource = true + let resource + const name = "palmas" + this.error = '' + const url = `${this.nodeUrl}/arbitrary/resources/search?service=DOCUMENT_PRIVATE&identifier=qortal_general_settings&name=${name}&prefix=true&exactmatchnames=true&excludeblocked=true&limit=20` + const res = await fetch(url) + let data = '' + try { + data = await res.json() + if(Array.isArray(data)){ + data = data.filter((item)=> item.identifier === 'qortal_general_settings') + + if(data.length > 0){ + + this.resourceExists = true + const dataItem = data[0] + try { + const response = await this.getRawData(dataItem) + console.log({response}) + if(response.version){ + this.setValues(response, dataItem) + } else { + this.error = "Cannot get saved user settings" + } + } catch (error) { + console.log({error}) + this.error = "Cannot get saved user settings" + } + } else { + this.resourceExists = false + } + } else { + this.error = "Unable to perform query" + } + } catch (error) { + data = { + error: 'No resource found' + } + } + + if(resource){ + this.hasRetrievedResource = true + } + } catch (error) { + console.log({error}) + + } +} + + + +stateChanged(state) { + if(state.app.nodeStatus && state.app.nodeStatus.syncPercent !== this.syncPercentage){ + this.syncPercentage = state.app.nodeStatus.syncPercent + + if(!this.hasAttemptedToFetchResource && state.app.nodeStatus.syncPercent === 100){ + console.log('hello') + this.getGeneralSettingsQdn() + } + + } +} + +async getArbitraryFee (){ + const timestamp = Date.now() + const url = `${this.nodeUrl}/transactions/unitfee?txType=ARBITRARY×tamp=${timestamp}` + const response = await fetch(url) + if (!response.ok) { + throw new Error('Error when fetching arbitrary fee'); + } + const data = await response.json() + const arbitraryFee = (Number(data) / 1e8).toFixed(8) + return { + timestamp, + fee : Number(data), + feeToShow: arbitraryFee + } +} + +async saveToQdn(){ + try { + this.isSaving = true + if(this.resourceExists === true && this.error) throw new Error('Unable to save') + + console.log('info', store.getState()) + const nameObject = store.getState().app.accountInfo.names[0] + if(!nameObject) throw new Error('no name') + const name = nameObject.name + console.log({name}) + const identifer = 'qortal_general_settings' + const filename = 'qortal_general_settings.json' + const selectedAddress = store.getState().app.selectedAddress + console.log({selectedAddress}) + const getArbitraryFee = await this.getArbitraryFee() + const feeAmount = getArbitraryFee.fee + console.log({feeAmount}) + const friendsList = JSON.parse(localStorage.getItem('friends-my-friend-list') || "[]") + const friendsFeed = JSON.parse(localStorage.getItem('friends-my-selected-feeds') || "[]") + + let newObject + + if(this.resourceExists === false){ + newObject = { + version: 1, + userLists: [friendsList], + friendsFeed + } + } else if(this.settingsRawData) { + const tempSettingsData= JSON.parse(localStorage.getItem('temp-settings-data') || "{}") + console.log({tempSettingsData}) + newObject = { + ...this.settingsRawData, + } + for (const key in tempSettingsData) { + if (tempSettingsData[key].hasOwnProperty('data')) { + newObject[key] = tempSettingsData[key].data; + } + } + + } + + console.log({newObject}) + const newObjectToBase64 = await objectToBase64(newObject); + console.log({newObjectToBase64}) + const encryptedData = encryptDataGroup({data64: newObjectToBase64, publicKeys: []}) + + console.log({encryptedData}) + const worker = new WebWorker(); + try { + const resPublish = await publishData({ + registeredName: encodeURIComponent(name), + file: encryptedData, + service: 'DOCUMENT_PRIVATE', + identifier: encodeURIComponent(identifer), + parentEpml: parentEpml, + uploadType: 'file', + selectedAddress: selectedAddress, + worker: worker, + isBase64: true, + filename: filename, + apiVersion: 2, + withFee: true, + feeAmount: feeAmount + }); + + this.resourceExists = true + this.setValues(newObject, { + updated: Date.now() + }) + localStorage.setItem('temp-settings-data', JSON.stringify({})); + this.valuesToBeSavedOnQdn = {} + worker.terminate(); + } catch (error) { + worker.terminate(); + + } + + + + } catch (error) { + console.log({error}) + } finally { + this.isSaving = false + } +} + +_updateTempSettingsData(){ + this.valuesToBeSavedOnQdn = JSON.parse(localStorage.getItem('temp-settings-data') || "{}") + +} + +connectedCallback() { + super.connectedCallback() + console.log('callback') + window.addEventListener('temp-settings-data-event', this._updateTempSettingsData) +} + +disconnectedCallback() { + window.removeEventListener('temp-settings-data-event', this._updateTempSettingsData) + super.disconnectedCallback() +} render() { + console.log('this.resourceExists', this.resourceExists) return html` - { - this.isOpen = !this.isOpen - }} style="color: var(--black); cursor:pointer;user-select:none" + ${this.isSaving || (!this.error && this.resourceExists === undefined) ? html` + + + ` : html` + 0 || this.resourceExists === false ? 'active' : 'notActive'} @click=${()=> { + if(Object.values(this.valuesToBeSavedOnQdn).length > 0 || this.resourceExists === false ){ + this.saveToQdn() + } + // this.isOpen = !this.isOpen + }} style="user-select:none" >save + 0 || this.resourceExists === false ? get('save.saving3') : get('save.saving2')}> + + `} + `; diff --git a/core/src/components/qort-theme-toggle.js b/core/src/components/qort-theme-toggle.js index 02833cea..6baa688c 100644 --- a/core/src/components/qort-theme-toggle.js +++ b/core/src/components/qort-theme-toggle.js @@ -109,12 +109,13 @@ class QortThemeToggle extends LitElement { toggleTheme() { + console.log('toggle') if (this.theme === 'light') { this.theme = 'dark'; } else { this.theme = 'light'; } - + console.log('dispatch') this.dispatchEvent( new CustomEvent('qort-theme-change', { bubbles: true, diff --git a/core/src/components/show-plugin.js b/core/src/components/show-plugin.js index abf21b9c..a0857176 100644 --- a/core/src/components/show-plugin.js +++ b/core/src/components/show-plugin.js @@ -532,6 +532,7 @@ class ShowPlugin extends connect(store)(LitElement) { }) window.addEventListener('storage', () => { + console.log('show plugin') const checkLanguage = localStorage.getItem('qortalLanguage') const checkTheme = localStorage.getItem('qortalTheme') diff --git a/core/src/components/theme-toggle.js b/core/src/components/theme-toggle.js index d2af6e82..8c833895 100644 --- a/core/src/components/theme-toggle.js +++ b/core/src/components/theme-toggle.js @@ -83,6 +83,7 @@ class ThemeToggle extends LitElement { } else { this.theme = 'light' } + console.log('theme toggle') this.dispatchEvent(new CustomEvent('qort-theme-change', { bubbles: true, diff --git a/plugins/plugins/core/components/qdn-action-encryption.js b/plugins/plugins/core/components/qdn-action-encryption.js index cfd7a092..06ada168 100644 --- a/plugins/plugins/core/components/qdn-action-encryption.js +++ b/plugins/plugins/core/components/qdn-action-encryption.js @@ -89,6 +89,47 @@ export function base64ToUint8Array(base64) { return bytes } +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 + const obj = JSON.parse(jsonString) + + return obj + } + + 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 const encryptData = ({ data64, recipientPublicKey }) => {