Skip to content

Commit

Permalink
Merge pull request RocketChat#341 from bhardwajaditya/push_notificati…
Browse files Browse the repository at this point in the history
…on_subscription_handle

Push notification subscription handle
  • Loading branch information
ear-dev committed Sep 8, 2020
2 parents 2a16d94 + 448e475 commit ea3a1cb
Show file tree
Hide file tree
Showing 25 changed files with 556 additions and 128 deletions.
15 changes: 15 additions & 0 deletions app/api/server/v1/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,21 @@ API.v1.addRoute('directory', { authRequired: true }, {
},
});

API.v1.addRoute('manifest', { authRequired: false }, {
get() {
const manifestFile = require('../../../../public/manifest.json');
const gcm_sender_id = settings.get('Gcm_sender_id');
const manifest = {
...manifestFile,
gcm_sender_id,
};
return {
headers: { 'Content-Type': 'application/json;charset=utf-8' },
body: manifest,
};
},
});

API.v1.addRoute('stdout.queue', { authRequired: true }, {
get() {
if (!hasPermission(this.userId, 'view-logs')) {
Expand Down
2 changes: 2 additions & 0 deletions app/custom-oauth/client/custom_oauth_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { OAuth } from 'meteor/oauth';
import s from 'underscore.string';

import { isURL } from '../../utils/lib/isURL';
import { callbacks } from '../../callbacks';

// Request custom OAuth credentials for the user
// @param options {optional}
Expand Down Expand Up @@ -66,6 +67,7 @@ export class CustomOAuth {

const credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
this.requestCredential(options, credentialRequestCompleteCallback);
callbacks.run('onUserLogin');
};
}

Expand Down
56 changes: 55 additions & 1 deletion app/lib/server/functions/notifications/mobile.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { Meteor } from 'meteor/meteor';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';

import { settings } from '../../../../settings';
import { Subscriptions } from '../../../../models';
import { Subscriptions, PushNotificationSubscriptions } from '../../../../models';
import { roomTypes } from '../../../../utils';

const CATEGORY_MESSAGE = 'MESSAGE';
const CATEGORY_MESSAGE_NOREPLY = 'MESSAGE_NOREPLY';
const webpush = require('web-push');

let SubscriptionRaw;
Meteor.startup(() => {
Expand Down Expand Up @@ -71,6 +72,59 @@ export async function getPushData({ room, message, userId, senderUsername, sende
};
}

export function sendWebPush({ room, message, userId, receiverUsername, senderUsername, senderName, notificationMessage }) {
const gcmKey = settings.get('Push_gcm_api_key');
const vapidPublic = settings.get('Vapid_public_key');
const vapidPrivate = settings.get('Vapid_private_key');
const vapidSubject = settings.get('Vapid_subject');

webpush.setGCMAPIKey(gcmKey);
webpush.setVapidDetails(
vapidSubject,
vapidPublic,
vapidPrivate,
);

const pushSubscriptions = PushNotificationSubscriptions.findByUserId(userId);
const options = {
TTL: 3600,
};
const title = room.t === 'd' ? message.u.name : room.name;
const displayMessage = room.t === 'd' ? notificationMessage : `${ message.u.name }: ${ notificationMessage }`;

let redirectURL;
if (room.t === 'd') {
redirectURL = '/direct/';
} else if (room.t === 'p') {
redirectURL = '/group/';
} else if (room.t === 'c') {
redirectURL = '/channel/';
}
redirectURL += message.rid;

const payload = {
host: Meteor.absoluteUrl(),
title,
message: displayMessage,
receiverUsername,
senderUsername,
senderName,
redirectURL,
vibrate: [100, 50, 100],
icon: '/images/icons/icon-72x72.png',
};
const stringifiedPayload = JSON.stringify(payload);

pushSubscriptions.forEach((pushSubscription) => {
webpush.sendNotification(pushSubscription, stringifiedPayload, options)
.catch((error) => {
if (error.statusCode === 410) {
PushNotificationSubscriptions.removeById(pushSubscription._id);
}
});
});
}

export function shouldNotifyMobile({
disableAllMessageNotifications,
mobilePushNotifications,
Expand Down
3 changes: 3 additions & 0 deletions app/lib/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import './oauth/google';
import './oauth/proxy';
import './oauth/twitter';
import './methods/addOAuthService';
import './methods/addUserToPushSubscription';
import './methods/addUsersToRoom';
import './methods/addUserToRoom';
import './methods/archiveRoom';
Expand Down Expand Up @@ -52,8 +53,10 @@ import './methods/joinRoom';
import './methods/leaveRoom';
import './methods/refreshOAuthService';
import './methods/removeOAuthService';
import './methods/removeUserFromPushSubscription';
import './methods/restartServer';
import './methods/robotMethods';
import './methods/savePushNotificationSubscription';
import './methods/saveSetting';
import './methods/saveSettings';
import './methods/sendInvitationEmail';
Expand Down
11 changes: 10 additions & 1 deletion app/lib/server/lib/sendNotificationsOnMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Subscriptions, Users } from '../../../models/server';
import { roomTypes } from '../../../utils';
import { callJoinRoom, messageContainsHighlight, parseMessageTextPerUser, replaceMentionedUsernamesWithFullNames } from '../functions/notifications';
import { getEmailData, shouldNotifyEmail } from '../functions/notifications/email';
import { getPushData, shouldNotifyMobile } from '../functions/notifications/mobile';
import { sendWebPush, getPushData, shouldNotifyMobile } from '../functions/notifications/mobile';
import { notifyDesktopUser, shouldNotifyDesktop } from '../functions/notifications/desktop';
import { notifyAudioUser, shouldNotifyAudio } from '../functions/notifications/audio';
import { Notification } from '../../../notification-queue/server/NotificationQueue';
Expand Down Expand Up @@ -143,6 +143,15 @@ export const sendNotification = async ({
receiver,
}),
});
sendWebPush({
notificationMessage,
room,
message,
userId: subscription.u._id,
senderUsername: sender.username,
senderName: sender.name,
receiverUsername: receiver.username,
});
}

if (receiver.emails && shouldNotifyEmail({
Expand Down
22 changes: 22 additions & 0 deletions app/lib/server/methods/addUserToPushSubscription.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';

import { PushNotificationSubscriptions } from '../../../models';

Meteor.methods({
addUserToPushSubscription(endpoint) {
check(endpoint, String);

let user = Meteor.user();
if (user) {
user = {
_id: user._id,
username: user.username,
};
}

PushNotificationSubscriptions.updateUserIdWithSubscriptionEndpoint(endpoint, user);

return endpoint;
},
});
14 changes: 14 additions & 0 deletions app/lib/server/methods/removeUserFromPushSubscription.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';

import { PushNotificationSubscriptions } from '../../../models';

Meteor.methods({
removeUserFromPushSubscription(endpoint) {
check(endpoint, String);

PushNotificationSubscriptions.updateUserIdWithSubscriptionEndpoint(endpoint);

return endpoint;
},
});
23 changes: 23 additions & 0 deletions app/lib/server/methods/savePushNotificationSubscription.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';

import { PushNotificationSubscriptions } from '../../../models';

Meteor.methods({
savePushNotificationSubscription(subscription) {
subscription = JSON.parse(subscription);

check(subscription.endpoint, String);

let user = Meteor.user();
if (user) {
user = {
_id: user._id,
username: user.username,
};
}
PushNotificationSubscriptions.createWithUserAndSubscription(user, subscription);

return subscription;
},
});
16 changes: 16 additions & 0 deletions app/lib/server/startup/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -1310,11 +1310,27 @@ settings.addGroup('Push', function() {
enableQuery: pushEnabledWithoutGateway,
secret: true,
});
this.add('Vapid_public_key', '', {
type: 'string',
public: true,
});
this.add('Vapid_private_key', '', {
type: 'string',
secret: true,
});
this.add('Vapid_subject', 'https://www.viasat.com', {
type: 'string',
public: false,
});
this.add('Push_gcm_api_key', '', {
type: 'string',
enableQuery: pushEnabledWithoutGateway,
secret: true,
});
this.add('Gcm_sender_id', '', {
type: 'string',
public: true,
});
return this.add('Push_gcm_project_number', '', {
type: 'string',
public: true,
Expand Down
2 changes: 2 additions & 0 deletions app/models/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import LivechatInquiry from './models/LivechatInquiry';
import ReadReceipts from './models/ReadReceipts';
import LivechatExternalMessage from './models/LivechatExternalMessages';
import Analytics from './models/Analytics';
import PushNotificationSubscriptions from './models/PushNotificationSubscriptions';

export { AppsLogsModel } from './models/apps-logs-model';
export { AppsPersistenceModel } from './models/apps-persistence-model';
Expand Down Expand Up @@ -84,6 +85,7 @@ export {
LivechatTrigger,
LivechatVisitors,
LivechatAgentActivity,
PushNotificationSubscriptions,
ReadReceipts,
LivechatExternalMessage,
LivechatInquiry,
Expand Down
58 changes: 58 additions & 0 deletions app/models/server/models/PushNotificationSubscriptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Base } from './_Base';

export class PushNotificationSubscriptions extends Base {
constructor(...args) {
super(...args);

this.tryEnsureIndex({ u: 1 });
}

createWithUserAndSubscription(user, subscription) {
const pushNotificationSubscription = {
...subscription,
};
if (user && user._id != null) {
pushNotificationSubscription.u = { ...user };
}
const result = this.insert(pushNotificationSubscription);
return result;
}

findByUserId(userId) {
const query = {
'u._id': userId,
};
return this.find(query);
}

findSubscriptionsWithNoUser() {
const query = {
user: {
$exists: false,
},
};
return this.find(query);
}

updateUserIdWithSubscriptionEndpoint(endpoint, user = null) {
const query = {
endpoint,
};

const update = {};

if (user !== null) {
update.$set = { u: user };
} else {
update.$unset = { u: 1 };
}

return this.update(query, update);
}

removeById(_id) {
return this.remove(_id);
}
}

export default new PushNotificationSubscriptions('pushNotificationSubscriptions');
2 changes: 2 additions & 0 deletions app/ui-login/client/login/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ Template.loginForm.events({
} if (error && error.error === 'error-user-is-not-activated') {
return instance.state.set('wait-activation');
}
callbacks.run('onUserLogin');
Session.set('forceLogin', false);
});
});
Expand Down Expand Up @@ -157,6 +158,7 @@ Template.loginForm.events({
return toastr.error(t('User_not_found_or_incorrect_password'));
}
}
callbacks.run('onUserLogin');
Session.set('forceLogin', false);
});
}
Expand Down
3 changes: 3 additions & 0 deletions app/ui-login/client/login/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import s from 'underscore.string';
import toastr from 'toastr';

import { CustomOAuth } from '../../../custom-oauth';
import { callbacks } from '../../../callbacks';

Meteor.startup(function() {
return ServiceConfiguration.configurations.find({
Expand Down Expand Up @@ -88,6 +89,8 @@ Template.loginServices.events({
} else {
toastr.error(error.message);
}
} else {
callbacks.run('onUserLogin');
}
});
},
Expand Down
1 change: 1 addition & 0 deletions app/ui/client/lib/iframeCommands.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const commands = {
const customLoginWith = Meteor[`loginWith${ s.capitalize(customOauth.service, true) }`];
const customRedirectUri = data.redirectUrl || siteUrl;
customLoginWith.call(Meteor, { redirectUrl: customRedirectUri }, customOAuthCallback);
callbacks.run('onUserLogin');
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion client/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<!-- iOS -->
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>

<link rel="manifest" href="manifest.json" />
<link rel="manifest" href="api/v1/manifest" />
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/nocfbnnmjnndkbipkabodnheejiegccf" />
<link rel="mask-icon" href="assets/safari_pinned.svg" color="#04436a">
<link rel="apple-touch-icon" sizes="180x180" href="assets/touchicon_180.png" />
Expand Down
Loading

0 comments on commit ea3a1cb

Please sign in to comment.