Browse Source

qortalrequest for local q-app notifications

resolve-20231003
Phillip 1 year ago
parent
commit
36ee386c82
  1. 3
      core/language/us.json
  2. 9
      core/src/components/show-plugin.js
  3. 6
      core/src/notifications/dispatcher.js
  4. 2
      core/src/notifications/notification-actions/index.js
  5. 154
      core/src/notifications/notification-actions/new-message.js
  6. 1
      core/src/notifications/types.js
  7. 2
      core/src/redux/app/version.js
  8. 11
      plugins/plugins/core/components/qdn-action-types.js
  9. 169
      plugins/plugins/core/qdn/browser/browser.src.js

3
core/language/us.json

@ -719,7 +719,8 @@
"bchange43": "Do you give this application permission to add to this list?",
"bchange44": "Do you give this application permission to delete from this list?",
"bchange45": "Encrypt",
"bchange46": "Do you give this application permission to save the following file"
"bchange46": "Do you give this application permission to save the following file",
"bchange47": "Do you give this application permission to send you notifications"
},
"datapage": {
"dchange1": "Data Management",

9
core/src/components/show-plugin.js

@ -396,14 +396,15 @@ class ShowPlugin extends connect(store)(LitElement) {
icon = 'tab'
}
if (tab.myPlugObj && (tab.myPlugObj.url === 'websites' || tab.myPlugObj.url === 'qapps') && this.tabInfo[tab.id]) {
title = this.tabInfo[tab.id].name
if (tab.myPlugObj && (tab.myPlugObj.url === 'myapp') && this.tabInfo[tab.id]) {
title = this.tabInfo[tab.id].name
}
if (tab.myPlugObj && (tab.myPlugObj.url === 'websites' || tab.myPlugObj.url === 'qapps') && this.tabInfo[tab.id]) {
count = this.tabInfo[tab.id].count
if (tab.myPlugObj && (tab.myPlugObj.url === 'myapp') && this.tabInfo[tab.id]) {
count = this.tabInfo[tab.id].count
}
if (tab.myPlugObj && tab.myPlugObj.url === 'q-chat') {
for (const chat of this.chatHeads) {

6
core/src/notifications/dispatcher.js

@ -1,5 +1,5 @@
import { NEW_MESSAGE, NEW_MESSAGE_NOTIFICATION_QAPP } from './types'
import { newMessage, newMessageNotificationQapp } from './notification-actions'
import { NEW_MESSAGE, NEW_MESSAGE_NOTIFICATION_QAPP, NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL } from './types'
import { newMessage, newMessageNotificationQapp, newMessageNotificationQappLocal } from './notification-actions'
export const dispatcher = function (notificationState) {
@ -8,6 +8,8 @@ export const dispatcher = function (notificationState) {
return newMessage(notificationState.data)
case NEW_MESSAGE_NOTIFICATION_QAPP:
return newMessageNotificationQapp(notificationState.data)
case NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL:
return newMessageNotificationQappLocal(notificationState.data)
default:
}
}

2
core/src/notifications/notification-actions/index.js

@ -1 +1 @@
export { newMessage, newMessageNotificationQapp } from './new-message'
export { newMessage, newMessageNotificationQapp, newMessageNotificationQappLocal } from './new-message'

154
core/src/notifications/notification-actions/new-message.js

@ -1,6 +1,9 @@
import { store } from '../../store.js'
import { doPageUrl, setNewTab } from '../../redux/app/app-actions.js'
import isElectron from 'is-electron'
import ShortUniqueId from 'short-unique-id';
const uid = new ShortUniqueId()
export const newMessage = (data) => {
const alert = playSound(data.sound)
@ -101,6 +104,157 @@ export const newMessageNotificationQapp = (data) => {
}
}
}
const extractComponents= async (url)=> {
if (!url.startsWith("qortal://")) {
return null;
}
url = url.replace(/^(qortal\:\/\/)/, "");
if (url.includes("/")) {
let parts = url.split("/");
const service = parts[0].toUpperCase();
parts.shift();
const name = parts[0];
parts.shift();
let identifier;
if (parts.length > 0) {
identifier = parts[0]; // Do not shift yet
// Check if a resource exists with this service, name and identifier combination
let responseObj = await parentEpml.request('apiCall', {
url: `/arbitrary/resource/status/${service}/${name}/${identifier}?apiKey=${this.getApiKey()}`
})
if (responseObj.totalChunkCount > 0) {
// Identifier exists, so don't include it in the path
parts.shift();
}
else {
identifier = null;
}
}
const path = parts.join("/");
const components = {};
components["service"] = service;
components["name"] = name;
components["identifier"] = identifier;
components["path"] = path;
return components;
}
return null;
}
export const newMessageNotificationQappLocal = (data) => {
const alert = playSound(data.sound)
// Should I show notification ?
if (store.getState().user.notifications.q_chat.showNotification) {
// Yes, I can but should I play sound ?
if (store.getState().user.notifications.q_chat.playSound) {
const notify = new Notification(data.title, data.options)
notify.onshow = (e) => {
alert.play()
}
notify.onclick = async(e) => {
const url = data?.url
const value = url
let newQuery = value;
if (newQuery.endsWith('/')) {
newQuery = newQuery.slice(0, -1);
}
const res = await extractComponents(newQuery)
if (!res) return
const { service, name, identifier, path } = res
let query = `?service=${service}`
if (name) {
query = query + `&name=${name}`
}
if (identifier) {
query = query + `&identifier=${identifier}`
}
if (path) {
query = query + `&path=${path}`
}
const tab = {
url: `qdn/browser/index.html${query}`,
id: uid(),
myPlugObj: {
"url": service === 'WEBSITE' ? "websites" : "qapps",
"domain": "core",
"page": `qdn/browser/index.html${query}`,
"title": name,
"icon": service === 'WEBSITE' ? 'vaadin:desktop' : 'vaadin:external-browser',
"mwcicon": service === 'WEBSITE' ? 'desktop_mac' : 'open_in_browser',
"menus": [],
"parent": false
}
}
store.dispatch(setNewTab(tab))
if (!isElectron()) {
window.focus();
} else {
window.electronAPI.focusApp()
}
}
} else {
const notify = new Notification(data.title, data.options)
notify.onclick = async(e) => {
const url = data?.url
const value = url
let newQuery = value;
if (newQuery.endsWith('/')) {
newQuery = newQuery.slice(0, -1);
}
const res = await extractComponents(newQuery)
if (!res) return
const { service, name, identifier, path } = res
let query = `?service=${service}`
if (name) {
query = query + `&name=${name}`
}
if (identifier) {
query = query + `&identifier=${identifier}`
}
if (path) {
query = query + `&path=${path}`
}
const tab = {
url: `qdn/browser/index.html${query}`,
id: uid(),
myPlugObj: {
"url": service === 'WEBSITE' ? "websites" : "qapps",
"domain": "core",
"page": `qdn/browser/index.html${query}`,
"title": name,
"icon": service === 'WEBSITE' ? 'vaadin:desktop' : 'vaadin:external-browser',
"mwcicon": service === 'WEBSITE' ? 'desktop_mac' : 'open_in_browser',
"menus": [],
"parent": false
}
}
store.dispatch(setNewTab(tab))
if (!isElectron()) {
window.focus();
} else {
window.electronAPI.focusApp()
}
}
}
}
}
const playSound = (soundUrl) => {

1
core/src/notifications/types.js

@ -1,2 +1,3 @@
export const NEW_MESSAGE = 'NEW_MESSAGE'
export const NEW_MESSAGE_NOTIFICATION_QAPP = 'NEW_MESSAGE_NOTIFICATION_QAPP'
export const NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL = 'NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL'

2
core/src/redux/app/version.js

@ -1 +1 @@
export const UI_VERSION = "4.0.4";
export const UI_VERSION = "4.3.0";

11
plugins/plugins/core/components/qdn-action-types.js

@ -50,4 +50,13 @@ export const DECRYPT_DATA_GROUP = 'DECRYPT_DATA_GROUP'
export const SAVE_FILE = 'SAVE_FILE'
//SET_TAB_NOTIFICATIONS
export const SET_TAB_NOTIFICATIONS = 'SET_TAB_NOTIFICATIONS'
export const SET_TAB_NOTIFICATIONS = 'SET_TAB_NOTIFICATIONS'
//OPEN_NEW_TAB
export const OPEN_NEW_TAB = 'OPEN_NEW_TAB'
//NOTIFICATIONS_PERMISSION
export const NOTIFICATIONS_PERMISSION = 'NOTIFICATIONS_PERMISSION'
//SEND_LOCAL_NOTIFICATION
export const SEND_LOCAL_NOTIFICATION = 'SEND_LOCAL_NOTIFICATION'

169
plugins/plugins/core/qdn/browser/browser.src.js

@ -3,6 +3,7 @@ import { render } from 'lit/html.js'
import { Epml } from '../../../../epml'
import isElectron from 'is-electron'
import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
import ShortUniqueId from 'short-unique-id';
registerTranslateConfig({
loader: (lang) => fetch(`/language/${lang}.json`).then((res) => res.json())
@ -131,6 +132,7 @@ class WebBrowser extends LitElement {
constructor() {
super();
this.url = 'about:blank';
this.uid = new ShortUniqueId()
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress
this._publicKey = { key: '', hasPubKey: false }
const urlParams = new URLSearchParams(window.location.search)
@ -318,6 +320,44 @@ class WebBrowser extends LitElement {
}
}
async linkOpenNewTab(link) {
const value = link
let newQuery = value;
if (newQuery.endsWith('/')) {
newQuery = newQuery.slice(0, -1);
}
const res = await this.extractComponents(newQuery)
if (!res) return
const { service, name, identifier, path } = res
let query = `?service=${service}`
if (name) {
query = query + `&name=${name}`
}
if (identifier) {
query = query + `&identifier=${identifier}`
}
if (path) {
query = query + `&path=${path}`
}
window.parent.reduxStore.dispatch(window.parent.reduxAction.setNewTab({
url: `qdn/browser/index.html${query}`,
id: this.uid(),
myPlugObj: {
"url": service === 'WEBSITE' ? "websites" : "qapps",
"domain": "core",
"page": `qdn/browser/index.html${query}`,
"title": name,
"icon": service === 'WEBSITE' ? 'vaadin:desktop' : 'vaadin:external-browser',
"mwcicon": service === 'WEBSITE' ? 'desktop_mac' : 'open_in_browser',
"menus": [],
"parent": false
}
}))
}
render() {
return html`
@ -1253,8 +1293,91 @@ class WebBrowser extends LitElement {
// If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}`
break;
}
case actions.OPEN_NEW_TAB: {
if(!data.qortalLink){
const obj = {};
const errorMsg = 'Please enter a qortal link - qortal://...';
obj['error'] = errorMsg;
response = JSON.stringify(obj);
break
}
try {
await this.linkOpenNewTab(data.qortalLink)
response = true
} catch (error) {
console.log('error', error)
const obj = {};
const errorMsg = "Invalid qortal link";
obj['error'] = errorMsg;
response = JSON.stringify(obj);
break;
}
}
case actions.NOTIFICATIONS_PERMISSION: {
try {
const res = await showModalAndWait(
actions.NOTIFICATIONS_PERMISSION,
{
name: this.name
}
);
if (res.action === 'accept'){
this.addAppToNotificationList(this.name)
response = true
break;
}
} catch (error) {
break;
}
}
case actions.SEND_LOCAL_NOTIFICATION: {
const {title, url, icon, message} = data
try {
const id = `appNotificationList-${this.selectedAddress.address}`
const checkData = localStorage.getItem(id) ? JSON.parse(localStorage.getItem(id)) : null;
if(!checkData || !checkData[this.name]) throw new Error('App not on permission list')
const appInfo = checkData[this.name]
const lastNotification = appInfo.lastNotification
const interval = appInfo.interval
if (lastNotification && interval) {
const timeDifference = Date.now() - lastNotification;
if (timeDifference > interval) {
parentEpml.request('showNotification', {
title, type: "qapp-local-notification", sound: '', url, options: { body: message, icon, badge: icon }
})
response = true
this.updateLastNotification(id, this.name)
break;
} else {
throw new Error(`duration until another notification can be sent: ${interval - timeDifference}`)
}
} else if(!lastNotification){
parentEpml.request('showNotification', {
title, type: "qapp-local-notification", sound: '', url, options: { body: message, icon, badge: icon }
})
response = true
this.updateLastNotification(id)
break;
} else {
throw new Error(`invalid data`)
}
} catch (error) {
const obj = {};
const errorMsg = error.message || "error in pushing notification";
obj['error'] = errorMsg;
response = JSON.stringify(obj);
break;
}
}
case actions.SEND_CHAT_MESSAGE: {
const message = data.message;
const recipient = data.destinationAddress;
@ -2707,6 +2830,45 @@ class WebBrowser extends LitElement {
use(checkLanguage);
}
}
addAppToNotificationList(appName) {
const id = `appNotificationList-${this.selectedAddress.address}`;
const checkData = localStorage.getItem(id) ? JSON.parse(localStorage.getItem(id)) : null;
if (!checkData) {
const newData = {
[appName]: {
interval: 900000, // 15mins in milliseconds
lastNotification: null,
},
};
localStorage.setItem(id, JSON.stringify(newData));
} else {
const copyData = { ...checkData };
copyData[appName] = {
interval: 900000, // 15mins in milliseconds
lastNotification: null,
};
localStorage.setItem(id, JSON.stringify(copyData));
}
}
updateLastNotification(id, appName) {
const checkData = localStorage.getItem(id) ? JSON.parse(localStorage.getItem(id)) : null;
if (checkData) {
const copyData = { ...checkData };
if (copyData[appName]) {
copyData[appName].lastNotification = Date.now(); // Make sure to use Date.now(), not date.now()
} else {
copyData[appName] = {
interval: 900000, // 15mins in milliseconds
lastNotification: Date.now(),
};
}
localStorage.setItem(id, JSON.stringify(copyData));
}
}
renderFollowUnfollowButton() {
// Only show the follow/unfollow button if we have permission to modify the list on this node
@ -3064,7 +3226,12 @@ async function showModalAndWait(type, data) {
<p class="modal-paragraph">${get("browserpage.bchange46")}: <span> ${data.filename}</span></p>
</div>
` : ''}
${type === actions.NOTIFICATIONS_PERMISSION ? `
<div class="modal-subcontainer">
<p class="modal-paragraph">${get("browserpage.bchange47")}</p>
</div>
` : ''}
${type === actions.DELETE_LIST_ITEM ? `
<div class="modal-subcontainer">
<p class="modal-paragraph">${get("browserpage.bchange44")}</p>

Loading…
Cancel
Save