@ -4,13 +4,14 @@ Environment variables:
- ADMIN_PASSWORD : password for admin user for sensitive endpoints , defaults to ' admin '
"""
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 flask import Flask , request , jsonify , Response
from models import db , VAPIDKey , Settings , Subscription
from pywebpush import webpush , WebPushException
from sqlalchemy import and_
from typing import Dict , Tuple
import base64
import datetime
@ -141,8 +142,10 @@ class WebPushService():
- message ( Dict ) : The actual message content to be sent as the push notification .
- vapid_key ( VAPIDKey ) : The VAPID key model instance containing the private key used for sending the notification .
Returns :
- request . Response https : / / requests . readthedocs . io / en / latest / api . html #requests.Response if the push notification was sent successfully
Returns Dict with the following keys
- success : True if the push notification was sent successfully , False otherwise
- message : a string message with the resulting text , usually nothing on success
- result : request . Response https : / / requests . readthedocs . io / en / latest / api . html #requests.Response if the push notification was sent successfully
or False if there was an exception
Notes :
@ -205,41 +208,108 @@ class WebPushService():
while True :
now = datetime . datetime . now ( ) . isoformat ( )
now = datetime . datetime . now ( )
print ( f " { now } - Starting to send subscriptions... " , flush = True )
# Creating a context for the application to enable database operations
with self . app . app_context ( ) :
# Retrieving all subscription data from the database
all_subscriptions = Subscription . query . all ( )
# Retrieving the VAPID key from the database
# Retrieve the VAPID key from the database
vapid_key = VAPIDKey . query . first ( )
# Constructing the push notification message
# Construct the push notification message
# The title value is a key, triggering the device to apply logic and customize both title and message.
# See https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/src/commit/1e6159869fc28ca6e6b5b3d186617d75705100b4/sw_scripts/additional-scripts.js#L65
UPDATE_TITLE = " DAILY_CHECK "
message = { " title " : UPDATE_TITLE , " message " : f " Update for { now } " }
# Sending a push notification to each subscribed client
for subscription in all_subscriptions :
subscription_info = {
" endpoint " : subscription . endpoint ,
" keys " : {
" p256dh " : subscription . p256dh ,
" auth " : subscription . auth
}
}
result = WebPushService . _send_push_notification ( subscription_info , message , vapid_key )
print ( f " Result from sub { subscription . id } : success= { result [ ' success ' ] } text= { result [ ' message ' ] } " , flush = True )
print ( f " { now } - Finished sending { len ( all_subscriptions ) } subscriptions. " , flush = True )
self . latest_subscription_run = now
# Sleeping for 24 hours before sending the next set of notifications
time . sleep ( 24 * 60 * 60 )
# Determine the beginning and end time to check for subscriptions
settings = Settings . query . first ( )
if settings . running_notify_end_time is None :
# set all subscriptions without a time to the current time
# (these won't be picked-up in the current run, since thie current minute is the end minute)
Subscription . query . filter_by ( notify_time = None ) . update ( { Subscription . notify_time : now . strftime ( ' % H: % M ' ) } )
"""
Storing the HH : MM for the desired notification time isn ' t a bad idea.
However , the logic that compares directly to it is a bit complicated .
It would be more straightforward to save the next notification time in each record
and then update that every time this process runs . It would require raw SQL with
some calculations and many DB updates each day , but the logic would be more clear .
"""
# get the previous notify end time from the DB as a datetime
prev_notify_end_time = datetime . datetime . fromisoformat ( settings . prev_notify_end_time )
# if it's before midnight this morning, catch us up to the beginning of today:
if prev_notify_end_time < now . replace ( hour = 0 , minute = 0 , second = 0 , microsecond = 0 ) :
# make the start time the later of: prevNotifyEndTime or yesterday at this time
start_time = max (
prev_notify_end_time ,
# if the current time is later in the day, use that
# because the next run will pick up everything from midnight until now today
now . replace ( second = 0 , microsecond = 0 ) - datetime . timedelta ( days = 1 )
)
# make the end time right at midnight at the beginning of today
end_time = now . replace ( hour = 0 , minute = 0 , second = 0 , microsecond = 0 )
start_minute = start_time . strftime ( ' % H: % M ' )
# (we'd never catch anything if we used a non-zero start_minute and "00:00" as the end_minute)
end_minute = ' 23:60 ' # gotta catch "23:59", too
else :
# the start time is OK
start_time = prev_notify_end_time
# the end time is now
end_time = now . replace ( second = 0 , microsecond = 0 )
start_minute = start_time . strftime ( ' % H: % M ' )
end_minute = end_time . strftime ( ' % H: % M ' )
# This really should update & continue only if the running_notify_end_time is still None,
# just in case another thread started.
settings . running_notify_end_time = end_time . isoformat ( )
db . session . commit ( )
# this check was generated by Copilot; it's probably unnecessary
if settings . running_notify_end_time == end_time . isoformat ( ) :
# Now get the 'HH:MM' value for start & end so we can compare to the notify_time field
# get all the subscriptions that have a notify_time between start_minute inclusive and end_minute exclusive
all_subscriptions = Subscription . query . filter (
and_ ( Subscription . notify_time > = start_minute , Subscription . notify_time < end_minute )
)
# Send a push notification to each subscribed client
num_subscriptions = all_subscriptions . count ( )
for subscription in all_subscriptions :
subscription_info = {
" endpoint " : subscription . endpoint ,
" keys " : {
" p256dh " : subscription . p256dh ,
" auth " : subscription . auth
}
}
result = WebPushService . _send_push_notification ( subscription_info , message , vapid_key )
print (
f " Result from sub { subscription . id } : success= { result [ ' success ' ] } text= { result [ ' message ' ] } " ,
flush = True
)
settings . prev_notify_end_time = end_time . isoformat ( )
settings . running_notify_end_time = None
db . session . commit ( )
print ( f " { now } - Finished sending { num_subscriptions } subscriptions. " , flush = True )
else :
print ( f " { now } - Failed to update running_notify_end_time " , flush = True )
else :
print ( f " { now } - Stopped because we ' re already running a notification check. " , flush = True )
self . latest_subscription_run = now . isoformat ( )
# Sleep before repeating
time . sleep ( 5 * 60 )
# This is an endpoint, routed in __init__