Browse Source

save settings to qdn

master
PhilReact 11 months ago
parent
commit
454e83cded
  1. 5
      core/language/us.json
  2. 3
      core/src/components/app-view.js
  3. 92
      core/src/components/friends-view/computePowWorkerFile.src.js
  4. 34
      core/src/components/friends-view/friends-feed.js
  5. 69
      core/src/components/friends-view/friends-view.js
  6. 322
      core/src/components/friends-view/save-settings-qdn.js
  7. 3
      core/src/components/qort-theme-toggle.js
  8. 1
      core/src/components/show-plugin.js
  9. 1
      core/src/components/theme-toggle.js
  10. 41
      plugins/plugins/core/components/qdn-action-encryption.js

5
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"
}
}

3
core/src/components/app-view.js

@ -560,10 +560,10 @@ class AppView extends connect(store)(LitElement) {
</span>
</div>
<div style="display:flex;align-items:center;gap:20px">
<!-- <save-settings-qdn></save-settings-qdn> -->
<friends-side-panel-parent></friends-side-panel-parent>
<notification-bell></notification-bell>
<notification-bell-general></notification-bell-general>
<save-settings-qdn></save-settings-qdn>
<core-sync-status></core-sync-status>
</div>
<div style="display: inline;">
@ -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) || "[]")

92
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
}

34
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) {

69
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`
<div class="container">
<div id="viewElement" class="container-body" style=${"position: relative"}>

322
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&timestamp=${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`
<mwc-icon @click=${()=> {
this.isOpen = !this.isOpen
}} style="color: var(--black); cursor:pointer;user-select:none"
${this.isSaving || (!this.error && this.resourceExists === undefined) ? html`
<paper-spinner-lite active style="display: block; margin: 0 auto;"></paper-spinner-lite>
` : html`
<mwc-icon id="save-icon" class=${Object.values(this.valuesToBeSavedOnQdn).length > 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</mwc-icon
>
<vaadin-tooltip
for="save-icon"
position="bottom"
hover-delay=${300}
hide-delay=${1}
text=${this.error ? get('save.saving1') : Object.values(this.valuesToBeSavedOnQdn).length > 0 || this.resourceExists === false ? get('save.saving3') : get('save.saving2')}>
</vaadin-tooltip>
`}
`;

3
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,

1
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')

1
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,

41
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 }) => {

Loading…
Cancel
Save