v0.12.8 FCM bug fix
This commit is contained in:
244
fcm-app/server.js
Normal file
244
fcm-app/server.js
Normal file
@@ -0,0 +1,244 @@
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const cors = require('cors');
|
||||
const admin = require('firebase-admin');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.static('public'));
|
||||
|
||||
// In-memory storage for FCM tokens (in production, use a database)
|
||||
const userTokens = new Map();
|
||||
|
||||
// Load tokens from file on startup (for persistence)
|
||||
const fs = require('fs');
|
||||
const TOKENS_FILE = './data/tokens.json';
|
||||
|
||||
function loadTokens() {
|
||||
try {
|
||||
if (fs.existsSync(TOKENS_FILE)) {
|
||||
const data = fs.readFileSync(TOKENS_FILE, 'utf8');
|
||||
const tokens = JSON.parse(data);
|
||||
for (const [user, tokenArray] of Object.entries(tokens)) {
|
||||
userTokens.set(user, new Set(tokenArray));
|
||||
}
|
||||
console.log(`Loaded tokens for ${userTokens.size} users from file`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('No existing tokens file found, starting fresh');
|
||||
}
|
||||
}
|
||||
|
||||
function saveTokens() {
|
||||
const tokens = {};
|
||||
for (const [user, tokenSet] of userTokens.entries()) {
|
||||
tokens[user] = Array.from(tokenSet);
|
||||
}
|
||||
fs.writeFileSync(TOKENS_FILE, JSON.stringify(tokens, null, 2));
|
||||
}
|
||||
|
||||
// Load existing tokens on startup
|
||||
loadTokens();
|
||||
|
||||
// Auto-save tokens every 30 seconds
|
||||
setInterval(() => {
|
||||
try {
|
||||
saveTokens();
|
||||
} catch (error) {
|
||||
console.error('Auto-save tokens failed:', error);
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
// Initialize Firebase Admin
|
||||
if (process.env.FIREBASE_PRIVATE_KEY) {
|
||||
const serviceAccount = {
|
||||
projectId: process.env.FIREBASE_PROJECT_ID,
|
||||
privateKeyId: process.env.FIREBASE_PRIVATE_KEY_ID,
|
||||
privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
|
||||
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
|
||||
clientId: process.env.FIREBASE_CLIENT_ID,
|
||||
authUri: process.env.FIREBASE_AUTH_URI,
|
||||
tokenUri: process.env.FIREBASE_TOKEN_URI,
|
||||
authProviderX509CertUrl: process.env.FIREBASE_AUTH_PROVIDER_X509_CERT_URL,
|
||||
clientC509CertUrl: process.env.FIREBASE_CLIENT_X509_CERT_URL
|
||||
};
|
||||
|
||||
admin.initializeApp({
|
||||
credential: admin.credential.cert(serviceAccount)
|
||||
});
|
||||
|
||||
console.log('Firebase Admin initialized successfully');
|
||||
} else {
|
||||
console.log('Firebase Admin not configured. Please set up .env file');
|
||||
}
|
||||
|
||||
// Routes
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||
});
|
||||
|
||||
// Register FCM token
|
||||
app.post('/register-token', (req, res) => {
|
||||
const { username, token } = req.body;
|
||||
|
||||
console.log(`Token registration request:`, { username, token: token?.substring(0, 20) + '...' });
|
||||
|
||||
if (!username || !token) {
|
||||
console.log('Token registration failed: missing username or token');
|
||||
return res.status(400).json({ error: 'Username and token are required' });
|
||||
}
|
||||
|
||||
// Store token for user
|
||||
if (!userTokens.has(username)) {
|
||||
userTokens.set(username, new Set());
|
||||
}
|
||||
|
||||
const userTokenSet = userTokens.get(username);
|
||||
if (userTokenSet.has(token)) {
|
||||
console.log(`Token already registered for user: ${username}`);
|
||||
} else {
|
||||
userTokenSet.add(token);
|
||||
console.log(`New token registered for user: ${username}`);
|
||||
// Save immediately after new registration
|
||||
try {
|
||||
saveTokens();
|
||||
} catch (saveError) {
|
||||
console.error('Failed to persist tokens to disk:', saveError);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Total tokens for ${username}: ${userTokenSet.size}`);
|
||||
console.log(`Total registered users: ${userTokens.size}`);
|
||||
|
||||
res.json({ success: true, message: 'Token registered successfully' });
|
||||
});
|
||||
|
||||
// Send notification to all other users
|
||||
app.post('/send-notification', async (req, res) => {
|
||||
const { fromUser, title, body } = req.body;
|
||||
|
||||
if (!fromUser || !title || !body) {
|
||||
return res.status(400).json({ error: 'fromUser, title, and body are required' });
|
||||
}
|
||||
|
||||
if (!admin.apps.length) {
|
||||
return res.status(500).json({ error: 'Firebase Admin not initialized' });
|
||||
}
|
||||
|
||||
try {
|
||||
let totalRecipients = 0;
|
||||
const promises = [];
|
||||
|
||||
// Send to all users except the sender
|
||||
for (const [username, tokens] of userTokens.entries()) {
|
||||
if (username === fromUser) continue; // Skip sender
|
||||
|
||||
for (const token of tokens) {
|
||||
const message = {
|
||||
token: token,
|
||||
notification: {
|
||||
title: title,
|
||||
body: body
|
||||
},
|
||||
webpush: {
|
||||
headers: {
|
||||
'Urgency': 'high'
|
||||
},
|
||||
notification: {
|
||||
icon: '/icon-192.png',
|
||||
badge: '/icon-192.png',
|
||||
tag: 'fcm-test'
|
||||
},
|
||||
fcm_options: {
|
||||
link: '/'
|
||||
}
|
||||
},
|
||||
android: {
|
||||
priority: 'high',
|
||||
notification: {
|
||||
sound: 'default',
|
||||
click_action: '/'
|
||||
}
|
||||
},
|
||||
apns: {
|
||||
payload: {
|
||||
aps: {
|
||||
sound: 'default',
|
||||
badge: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
promises.push(
|
||||
admin.messaging().send(message)
|
||||
.then(() => {
|
||||
console.log(`Notification sent to ${username} successfully`);
|
||||
totalRecipients++;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Error sending notification to ${username}:`, error);
|
||||
// Remove invalid token
|
||||
if (error.code === 'messaging/registration-token-not-registered') {
|
||||
tokens.delete(token);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
recipients: totalRecipients,
|
||||
message: `Notification sent to ${totalRecipients} recipient(s)`
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error sending notifications:', error);
|
||||
res.status(500).json({ error: 'Failed to send notifications' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get all registered users (for debugging)
|
||||
app.get('/users', (req, res) => {
|
||||
const users = {};
|
||||
console.log('Current userTokens map:', userTokens);
|
||||
console.log('Number of registered users:', userTokens.size);
|
||||
|
||||
for (const [username, tokens] of userTokens.entries()) {
|
||||
users[username] = {
|
||||
tokenCount: tokens.size,
|
||||
tokens: Array.from(tokens)
|
||||
};
|
||||
}
|
||||
res.json(users);
|
||||
});
|
||||
|
||||
// Debug endpoint to check server status
|
||||
app.get('/debug', (req, res) => {
|
||||
res.json({
|
||||
firebaseAdminInitialized: admin.apps.length > 0,
|
||||
registeredUsers: userTokens.size,
|
||||
userTokens: Object.fromEntries(
|
||||
Array.from(userTokens.entries()).map(([user, tokens]) => [user, {
|
||||
count: tokens.size,
|
||||
tokens: Array.from(tokens)
|
||||
}])
|
||||
),
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`FCM Test PWA server running on port ${PORT}`);
|
||||
console.log(`Open http://localhost:${PORT} in your browser`);
|
||||
console.log(`Server listening on all interfaces (0.0.0.0:${PORT})`);
|
||||
});
|
||||
Reference in New Issue
Block a user