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">
<head>
<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" />
<title>Databag</title>
</head>

View File

@ -2,12 +2,27 @@ import { useEffect, useState, useContext, useRef } from 'react'
import { AppContext } from '../context/AppContext'
import { DisplayContext } from '../context/DisplayContext'
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'
const IMAGE_DIM = 192
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() {
const display = useContext(DisplayContext) as ContextType
const app = useContext(AppContext) as ContextType
@ -46,6 +61,7 @@ export function useSettings() {
secretImage: '',
code: '',
editImage: '',
webPushKey: null,
sealPassword: '',
sealConfirm: '',
sealDelete: '',
@ -127,10 +143,8 @@ export function useSettings() {
updateState({ blockedMessages });
},
loadBlockedChannels: async () => {
console.log("LOAD BLOCKED");
const settings = app.state.session.getSettings();
const blockedChannels = await settings.getBlockedChannels();
console.log("LOADED: ", blockedChannels);
updateState({ blockedChannels });
},
unblockChannel: async (cardId: string | null, channelId: string) => {
@ -159,8 +173,38 @@ console.log("LOADED: ", blockedChannels);
await settings.setLogin(state.handle, state.password)
},
enableNotifications: async () => {
const { settings } = getSession()
await settings.enableNotifications()
const webPushKey = state.config?.webPushKey;
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 () => {
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 {
getSettings(): Settings;
@ -31,7 +31,7 @@ export interface Ring {
export interface Settings {
getUsernameStatus(username: string): Promise<boolean>;
setLogin(username: string, password: string): Promise<void>;
enableNotifications(): Promise<void>;
enableNotifications(params?: PushParams): Promise<void>;
disableNotifications(): Promise<void>;
enableRegistry(): Promise<void>;
disableRegistry(): Promise<void>;

View File

@ -1,7 +1,12 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
import { PushParams } from '../types';
export async function setAccountNotifications(node: string, secure: boolean, token: string, flag: boolean) {
const endpoint = `http${secure ? 's' : ''}://${node}/account/notification?agent=${token}`;
export async function setAccountNotifications(node: string, secure: boolean, token: string, flag: boolean, pushParams: PushParams) {
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, {
method: 'PUT',
body: JSON.stringify(flag),

View File

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

View File

@ -258,3 +258,17 @@ export type SessionParams = {
version: 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;
}