I am making a Flask web app that uses the Google Sheets API to scan a school bus position spreadsheet and determine which section a bus is in. Then, it sends a notification with the user’s bus number, quadrant/section, and the buses it’s in between. The app works fine on desktop devices, but on Android, it sends duplicate notifications. One contains the site favicon, while the other doesn’t.
I thought this was a problem with ngrok, the tunneling service I was using to connect my phone to my laptop which is hosting the app over HTTPS, but as it turns out, connecting from a desktop device still doesn’t send duplicate notifications and works as expected, so I don’t think this is a problem with ngrok.Here is an extremely simplified version of my code, with all the irrelevant parts removed. It has the same issue as the extensive code.
Flask app:
from flask import Flask, request, jsonify, render_template, send_from_directory
import firebase_admin
from firebase_admin import credentials, messaging
from flask_cors import CORS
import os
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
app = Flask(__name__,
template_folder='templates',
static_folder='static',
static_url_path=''
)
CORS(app)
# Initialize Firebase Admin SDK
cred = credentials.Certificate('Core/firetoken.json') # Your Firebase credentials file
firebase_admin.initialize_app(cred)
@app.route('/firebase-messaging-sw.js')
def sw():
response = send_from_directory(app.static_folder, 'firebase-messaging-sw.js')
response.headers['Content-Type'] = 'application/javascript'
response.headers['Service-Worker-Allowed'] = '/'
return response
@app.route('/')
def home():
return render_template('index.html',
firebase_config=dict(
api_key=os.getenv('FIREBASE_API_KEY'),
auth_domain=os.getenv('FIREBASE_AUTH_DOMAIN'),
project_id=os.getenv('FIREBASE_PROJECT_ID'),
storage_bucket=os.getenv('FIREBASE_STORAGE_BUCKET'),
messaging_sender_id=os.getenv('FIREBASE_MESSAGING_SENDER_ID'),
app_id=os.getenv('FIREBASE_APP_ID'),
measurement_id=os.getenv('FIREBASE_MEASUREMENT_ID')
),
vapid_key=os.getenv('VAPID_KEY')
)
@app.route('/store_token', methods=['POST'])
def store_token():
data = request.json
token = data.get('token')
if not token:
return jsonify({'error': 'Token is required'}), 400
try:
# Send a test notification
message = messaging.Message(
notification=messaging.Notification(
title="Test Notification",
body="This is a test notification!"
),
token=token
)
messaging.send(message)
return jsonify({'status': 'Notification sent successfully'})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(debug=True)
HTML Template:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Notification Test</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.container {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 10px 0;
}
#status {
margin: 20px 0;
padding: 10px;
border-radius: 4px;
}
.success { background-color: #dff0d8; color: #3c763d; }
.error { background-color: #f2dede; color: #a94442; }
</style>
</head>
<body>
<div class="container">
<h1>Notification Test</h1>
<button id="send-notification">Send Test Notification</button>
<p id="status"></p>
</div>
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.0.1/firebase-app.js";
import { getMessaging, getToken, onMessage } from "https://www.gstatic.com/firebasejs/11.0.1/firebase-messaging.js";
const firebaseConfig = {
apiKey: "{{ firebase_config.api_key }}",
authDomain: "{{ firebase_config.auth_domain }}",
projectId: "{{ firebase_config.project_id }}",
storageBucket: "{{ firebase_config.storage_bucket }}",
messagingSenderId: "{{ firebase_config.messaging_sender_id }}",
appId: "{{ firebase_config.app_id }}",
measurementId: "{{ firebase_config.measurement_id }}"
};
const vapidKey = "{{ vapid_key }}";
try {
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);
// Register service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/firebase-messaging-sw.js')
.then(registration => console.log('Service Worker registered'))
.catch(err => console.error('Service Worker registration failed:', err));
}
document.getElementById('send-notification').addEventListener('click', async () => {
try {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
const currentRegistration = await navigator.serviceWorker.getRegistration();
const token = await getToken(messaging, {
vapidKey: vapidKey,
serviceWorkerRegistration: currentRegistration
});
const response = await fetch('/store_token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: token })
});
const result = await response.json();
if (!response.ok) throw new Error(result.error);
document.getElementById('status').innerText = 'Notification sent successfully!';
document.getElementById('status').className = 'success';
} else {
throw new Error('Notification permission denied');
}
} catch (error) {
document.getElementById('status').innerText = `Error: ${error.message}`;
document.getElementById('status').className = 'error';
}
});
// Listen for messages
onMessage(messaging, (payload) => {
document.getElementById('status').innerText = `Received: ${payload.notification.title} - ${payload.notification.body}`;
document.getElementById('status').className = 'success';
});
} catch (error) {
console.error('Initialization error:', error);
document.getElementById('status').innerText = `Error: ${error.message}`;
document.getElementById('status').className = 'error';
}
</script>
</body>
</html>
Here is a screenshot of the problem:
Screenshot of duplicate Android notifications