Browse Source

Format emojis in the service worker directly

nimbleghost 2 năm trước cách đây
mục cha
commit
4648f83669

+ 6 - 3
docs/develop.md

@@ -261,8 +261,11 @@ Reference: <https://stackoverflow.com/questions/34160509/options-for-testing-ser
         --web-push-subscriptions-file=/tmp/subscriptions.db
     ```
 
-3. In `web/public/config.js` set `base_url` to `http://localhost`. This is required as web push can only be used
-   with the server matching the `base_url`
+3. In `web/public/config.js`:
+
+   - Set `base_url` to `http://localhost`, This is required as web push can only be used with the server matching the `base_url`.
+
+   - Set the `web_push_public_key` correctly.
 
 4. Run `ENABLE_DEV_PWA=1 npm run start` - this enables the dev service worker
 
@@ -270,7 +273,7 @@ Reference: <https://stackoverflow.com/questions/34160509/options-for-testing-ser
 
    - Chrome:
 
-      Open Chrome with special flags allowing insecure localhost service worker testing (regularly dismissing SSL warnings is not enough)
+      Open Chrome with special flags allowing insecure localhost service worker testing insecurely
 
       ```sh
       # for example, macOS

+ 0 - 23
server/server_web_push.go

@@ -6,7 +6,6 @@ import (
 	"github.com/SherClockHolmes/webpush-go"
 	"heckel.io/ntfy/log"
 	"net/http"
-	"strings"
 )
 
 func (s *Server) handleTopicWebPushSubscribe(w http.ResponseWriter, r *http.Request, v *visitor) error {
@@ -55,27 +54,6 @@ func (s *Server) publishToWebPushEndpoints(v *visitor, m *message) {
 		return
 	}
 
-	ctx := log.Context{"topic": m.Topic, "message_id": m.ID, "total_count": len(subscriptions)}
-
-	// Importing the emojis in the service worker would add unnecessary complexity,
-	// simply do it here for web push notifications instead
-	var titleWithDefault, formattedTitle string
-	emojis, _, err := toEmojis(m.Tags)
-	if err != nil {
-		logvm(v, m).Err(err).Fields(ctx).Debug("Unable to publish web push message")
-		return
-	}
-	if m.Title == "" {
-		titleWithDefault = m.Topic
-	} else {
-		titleWithDefault = m.Title
-	}
-	if len(emojis) > 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)
 

+ 24 - 0
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
+}

+ 0 - 1
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 {

+ 0 - 24
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
-}

+ 9 - 7
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());
+}

+ 1 - 1
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,
       }),
     });
 

+ 2 - 1
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";

+ 4 - 0
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])));

+ 35 - 0
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;
+};

+ 2 - 41
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);

+ 2 - 11
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";