v0.12.8 FCM bug fix
This commit is contained in:
334
fcm-app/public/app.js
Normal file
334
fcm-app/public/app.js
Normal file
@@ -0,0 +1,334 @@
|
||||
// Load Firebase SDK immediately
|
||||
const script1 = document.createElement('script');
|
||||
script1.src = 'https://www.gstatic.com/firebasejs/10.7.1/firebase-app-compat.js';
|
||||
script1.onload = () => {
|
||||
const script2 = document.createElement('script');
|
||||
script2.src = 'https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging-compat.js';
|
||||
script2.onload = () => {
|
||||
// Initialize Firebase immediately
|
||||
initializeFirebase();
|
||||
console.log('Firebase SDK and initialization complete');
|
||||
|
||||
// Now that Firebase is ready, set up the app
|
||||
setupApp();
|
||||
};
|
||||
document.head.appendChild(script2);
|
||||
};
|
||||
document.head.appendChild(script1);
|
||||
|
||||
// Global variables
|
||||
let currentUser = null;
|
||||
let fcmToken = null;
|
||||
let messaging = null;
|
||||
let swRegistration = null;
|
||||
let initPromise = null;
|
||||
let foregroundHandlerSetup = false;
|
||||
|
||||
const VAPID_KEY = 'BE6hPKkbf-h0lUQ1tYo249pBOdZFFcWQn9suwg3NDwSE8C_hv8hk1dUY9zxHBQEChO_IAqyFZplF_SUb5c4Ofrw';
|
||||
|
||||
// Simple user authentication
|
||||
const users = {
|
||||
'pwau1': { password: 'test123', name: 'Desktop User' },
|
||||
'pwau2': { password: 'test123', name: 'Mobile User 1' },
|
||||
'pwau3': { password: 'test123', name: 'Mobile User 2' }
|
||||
};
|
||||
|
||||
// Initialize Firebase — returns a promise that resolves when messaging is ready
|
||||
function initializeFirebase() {
|
||||
if (initPromise) return initPromise;
|
||||
|
||||
const firebaseConfig = {
|
||||
apiKey: "AIzaSyAw1v4COZ68Po8CuwVKrQq0ygf7zFd2QCA",
|
||||
authDomain: "fcmtest-push.firebaseapp.com",
|
||||
projectId: "fcmtest-push",
|
||||
storageBucket: "fcmtest-push.firebasestorage.app",
|
||||
messagingSenderId: "439263996034",
|
||||
appId: "1:439263996034:web:9b3d52af2c402e65fdec9b"
|
||||
};
|
||||
|
||||
if (firebase.apps.length === 0) {
|
||||
firebase.initializeApp(firebaseConfig);
|
||||
console.log('Firebase app initialized');
|
||||
}
|
||||
|
||||
initPromise = navigator.serviceWorker.register('/sw.js')
|
||||
.then((registration) => {
|
||||
console.log('Service Worker registered:', registration);
|
||||
swRegistration = registration;
|
||||
messaging = firebase.messaging();
|
||||
console.log('Firebase messaging initialized successfully');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Service Worker registration failed:', error);
|
||||
initPromise = null;
|
||||
throw error;
|
||||
});
|
||||
|
||||
return initPromise;
|
||||
}
|
||||
|
||||
// Show user info panel and hide login form
|
||||
function showUserInfo() {
|
||||
document.getElementById('loginForm').style.display = 'none';
|
||||
document.getElementById('userInfo').style.display = 'block';
|
||||
document.getElementById('currentUser').textContent = users[currentUser]?.name || currentUser;
|
||||
}
|
||||
|
||||
// Setup app after Firebase is ready
|
||||
function setupApp() {
|
||||
// Set up event listeners
|
||||
document.getElementById('loginForm').addEventListener('submit', login);
|
||||
document.getElementById('sendNotificationBtn').addEventListener('click', sendNotification);
|
||||
document.getElementById('logoutBtn').addEventListener('click', logout);
|
||||
|
||||
// Restore session and re-register FCM token if notifications were already granted
|
||||
const savedUser = localStorage.getItem('currentUser');
|
||||
if (savedUser) {
|
||||
currentUser = savedUser;
|
||||
showUserInfo();
|
||||
if (Notification.permission === 'granted') {
|
||||
initializeFirebase()
|
||||
.then(() => messaging.getToken({ vapidKey: VAPID_KEY, serviceWorkerRegistration: swRegistration }))
|
||||
.then(token => { if (token) return registerToken(currentUser, token); })
|
||||
.catch(err => console.error('Token refresh on session restore failed:', err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Request notification permission and get FCM token
|
||||
async function requestNotificationPermission() {
|
||||
try {
|
||||
console.log('Requesting notification permission...');
|
||||
const permission = await Notification.requestPermission();
|
||||
console.log('Permission result:', permission);
|
||||
|
||||
if (permission === 'granted') {
|
||||
console.log('Notification permission granted.');
|
||||
showStatus('Getting FCM token...', 'info');
|
||||
|
||||
try {
|
||||
const token = await messaging.getToken({ vapidKey: VAPID_KEY, serviceWorkerRegistration: swRegistration });
|
||||
console.log('FCM Token generated:', token);
|
||||
|
||||
if (!token) {
|
||||
throw new Error('getToken() returned empty — check VAPID key and service worker');
|
||||
}
|
||||
|
||||
fcmToken = token;
|
||||
|
||||
// Send token to server
|
||||
await registerToken(currentUser, token);
|
||||
showStatus('Notifications enabled successfully!', 'success');
|
||||
} catch (tokenError) {
|
||||
console.error('Error getting FCM token:', tokenError);
|
||||
showStatus('Failed to get FCM token: ' + tokenError.message, 'error');
|
||||
}
|
||||
} else {
|
||||
console.log('Notification permission denied.');
|
||||
showStatus('Notification permission denied.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error requesting notification permission:', error);
|
||||
showStatus('Failed to enable notifications: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Register FCM token with server
|
||||
async function registerToken(username, token) {
|
||||
try {
|
||||
console.log('Attempting to register token:', { username, token: token.substring(0, 20) + '...' });
|
||||
|
||||
const response = await fetch('/register-token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ username, token })
|
||||
});
|
||||
|
||||
console.log('Registration response status:', response.status);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Server returned ${response.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('Token registered successfully:', result);
|
||||
showStatus(`Token registered for ${username}`, 'success');
|
||||
} catch (error) {
|
||||
console.error('Error registering token:', error);
|
||||
showStatus('Failed to register token with server: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Handle foreground messages (guard against duplicate registration)
|
||||
function handleForegroundMessages() {
|
||||
if (foregroundHandlerSetup) return;
|
||||
foregroundHandlerSetup = true;
|
||||
messaging.onMessage(function(payload) {
|
||||
console.log('Received foreground message: ', payload);
|
||||
|
||||
// Show notification in foreground
|
||||
const notificationTitle = payload.notification.title;
|
||||
const notificationOptions = {
|
||||
body: payload.notification.body,
|
||||
icon: '/icon-192.png',
|
||||
badge: '/icon-192.png'
|
||||
};
|
||||
|
||||
new Notification(notificationTitle, notificationOptions);
|
||||
showStatus(`New notification: ${payload.notification.body}`, 'info');
|
||||
});
|
||||
}
|
||||
|
||||
// Login function
|
||||
async function login(event) {
|
||||
if (event) event.preventDefault();
|
||||
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
if (!users[username] || users[username].password !== password) {
|
||||
showStatus('Invalid username or password', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
currentUser = username;
|
||||
localStorage.setItem('currentUser', username);
|
||||
showUserInfo();
|
||||
|
||||
showStatus(`Logged in as ${users[username].name}`, 'success');
|
||||
|
||||
// Initialize Firebase and request notifications
|
||||
if (typeof firebase !== 'undefined') {
|
||||
await initializeFirebase();
|
||||
await requestNotificationPermission();
|
||||
handleForegroundMessages();
|
||||
} else {
|
||||
showStatus('Firebase not loaded. Please check your connection.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Logout function
|
||||
function logout() {
|
||||
currentUser = null;
|
||||
fcmToken = null;
|
||||
localStorage.removeItem('currentUser');
|
||||
document.getElementById('loginForm').style.display = 'block';
|
||||
document.getElementById('userInfo').style.display = 'none';
|
||||
document.getElementById('username').value = '';
|
||||
document.getElementById('password').value = '';
|
||||
showStatus('Logged out successfully.', 'info');
|
||||
}
|
||||
|
||||
// Send notification function
|
||||
async function sendNotification() {
|
||||
if (!currentUser) {
|
||||
showStatus('Please login first.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// First check registered users
|
||||
const usersResponse = await fetch('/users');
|
||||
const users = await usersResponse.json();
|
||||
console.log('Registered users:', users);
|
||||
|
||||
const response = await fetch('/send-notification', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
fromUser: currentUser,
|
||||
title: 'Test Notification',
|
||||
body: `Notification sent from ${currentUser} at ${new Date().toLocaleTimeString()}`
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to send notification');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('Send result:', result);
|
||||
|
||||
if (result.recipients === 0) {
|
||||
showStatus('No other users have registered tokens. Open the app on other devices and enable notifications.', 'error');
|
||||
} else {
|
||||
showStatus(`Notification sent to ${result.recipients} user(s)!`, 'success');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending notification:', error);
|
||||
showStatus('Failed to send notification.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Show status message
|
||||
function showStatus(message, type) {
|
||||
const statusEl = document.getElementById('status');
|
||||
statusEl.textContent = message;
|
||||
statusEl.className = `status ${type}`;
|
||||
statusEl.style.display = 'block';
|
||||
|
||||
setTimeout(() => {
|
||||
statusEl.style.display = 'none';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Register service worker and handle PWA installation
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function() {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
.then(function(registration) {
|
||||
console.log('ServiceWorker registration successful with scope: ', registration.scope);
|
||||
|
||||
// Handle PWA installation
|
||||
let deferredPrompt;
|
||||
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
console.log('beforeinstallprompt fired');
|
||||
e.preventDefault();
|
||||
deferredPrompt = e;
|
||||
|
||||
// Show install button or banner
|
||||
showInstallButton();
|
||||
});
|
||||
|
||||
function showInstallButton() {
|
||||
const installBtn = document.createElement('button');
|
||||
installBtn.textContent = 'Install App';
|
||||
installBtn.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: #2196F3;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
z-index: 1000;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
`;
|
||||
|
||||
installBtn.addEventListener('click', async () => {
|
||||
if (deferredPrompt) {
|
||||
deferredPrompt.prompt();
|
||||
const { outcome } = await deferredPrompt.userChoice;
|
||||
console.log(`User response to the install prompt: ${outcome}`);
|
||||
deferredPrompt = null;
|
||||
installBtn.remove();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(installBtn);
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log('ServiceWorker registration failed: ', error);
|
||||
});
|
||||
});
|
||||
}
|
||||
48
fcm-app/public/firebase-messaging-sw.js
Normal file
48
fcm-app/public/firebase-messaging-sw.js
Normal file
@@ -0,0 +1,48 @@
|
||||
importScripts('https://www.gstatic.com/firebasejs/10.7.1/firebase-app-compat.js');
|
||||
importScripts('https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging-compat.js');
|
||||
|
||||
firebase.initializeApp({
|
||||
apiKey: "AIzaSyAw1v4COZ68Po8CuwVKrQq0ygf7zFd2QCA",
|
||||
authDomain: "fcmtest-push.firebaseapp.com",
|
||||
projectId: "fcmtest-push",
|
||||
storageBucket: "fcmtest-push.firebasestorage.app",
|
||||
messagingSenderId: "439263996034",
|
||||
appId: "1:439263996034:web:9b3d52af2c402e65fdec9b"
|
||||
});
|
||||
|
||||
const messaging = firebase.messaging();
|
||||
|
||||
messaging.onBackgroundMessage(function(payload) {
|
||||
console.log('Received background message:', payload);
|
||||
|
||||
const notificationTitle = payload.notification.title;
|
||||
const notificationOptions = {
|
||||
body: payload.notification.body,
|
||||
icon: '/icon-192.png',
|
||||
badge: '/icon-192.png',
|
||||
tag: 'fcm-test',
|
||||
data: payload.data
|
||||
};
|
||||
|
||||
self.registration.showNotification(notificationTitle, notificationOptions);
|
||||
});
|
||||
|
||||
self.addEventListener('notificationclick', function(event) {
|
||||
console.log('Notification clicked:', event);
|
||||
event.notification.close();
|
||||
|
||||
if (event.action === 'close') return;
|
||||
|
||||
event.waitUntil(
|
||||
clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function(clientList) {
|
||||
for (const client of clientList) {
|
||||
if (client.url === '/' && 'focus' in client) {
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
if (clients.openWindow) {
|
||||
return clients.openWindow('/');
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
BIN
fcm-app/public/icon-192.png
Normal file
BIN
fcm-app/public/icon-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
fcm-app/public/icon-512.png
Normal file
BIN
fcm-app/public/icon-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
111
fcm-app/public/index.html
Normal file
111
fcm-app/public/index.html
Normal file
@@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FCM Test PWA</title>
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<meta name="theme-color" content="#2196F3">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.login-form {
|
||||
display: block;
|
||||
}
|
||||
.user-info {
|
||||
display: none;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #1976D2;
|
||||
}
|
||||
button:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.status {
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
.info {
|
||||
background-color: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
.user-display {
|
||||
background-color: #e3f2fd;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>FCM Test PWA</h1>
|
||||
|
||||
<div id="status" class="status" style="display: none;"></div>
|
||||
|
||||
<div id="loginForm" class="login-form">
|
||||
<h2>Login</h2>
|
||||
<input type="text" id="username" placeholder="Username (pwau1, pwau2, or pwau3)" required>
|
||||
<input type="password" id="password" placeholder="Password" required>
|
||||
<button onclick="login()">Login</button>
|
||||
</div>
|
||||
|
||||
<div id="userInfo" class="user-info">
|
||||
<div class="user-display">
|
||||
Logged in as: <span id="currentUser"></span>
|
||||
</div>
|
||||
<button id="sendNotificationBtn" onclick="sendNotification()">Send Notification</button>
|
||||
<button id="logoutBtn" onclick="logout()">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
28
fcm-app/public/manifest.json
Normal file
28
fcm-app/public/manifest.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "FCM Test PWA",
|
||||
"short_name": "FCM Test",
|
||||
"description": "PWA for testing Firebase Cloud Messaging",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"display_override": ["window-controls-overlay", "standalone"],
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#2196F3",
|
||||
"orientation": "portrait-primary",
|
||||
"scope": "/",
|
||||
"icons": [
|
||||
{
|
||||
"purpose": "any maskable",
|
||||
"src": "/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"purpose": "any maskable",
|
||||
"src": "/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"categories": ["utilities", "productivity"],
|
||||
"lang": "en-US"
|
||||
}
|
||||
82
fcm-app/public/sw.js
Normal file
82
fcm-app/public/sw.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const CACHE_NAME = 'fcm-test-pwa-v1';
|
||||
const urlsToCache = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/app.js',
|
||||
'/manifest.json'
|
||||
];
|
||||
|
||||
// Install event
|
||||
self.addEventListener('install', function(event) {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME)
|
||||
.then(function(cache) {
|
||||
return cache.addAll(urlsToCache);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Fetch event
|
||||
self.addEventListener('fetch', function(event) {
|
||||
event.respondWith(
|
||||
caches.match(event.request)
|
||||
.then(function(response) {
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
return fetch(event.request);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Background sync for FCM
|
||||
importScripts('https://www.gstatic.com/firebasejs/10.7.1/firebase-app-compat.js');
|
||||
importScripts('https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging-compat.js');
|
||||
|
||||
// Initialize Firebase in service worker
|
||||
firebase.initializeApp({
|
||||
apiKey: "AIzaSyAw1v4COZ68Po8CuwVKrQq0ygf7zFd2QCA",
|
||||
authDomain: "fcmtest-push.firebaseapp.com",
|
||||
projectId: "fcmtest-push",
|
||||
storageBucket: "fcmtest-push.firebasestorage.app",
|
||||
messagingSenderId: "439263996034",
|
||||
appId: "1:439263996034:web:9b3d52af2c402e65fdec9b"
|
||||
});
|
||||
|
||||
const messaging = firebase.messaging();
|
||||
|
||||
// Handle notification clicks
|
||||
self.addEventListener('notificationclick', function(event) {
|
||||
console.log('Notification clicked:', event);
|
||||
event.notification.close();
|
||||
|
||||
if (event.action === 'close') return;
|
||||
|
||||
event.waitUntil(
|
||||
clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function(clientList) {
|
||||
for (const client of clientList) {
|
||||
if (client.url === '/' && 'focus' in client) {
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
if (clients.openWindow) {
|
||||
return clients.openWindow('/');
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Handle background messages
|
||||
messaging.onBackgroundMessage(function(payload) {
|
||||
console.log('Received background message ', payload);
|
||||
|
||||
const notificationTitle = payload.notification.title;
|
||||
const notificationOptions = {
|
||||
body: payload.notification.body,
|
||||
icon: '/icon-192.png',
|
||||
badge: '/icon-192.png',
|
||||
tag: 'fcm-test'
|
||||
};
|
||||
|
||||
return self.registration.showNotification(notificationTitle, notificationOptions);
|
||||
});
|
||||
Reference in New Issue
Block a user