Skip to content

Commit

Permalink
Adds firebase configuration (#39)
Browse files Browse the repository at this point in the history
https://www.loom.com/share/00f815219f1b446aa25fa3be9a57d960

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Integrated Firebase push notifications, providing an additional method
to send notifications.
	- Added a new route for handling Firebase-related requests.
- Introduced new middleware for validating requests to the Firebase
endpoint.
- Implemented functionality for sending push notifications using the
Firebase Admin SDK.
- Added support for additional parameters in token registration and user
token retrieval.
- Added a new entry to the `.prettierignore` file to specify ignored
paths for formatting.
- Created a new `.prettierrc` configuration file for code formatting
options.
- **Refactor**
- Enhanced asynchronous processing and refined error handling for
improved reliability.
	- Streamlined internal logging and parameter management.
- **Chores**
- Updated configurations and dependencies to support the new features
and improvements.
	- Added `pnpm-lock.yaml` to `.gitignore`.
	- Added new columns to the `push_tokens` table in the database schema.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
PanchoBubble authored Feb 12, 2025
2 parents 2fcb079 + ba2af66 commit a8f05fc
Show file tree
Hide file tree
Showing 17 changed files with 384 additions and 124 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jspm_packages/

# Temporary
package-lock.json
pnpm-lock.yaml
data/

# MacOS
Expand Down
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.git
.github
node_modules
chk-sig
data
11 changes: 11 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
8 changes: 5 additions & 3 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const sendRouter = require('./routes/send');
const cancelRouter = require('./routes/cancel_reminders');
const adminRouter = require('./routes/admin');
const healthRouter = require('./routes/health');
const firebaseRouter = require('./routes/sendFirebase');

const app = express();

Expand All @@ -24,15 +25,16 @@ app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/register', registerRouter);
app.use('/send', sendRouter);
app.use('/send-firebase', firebaseRouter);
app.use('/cancel-reminders', cancelRouter);
app.use('/super-duper-only', adminRouter);
app.use('/health', healthRouter);

app.use(function(err, req, res, next) {
app.use(function (err, _req, res, next) {
if (res.headersSent) {
return next(err);
return next(err);
}
console.error(err)
console.error(err);
return res.json(err);
});
module.exports = app;
115 changes: 70 additions & 45 deletions lib/database.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const debug = require('debug')('aurora_push:db_pool');
const { Pool } = require('pg');

const useSsl = process.env.PG_SSL === "1" || false;
const useSsl = process.env.PG_SSL === '1' || false;
const pool = new Pool(useSsl ? { ssl: { rejectUnauthorized: false } } : {});

function query(text, params) {
Expand All @@ -10,55 +10,79 @@ function query(text, params) {

/**
* Checks whether this pub_key has a set push notification token
* @param pub_key
* @returns {PromiseLike<boolean> | Promise<boolean>}
* @param string pubKey - The parameters to look up the token
* @returns {PromiseLike<Array<{token: string, sandbox: boolean}>> | Promise<Array<{token: string, sandbox: boolean}>>}
*/
function get_user_token(pub_key) {
debug(`Checking for user with pub_key ${pub_key}`);
const q = `SELECT token, sandbox
async function get_user_token(pubKey) {
debug(`Checking for user with pub_key:${pubKey}`);
const q = `SELECT token, sandbox, pub_key
from push_tokens
WHERE pub_key = $1`;
return pool.query(q, [pub_key.toLowerCase()]).then(res => {
return res.rows;
});
const res = await pool.query(q, [pubKey?.toLowerCase()]);
return res.rows;
}

/**
* Checks whether this pub_key has a set push notification token
* @param {Object} params - The parameters to look up the token
* @param {string} params.appId - The application ID
* @param {string} params.userId - The user ID
* @returns {PromiseLike<Array<{token: string, sandbox: boolean}>> | Promise<Array<{token: string, sandbox: boolean}>>}
*/
async function get_user_token_firebase(params) {
const { appId, userId } = params;
debug(`Checking for user with appId:${appId} userId:${userId}`);
const q = `SELECT token, sandbox, pub_key
from push_tokens
WHERE app_id = $1 OR user_id = $2`;
const res = await pool.query(q, [appId, userId]);
return res.rows;
}

/**
* Inserts/updates device token and platform for a public key
* @param pub_key, token, platform
* @returns {PromiseLike<boolean> | Promise<boolean>}
* @param {Object} params - The parameters for registering the token
* @param {string} params.pub_key - The public key associated with the device
* @param {string} params.token - The device's token
* @param {string} params.platform - The platform (e.g., iOS, Android)
* @param {boolean} params.sandbox - Flag to indicate if it's in sandbox mode
* @param {string} params.appId - The application ID
* @param {string} params.userId - The user ID
* @returns {Promise<boolean>} - Returns a promise resolving to a boolean
*/
function register_token(pub_key, token, platform, sandbox) {
async function register_token({ pub_key, token, platform, sandbox, appId, userId }) {
const q = `
INSERT INTO push_tokens (pub_key, token, platform, sandbox, updated_at)
VALUES($1, $2, $3, $4, CURRENT_TIMESTAMP)
ON CONFLICT ON CONSTRAINT index_pub_key_token
DO UPDATE SET platform = $3, sandbox = $4, updated_at = CURRENT_TIMESTAMP
`;
INSERT INTO push_tokens (pub_key, token, platform, sandbox, app_id, user_id, updated_at)
VALUES($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP)
ON CONFLICT ON CONSTRAINT index_pub_key_token
DO UPDATE SET
platform = $3,
sandbox = $4,
app_id = COALESCE(EXCLUDED.app_id, push_tokens.app_id), -- Update app_id if $5 is not null
user_id = COALESCE(EXCLUDED.user_id, push_tokens.user_id), -- Update user_id if $6 is not null
updated_at = CURRENT_TIMESTAMP
`;
debug(`Setting token for ${pub_key}`);
return pool.query(q, [pub_key.toLowerCase(), token, platform, sandbox]).then(res => {
const success = res.rowCount > 0;
if (!success) {
throw "pub_key not added/updated.";
}

return true
});
const res = await pool.query(q, [pub_key.toLowerCase(), token, platform, sandbox, appId, userId]);
if (res.rowCount === 0) {
throw new Error('Failed to register/update push token');
}
return true;
}
/**
* Remove a pubkey/token pair from the database
* @param {string} pub_key
* @param {string} token
*/
function delete_token(pub_key, token) {
async function delete_token(pub_key, token) {
const q = `DELETE FROM push_tokens WHERE pub_key = $1 AND token = $2`;
debug(`Removing token ${token.substring(0, 4)}... for ${pub_key}`);
return pool.query(q, [pub_key.toLowerCase(), token]).then(res => {
return pool.query(q, [pub_key.toLowerCase(), token]).then((res) => {
const success = res.rowCount > 0;
if (!success) {
throw `pub_key for token ${token.substring(0, 4)}... not deleted.`;
}
return true
return true;
});
}

Expand All @@ -67,19 +91,19 @@ function delete_token(pub_key, token) {
* @param pub_key, reminder_type, send_at
* @returns {PromiseLike<boolean> | Promise<boolean>}
*/
function schedule_reminder(pub_key, reminder_type, send_at) {
async function schedule_reminder(pub_key, reminder_type, send_at) {
const q = `
INSERT INTO reminder_notifications (pub_key, reminder_type, send_at)
VALUES($1, $2, $3)
`;
debug(`Scheduling ${reminder_type} notification for ${pub_key}`);
return pool.query(q, [pub_key.toLowerCase(), reminder_type, send_at]).then(res => {
return pool.query(q, [pub_key?.toLowerCase(), reminder_type, send_at]).then((res) => {
const success = res.rowCount > 0;
if (!success) {
throw "reminder_not_scheduled";
throw 'reminder_not_scheduled';
}

return true
return true;
});
}

Expand All @@ -88,14 +112,14 @@ function schedule_reminder(pub_key, reminder_type, send_at) {
* @param pub_key
* @returns {PromiseLike<boolean> | Promise<boolean>}
*/
function cancel_all_reminders(pub_key) {
async function cancel_all_reminders(pub_key) {
const q = `
DELETE FROM reminder_notifications
WHERE pub_key = $1
`;
debug(`Removing reminders for ${pub_key}`);
return pool.query(q, [pub_key.toLowerCase()]).then(res => {
return true
return pool.query(q, [pub_key.toLowerCase()]).then((_) => {
return true;
});
}

Expand All @@ -104,13 +128,13 @@ function cancel_all_reminders(pub_key) {
* @param pub_key
* @returns {PromiseLike<boolean> | Promise<boolean>}
*/
function list_reminders(send_at_before) {
async function list_reminders(send_at_before) {
const q = `
SELECT id, pub_key, reminder_type, send_at
FROM reminder_notifications
WHERE send_at < $1`;
return pool.query(q, [send_at_before]).then(res => {
return res.rows
return pool.query(q, [send_at_before]).then((res) => {
return res.rows;
});
}

Expand All @@ -119,33 +143,34 @@ function list_reminders(send_at_before) {
* @param pub_key
* @returns {PromiseLike<boolean> | Promise<boolean>}
*/
function delete_reminder(id) {
async function delete_reminder(id) {
const q = `
DELETE FROM reminder_notifications
WHERE id = $1
`;
return pool.query(q, [id]).then(res => {
return true
return pool.query(q, [id]).then((_) => {
return true;
});
}

function admin_list() {
async function admin_list() {
const q = `SELECT platform, updated_at from push_tokens`;
return pool.query(q, []).then(res => {
return pool.query(q, []).then((res) => {
return res.rows;
});
}

function healthCheck() {
async function healthCheck() {
const q = `SELECT 1`;
return pool.query(q, []).then(res => {
return pool.query(q, []).then((res) => {
return res.rowCount > 0;
});
}

module.exports = {
query,
get_user_token,
get_user_token_firebase,
register_token,
delete_token,
schedule_reminder,
Expand Down
65 changes: 65 additions & 0 deletions lib/push_notifications_firebase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const admin = require('firebase-admin');
const debug = require('debug')('aurora_push:routes:send');

// Firebase Admin SDK Initialization
const firebaseConfig = {
type: 'service_account',
auth_uri: 'https://accounts.google.com/o/oauth2/auth',
token_uri: 'https://oauth2.googleapis.com/token',
auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs',
project_id: process.env.FIREBASE_PROJECT_ID,
private_key_id: process.env.FIREBASE_PRIVATE_KEY_ID,
private_key: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'), // Handle multiline env variables
client_email: process.env.FIREBASE_CLIENT_EMAIL,
client_id: process.env.FIREBASE_CLIENT_ID,
client_x509_cert_url: process.env.FIREBASE_CLIENT_X509_CERT_URL
};

// Initialize Firebase Admin SDK only once
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(firebaseConfig)
});
}

const sendPushNotification = async (deviceToken, payload) => {
const { title, pushType = 'alert', sound = 'default', body, expiry, topic } = payload;

let message = {
notification: { title, body },
data: {},
android: { notification: { sound } },
apns: {
headers: {
'apns-expiration': expiry ? String(expiry) : undefined,
'apns-push-type': pushType
},
payload: {
aps: {
alert: { title, body },
sound,
mutableContent: 1
}
}
}
};

if (topic) {
message.topic = topic;
} else if (deviceToken) {
message.token = deviceToken;
} else {
throw new Error('You must provide either a deviceToken or a topic.');
}

try {
const response = await admin.messaging().send(message);
debug(`Notification sent successfully: ${response}`);
return response;
} catch (error) {
debug(`Error sending notification: ${error.message}`);
throw error;
}
};

module.exports = { sendPushNotification };
Loading

0 comments on commit a8f05fc

Please sign in to comment.