mirror of
https://github.com/SrIzan10/vdo.ninja.git
synced 2026-05-01 11:05:24 +00:00
662 lines
17 KiB
HTML
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>&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> |