diff --git a/server/server.go b/server/server.go index 3aff4e1..a2de50d 100644 --- a/server/server.go +++ b/server/server.go @@ -340,6 +340,7 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, r *http.Request) error { appRoot = "/app" } disallowedTopicsStr := `"` + strings.Join(disallowedTopics, `", "`) + `"` + w.Header().Set("Content-Type", "application/json") _, err := io.WriteString(w, fmt.Sprintf(`// Generated server configuration var config = { appRoot: "%s", diff --git a/web/package-lock.json b/web/package-lock.json index f7355be..bd497c3 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -14,6 +14,7 @@ "@mui/material": "latest", "dexie": "^3.2.1", "dexie-react-hooks": "^1.1.1", + "js-base64": "^3.7.2", "react": "latest", "react-dom": "latest", "react-infinite-scroll-component": "^6.1.0", @@ -10775,6 +10776,11 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-base64": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz", + "integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -23917,6 +23923,11 @@ } } }, + "js-base64": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz", + "integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/web/package.json b/web/package.json index 8fe0ada..733e225 100644 --- a/web/package.json +++ b/web/package.json @@ -15,6 +15,7 @@ "@mui/material": "latest", "dexie": "^3.2.1", "dexie-react-hooks": "^1.1.1", + "js-base64": "^3.7.2", "react": "latest", "react-dom": "latest", "react-infinite-scroll-component": "^6.1.0", diff --git a/web/src/app/Notifier.js b/web/src/app/Notifier.js index 3129edf..3317a59 100644 --- a/web/src/app/Notifier.js +++ b/web/src/app/Notifier.js @@ -5,6 +5,9 @@ import logo from "../img/ntfy.png"; class Notifier { async notify(subscriptionId, notification, onClickFallback) { + if (!this.supported()) { + return; + } const subscription = await subscriptionManager.get(subscriptionId); const shouldNotify = await this.shouldNotify(subscription, notification); if (!shouldNotify) { @@ -38,10 +41,14 @@ class Notifier { } granted() { - return Notification.permission === 'granted'; + return this.supported() && Notification.permission === 'granted'; } maybeRequestPermission(cb) { + if (!this.supported()) { + cb(false); + return; + } if (!this.granted()) { Notification.requestPermission().then((permission) => { const granted = permission === 'granted'; @@ -61,6 +68,10 @@ class Notifier { } return true; } + + supported() { + return 'Notification' in window; + } } const notifier = new Notifier(); diff --git a/web/src/app/utils.js b/web/src/app/utils.js index 13ff76f..c7aabaf 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -7,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 {Base64} from 'js-base64'; export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`; export const topicUrlWs = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/ws` @@ -96,14 +97,11 @@ export const basicAuth = (username, password) => { } export const encodeBase64 = (s) => { - return new Buffer(s).toString('base64'); + return Base64.encode(s); } export const encodeBase64Url = (s) => { - return encodeBase64(s) - .replaceAll('+', '-') - .replaceAll('/', '_') - .replaceAll('=', ''); + return Base64.encodeURI(s); } // https://jameshfisher.com/2017/10/30/web-cryptography-api-hello-world/ diff --git a/web/src/components/App.js b/web/src/components/App.js index 0acd725..fc58f16 100644 --- a/web/src/components/App.js +++ b/web/src/components/App.js @@ -20,7 +20,6 @@ import ErrorBoundary from "./ErrorBoundary"; import routes from "./routes"; import {useAutoSubscribe, useConnectionListeners} from "./hooks"; -// TODO iPhone blank screen // TODO better "send test message" (a la android app) // TODO docs // TODO screenshot on homepage diff --git a/web/src/components/Navigation.js b/web/src/components/Navigation.js index 0580543..fce88d6 100644 --- a/web/src/components/Navigation.js +++ b/web/src/components/Navigation.js @@ -82,13 +82,15 @@ const NavList = (props) => { }; const showSubscriptionsList = props.subscriptions?.length > 0; - const showGrantPermissionsBox = props.subscriptions?.length > 0 && !props.notificationsGranted; + const showNotificationNotSupportedBox = !notifier.supported(); + const showNotificationGrantBox = notifier.supported() && props.subscriptions?.length > 0 && !props.notificationsGranted; return ( <> - - {showGrantPermissionsBox && } + + {showNotificationNotSupportedBox && } + {showNotificationGrantBox && } {!showSubscriptionsList && navigate(routes.root)} selected={location.pathname === config.appRoot}> @@ -167,7 +169,7 @@ const SubscriptionItem = (props) => { ); }; -const PermissionAlert = (props) => { +const NotificationGrantAlert = (props) => { return ( <> @@ -189,4 +191,18 @@ const PermissionAlert = (props) => { ); }; +const NotificationNotSupportedAlert = () => { + return ( + <> + + Notifications not supported + + Notifications are not supported in your browser. + + + + + ); +}; + export default Navigation;