110 lines
3.8 KiB
Python
110 lines
3.8 KiB
Python
import logging
|
|
import firebase_admin
|
|
from firebase_admin import credentials, messaging
|
|
from odoo.tools import config
|
|
|
|
# Fetch the credential file path safely from odoo.conf
|
|
firebase_key_path = config.get("firebase_private_key_path")
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
# Initialize the app safely to prevent "app already exists" runtime crashes
|
|
try:
|
|
firebase_app = firebase_admin.get_app()
|
|
except ValueError:
|
|
if firebase_key_path:
|
|
firebase_cred = credentials.Certificate(firebase_key_path)
|
|
firebase_app = firebase_admin.initialize_app(firebase_cred)
|
|
else:
|
|
# Fallback to default application credentials if config key is missing
|
|
firebase_app = firebase_admin.initialize_app()
|
|
|
|
|
|
def send_firebase_notifications(messages: list) -> int:
|
|
"""Sends list of notification messages using Firebase Cloud Messaging.
|
|
|
|
Expecting input layout:
|
|
[
|
|
{'token': '...', 'body': '...', 'title': '...'},
|
|
]
|
|
"""
|
|
if not messages:
|
|
return 0
|
|
|
|
# Build the payload using native Firebase messaging objects
|
|
fcm_messages = []
|
|
for msg in messages:
|
|
token = msg.get("token")
|
|
if not token:
|
|
continue
|
|
notification = messaging.Notification(
|
|
title=msg.get("title", ""),
|
|
body=msg.get("body", ""),
|
|
)
|
|
# include optional data payload
|
|
data_payload = msg.get("data")
|
|
fcm_messages.append(
|
|
messaging.Message(
|
|
notification=notification,
|
|
token=token,
|
|
data=data_payload if isinstance(data_payload, dict) else None,
|
|
)
|
|
)
|
|
|
|
if not fcm_messages:
|
|
return 0
|
|
|
|
# Execute the batch transmission using the Firebase Admin SDK
|
|
try:
|
|
# The SDK / FCM backend limits batch sizes; be conservative and chunk (500 is a safe upper bound).
|
|
success_total = 0
|
|
chunk_size = 500
|
|
|
|
for i in range(0, len(fcm_messages), chunk_size):
|
|
batch = fcm_messages[i : i + chunk_size]
|
|
|
|
# Try newer API first (send_all), fall back to send_multicast for older SDK versions
|
|
if hasattr(messaging, 'send_all'):
|
|
response = messaging.send_all(batch)
|
|
_logger.info(
|
|
"Firebase Success Count: %s",
|
|
response.success_count
|
|
)
|
|
|
|
_logger.info(
|
|
"Firebase Failure Count: %s",
|
|
response.failure_count
|
|
)
|
|
|
|
for idx, resp in enumerate(response.responses):
|
|
if not resp.success:
|
|
_logger.error(
|
|
"Firebase Error %s: %s",
|
|
idx,
|
|
resp.exception
|
|
)
|
|
else:
|
|
# Older firebase-admin SDK: use send_multicast
|
|
from firebase_admin.messaging import MulticastMessage
|
|
multicast = MulticastMessage(tokens=[m.token for m in batch if m.token])
|
|
# send_multicast doesn't support per-token custom payloads; use individual sends as fallback
|
|
success_count = 0
|
|
for msg in batch:
|
|
try:
|
|
messaging.send(msg)
|
|
success_count += 1
|
|
except Exception as e:
|
|
_logger.debug("Failed to send message to token %s: %s", msg.token, e)
|
|
response = type('obj', (object,), {'success_count': success_count})()
|
|
|
|
sent = getattr(response, "success_count", 0)
|
|
success_total += sent
|
|
_logger.info("Firebase: sent %s/%s messages in chunk", sent, len(batch))
|
|
return success_total
|
|
except Exception as exc: # catch firebase exceptions and log them
|
|
_logger.exception("Failed to send firebase notifications: %s", exc)
|
|
return 0
|
|
|
|
|
|
|
|
|