forked from coracle/flotilla
Get web push working
This commit is contained in:
@@ -13,5 +13,6 @@ VITE_INDEXER_RELAYS=wss://purplepag.es/,wss://relay.damus.io/,wss://relay.nostr.
|
||||
VITE_SIGNER_RELAYS=wss://relay.nsec.app/,wss://bucket.coracle.social/
|
||||
VITE_NOTIFIER_PUBKEY=27b7c2ed89ef78322114225ea3ebf5f72c7767c2528d4d0c1854d039c00085df
|
||||
VITE_NOTIFIER_RELAY=wss://anchor.coracle.social/
|
||||
VITE_VAPID_PUBLIC_KEY=
|
||||
VITE_GLITCHTIP_API_KEY=
|
||||
GLITCHTIP_AUTH_TOKEN=
|
||||
|
||||
+48
-59
@@ -1,6 +1,7 @@
|
||||
import * as nip19 from "nostr-tools/nip19"
|
||||
import {get} from "svelte/store"
|
||||
import {randomId, poll, uniq, equals, TIMEZONE, LOCALE} from "@welshman/lib"
|
||||
import {Capacitor} from "@capacitor/core"
|
||||
import {randomId, flatten, poll, uniq, equals, TIMEZONE, LOCALE} from "@welshman/lib"
|
||||
import type {Feed} from "@welshman/feeds"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {
|
||||
@@ -14,8 +15,10 @@ import {
|
||||
AUTH_JOIN,
|
||||
ROOMS,
|
||||
COMMENT,
|
||||
ALERT_REQUEST_PUSH,
|
||||
ALERT_REQUEST_EMAIL,
|
||||
ALERT_EMAIL,
|
||||
ALERT_WEB,
|
||||
ALERT_IOS,
|
||||
ALERT_ANDROID,
|
||||
isSignedEvent,
|
||||
makeEvent,
|
||||
displayProfile,
|
||||
@@ -60,6 +63,7 @@ import {
|
||||
NOTIFIER_RELAY,
|
||||
userRoomsByUrl,
|
||||
} from "@app/state"
|
||||
import {getPushInfo} from "@app/push"
|
||||
|
||||
// Utils
|
||||
|
||||
@@ -369,41 +373,58 @@ export const makeComment = ({event, content, tags = []}: CommentParams) =>
|
||||
export const publishComment = ({relays, ...params}: CommentParams & {relays: string[]}) =>
|
||||
publishThunk({event: makeComment(params), relays})
|
||||
|
||||
export type EmailAlertParams = {
|
||||
export type AlertParams = {
|
||||
feed: Feed
|
||||
cron: string
|
||||
email: string
|
||||
description: string
|
||||
claims: Record<string, string>
|
||||
email?: {
|
||||
cron: string
|
||||
email: string
|
||||
handler: string[]
|
||||
}
|
||||
web?: {
|
||||
endpoint: string
|
||||
p256dh: string
|
||||
auth: string
|
||||
}
|
||||
ios?: {
|
||||
nothing: string
|
||||
}
|
||||
android?: {
|
||||
nothing: string
|
||||
}
|
||||
}
|
||||
|
||||
export const makeEmailAlert = async ({
|
||||
cron,
|
||||
email,
|
||||
feed,
|
||||
claims,
|
||||
description,
|
||||
}: EmailAlertParams) => {
|
||||
export const makeAlert = async (params: AlertParams) => {
|
||||
const tags = [
|
||||
["feed", JSON.stringify(feed)],
|
||||
["cron", cron],
|
||||
["email", email],
|
||||
["feed", JSON.stringify(params.feed)],
|
||||
["locale", LOCALE],
|
||||
["timezone", TIMEZONE],
|
||||
["description", description],
|
||||
[
|
||||
"handler",
|
||||
"31990:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:1737058597050",
|
||||
"wss://relay.nostr.band/",
|
||||
"web",
|
||||
],
|
||||
["description", params.description],
|
||||
]
|
||||
|
||||
for (const [relay, claim] of Object.entries(claims)) {
|
||||
for (const [relay, claim] of Object.entries(params.claims)) {
|
||||
tags.push(["claim", relay, claim])
|
||||
}
|
||||
|
||||
return makeEvent(ALERT_REQUEST_EMAIL, {
|
||||
let kind: number
|
||||
if (params.email) {
|
||||
kind = ALERT_EMAIL
|
||||
tags.push(...Object.entries(params.email).map(flatten))
|
||||
} else if (params.web) {
|
||||
kind = ALERT_WEB
|
||||
tags.push(...Object.entries(params.web).map(flatten))
|
||||
} else if (params.ios) {
|
||||
kind = ALERT_IOS
|
||||
tags.push(...Object.entries(params.ios).map(flatten))
|
||||
} else if (params.android) {
|
||||
kind = ALERT_ANDROID
|
||||
tags.push(...Object.entries(params.android).map(flatten))
|
||||
} else {
|
||||
throw new Error("Alert has invalid params")
|
||||
}
|
||||
|
||||
return makeEvent(kind, {
|
||||
content: await signer.get().nip44.encrypt(NOTIFIER_PUBKEY, JSON.stringify(tags)),
|
||||
tags: [
|
||||
["d", randomId()],
|
||||
@@ -412,37 +433,5 @@ export const makeEmailAlert = async ({
|
||||
})
|
||||
}
|
||||
|
||||
export const publishEmailAlert = async (params: EmailAlertParams) =>
|
||||
publishThunk({event: await makeEmailAlert(params), relays: [NOTIFIER_RELAY]})
|
||||
|
||||
export type PushAlertParams = {
|
||||
feed: Feed
|
||||
description: string
|
||||
claims: Record<string, string>
|
||||
}
|
||||
|
||||
export const makePushAlert = async ({feed, claims, description}: PushAlertParams) => {
|
||||
const tags = [
|
||||
["feed", JSON.stringify(feed)],
|
||||
["locale", LOCALE],
|
||||
["timezone", TIMEZONE],
|
||||
["description", description],
|
||||
["token", ""],
|
||||
["platform", ""],
|
||||
]
|
||||
|
||||
for (const [relay, claim] of Object.entries(claims)) {
|
||||
tags.push(["claim", relay, claim])
|
||||
}
|
||||
|
||||
return makeEvent(ALERT_REQUEST_PUSH, {
|
||||
content: await signer.get().nip44.encrypt(NOTIFIER_PUBKEY, JSON.stringify(tags)),
|
||||
tags: [
|
||||
["d", randomId()],
|
||||
["p", NOTIFIER_PUBKEY],
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
export const publishPushAlert = async (params: PushAlertParams) =>
|
||||
publishThunk({event: await makePushAlert(params), relays: [NOTIFIER_RELAY]})
|
||||
export const publishAlert = async (params: AlertParams) =>
|
||||
publishThunk({event: await makeAlert(params), relays: [NOTIFIER_RELAY]})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {preventDefault} from "@lib/html"
|
||||
import {randomInt, displayList, TIMEZONE, identity} from "@welshman/lib"
|
||||
import {displayRelayUrl, getTagValue, THREAD, MESSAGE, EVENT_TIME, COMMENT} from "@welshman/util"
|
||||
@@ -13,7 +14,9 @@
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import {alerts, getMembershipUrls, getMembershipRoomsByUrl, userMembership} from "@app/state"
|
||||
import {loadAlertStatuses, requestRelayClaims} from "@app/requests"
|
||||
import {publishEmailAlert, publishPushAlert} from "@app/commands"
|
||||
import {publishAlert} from "@app/commands"
|
||||
import type {AlertParams} from "@app/commands"
|
||||
import {platform, canSendPushNotifications, getPushInfo} from "@app/push"
|
||||
import {pushToast} from "@app/toast"
|
||||
|
||||
type Props = {
|
||||
@@ -45,7 +48,7 @@
|
||||
const back = () => history.back()
|
||||
|
||||
const submit = async () => {
|
||||
if (!email.includes("@")) {
|
||||
if (channel === "email" && !email.includes("@")) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: "Please provide an email address",
|
||||
@@ -83,23 +86,45 @@
|
||||
|
||||
if (notifyChat) {
|
||||
display.push("chat")
|
||||
filters.push({
|
||||
kinds: [MESSAGE],
|
||||
"#h": getMembershipRoomsByUrl(relay, $userMembership),
|
||||
})
|
||||
filters.push({kinds: [MESSAGE]})
|
||||
}
|
||||
|
||||
loading = true
|
||||
|
||||
try {
|
||||
const claims = await requestRelayClaims([relay])
|
||||
const cadence = cron?.endsWith("1") ? "Weekly" : "Daily"
|
||||
const description = `${cadence} alert for ${displayList(display)} on ${displayRelayUrl(relay)}, sent via email.`
|
||||
const feed = makeIntersectionFeed(feedFromFilters(filters), makeRelayFeed(relay))
|
||||
const thunk =
|
||||
channel === "email"
|
||||
? await publishEmailAlert({cron, email, feed, claims, description})
|
||||
: await publishPushAlert({feed, claims, description})
|
||||
const description = `for ${displayList(display)} on ${displayRelayUrl(relay)}`
|
||||
const params: AlertParams = {feed, claims, description}
|
||||
|
||||
if (channel === "email") {
|
||||
const cadence = cron?.endsWith("1") ? "Weekly" : "Daily"
|
||||
|
||||
params.description = `${cadence} alert ${description}, sent via email.`
|
||||
params.email = {
|
||||
cron,
|
||||
email,
|
||||
handler: [
|
||||
"31990:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:1737058597050",
|
||||
"wss://relay.nostr.band/",
|
||||
"web",
|
||||
],
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
// @ts-ignore
|
||||
params[platform] = await getPushInfo()
|
||||
} catch (e: any) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: String(e),
|
||||
})
|
||||
}
|
||||
|
||||
params.description = `Push notification alert ${description}.`
|
||||
}
|
||||
|
||||
const thunk = await publishAlert(params)
|
||||
|
||||
await thunk.result
|
||||
await loadAlertStatuses($pubkey!)
|
||||
@@ -110,6 +135,12 @@
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!canSendPushNotifications()) {
|
||||
channel = "email"
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<form class="column gap-4" onsubmit={preventDefault(submit)}>
|
||||
@@ -118,17 +149,19 @@
|
||||
Add an Alert
|
||||
{/snippet}
|
||||
</ModalHeader>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Alert Type*</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<select bind:value={channel} class="select select-bordered">
|
||||
<option value="push">Push Notification</option>
|
||||
<option value="email">Email Digest</option>
|
||||
</select>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
{#if canSendPushNotifications()}
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Alert Type*</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<select bind:value={channel} class="select select-bordered">
|
||||
<option value="email">Email Digest</option>
|
||||
<option value="push">Push Notification</option>
|
||||
</select>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
{/if}
|
||||
{#if channel === "email"}
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
|
||||
@@ -67,9 +67,9 @@
|
||||
|
||||
const addRoom = () => pushModal(RoomCreate, {url}, {replaceState})
|
||||
|
||||
const addAlert = () => pushModal(AlertAdd, {relay: url, channel: "push"})
|
||||
const addAlert = () => pushModal(AlertAdd, {relay: url, channel: "push"}, {replaceState})
|
||||
|
||||
const deleteAlert = () => pushModal(AlertDelete, {alert})
|
||||
const deleteAlert = () => pushModal(AlertDelete, {alert}, {replaceState})
|
||||
|
||||
let showMenu = $state(false)
|
||||
let replaceState = $state(false)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import {Capacitor} from "@capacitor/core"
|
||||
import {VAPID_PUBLIC_KEY} from "@app/state"
|
||||
|
||||
export const platform = Capacitor.getPlatform()
|
||||
|
||||
export const canSendPushNotifications = () => ["web", "android", "ios"].includes(platform)
|
||||
|
||||
export const getWebPushInfo = async () => {
|
||||
if (!("serviceWorker" in navigator)) {
|
||||
throw new Error("Service Worker not supported")
|
||||
}
|
||||
|
||||
if (!("PushManager" in window)) {
|
||||
throw new Error("Push messaging not supported")
|
||||
}
|
||||
|
||||
if (Notification.permission === "denied") {
|
||||
throw new Error("Push notifications are blocked")
|
||||
}
|
||||
|
||||
if (Notification.permission !== "granted") {
|
||||
const permission = await Notification.requestPermission()
|
||||
|
||||
if (permission !== "granted") {
|
||||
throw new Error("Push notification permission denied")
|
||||
}
|
||||
}
|
||||
|
||||
const registration = await navigator.serviceWorker.ready
|
||||
let subscription = await registration.pushManager.getSubscription()
|
||||
|
||||
if (!subscription) {
|
||||
subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: VAPID_PUBLIC_KEY,
|
||||
})
|
||||
}
|
||||
|
||||
const {endpoint, keys} = subscription.toJSON()
|
||||
|
||||
if (!keys) {
|
||||
throw new Error(`Failed to get push info: no keys were returned`)
|
||||
}
|
||||
|
||||
return {
|
||||
endpoint: subscription.endpoint,
|
||||
p256dh: keys.p256dh,
|
||||
auth: keys.auth,
|
||||
}
|
||||
}
|
||||
|
||||
export const getIosPushInfo = async () => {
|
||||
return {}
|
||||
}
|
||||
|
||||
export const getAndroidPushInfo = async () => {
|
||||
return {}
|
||||
}
|
||||
|
||||
export const getPushInfo = (): Promise<Record<string, string>> => {
|
||||
switch (platform) {
|
||||
case "web":
|
||||
return getWebPushInfo()
|
||||
case "ios":
|
||||
return getIosPushInfo()
|
||||
case "android":
|
||||
return getAndroidPushInfo()
|
||||
default:
|
||||
throw new Error(`Invalid push platform: ${platform}`)
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -25,8 +25,10 @@ import {
|
||||
EVENT_TIME,
|
||||
AUTH_INVITE,
|
||||
COMMENT,
|
||||
ALERT_REQUEST_EMAIL,
|
||||
ALERT_REQUEST_PUSH,
|
||||
ALERT_EMAIL,
|
||||
ALERT_WEB,
|
||||
ALERT_IOS,
|
||||
ALERT_ANDROID,
|
||||
ALERT_STATUS,
|
||||
matchFilters,
|
||||
getTagValues,
|
||||
@@ -349,7 +351,7 @@ export const makeCalendarFeed = ({
|
||||
export const loadAlerts = (pubkey: string) =>
|
||||
load({
|
||||
relays: [NOTIFIER_RELAY],
|
||||
filters: [{kinds: [ALERT_REQUEST_EMAIL, ALERT_REQUEST_PUSH], authors: [pubkey]}],
|
||||
filters: [{kinds: [ALERT_EMAIL, ALERT_WEB, ALERT_IOS, ALERT_ANDROID], authors: [pubkey]}],
|
||||
})
|
||||
|
||||
export const loadAlertStatuses = (pubkey: string) =>
|
||||
|
||||
+22
-16
@@ -61,34 +61,40 @@ export const getPrimaryNavItemIndex = ($page: Page) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const goToMessage = async (url: string, room: string | undefined, id: string) => {
|
||||
await goto(room ? makeRoomPath(url, room) : makeSpacePath(url, "chat"))
|
||||
await sleep(300)
|
||||
|
||||
return scrollToEvent(id)
|
||||
}
|
||||
|
||||
export const goToEvent = async (event: TrustedEvent) => {
|
||||
export const goToEvent = async (event: TrustedEvent, options: Record<string, any> = {}) => {
|
||||
if (event.kind === DIRECT_MESSAGE || event.kind === DIRECT_MESSAGE_FILE) {
|
||||
return await scrollToEvent(event.id)
|
||||
await scrollToEvent(event.id)
|
||||
}
|
||||
|
||||
const urls = Array.from(tracker.getRelays(event.id))
|
||||
const path = await getEventPath(event, urls)
|
||||
|
||||
if (path.includes('://')) {
|
||||
window.open(path)
|
||||
} else {
|
||||
goto(path, options)
|
||||
|
||||
await sleep(300)
|
||||
await scrollToEvent(event.id)
|
||||
}
|
||||
}
|
||||
|
||||
export const getEventPath = async (event: TrustedEvent, urls: string[]) => {
|
||||
const room = getTagValue(ROOM, event.tags)
|
||||
|
||||
if (urls.length > 0) {
|
||||
const url = urls[0]
|
||||
|
||||
if (event.kind === THREAD) {
|
||||
return goto(makeThreadPath(url, event.id))
|
||||
return makeThreadPath(url, event.id)
|
||||
}
|
||||
|
||||
if (event.kind === EVENT_TIME) {
|
||||
return goto(makeCalendarPath(url, event.id))
|
||||
return makeCalendarPath(url, event.id)
|
||||
}
|
||||
|
||||
if (event.kind === MESSAGE) {
|
||||
return goToMessage(url, room, event.id)
|
||||
return room ? makeRoomPath(url, room) : makeSpacePath(url, 'chat')
|
||||
}
|
||||
|
||||
const kind = event.tags.find(nthEq(0, "K"))?.[1]
|
||||
@@ -96,18 +102,18 @@ export const goToEvent = async (event: TrustedEvent) => {
|
||||
|
||||
if (id && kind) {
|
||||
if (parseInt(kind) === THREAD) {
|
||||
return goto(makeThreadPath(url, id))
|
||||
return makeThreadPath(url, id)
|
||||
}
|
||||
|
||||
if (parseInt(kind) === EVENT_TIME) {
|
||||
return goto(makeCalendarPath(url, id))
|
||||
return makeCalendarPath(url, id)
|
||||
}
|
||||
|
||||
if (parseInt(kind) === MESSAGE) {
|
||||
return goToMessage(url, room, id)
|
||||
return room ? makeRoomPath(url, room) : makeSpacePath(url, 'chat')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.open(entityLink(nip19.neventEncode({id: event.id, relays: urls})))
|
||||
return entityLink(nip19.neventEncode({id: event.id, relays: urls}))
|
||||
}
|
||||
|
||||
+7
-3
@@ -41,8 +41,10 @@ import {
|
||||
ROOM_JOIN,
|
||||
ROOM_ADD_USER,
|
||||
ROOM_REMOVE_USER,
|
||||
ALERT_REQUEST_EMAIL,
|
||||
ALERT_REQUEST_PUSH,
|
||||
ALERT_EMAIL,
|
||||
ALERT_WEB,
|
||||
ALERT_IOS,
|
||||
ALERT_ANDROID,
|
||||
ALERT_STATUS,
|
||||
getGroupTags,
|
||||
getRelayTagValues,
|
||||
@@ -91,6 +93,8 @@ export const NOTIFIER_PUBKEY = import.meta.env.VITE_NOTIFIER_PUBKEY
|
||||
|
||||
export const NOTIFIER_RELAY = import.meta.env.VITE_NOTIFIER_RELAY
|
||||
|
||||
export const VAPID_PUBLIC_KEY = import.meta.env.VITE_VAPID_PUBLIC_KEY
|
||||
|
||||
export const INDEXER_RELAYS = fromCsv(import.meta.env.VITE_INDEXER_RELAYS)
|
||||
|
||||
export const SIGNER_RELAYS = fromCsv(import.meta.env.VITE_SIGNER_RELAYS)
|
||||
@@ -343,7 +347,7 @@ export type Alert = {
|
||||
}
|
||||
|
||||
export const alerts = deriveEventsMapped<Alert>(repository, {
|
||||
filters: [{kinds: [ALERT_REQUEST_EMAIL, ALERT_REQUEST_PUSH]}],
|
||||
filters: [{kinds: [ALERT_EMAIL, ALERT_WEB, ALERT_IOS, ALERT_ANDROID]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: async event => {
|
||||
const tags = parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, event.content))
|
||||
|
||||
+5
-2
@@ -102,6 +102,7 @@ export const isIntersecting = async (element: Element) =>
|
||||
|
||||
export const scrollToEvent = async (id: string, attempts = 3): Promise<boolean> => {
|
||||
const element = document.querySelector(`[data-event="${id}"]`) as any
|
||||
const elements = Array.from(document.querySelectorAll("[data-event]"))
|
||||
|
||||
if (element) {
|
||||
element.scrollIntoView({behavior: "smooth", block: "center"})
|
||||
@@ -116,8 +117,8 @@ export const scrollToEvent = async (id: string, attempts = 3): Promise<boolean>
|
||||
}, 800 + 400)
|
||||
|
||||
return true
|
||||
} else {
|
||||
const lastElement = last(Array.from(document.querySelectorAll("[data-event]")))
|
||||
} else if (elements.length > 0) {
|
||||
const lastElement = last(elements)
|
||||
|
||||
if (lastElement && !isIntersecting(lastElement)) {
|
||||
lastElement.scrollIntoView({behavior: "smooth", block: "center"})
|
||||
@@ -131,4 +132,6 @@ export const scrollToEvent = async (id: string, attempts = 3): Promise<boolean>
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
} from "@welshman/app"
|
||||
import * as lib from "@welshman/lib"
|
||||
import * as util from "@welshman/util"
|
||||
import * as feeds from "@welshman/feeds"
|
||||
import * as router from "@welshman/router"
|
||||
import * as welshmanSigner from "@welshman/signer"
|
||||
import * as net from "@welshman/net"
|
||||
@@ -84,6 +85,7 @@
|
||||
...welshmanSigner,
|
||||
...router,
|
||||
...util,
|
||||
...feeds,
|
||||
...net,
|
||||
...app,
|
||||
...appState,
|
||||
@@ -92,6 +94,13 @@
|
||||
...notifications,
|
||||
})
|
||||
|
||||
// Listen for navigation messages from service worker
|
||||
navigator.serviceWorker?.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'NAVIGATE') {
|
||||
goto(event.data.url)
|
||||
}
|
||||
})
|
||||
|
||||
// Nstart login
|
||||
if (window.location.hash?.startsWith("#nostr-login")) {
|
||||
const params = new URLSearchParams(window.location.hash.slice(1))
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
import * as nip19 from "nostr-tools/nip19"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {Address, getIdFilters, getTagValue} from "@welshman/util"
|
||||
import {LOCAL_RELAY_URL} from "@welshman/relay"
|
||||
import {load} from "@welshman/net"
|
||||
import {page} from "$app/stores"
|
||||
import {goto} from "$app/navigation"
|
||||
import {scrollToEvent} from "@lib/html"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import {makeRoomPath, makeThreadPath} from "@app/routes"
|
||||
import {goToEvent} from "@app/routes"
|
||||
|
||||
const {bech32} = $page.params
|
||||
|
||||
@@ -22,19 +22,11 @@
|
||||
let found = false
|
||||
|
||||
load({
|
||||
relays: data.relays,
|
||||
relays: [LOCAL_RELAY_URL, ...data.relays],
|
||||
filters: getIdFilters([type === "nevent" ? data.id : Address.fromNaddr(bech32).toString()]),
|
||||
onEvent: (event: TrustedEvent) => {
|
||||
found = true
|
||||
|
||||
if (event.kind === 9) {
|
||||
goto(makeRoomPath(data.relays[0], getTagValue("h", event.tags)!), {replaceState: true})
|
||||
scrollToEvent(event.id)
|
||||
} else if (event.kind === 11) {
|
||||
goto(makeThreadPath(data.relays[0], event.id), {replaceState: true})
|
||||
} else {
|
||||
goto("/", {replaceState: true})
|
||||
}
|
||||
goToEvent(event, {replaceState: true})
|
||||
},
|
||||
onClose: () => {
|
||||
if (!found) {
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import * as nip19 from 'nostr-tools/nip19'
|
||||
import {parse, renderAsText} from '@welshman/content'
|
||||
import {MESSAGE, THREAD, COMMENT, EVENT_TIME} from '@welshman/util'
|
||||
|
||||
const renderOptions = {
|
||||
createElement: tag => ({
|
||||
_text: "",
|
||||
set innerText(text) {
|
||||
this._text = text
|
||||
},
|
||||
get innerHTML() {
|
||||
return this._text
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
self.addEventListener('install', event => {
|
||||
self.skipWaiting()
|
||||
})
|
||||
|
||||
self.addEventListener('activate', event => {
|
||||
event.waitUntil(self.clients.claim())
|
||||
})
|
||||
|
||||
self.addEventListener("push", e => {
|
||||
console.log("Service Worker: Push event received", e)
|
||||
|
||||
let url = "/"
|
||||
let body = "You have a new message"
|
||||
|
||||
try {
|
||||
const {event, relays = []} = e.data?.json() || {}
|
||||
|
||||
if (event) {
|
||||
url += nip19.neventEncode({id: event.id, relays})
|
||||
body = renderAsText(parse(event), renderOptions).toString()
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Service Worker: Failed to parse push data", e)
|
||||
}
|
||||
|
||||
e.waitUntil(
|
||||
self.registration.showNotification("New message", {
|
||||
body,
|
||||
data: {url},
|
||||
icon: "/pwa-192x192.png",
|
||||
badge: "/pwa-64x64.png",
|
||||
tag: 'flotilla-notification',
|
||||
requireInteraction: false,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
self.addEventListener("notificationclick", e => {
|
||||
console.log("Service Worker: Notification click event", e)
|
||||
|
||||
e.notification.close()
|
||||
|
||||
if (e.action === "close") {
|
||||
return
|
||||
}
|
||||
|
||||
// Default action or 'open' action
|
||||
const url = e.notification.data?.url
|
||||
|
||||
e.waitUntil(
|
||||
clients
|
||||
.matchAll({
|
||||
type: "window",
|
||||
includeUncontrolled: true,
|
||||
})
|
||||
.then(clientList => {
|
||||
// Check if app is already open and send navigation message
|
||||
for (const client of clientList) {
|
||||
if (client.url.includes(location.origin)) {
|
||||
client.postMessage({
|
||||
type: 'NAVIGATE',
|
||||
url: url
|
||||
})
|
||||
|
||||
return client.focus()
|
||||
}
|
||||
}
|
||||
|
||||
// Open new window if app is not open
|
||||
if (clients.openWindow) {
|
||||
return clients.openWindow(url)
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user