precise-time-ntp
Version:
⏰ Simple NTP time sync for Node.js - Auto-drift, WebSocket & HTML clocks
904 lines (729 loc) • 22.4 kB
Markdown
# 🌐 WebSocket Integration Guide
## Simple Integration
Add a real-time synchronized clock to any HTML page in 2 steps:
### Step 1: Server Code (Save as `server.js`)
```javascript
const timeSync = require('precise-time-ntp');
async function start() {
await timeSync.sync(); // Sync with atomic time
timeSync.startWebSocketServer(8080); // Start WebSocket server
timeSync.startAutoSync(300000); // Re-sync every 5 minutes
console.log('⏰ Time server ready at ws://localhost:8080');
}
start().catch(console.error);
```
### Step 2: HTML Code (Copy-paste into any webpage)
```html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Atomic Clock</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
padding: 50px;
background: #f5f5f5;
}
.clock {
font-size: 4rem;
font-weight: bold;
color: #2c3e50;
margin: 20px 0;
font-family: 'Courier New', monospace;
}
.status {
font-size: 1.2rem;
margin: 20px 0;
padding: 10px;
border-radius: 5px;
}
.connected {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
</style>
</head>
<body>
<h1>⏰ Atomic Clock</h1>
<div class="clock" id="clock">--:--:--</div>
<div class="status info" id="status">Connecting...</div>
<script>
// Connect to precise-time-ntp WebSocket server
const ws = new WebSocket('ws://localhost:8080');
const clock = document.getElementById('clock');
const status = document.getElementById('status');
ws.onopen = () => {
status.textContent = 'Connected to atomic time servers ✅';
status.className = 'status connected';
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'time') {
// Update clock display
const time = new Date(data.data.timestamp);
clock.textContent = time.toLocaleTimeString();
}
};
ws.onerror = () => {
status.textContent = 'Connection failed ❌';
status.className = 'status error';
};
ws.onclose = () => {
status.textContent = 'Disconnected - Reconnecting...';
status.className = 'status error';
// Auto-reconnect after 3 seconds
setTimeout(() => location.reload(), 3000);
};
// Request time every second
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'getTime' }));
}
}, 1000);
</script>
</body>
</html>
```
### Step 3: Run
```bash
# Start the server
node server.js
# Open the HTML file in your browser
# You now have a live atomic clock! 🎉
```
## Advanced Integration
### Custom Clock Styles
```html
<style>
.digital-clock {
font-family: 'Courier New', monospace;
font-size: 5rem;
background: #000;
color: #00ff00;
padding: 20px;
border-radius: 10px;
display: inline-block;
}
.analog-clock {
width: 200px;
height: 200px;
border: 8px solid #333;
border-radius: 50%;
margin: 20px auto;
position: relative;
}
</style>
```
### Multiple Clock Formats
```javascript
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'time') {
const time = new Date(data.data.timestamp);
// Different formats
document.getElementById('time-12h').textContent =
time.toLocaleTimeString('en-US', { hour12: true });
document.getElementById('time-24h').textContent =
time.toLocaleTimeString('en-US', { hour12: false });
document.getElementById('time-iso').textContent =
time.toISOString();
document.getElementById('time-unix').textContent =
Math.floor(time.getTime() / 1000);
}
};
```
### Server with HTTPS/WSS
```javascript
const timeSync = require('precise-time-ntp');
const https = require('https');
const fs = require('fs');
async function start() {
await timeSync.sync();
// HTTPS server for secure WebSocket
const server = https.createServer({
cert: fs.readFileSync('cert.pem'),
key: fs.readFileSync('key.pem')
});
timeSync.startWebSocketServer(8080, { server });
timeSync.startAutoSync(300000);
console.log('Secure time server ready at wss://localhost:8080');
}
start().catch(console.error);
```
### Real-time Dashboard
```html
<html>
<head>
<title>Time Dashboard</title>
<style>
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
padding: 20px;
}
.widget {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.big-number {
font-size: 2rem;
font-weight: bold;
color: #2c3e50;
}
</style>
</head>
<body>
<div class="dashboard">
<div class="widget">
<h3>Current Time</h3>
<div class="big-number" id="current-time">--:--:--</div>
</div>
<div class="widget">
<h3>System Offset</h3>
<div class="big-number" id="offset">-- ms</div>
</div>
<div class="widget">
<h3>Last Sync</h3>
<div class="big-number" id="last-sync">--</div>
</div>
<div class="widget">
<h3>Uptime</h3>
<div class="big-number" id="uptime">--</div>
</div>
</div>
<script>
const ws = new WebSocket('ws://localhost:8080');
let startTime = Date.now();
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'time') {
const time = new Date(data.data.timestamp);
document.getElementById('current-time').textContent =
time.toLocaleTimeString();
document.getElementById('offset').textContent =
(data.data.offset || 0) + ' ms';
document.getElementById('last-sync').textContent =
'Just now';
const uptime = Math.floor((Date.now() - startTime) / 1000);
document.getElementById('uptime').textContent =
uptime + 's';
}
};
// Request time every second
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'getTime' }));
}
}, 1000);
</script>
</body>
</html>
```
## WebSocket Protocol
The WebSocket server uses a simple JSON protocol:
### Client → Server Messages
```javascript
// Request current time
{ "type": "getTime" }
// Subscribe to automatic updates (every second)
{ "type": "subscribe" }
// Unsubscribe from updates
{ "type": "unsubscribe" }
```
### Server → Client Messages
```javascript
// Time response
{
"type": "time",
"data": {
"timestamp": "2025-06-30T14:30:45.123Z",
"offset": -1250,
"server": "time.cloudflare.com"
}
}
// Error response
{
"type": "error",
"message": "Synchronization failed"
}
```
## Production Deployment
### PM2 Process Manager
```bash
# Install PM2
npm install -g pm2
# Start with PM2
pm2 start server.js --name "time-server"
# Monitor
pm2 status
pm2 logs time-server
```
### Docker Deployment
```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]
```
### Nginx Proxy
```nginx
server {
listen 80;
server_name time.yourdomaincom;
location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
```
## Troubleshooting
### Common Issues
**Connection refused:**
- Check if server is running: `node server.js`
- Verify port is not blocked: `netstat -an | grep 8080`
**Time not updating:**
- Check WebSocket connection in browser dev tools
- Verify server has internet access for NTP sync
**Incorrect time:**
- Server system clock might be very wrong
- Check if NTP servers are reachable
- Enable smooth correction if large offset
### Debug Mode
```javascript
const timeSync = require('precise-time-ntp');
// Enable debug logging
timeSync.on('sync', (data) => {
console.log('Sync successful:', data);
});
timeSync.on('error', (error) => {
console.error('Sync error:', error);
});
await timeSync.sync();
```
This guide provides everything you need to integrate real-time atomic clocks into your web applications! 🎯
status.className = 'status connected';
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'time') {
const time = new Date(data.data.timestamp);
clock.textContent = time.toLocaleTimeString();
}
};
ws.onclose = () => {
status.textContent = 'Disconnected ❌';
status.className = 'status error';
};
// Request time every second
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'getTime' }));
}
}, 1000);
</script>
</body>
</html>
```
### 3. Run It
```bash
node server.js
# Open your HTML file in any browser
```
**You now have a live synchronized clock!** 🎉
## How It Works
1. **Server**: Connects to NTP servers for precise time
2. **WebSocket**: Broadcasts time data to connected browsers
3. **HTML**: Displays the synchronized time in real-time
## Integration Options
### Minimal Integration (Just the Clock)
```javascript
// In your existing HTML page
const ws = new WebSocket('ws://localhost:8080');
const clockElement = document.getElementById('your-clock-element');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'time') {
clockElement.textContent = new Date(data.data.timestamp).toLocaleTimeString();
}
};
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'getTime' }));
}
}, 1000);
```
### Custom Styling
The HTML/CSS above is just an example. You can style the clock however you want:
```css
.clock {
font-family: 'Courier New', monospace;
font-size: 4rem;
color: #2c3e50;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
```
### Different Time Formats
```javascript
// 12-hour format
clock.textContent = new Date(data.data.timestamp).toLocaleTimeString('en-US', {
hour12: true
});
// With date
clock.textContent = new Date(data.data.timestamp).toLocaleString();
// Custom format
const time = new Date(data.data.timestamp);
clock.textContent = `${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`;
```
## Production Tips
- Use `wss://` instead of `ws://` for HTTPS sites
- Add reconnection logic for robust connections
- Consider using environment variables for the WebSocket URL
- Monitor connection status and handle errors gracefully
## Troubleshooting
**"WebSocket connection failed"**
- Make sure the server is running (`node server.js`)
- Check the port (default: 8080)
- Verify firewall settings
**"Time not updating"**
- Check browser console for JavaScript errors
- Ensure the WebSocket connection is established
- Verify the server is syncing with NTP servers
### 1. Start the TimeSync Server
```javascript
const timeSync = require('precise-time-ntp');
async function startClockServer() {
// Synchronize first
await timeSync.sync();
// Start WebSocket server on port 8080
timeSync.startWebSocketServer(8080);
// Keep synchronized
timeSync.startAutoSync(60000); // Every minute
console.log('🌐 Clock server running on ws://localhost:8080');
}
startClockServer();
```
### 2. Create HTML Client
```html
<html>
<head>
<title>My Precise Clock</title>
</head>
<body>
<div id="clock">Loading...</div>
<script>
// Connect to TimeSync server
const ws = new WebSocket('ws://localhost:8080');
const clockEl = document.getElementById('clock');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'time') {
const time = new Date(data.data.timestamp);
clockEl.textContent = time.toLocaleTimeString();
}
};
// Request time every second
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'getTime' }));
}
}, 1000);
</script>
</body>
</html>
```
That's it! You now have a real-time synchronized clock.
## Complete Implementation
### Server Side (Node.js)
```javascript
const timeSync = require('precise-time-ntp');
async function createClockServer() {
try {
// Initial synchronization
console.log('Synchronizing with NTP servers...');
await timeSync.sync();
// Start WebSocket server
const port = timeSync.startWebSocketServer(8080);
console.log(`WebSocket server started on port ${port}`);
// Auto-sync every 5 minutes
timeSync.startAutoSync(300000);
// Listen to sync events
timeSync.on('sync', (data) => {
console.log(`Synchronized with ${data.server} (offset: ${data.offset}ms)`);
});
timeSync.on('error', (error) => {
console.error('Sync error:', error.message);
});
console.log('✅ Real-time clock server ready!');
console.log('Open your HTML file in a browser to see the clock');
} catch (error) {
console.error('Failed to start server:', error.message);
}
}
createClockServer();
```
### Client Side (HTML + JavaScript)
```html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Synchronized Clock</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: #f0f0f0;
}
.clock {
background: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
text-align: center;
}
.time {
font-size: 2rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.status {
margin-top: 1rem;
padding: 0.5rem;
border-radius: 5px;
}
.connected { background: #d4edda; color: #155724; }
.error { background: #f8d7da; color: #721c24; }
</style>
</head>
<body>
<div class="clock">
<div class="time" id="time">--:--:--</div>
<div id="date">Loading...</div>
<div class="status" id="status">Connecting...</div>
<button onclick="forceSync()">Sync Now</button>
</div>
<script>
let ws;
const timeEl = document.getElementById('time');
const dateEl = document.getElementById('date');
const statusEl = document.getElementById('status');
function connect() {
ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
statusEl.textContent = 'Connected to TimeSync';
statusEl.className = 'status connected';
requestTime();
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'time':
updateDisplay(data.data);
break;
case 'syncComplete':
statusEl.textContent = 'Sync completed';
break;
case 'error':
statusEl.textContent = 'Error: ' + data.message;
statusEl.className = 'status error';
break;
}
};
ws.onerror = () => {
statusEl.textContent = 'Connection error';
statusEl.className = 'status error';
};
ws.onclose = () => {
statusEl.textContent = 'Disconnected - Reconnecting...';
statusEl.className = 'status error';
setTimeout(connect, 3000);
};
}
function updateDisplay(timeData) {
const time = new Date(timeData.timestamp);
timeEl.textContent = time.toLocaleTimeString();
dateEl.textContent = time.toLocaleDateString();
}
function requestTime() {
if (ws?.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'getTime' }));
}
}
function forceSync() {
if (ws?.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'sync' }));
statusEl.textContent = 'Synchronizing...';
}
}
// Start connection
connect();
// Update every second
setInterval(requestTime, 1000);
</script>
</body>
</html>
```
## WebSocket Messages
### From Client to Server
```javascript
// Request current time
ws.send(JSON.stringify({ type: 'getTime' }));
// Request synchronization
ws.send(JSON.stringify({ type: 'sync' }));
```
### From Server to Client
```javascript
// Time update
{
type: 'time',
data: {
timestamp: '2024-12-30T15:30:45.123Z',
offset: 42,
synchronized: true
}
}
// Sync completed
{
type: 'syncComplete',
message: 'Synchronization completed'
}
// Error occurred
{
type: 'error',
message: 'Error description'
}
```
## Advanced Features
### Multiple Clocks
```html
<div class="clock-grid">
<div class="clock" data-timezone="UTC">
<div class="time" id="time-utc">--:--:--</div>
<div class="label">UTC</div>
</div>
<div class="clock" data-timezone="local">
<div class="time" id="time-local">--:--:--</div>
<div class="label">Local</div>
</div>
</div>
<script>
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'time') {
const time = new Date(data.data.timestamp);
// UTC time
document.getElementById('time-utc').textContent =
time.toLocaleTimeString('en-US', { timeZone: 'UTC' });
// Local time
document.getElementById('time-local').textContent =
time.toLocaleTimeString();
}
};
</script>
```
### Clock with Milliseconds
```javascript
function updateDisplay(timeData) {
const time = new Date(timeData.timestamp);
const hours = time.getHours().toString().padStart(2, '0');
const minutes = time.getMinutes().toString().padStart(2, '0');
const seconds = time.getSeconds().toString().padStart(2, '0');
const ms = time.getMilliseconds().toString().padStart(3, '0');
timeEl.textContent = `${hours}:${minutes}:${seconds}.${ms}`;
}
// Update more frequently for milliseconds
setInterval(requestTime, 100); // Every 100ms
```
### Connection Status Indicator
```css
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
margin-right: 5px;
}
.connected { background: #28a745; }
.error { background: #dc3545; }
.connecting { background: #ffc107; }
```
```javascript
function updateStatus(status) {
const indicator = document.querySelector('.status-indicator');
indicator.className = `status-indicator ${status}`;
}
ws.onopen = () => {
updateStatus('connected');
statusEl.textContent = 'Connected';
};
ws.onclose = () => {
updateStatus('error');
statusEl.textContent = 'Disconnected';
};
```
## Production Considerations
### Error Handling
```javascript
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
ws.onclose = () => {
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
console.log(`Reconnection attempt ${reconnectAttempts}/${maxReconnectAttempts}`);
setTimeout(connect, 3000 * reconnectAttempts);
} else {
statusEl.textContent = 'Connection failed - Please refresh';
}
};
ws.onopen = () => {
reconnectAttempts = 0; // Reset on successful connection
};
```
### Secure Connection (WSS)
```javascript
// For HTTPS sites, use secure WebSocket
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(`${protocol}//your-server.com:8080`);
```
### Performance Optimization
```javascript
// Throttle updates when tab is not visible
let updateInterval;
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
clearInterval(updateInterval);
} else {
updateInterval = setInterval(requestTime, 1000);
}
});
```
Your HTML page now has a real-time, NTP-synchronized clock that automatically stays precise! 🎯