ntfy/server/index.html

154 lines
5.5 KiB
HTML
Raw Normal View History

2021-10-23 01:26:01 +00:00
<!DOCTYPE html>
<html lang="en">
<head>
<title>ntfy.sh</title>
<style>
body { font-size: 1.3em; line-height: 140%; }
#error { color: darkred; font-style: italic; }
#main { max-width: 800px; margin: 0 auto; }
2021-10-23 01:26:01 +00:00
</style>
</head>
<body>
<div id="main">
<h1>ntfy.sh</h1>
2021-10-23 01:26:01 +00:00
<p>
<b>ntfy</b> (pronounce: <i>notify</i>) is a simple HTTP-based pub-sub notification service. It allows you to send desktop and (soon) phone notifications
via scripts, without signup or cost. It's entirely free and open source. You can find the source code <a href="https://github.com/binwiederhier/ntfy">on GitHub</a>.
</p>
2021-10-23 01:26:01 +00:00
<p>
You can subscribe to a topic either in this web UI, or in your own app by subscribing to an SSE/EventSource
or JSON feed. Once subscribed, you can publish messages via PUT or POST.
</p>
2021-10-23 01:26:01 +00:00
<p id="error"></p>
2021-10-23 01:26:01 +00:00
<form id="subscribeForm">
<p>
<input type="text" id="topicField" size="64" placeholder="Topic ID (letters, numbers, _ and -)" pattern="[-_A-Za-z]{1,64}" autofocus />
<input type="submit" id="subscribeButton" value="Subscribe topic" />
</p>
</form>
2021-10-23 01:26:01 +00:00
<p id="topicsHeader"><b>Subscribed topics:</b></p>
<ul id="topicsList"></ul>
</div>
2021-10-23 01:26:01 +00:00
2021-10-23 17:21:33 +00:00
<script type="text/javascript">
let topics = {};
2021-10-23 01:26:01 +00:00
const topicsHeader = document.getElementById("topicsHeader");
2021-10-23 17:21:33 +00:00
const topicsList = document.getElementById("topicsList");
const topicField = document.getElementById("topicField");
2021-10-23 17:21:33 +00:00
const subscribeButton = document.getElementById("subscribeButton");
const subscribeForm = document.getElementById("subscribeForm");
const errorField = document.getElementById("error");
2021-10-23 19:22:17 +00:00
const subscribe = (topic) => {
2021-10-23 17:21:33 +00:00
if (Notification.permission !== "granted") {
2021-10-23 19:22:17 +00:00
Notification.requestPermission().then((permission) => {
2021-10-23 17:21:33 +00:00
if (permission === "granted") {
2021-10-23 19:22:17 +00:00
subscribeInternal(topic, 0);
} else {
showNotificationDeniedError();
2021-10-23 17:21:33 +00:00
}
});
} else {
2021-10-23 19:22:17 +00:00
subscribeInternal(topic, 0);
2021-10-23 01:26:01 +00:00
}
2021-10-23 17:21:33 +00:00
};
2021-10-23 01:26:01 +00:00
2021-10-23 19:22:17 +00:00
const subscribeInternal = (topic, delaySec) => {
setTimeout(() => {
// Render list entry
let topicEntry = document.getElementById(`topic-${topic}`);
if (!topicEntry) {
topicEntry = document.createElement('li');
topicEntry.id = `topic-${topic}`;
topicEntry.innerHTML = `${topic} <button onclick="unsubscribe('${topic}')">Unsubscribe</button>`;
topicsList.appendChild(topicEntry);
}
topicsHeader.style.display = '';
2021-10-23 19:22:17 +00:00
// Open event source
let eventSource = new EventSource(`${topic}/sse`);
eventSource.onopen = () => {
topicEntry.innerHTML = `${topic} <button onclick="unsubscribe('${topic}')">Unsubscribe</button>`;
delaySec = 0; // Reset on successful connection
};
eventSource.onerror = (e) => {
const newDelaySec = (delaySec + 5 <= 30) ? delaySec + 5 : 30;
topicEntry.innerHTML = `${topic} <i>(Reconnecting in ${newDelaySec}s ...)</i> <button onclick="unsubscribe('${topic}')">Unsubscribe</button>`;
eventSource.close()
subscribeInternal(topic, newDelaySec);
};
eventSource.onmessage = (e) => {
const event = JSON.parse(e.data);
new Notification(event.message);
};
topics[topic] = eventSource;
localStorage.setItem('topics', JSON.stringify(Object.keys(topics)));
}, delaySec * 1000);
2021-10-23 17:21:33 +00:00
};
2021-10-23 01:26:01 +00:00
2021-10-23 19:22:17 +00:00
const unsubscribe = (topic) => {
2021-10-23 17:21:33 +00:00
topics[topic].close();
2021-10-23 19:22:17 +00:00
delete topics[topic];
localStorage.setItem('topics', JSON.stringify(Object.keys(topics)));
2021-10-23 17:21:33 +00:00
document.getElementById(`topic-${topic}`).remove();
if (Object.keys(topics).length === 0) {
topicsHeader.style.display = 'none';
}
};
const showError = (msg) => {
errorField.innerHTML = msg;
topicField.disabled = true;
subscribeButton.disabled = true;
};
const showBrowserIncompatibleError = () => {
showError("Your browser is not compatible to use the web-based desktop notifications.");
};
const showNotificationDeniedError = () => {
showError("You have blocked desktop notifications for this website. Please unblock them and refresh to use the web-based desktop notifications.");
2021-10-23 17:21:33 +00:00
};
subscribeForm.onsubmit = function () {
if (!topicField.value) {
return false;
2021-10-23 01:26:01 +00:00
}
2021-10-23 17:21:33 +00:00
subscribe(topicField.value);
2021-10-23 19:22:17 +00:00
topicField.value = "";
2021-10-23 17:21:33 +00:00
return false;
2021-10-23 01:26:01 +00:00
};
2021-10-23 17:21:33 +00:00
2021-10-23 19:22:17 +00:00
// Disable Web UI if notifications of EventSource are not available
2021-10-23 17:21:33 +00:00
if (!window["Notification"] || !window["EventSource"]) {
showBrowserIncompatibleError();
2021-10-23 17:21:33 +00:00
} else if (Notification.permission === "denied") {
showNotificationDeniedError();
2021-10-23 17:21:33 +00:00
}
2021-10-23 19:22:17 +00:00
// Reset UI
topicField.value = "";
// Restore topics
const storedTopics = localStorage.getItem('topics');
if (storedTopics && Notification.permission === "granted") {
const storedTopicsArray = JSON.parse(storedTopics)
storedTopicsArray.forEach((topic) => { subscribeInternal(topic, 0); });
if (storedTopicsArray.length === 0) {
topicsHeader.style.display = 'none';
}
} else {
topicsHeader.style.display = 'none';
2021-10-23 19:22:17 +00:00
}
2021-10-23 01:26:01 +00:00
</script>
</body>
</html>