You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
5.6 KiB
153 lines
5.6 KiB
|
|
from typing import Dict, Tuple, Union, Optional
|
|
from flask import Flask, request, jsonify, Response
|
|
from models import db, VAPIDKey, Subscription
|
|
from cryptography.hazmat.backends import default_backend
|
|
from cryptography.hazmat.primitives import serialization
|
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
from pywebpush import webpush, WebPushException
|
|
|
|
import base64
|
|
import threading
|
|
import time
|
|
|
|
class WebPushService:
|
|
|
|
def __init__(self, config_name: str) -> None:
|
|
self.app = Flask(__name__)
|
|
self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data/webpush.db'
|
|
db.init_app(self.app)
|
|
with self.app.app_context():
|
|
self._initialize()
|
|
|
|
self.daily_notification_thread = threading.Thread(target=self._send_daily_notifications)
|
|
self.daily_notification_thread.start()
|
|
|
|
|
|
def _generate_and_save_vapid_keys(self) -> None:
|
|
try:
|
|
private_key = ec.generate_private_key(ec.SECP256R1(), default_backend())
|
|
public_key_bytes = private_key.public_key().public_bytes(
|
|
encoding=serialization.Encoding.X962,
|
|
format=serialization.PublicFormat.UncompressedPoint
|
|
)
|
|
public_key_base64 = base64.b64encode(public_key_bytes).decode()
|
|
|
|
private_key_bytes = private_key.private_bytes(
|
|
encoding=serialization.Encoding.DER,
|
|
format=serialization.PrivateFormat.PKCS8,
|
|
encryption_algorithm=serialization.NoEncryption()
|
|
)
|
|
private_key_base64 = base64.b64encode(private_key_bytes).decode()
|
|
|
|
key = VAPIDKey(public_key=public_key_base64, private_key=private_key_base64)
|
|
db.session.add(key)
|
|
db.session.commit()
|
|
|
|
except Exception as e:
|
|
print(f"Error generating VAPID keys: {e}")
|
|
|
|
|
|
def _initialize(self) -> None:
|
|
if not VAPIDKey.query.first():
|
|
self._generate_and_save_vapid_keys()
|
|
|
|
|
|
def _send_push_notification(self, subscription_info: Dict, message: Dict, vapid_key: VAPIDKey) -> bool:
|
|
try:
|
|
webpush(
|
|
subscription_info=subscription_info,
|
|
data=json.dumps(message),
|
|
vapid_private_key=vapid_key.private_key,
|
|
vapid_claims={"sub": "mailto:admin@yourdomain.com"}
|
|
)
|
|
return True
|
|
|
|
except WebPushException as ex:
|
|
print(f"Failed to send push notification: {ex}")
|
|
return False
|
|
|
|
|
|
def _send_daily_notifications(self) -> None:
|
|
while True:
|
|
with self.app.app_context():
|
|
all_subscriptions = Subscription.query.all()
|
|
vapid_key = VAPIDKey.query.first()
|
|
message = {"title": "Daily Update", "message": "Here's your daily update!"}
|
|
for subscription in all_subscriptions:
|
|
subscription_info = {
|
|
"endpoint": subscription.endpoint,
|
|
"keys": {
|
|
"p256dh": subscription.p256dh,
|
|
"auth": subscription.auth
|
|
}
|
|
}
|
|
self._send_push_notification(subscription_info, message, vapid_key)
|
|
|
|
time.sleep(24 * 60 * 60)
|
|
|
|
# Route handlers and other methods would go here...
|
|
|
|
@app.route('/regenerate_vapid', methods=['POST'])
|
|
def regenerate_vapid(self) -> Tuple[str, int]:
|
|
try:
|
|
with self.app.app_context():
|
|
VAPIDKey.query.delete()
|
|
db.session.commit()
|
|
self._generate_and_save_vapid_keys()
|
|
|
|
return jsonify(success=True, message="VAPID keys regenerated successfully"), 200
|
|
|
|
except Exception as e:
|
|
return jsonify(error=f'Error regenerating VAPID keys: {str(e)}'), 500
|
|
|
|
|
|
@app.route('/get_vapid')
|
|
def get_vapid(self) -> Response:
|
|
key = VAPIDKey.query.first()
|
|
return jsonify(vapidKey=key.public_key)
|
|
|
|
|
|
@app.route('/subscribe', methods=['POST'])
|
|
def subscribe(self) -> Tuple[str, int]:
|
|
content = request.json
|
|
vapid_key = VAPIDKey.query.first()
|
|
|
|
if not vapid_key:
|
|
return jsonify(success=False, error="No VAPID keys available"), 500
|
|
|
|
subscription = Subscription(endpoint=content['endpoint'],
|
|
p256dh=content['keys']['p256dh'],
|
|
auth=content['keys']['auth'],
|
|
vapid_key_id=vapid_key.id)
|
|
db.session.add(subscription)
|
|
db.session.commit()
|
|
|
|
time.sleep(5)
|
|
|
|
subscription_info = {
|
|
"endpoint": subscription.endpoint,
|
|
"keys": {
|
|
"p256dh": subscription.p256dh,
|
|
"auth": subscription.auth
|
|
}
|
|
}
|
|
|
|
message = {"title": "Subscription Successful", "message": "Thank you for subscribing!"}
|
|
success = self._send_push_notification(subscription_info, message, vapid_key)
|
|
|
|
return jsonify(success=success, message=vapid_key.private_key)
|
|
|
|
|
|
@app.route('/unsubscribe', methods=['POST'])
|
|
def unsubscribe(self) -> Tuple[str, int]:
|
|
content = request.json
|
|
endpoint = content['endpoint']
|
|
subscription = Subscription.query.filter_by(endpoint=endpoint).first()
|
|
|
|
if subscription:
|
|
db.session.delete(subscription)
|
|
db.session.commit()
|
|
return jsonify(success=True, message="Subscription deleted successfully")
|
|
else:
|
|
return jsonify(success=False, error="Subscription not found"), 404
|
|
|