Files
archived-vdo.ninja/notifications/index.html
steveseguin c9380616de merge fix
2025-05-12 21:45:08 -04:00

662 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VDO.Ninja Notifications</title>
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#1565c0">
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
<link id="favicon1" rel="icon" type="image/png" sizes="32x32" href="/notifications/media/favicon-32x32.png" />
<link id="favicon2" rel="icon" type="image/png" sizes="16x16" href="/notifications/media/favicon-16x16.png" />
<link id="favicon3" rel="icon" href="/notifications/media/favicon.ico" />
<style>body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
margin: 0;
padding: 0;
background-color: #121212;
color: #e0e0e0;
}
#subscription-status-panel {
margin-top: 20px;
margin-bottom: 20px;
}
#subscription-status-details .status {
display: flex;
flex-direction: column;
gap: 10px;
}
#subscription-status-details p {
margin: 0;
}
#subscription-status-details .status {
display: flex;
flex-direction: column;
gap: 10px;
}
#subscription-status-details p {
margin: 0;
}
#sw-connection-status {
font-weight: bold;
}
#sw-connection-status.connected {
color: #4caf50;
}
#sw-connection-status.polling {
color: #ff9800;
}
#sw-connection-status.disconnected {
color: #f44336;
}
#unsubscribe-button {
align-self: flex-start;
margin-top: 5px;
}
#connection-status-display {
font-weight: bold;
}
#connection-status-display.connected {
color: #4caf50;
}
#connection-status-display.polling {
color: #ff9800;
}
#connection-status-display.disconnected {
color: #f44336;
}
#unsubscribe-button {
align-self: flex-start;
margin-top: 5px;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.header {
background-color: #1565c0;
color: white;
padding: 20px;
text-align: center;
border-radius: 5px 5px 0 0;
margin-bottom: 30px;
}
.notification-card {
background-color: #1e1e1e;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
padding: 20px;
margin-bottom: 30px;
border: 1px solid #333;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
input[type="text"], select {
width: 100%;
padding: 10px;
border: 1px solid #444;
border-radius: 4px;
font-size: 16px;
background-color: #2a2a2a;
color: #e0e0e0;
}
small {
color: #aaa;
}
button {
background-color: #1565c0;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
button:hover {
background-color: #0d47a1;
}
button.secondary {
background-color: #424242;
color: #e0e0e0;
}
button.secondary:hover {
background-color: #616161;
}
button.danger {
background-color: #c62828;
}
button.danger:hover {
background-color: #b71c1c;
}
.status {
margin-top: 20px;
padding: 15px;
border-radius: 4px;
}
.status.active {
background-color: rgba(33, 150, 243, 0.15);
border-left: 4px solid #2196F3;
}
.status.inactive {
background-color: #2a2a2a;
border-left: 4px solid #9e9e9e;
}
.notification-history {
margin-top: 30px;
}
.notification-item {
padding: 15px;
background-color: #1e1e1e;
border-radius: 4px;
margin-bottom: 10px;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
display: flex;
align-items: center;
border: 1px solid #333;
}
.notification-item .time {
color: #999;
font-size: 14px;
margin-top: 5px;
}
.notification-item .content {
flex: 1;
}
.notification-item .actions {
margin-left: 15px;
}
.notification-icon {
margin-right: 15px;
font-size: 24px;
}
.tabs {
display: flex;
margin-bottom: 20px;
border-bottom: 1px solid #444;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border-bottom: 3px solid transparent;
color: #aaa;
}
.tab.active {
border-bottom-color: #2196F3;
color: #2196F3;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.info-box {
background-color: rgba(255, 248, 225, 0.1);
border-left: 4px solid #ffca28;
padding: 15px;
margin-bottom: 20px;
border-radius: 4px;
}
.footer {
text-align: center;
margin-top: 50px;
padding-top: 20px;
border-top: 1px solid #444;
color: #999;
}
.status.warning {
background-color: rgba(255, 152, 0, 0.15);
border-left: 4px solid #ff9800;
}
@keyframes slide-in {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* Add animation for shake effect */
@keyframes shake {
0% { transform: translateX(0); }
10% { transform: translateX(-5px); }
20% { transform: translateX(5px); }
30% { transform: translateX(-5px); }
40% { transform: translateX(5px); }
50% { transform: translateX(-5px); }
60% { transform: translateX(5px); }
70% { transform: translateX(-5px); }
80% { transform: translateX(5px); }
90% { transform: translateX(-5px); }
100% { transform: translateX(0); }
}
.shake {
animation: shake 0.5s ease-in-out;
}
/* Add styles for the visual notification popup */
.notification-popup {
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
width: 350px;
background-color: #1e1e1e;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
padding: 15px;
border-left: 4px solid #2196F3;
animation: slide-in 0.3s ease;
color: #e0e0e0;
opacity: 0.95;
backdrop-filter: blur(4px);
border: 1px solid #2196F3;
}
.notification-popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.notification-popup-title {
margin: 0;
font-size: 18px;
color: #2196F3;
}
.notification-popup-close {
background: none;
border: none;
color: #999;
cursor: pointer;
font-size: 18px;
padding: 0;
}
.notification-popup-body {
margin-bottom: 15px;
}
.notification-popup-actions {
display: flex;
justify-content: flex-end;
}
.notification-popup-actions button {
margin-left: 10px;
padding: 8px 15px;
font-size: 14px;
}
/* Add a pulsating glow effect to the active status */
.status.active {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(33, 150, 243, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(33, 150, 243, 0); }
100% { box-shadow: 0 0 0 0 rgba(33, 150, 243, 0); }
}
/* Add styles for a fullscreen flash effect */
.notification-flash {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(33, 150, 243, 0.2);
z-index: 9999;
pointer-events: none;
animation: flash 0.5s ease-out;
}
@keyframes flash {
0% { opacity: 0; }
50% { opacity: 1; }
100% { opacity: 0; }
}
code {
background-color: #333;
padding: 2px 4px;
border-radius: 3px;
color: #ff9800;
}
a {
color: #64b5f6;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* Add highlight for new notifications in history */
.notification-item.new {
border-left: 4px solid #2196F3;
animation: highlight-new 2s ease;
}
@keyframes highlight-new {
0% { background-color: rgba(33, 150, 243, 0.3); }
100% { background-color: #1e1e1e; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>VDO.Ninja Notifications</h1>
<p>Get notified when someone joins your rooms</p>
</div>
<div class="tabs">
<div class="tab active" data-tab="setup">Setup</div>
<div class="tab" data-tab="history">Notification History</div>
<div class="tab" data-tab="settings">Settings</div>
<div class="tab" data-tab="developer">Developers</div>
</div>
<content>
<div class="tab-content active" id="setup-tab">
<div class="notification-card">
<h2>Subscribe to Notifications</h2>
<div class="form-group">
<label for="topic-input">Notification Topic:</label>
<input type="text" id="topic-input" placeholder="Enter a topic name">
<small>This is the value that will be used with ?poke=xxx in your room URL</small>
</div>
<button id="subscribe-btn">Subscribe to Notifications</button>
<div id="notification-status"></div>
</div>
<div class="notification-card">
<h2>How to Use</h2>
<ol>
<li>Enter a notification topic above and click Subscribe.</li>
<li>Or have a topic automatically generated from your VDO.Ninja room invite link.</li>
<li>Add <code>&amp;poke=YOUR_TOPIC</code> to your VDO.Ninja room URL.</li>
<li>When someone joins that room, you'll receive a notification.</li>
<li><strong>Important:</strong> Allow Notifications for this site, or keep this page open.</li>
</ol>
<div class="form-group">
<label>Example Room URL:</label>
<input type="text" id="example-url" readonly>
</div>
<div class="info-box">
<p>For easier setup, use the "Create from VDO.Ninja URL" option below. It will automatically generate a topic based on your room settings and give you a ready-to-use link.</p>
</div>
</div>
<div class="notification-card">
<h2>Create from VDO.Ninja URL</h2>
<div class="info-box">
<p>Enter your VDO.Ninja URL to automatically create a notification topic</p>
</div>
<div class="form-group">
<label for="vdo-url-input">VDO.Ninja URL:</label>
<input type="text" id="vdo-url-input" placeholder="https://vdo.ninja/?room=yourroom">
</div>
<button id="parse-url-btn">Create Notification Topic</button>
<div id="parsed-url-output" style="margin-top: 15px; display: none;">
<div class="form-group">
<label>Generated Topic:</label>
<input type="text" id="generated-topic" readonly>
</div>
<div class="form-group">
<label>Notification URL for Guests:</label>
<input type="text" id="notification-url" readonly>
<button id="copy-url-btn" class="secondary" style="margin-top: 10px;">Copy URL</button>
</div>
<button id="use-generated-topic" style="margin-top: 10px;">Use This Topic</button>
</div>
</div>
<div class="notification-card">
<h2>Test Notifications</h2>
<p>Click a button below to test the notification system.</p>
<button id="fake-notification-btn">Fake Test Notification</button> <button id="test-notification-btn">Send Real Notification</button>
</div>
</div>
<div class="tab-content" id="history-tab">
<div class="notification-card">
<h2>Recent Notifications</h2>
<div id="notification-history">
<p>No notifications yet.</p>
</div>
</div>
</div>
<div class="tab-content" id="settings-tab">
<div class="notification-card">
<h2>Notification Settings</h2>
<div class="form-group">
<label for="notification-sound">Notification Sound:</label>
<select id="notification-sound">
<option value="default">Default Sound</option>
<option value="chime">Chime</option>
<option value="bell">Bell</option>
<option value="none">No Sound</option>
</select>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="desktop-notifications" checked>
Enable Desktop Notifications
</label>
</div>
<button id="clear-history" class="secondary">Clear Notification History</button>
<button id="unsubscribe-all" class="danger">Unsubscribe from All Topics</button>
</div>
</div>
<div class="tab-content" id="developer-tab">
<div class="notification-card">
<h2>Developer Integration</h2>
<p>Learn how to integrate with the VDO.Ninja notification system using Server-Sent Events (SSE).</p>
<div class="info-box">
<p>This API allows developers to subscribe to room join events programmatically using SSE.</p>
</div>
<h3>SSE Endpoint</h3>
<div class="form-group">
<label>Base URL:</label>
<input type="text" value="https://notify.vdo.ninja" readonly>
</div>
<h3>Connection Example</h3>
<pre style="background-color: #2a2a2a; padding: 15px; border-radius: 4px; overflow: auto;">
// Basic SSE connection
const topic = "your_notification_topic";
const evtSource = new EventSource(\`https://notify.vdo.ninja/events?topic=\${topic}\`);
// Handle connection
evtSource.addEventListener('connect', (event) => {
console.log('Connected to notification service', JSON.parse(event.data));
});
// Listen for notifications
evtSource.addEventListener('notification', (event) => {
const notification = JSON.parse(event.data);
console.log('New notification received:', notification);
// Handle notification - show alert, update UI, etc.
});
// Handle errors and reconnection
evtSource.onerror = (error) => {
console.error('SSE connection error:', error);
// Implement reconnection logic here
if (evtSource.readyState === EventSource.CLOSED) {
// Connection closed, try to reconnect
setTimeout(() => {
// Reconnect logic
}, 2000);
}
};
// To disconnect
function disconnect() {
if (evtSource) {
evtSource.close();
evtSource = null;
}
}</pre>
<h3>Fallback Polling</h3>
<p>For browsers that don't support SSE or in cases where long-lived connections are problematic:</p>
<pre style="background-color: #2a2a2a; padding: 15px; border-radius: 4px; overflow: auto;">
// Polling fallback example
async function pollForNotifications(topic, lastPollTime) {
try {
const response = await fetch(
\`https://notify.vdo.ninja/poll?topic=\${topic}&since=\${lastPollTime}\`
);
const data = await response.json();
if (data.error) {
console.error('Error during polling:', data.error);
} else if (data.notifications && data.notifications.length > 0) {
data.notifications.forEach(notification => {
console.log('New notification:', notification);
// Handle notification
});
}
return Date.now(); // Return current time for next poll
} catch (error) {
console.error('Error during fallback polling:', error);
return lastPollTime; // Keep the same lastPollTime on error
}
}
// Start polling every minute
let lastPollTime = Date.now();
const pollingInterval = setInterval(async () => {
lastPollTime = await pollForNotifications('your_topic', lastPollTime);
}, 60000);</pre>
<h3>Best Practices</h3>
<ul style="margin-left: 20px; line-height: 1.6;">
<li><strong>Exponential Backoff:</strong> Implement exponential backoff for reconnection attempts to avoid overwhelming the server.</li>
<li><strong>Fallback Mechanism:</strong> Always provide a polling fallback for browsers that don't support SSE.</li>
<li><strong>Topic Security:</strong> Use a secure hash function to generate topics from sensitive room data.</li>
<li><strong>Error Handling:</strong> Properly handle network errors and reconnection scenarios.</li>
<li><strong>Offline Detection:</strong> Detect when the client goes offline and handle reconnection appropriately when back online.</li>
<li><strong>Testing:</strong> Periodically test the connection to ensure it's still alive, as some proxies might silently kill long-lived connections.</li>
</ul>
<h3>API Reference</h3>
<table style="width: 100%; border-collapse: collapse; margin-top: 15px;">
<tr style="background-color: #333; text-align: left;">
<th style="padding: 10px;">Endpoint</th>
<th style="padding: 10px;">Description</th>
</tr>
<tr style="border-bottom: 1px solid #444;">
<td style="padding: 10px;"><code>/events?topic={topic}</code></td>
<td style="padding: 10px;">SSE endpoint for real-time notifications</td>
</tr>
<tr style="border-bottom: 1px solid #444;">
<td style="padding: 10px;"><code>/poll?topic={topic}&since={timestamp}</code></td>
<td style="padding: 10px;">Polling endpoint for fetching notifications since a timestamp</td>
</tr>
<tr>
<td style="padding: 10px;"><code>/?notify={topic}&message={message}</code></td>
<td style="padding: 10px;">Send a notification to a topic</td>
</tr>
</table>
<div class="info-box" style="margin-top: 20px;">
<h4 style="margin-top: 0;">Complete Integration</h4>
<p>For a complete integration example, check out the <code>NotificationManager</code> class in the source code of this page.</p>
</div>
</div>
</div>
</content>
<div class="footer">
<p>VDO.Ninja Notifications | <a href="https://vdo.ninja" target="_blank">Return to VDO.Ninja</a></p>
</div>
</div>
<script src="notifications.js?v=36"></script>
<script src="app.js?v=32"></script>
</body>
</html>