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;