Skip to content

Commit

Permalink
push notification done.
Browse files Browse the repository at this point in the history
  • Loading branch information
dhrumilp12 committed Jun 17, 2024
1 parent 4d176be commit 0763c5e
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 43 deletions.
Binary file added client/Images/Aria.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions client/service-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
self.addEventListener('push', event => {
try {
const data = event.data.json(); // Tries to parse JSON data
self.registration.showNotification(data.title, {
body: data.body,
icon: './Images/Aria.jpg'
});
} catch (error) {
console.error('Error parsing push notification data:', error);
self.registration.showNotification('Notification', {
body: event.data.text(),
icon: './Images/Aria.jpg'
});
}
});

self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil(
self.clients.matchAll({type: 'window'}).then(clientList => {
for (let client of clientList) {
if (client.url === '/' && 'focus' in client) {
return client.focus();
}
}
if (self.clients.openWindow) {
return self.clients.openWindow('/');
}
})
);
});

93 changes: 93 additions & 0 deletions client/src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,99 @@ import App from './App.jsx'
import { BrowserRouter } from 'react-router-dom';
import { UserProvider } from './Components/userContext';

function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');

const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);

for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}

return outputArray;
}

function getToken() {
// Implement a function to retrieve the JWT token
return localStorage.getItem('token');
}

const VAPID_PUBLIC_KEY = "BJO2lvL7cXXdg0MqKdCtQyWOz3Nb1Ny-X8x_67MKdRtQOLLl3FRpAPJOUJEzjaQGNBcIOqwjeS165Rb3Pl0x2ZI";

if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('../service-worker.js')
.then(function(registration) {
console.log('Service Worker registered with scope:', registration.scope);

// Request notification permission
return Notification.requestPermission().then(permission => {
if (permission !== 'granted') {
throw new Error('Permission not granted for Notification');
}

// Check for permission and subscribe for push notifications
return registration.pushManager.getSubscription();
}).then(function(subscription) {
if (!subscription) {
return registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
});
}
return subscription;

})
.then(function(subscription) {
console.log('Subscription:', subscription);

// Ensure the keys are properly encoded
const keys = {
p256dh: btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('p256dh')))),
auth: btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('auth'))))
};
console.log('Subscription keys:', keys);

if (!keys.p256dh || !keys.auth) {
console.error('Subscription object:', subscription);
throw new Error('Subscription keys are missing');
}
const subscriptionData = {
endpoint: subscription.endpoint,
keys: keys
};

const token = getToken();

if (!token) {
throw new Error('No token found');
}

return fetch('/api/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(subscriptionData),
});
})
.then(response => response.json())
.then(data => console.log('Subscription response:', data))
.catch(err => console.error('Subscription failed:', err));


})
.catch(function(err) {
console.error('Service Worker registration failed:', err);
});
});
}

ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
Expand Down
65 changes: 59 additions & 6 deletions server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,85 @@
API entrypoint for backend API.
"""
import os
from flask import Flask
from flask import Flask, request, jsonify
from flask_cors import CORS
from flask_jwt_extended import JWTManager
from flask_jwt_extended import JWTManager, jwt_required , get_jwt_identity
from models.subscription import Subscription,db

from routes.scheduler import init_scheduler
from routes.user import user_routes
from routes.ai import ai_routes
from routes.checkIn import checkIn_routes
from services.azure_mongodb import MongoDBClient

from routes.scheduler import send_push_notification
from agents.mental_health_agent import MentalHealthAIAgent

# Set up the app
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = os.environ.get("JWT_SECRET_KEY")
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mydatabase.db'

jwt = JWTManager(app)
CORS(app)

db.init_app(app)


# Create the tables
with app.app_context():
db.create_all()

@app.route('/subscribe', methods=['POST'])
@jwt_required()
def subscribe():
data = request.json
print(f"Received subscription data: {data}")

if not data or 'endpoint' not in data or 'keys' not in data or 'p256dh' not in data['keys'] or 'auth' not in data['keys']:
return jsonify({'error': 'Missing required fields'}), 400


subscription_info = {
'endpoint': data['endpoint'],
'keys': {
'p256dh': data['keys']['p256dh'],
'auth': data['keys']['auth']
}
}

user_id = get_jwt_identity()

# Check if the subscription already exists
existing_subscription = Subscription.query.filter_by(user_id=user_id).first()
if existing_subscription:
# Update existing subscription
existing_subscription.subscription_info = subscription_info
else:
# Create new subscription
new_subscription = Subscription(user_id=user_id, subscription_info=subscription_info)
db.session.add(new_subscription)

db.session.commit()

return jsonify({'message': 'Subscription saved successfully'}), 200

@app.route('/send_push', methods=['POST'])
@jwt_required()
def send_push():
data = request.json
user_id = data['user_id']
message = data['message']
success = send_push_notification(user_id, message)
if success:
return jsonify({'message': 'Push notification sent successfully'}), 200
else:
return jsonify({'error': 'Failed to send push notification'}), 500


# Register routes
app.register_blueprint(user_routes)
app.register_blueprint(ai_routes)
app.register_blueprint(checkIn_routes)

# Initialize the scheduler
init_scheduler(app)

# DB pre-load
MentalHealthAIAgent.load_agent_facts_to_db()
Expand Down
Binary file added server/instance/mydatabase.db
Binary file not shown.
8 changes: 8 additions & 0 deletions server/models/subscription.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class Subscription(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.String(120), unique=True, nullable=False)
subscription_info = db.Column(db.String, nullable=False)
65 changes: 28 additions & 37 deletions server/routes/scheduler.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,30 @@
from apscheduler.schedulers.background import BackgroundScheduler
from flask import current_app as app
from datetime import datetime, timedelta
from services.azure_mongodb import MongoDBClient
import pymongo
from models.subscription import db, Subscription
from pywebpush import webpush, WebPushException
import json
import os
from dotenv import load_dotenv
load_dotenv()

def notify_check_ins():
db_client = MongoDBClient.get_client()
db = db_client[MongoDBClient.get_db_name()]
now = datetime.now()
upcoming_check_ins = db.check_ins.find({
'check_in_time': {'$gte': now, '$lt': now + timedelta(days=7)},
'notify': True,
'status': 'upcoming'
})

for check_in in upcoming_check_ins:
delta = check_in['check_in_time'] - now
if delta.days == 7 or delta.days == 1 or delta.total_seconds() / 3600 <= 1:
send_notification(check_in['user_id'], check_in['check_in_time'], delta)

def send_notification(user_id, check_in_time, delta):
message = ""
if delta.days == 7:
message = "Your check-in is scheduled in 1 week."
elif delta.days == 1:
message = "Your check-in is scheduled tomorrow."
elif delta.total_seconds() / 3600 <= 1:
message = "Your check-in is in less than 1 hour."

# This is where you'd integrate your actual notification logic
print(f"Notify {user_id}: {message}")

scheduler = BackgroundScheduler()
scheduler.add_job(func=notify_check_ins, trigger='interval', hours=1)
scheduler.start()

def init_scheduler(app):
app.config['scheduler'] = scheduler
def send_push_notification(user_id, message):
subscription = Subscription.query.filter_by(user_id=user_id).first()
if not subscription:
print(f"No subscription found for user {user_id}")
return False

try:
webpush(
subscription_info=json.loads(subscription.subscription_info),
data=json.dumps({
"title": "Notification Title",
"body": message
}),
vapid_private_key=os.environ.get("VAPID_PRIVATE_KEY"),
vapid_claims={"sub": "mailto:[email protected]"}
)
print("Notification sent successfully")
return True
except WebPushException as e:
print(f"Failed to send notification: {e}")
if e.response and e.response.json():
print(e.response.json())

0 comments on commit 0763c5e

Please sign in to comment.