adding push notifications to web

This commit is contained in:
balzack 2025-01-14 22:11:22 -08:00
parent 0290ae98b1
commit 8b6eb48161
7 changed files with 76 additions and 13 deletions

BIN
app/client/web/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/src/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Databag</title> <title>Databag</title>
</head> </head>

View File

@ -2,12 +2,27 @@ import { useEffect, useState, useContext, useRef } from 'react'
import { AppContext } from '../context/AppContext' import { AppContext } from '../context/AppContext'
import { DisplayContext } from '../context/DisplayContext' import { DisplayContext } from '../context/DisplayContext'
import { ContextType } from '../context/ContextType' import { ContextType } from '../context/ContextType'
import { type Profile, type Config } from 'databag-client-sdk' import { type Profile, type Config, type PushParams, PushType } from 'databag-client-sdk'
import { Point, Area } from 'react-easy-crop/types' import { Point, Area } from 'react-easy-crop/types'
const IMAGE_DIM = 192 const IMAGE_DIM = 192
const DEBOUNCE_MS = 1000 const DEBOUNCE_MS = 1000
function urlB64ToUint8Array(b64: string) {
const padding = '='.repeat((4 - b64.length % 4) % 4);
const base64 = (b64 + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (var i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
export function useSettings() { export function useSettings() {
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType
@ -46,6 +61,7 @@ export function useSettings() {
secretImage: '', secretImage: '',
code: '', code: '',
editImage: '', editImage: '',
webPushKey: null,
sealPassword: '', sealPassword: '',
sealConfirm: '', sealConfirm: '',
sealDelete: '', sealDelete: '',
@ -127,10 +143,8 @@ export function useSettings() {
updateState({ blockedMessages }); updateState({ blockedMessages });
}, },
loadBlockedChannels: async () => { loadBlockedChannels: async () => {
console.log("LOAD BLOCKED");
const settings = app.state.session.getSettings(); const settings = app.state.session.getSettings();
const blockedChannels = await settings.getBlockedChannels(); const blockedChannels = await settings.getBlockedChannels();
console.log("LOADED: ", blockedChannels);
updateState({ blockedChannels }); updateState({ blockedChannels });
}, },
unblockChannel: async (cardId: string | null, channelId: string) => { unblockChannel: async (cardId: string | null, channelId: string) => {
@ -159,8 +173,38 @@ console.log("LOADED: ", blockedChannels);
await settings.setLogin(state.handle, state.password) await settings.setLogin(state.handle, state.password)
}, },
enableNotifications: async () => { enableNotifications: async () => {
const { settings } = getSession() const webPushKey = state.config?.webPushKey;
await settings.enableNotifications() if (!webPushKey) {
throw new Error('web push key not set');
}
const status = await Notification.requestPermission();
if (status === 'granted') {
const registration = await navigator.serviceWorker.register('push.js');
await navigator.serviceWorker.ready;
const params = { userVisibleOnly: true, applicationServerKey: urlB64ToUint8Array(webPushKey) };
const subscription = await registration.pushManager.subscribe(params);
const endpoint = subscription.endpoint;
const binPublicKey = subscription.getKey('p256dh');
const binAuth = subscription.getKey('auth');
if (endpoint && binPublicKey && binAuth) {
const numPublicKey: number[] = [];
(new Uint8Array(binPublicKey)).forEach(val => {
numPublicKey.push(val);
});
const numAuth: number[] = [];
(new Uint8Array(binAuth)).forEach(val => {
numAuth.push(val);
});
const publicKey = btoa(String.fromCharCode.apply(null, numPublicKey));
const auth = btoa(String.fromCharCode.apply(null, numAuth));
const pushParams = { endpoint, publicKey, auth, type: PushType.Web };
const { settings } = getSession()
await settings.enableNotifications(pushParams)
}
}
}, },
disableNotifications: async () => { disableNotifications: async () => {
const { settings } = getSession() const { settings } = getSession()

View File

@ -1,4 +1,4 @@
import type { Channel, Topic, AssetSource, Asset, Tag, Article, Group, Card, Profile, Call, FocusDetail, Config, NodeConfig, NodeAccount, Participant } from './types'; import type { Channel, Topic, AssetSource, Asset, Tag, Article, Group, Card, Profile, Call, FocusDetail, Config, NodeConfig, NodeAccount, Participant, PushParams } from './types';
export interface Session { export interface Session {
getSettings(): Settings; getSettings(): Settings;
@ -31,7 +31,7 @@ export interface Ring {
export interface Settings { export interface Settings {
getUsernameStatus(username: string): Promise<boolean>; getUsernameStatus(username: string): Promise<boolean>;
setLogin(username: string, password: string): Promise<void>; setLogin(username: string, password: string): Promise<void>;
enableNotifications(): Promise<void>; enableNotifications(params?: PushParams): Promise<void>;
disableNotifications(): Promise<void>; disableNotifications(): Promise<void>;
enableRegistry(): Promise<void>; enableRegistry(): Promise<void>;
disableRegistry(): Promise<void>; disableRegistry(): Promise<void>;

View File

@ -1,7 +1,12 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { checkResponse, fetchWithTimeout } from './fetchUtil';
import { PushParams } from '../types';
export async function setAccountNotifications(node: string, secure: boolean, token: string, flag: boolean) { export async function setAccountNotifications(node: string, secure: boolean, token: string, flag: boolean, pushParams: PushParams) {
const endpoint = `http${secure ? 's' : ''}://${node}/account/notification?agent=${token}`; const pushEndpoint = pushParams ? encodeURIComponent(pushParams.endpoint) : '';
const publicKey = pushParams ? encodeURIComponent(pushParams.publicKey) : '';
const auth = pushParams ? encodeURIComponent(pushParams.auth) : '';
const params = pushParams ? `&webEndpoint=${pushEndpoint}&webPublicKey=${publicKey}&webAuth=${auth}&pushType=${pushParams.type}` : ''
const endpoint = `http${secure ? 's' : ''}://${node}/account/notification?agent=${token}${params}`;
const { status } = await fetchWithTimeout(endpoint, { const { status } = await fetchWithTimeout(endpoint, {
method: 'PUT', method: 'PUT',
body: JSON.stringify(flag), body: JSON.stringify(flag),

View File

@ -1,6 +1,6 @@
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import type { Settings } from './api'; import type { Settings } from './api';
import type { Config } from './types'; import type { Config, PushParams } from './types';
import { Store } from './store'; import { Store } from './store';
import { Crypto } from './crypto'; import { Crypto } from './crypto';
import { Logging } from './logging'; import { Logging } from './logging';
@ -152,9 +152,9 @@ export class SettingsModule implements Settings {
await this.sync(); await this.sync();
} }
public async enableNotifications(): Promise<void> { public async enableNotifications(params?: PushParams): Promise<void> {
const { node, secure, token } = this; const { node, secure, token } = this;
await setAccountNotifications(node, secure, token, true); await setAccountNotifications(node, secure, token, true, params);
} }
public async disableNotifications(): Promise<void> { public async disableNotifications(): Promise<void> {

View File

@ -258,3 +258,17 @@ export type SessionParams = {
version: string; version: string;
appName: string; appName: string;
}; };
export enum PushType {
UPN = 'upn', // unified push notifications
Web = 'web', // browser push notifications
FCM = 'fcm', // firebase cloud messaging
}
export type PushParams = {
endpoint: string;
publicKey: string;
auth: string;
type: PushType;
}