[Python] Receiving duplicate FCM notifications on Android phone, works normally on desktop

Discussão em 'Python' iniciado por Stack, Dezembro 22, 2024.

  1. Stack

    Stack Membro Participativo

    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

    Continue reading...

Compartilhe esta Página