From 4648f83669d97aee30d71b6a4c7d0e75a85e337d Mon Sep 17 00:00:00 2001 From: nimbleghost <132819643+nimbleghost@users.noreply.github.com> Date: Wed, 31 May 2023 18:27:32 +0200 Subject: [PATCH] Format emojis in the service worker directly --- docs/develop.md | 9 ++++-- server/server_web_push.go | 23 --------------- server/smtp_sender.go | 24 ++++++++++++++++ server/types.go | 1 - server/util.go | 24 ---------------- web/public/sw.js | 16 ++++++----- web/src/app/Api.js | 2 +- web/src/app/Notifier.js | 3 +- web/src/app/emojisMapped.js | 4 +++ web/src/app/notificationUtils.js | 35 ++++++++++++++++++++++ web/src/app/utils.js | 43 ++-------------------------- web/src/components/Notifications.jsx | 13 ++------- 12 files changed, 85 insertions(+), 112 deletions(-) create mode 100644 web/src/app/emojisMapped.js create mode 100644 web/src/app/notificationUtils.js diff --git a/docs/develop.md b/docs/develop.md index 09215d9a..49ed8dbc 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -261,8 +261,11 @@ Reference: 0 { - formattedTitle = fmt.Sprintf("%s %s", strings.Join(emojis[:], " "), titleWithDefault) - } else { - formattedTitle = titleWithDefault - } - for i, xi := range subscriptions { go func(i int, sub webPushSubscription) { ctx := log.Context{"endpoint": sub.BrowserSubscription.Endpoint, "username": sub.UserID, "topic": m.Topic, "message_id": m.ID} @@ -83,7 +61,6 @@ func (s *Server) publishToWebPushEndpoints(v *visitor, m *message) { payload := &webPushPayload{ SubscriptionID: fmt.Sprintf("%s/%s", s.config.BaseURL, m.Topic), Message: *m, - FormattedTitle: formattedTitle, } jsonPayload, err := json.Marshal(payload) diff --git a/server/smtp_sender.go b/server/smtp_sender.go index 759ef396..9093687e 100644 --- a/server/smtp_sender.go +++ b/server/smtp_sender.go @@ -1,6 +1,8 @@ package server import ( + _ "embed" // required by go:embed + "encoding/json" "fmt" "mime" "net" @@ -128,3 +130,25 @@ This message was sent by {ip} at {time} via {topicURL}` body = strings.ReplaceAll(body, "{ip}", senderIP) return body, nil } + +var ( + //go:embed "mailer_emoji_map.json" + emojisJSON string +) + +func toEmojis(tags []string) (emojisOut []string, tagsOut []string, err error) { + var emojiMap map[string]string + if err = json.Unmarshal([]byte(emojisJSON), &emojiMap); err != nil { + return nil, nil, err + } + tagsOut = make([]string, 0) + emojisOut = make([]string, 0) + for _, t := range tags { + if emoji, ok := emojiMap[t]; ok { + emojisOut = append(emojisOut, emoji) + } else { + tagsOut = append(tagsOut, t) + } + } + return +} diff --git a/server/types.go b/server/types.go index 9f436152..1c124c7a 100644 --- a/server/types.go +++ b/server/types.go @@ -469,7 +469,6 @@ type apiStripeSubscriptionDeletedEvent struct { type webPushPayload struct { SubscriptionID string `json:"subscription_id"` Message message `json:"message"` - FormattedTitle string `json:"formatted_title"` } type webPushSubscription struct { diff --git a/server/util.go b/server/util.go index be724c76..03eb8661 100644 --- a/server/util.go +++ b/server/util.go @@ -2,8 +2,6 @@ package server import ( "context" - _ "embed" // required by go:embed - "encoding/json" "fmt" "heckel.io/ntfy/util" "io" @@ -135,25 +133,3 @@ func maybeDecodeHeader(header string) string { } return decoded } - -var ( - //go:embed "mailer_emoji_map.json" - emojisJSON string -) - -func toEmojis(tags []string) (emojisOut []string, tagsOut []string, err error) { - var emojiMap map[string]string - if err = json.Unmarshal([]byte(emojisJSON), &emojiMap); err != nil { - return nil, nil, err - } - tagsOut = make([]string, 0) - emojisOut = make([]string, 0) - for _, t := range tags { - if emoji, ok := emojiMap[t]; ok { - emojisOut = append(emojisOut, emoji) - } else { - tagsOut = append(tagsOut, t) - } - } - return -} diff --git a/web/public/sw.js b/web/public/sw.js index 43a2e3b3..70ad9a7d 100644 --- a/web/public/sw.js +++ b/web/public/sw.js @@ -4,6 +4,7 @@ import { NavigationRoute, registerRoute } from "workbox-routing"; import { NetworkFirst } from "workbox-strategies"; import { getDbAsync } from "../src/app/getDb"; +import { formatMessage, formatTitleWithDefault } from "../src/app/notificationUtils"; // See WebPushWorker, this is to play a sound on supported browsers, // if the app is in the foreground @@ -27,11 +28,11 @@ self.addEventListener("pushsubscriptionchange", (event) => { }); self.addEventListener("push", (event) => { - console.log("[ServiceWorker] Received Web Push Event", { event }); // server/types.go webPushPayload const data = event.data.json(); + console.log("[ServiceWorker] Received Web Push Event", { event, data }); - const { formatted_title: formattedTitle, subscription_id: subscriptionId, message } = data; + const { subscription_id: subscriptionId, message } = data; broadcastChannel.postMessage(message); event.waitUntil( @@ -53,9 +54,9 @@ self.addEventListener("push", (event) => { db.subscriptions.update(subscriptionId, { last: message.id, }), - self.registration.showNotification(formattedTitle, { + self.registration.showNotification(formatTitleWithDefault(message, message.topic), { tag: subscriptionId, - body: message.message, + body: formatMessage(message), icon: "/static/images/ntfy.png", data, }), @@ -106,6 +107,7 @@ precacheAndRoute(self.__WB_MANIFEST); cleanupOutdatedCaches(); // to allow work offline -registerRoute(new NavigationRoute(createHandlerBoundToURL("/"))); - -registerRoute(({ url }) => url.pathname === "/config.js", new NetworkFirst()); +if (import.meta.env.MODE !== "development") { + registerRoute(new NavigationRoute(createHandlerBoundToURL("/"))); + registerRoute(({ url }) => url.pathname === "/config.js", new NetworkFirst()); +} diff --git a/web/src/app/Api.js b/web/src/app/Api.js index c3effb92..377dcccb 100644 --- a/web/src/app/Api.js +++ b/web/src/app/Api.js @@ -144,7 +144,7 @@ class Api { method: "POST", headers: maybeWithAuth({}, user), body: JSON.stringify({ - endpoint: subscription.webPushEndpoint + endpoint: subscription.webPushEndpoint, }), }); diff --git a/web/src/app/Notifier.js b/web/src/app/Notifier.js index c9e3c182..0ace7b39 100644 --- a/web/src/app/Notifier.js +++ b/web/src/app/Notifier.js @@ -1,4 +1,5 @@ -import { formatMessage, formatTitleWithDefault, openUrl, playSound, topicDisplayName, topicShortUrl, urlB64ToUint8Array } from "./utils"; +import { openUrl, playSound, topicDisplayName, topicShortUrl, urlB64ToUint8Array } from "./utils"; +import { formatMessage, formatTitleWithDefault } from "./notificationUtils"; import prefs from "./Prefs"; import logo from "../img/ntfy.png"; import api from "./Api"; diff --git a/web/src/app/emojisMapped.js b/web/src/app/emojisMapped.js new file mode 100644 index 00000000..d823bbe0 --- /dev/null +++ b/web/src/app/emojisMapped.js @@ -0,0 +1,4 @@ +import { rawEmojis } from "./emojis"; + +// Format emojis (see emoji.js) +export default Object.fromEntries(rawEmojis.flatMap((emoji) => emoji.aliases.map((alias) => [alias, emoji.emoji]))); diff --git a/web/src/app/notificationUtils.js b/web/src/app/notificationUtils.js new file mode 100644 index 00000000..b385f481 --- /dev/null +++ b/web/src/app/notificationUtils.js @@ -0,0 +1,35 @@ +// This is a separate file since the other utils import `config.js`, which depends on `window` +// and cannot be used in the service worker + +import emojisMapped from "./emojisMapped"; + +const toEmojis = (tags) => { + if (!tags) return []; + return tags.filter((tag) => tag in emojisMapped).map((tag) => emojisMapped[tag]); +}; + +export const formatTitle = (m) => { + const emojiList = toEmojis(m.tags); + if (emojiList.length > 0) { + return `${emojiList.join(" ")} ${m.title}`; + } + return m.title; +}; + +export const formatTitleWithDefault = (m, fallback) => { + if (m.title) { + return formatTitle(m); + } + return fallback; +}; + +export const formatMessage = (m) => { + if (m.title) { + return m.message; + } + const emojiList = toEmojis(m.tags); + if (emojiList.length > 0) { + return `${emojiList.join(" ")} ${m.message}`; + } + return m.message; +}; diff --git a/web/src/app/utils.js b/web/src/app/utils.js index 69132b2e..906a88a4 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -1,5 +1,4 @@ import { Base64 } from "js-base64"; -import { rawEmojis } from "./emojis"; import beep from "../sounds/beep.mp3"; import juntos from "../sounds/juntos.mp3"; import pristine from "../sounds/pristine.mp3"; @@ -8,6 +7,7 @@ import dadum from "../sounds/dadum.mp3"; import pop from "../sounds/pop.mp3"; import popSwoosh from "../sounds/pop-swoosh.mp3"; import config from "./config"; +import emojisMapped from "./emojisMapped"; export const tiersUrl = (baseUrl) => `${baseUrl}/v1/tiers`; export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, ""); @@ -56,48 +56,9 @@ export const topicDisplayName = (subscription) => { return topicShortUrl(subscription.baseUrl, subscription.topic); }; -// Format emojis (see emoji.js) -const emojis = {}; -rawEmojis.forEach((emoji) => { - emoji.aliases.forEach((alias) => { - emojis[alias] = emoji.emoji; - }); -}); - -const toEmojis = (tags) => { - if (!tags) return []; - return tags.filter((tag) => tag in emojis).map((tag) => emojis[tag]); -}; - -export const formatTitle = (m) => { - const emojiList = toEmojis(m.tags); - if (emojiList.length > 0) { - return `${emojiList.join(" ")} ${m.title}`; - } - return m.title; -}; - -export const formatTitleWithDefault = (m, fallback) => { - if (m.title) { - return formatTitle(m); - } - return fallback; -}; - -export const formatMessage = (m) => { - if (m.title) { - return m.message; - } - const emojiList = toEmojis(m.tags); - if (emojiList.length > 0) { - return `${emojiList.join(" ")} ${m.message}`; - } - return m.message; -}; - export const unmatchedTags = (tags) => { if (!tags) return []; - return tags.filter((tag) => !(tag in emojis)); + return tags.filter((tag) => !(tag in emojisMapped)); }; export const encodeBase64 = (s) => Base64.encode(s); diff --git a/web/src/components/Notifications.jsx b/web/src/components/Notifications.jsx index 2faf2fd2..fe9fcc48 100644 --- a/web/src/components/Notifications.jsx +++ b/web/src/components/Notifications.jsx @@ -24,17 +24,8 @@ import { useLiveQuery } from "dexie-react-hooks"; import InfiniteScroll from "react-infinite-scroll-component"; import { Trans, useTranslation } from "react-i18next"; import { useOutletContext } from "react-router-dom"; -import { - formatBytes, - formatMessage, - formatShortDateTime, - formatTitle, - maybeAppendActionErrors, - openUrl, - shortUrl, - topicShortUrl, - unmatchedTags, -} from "../app/utils"; +import { formatBytes, formatShortDateTime, maybeAppendActionErrors, openUrl, shortUrl, topicShortUrl, unmatchedTags } from "../app/utils"; +import { formatMessage, formatTitle } from "../app/notificationUtils"; import { LightboxBackdrop, Paragraph, VerticallyCenteredContainer } from "./styles"; import subscriptionManager from "../app/SubscriptionManager"; import priority1 from "../img/priority-1.svg";