bastodon/app/javascript/mastodon/service_worker/web_push_notifications.js
Sorin Davidoi 0c7c188c45 Web Push Notifications (#3243)
* feat: Register push subscription

* feat: Notify when mentioned

* feat: Boost, favourite, reply, follow, follow request

* feat: Notification interaction

* feat: Handle change of public key

* feat: Unsubscribe if things go wrong

* feat: Do not send normal notifications if push is enabled

* feat: Focus client if open

* refactor: Move push logic to WebPushSubscription

* feat: Better title and body

* feat: Localize messages

* chore: Fix lint errors

* feat: Settings

* refactor: Lazy load

* fix: Check if push settings exist

* feat: Device-based preferences

* refactor: Simplify logic

* refactor: Pull request feedback

* refactor: Pull request feedback

* refactor: Create /api/web/push_subscriptions endpoint

* feat: Spec PushSubscriptionController

* refactor: WebPushSubscription => Web::PushSubscription

* feat: Spec Web::PushSubscription

* feat: Display first media attachment

* feat: Support direction

* fix: Stuff broken while rebasing

* refactor: Integration with session activations

* refactor: Cleanup

* refactor: Simplify implementation

* feat: Set VAPID keys via environment

* chore: Comments

* fix: Crash when no alerts

* fix: Set VAPID keys in testing environment

* fix: Follow link

* feat: Notification actions

* fix: Delete previous subscription

* chore: Temporary logs

* refactor: Move migration to a later date

* fix: Fetch the correct session activation and misc bugs

* refactor: Move migration to a later date

* fix: Remove follow request (no notifications)

* feat: Send administrator contact to push service

* feat: Set time-to-live

* fix: Do not show sensitive images

* fix: Reducer crash in error handling

* feat: Add badge

* chore: Fix lint error

* fix: Checkbox label overlap

* fix: Check for payload support

* fix: Rename action "type" (crash in latest Chrome)

* feat: Action to expand notification

* fix: Lint errors

* fix: Unescape notification body

* fix: Do not allow boosting if the status is hidden

* feat: Add VAPID keys to the production sample environment

* fix: Strip HTML tags from status

* refactor: Better error messages

* refactor: Handle browser not implementing the VAPID protocol (Samsung Internet)

* fix: Error when target_status is nil

* fix: Handle lack of image

* fix: Delete reference to invalid subscriptions

* feat: Better error handling

* fix: Unescape HTML characters after tags are striped

* refactor: Simpify code

* fix: Modify to work with #4091

* Sort strings alphabetically

* i18n: Updated Polish translation

it annoys me that it's not fully localized :P

* refactor: Use current_session in PushSubscriptionController

* fix: Rebase mistake

* fix: Set cacheName to mastodon

* refactor: Pull request feedback

* refactor: Remove logging statements

* chore(yarn): Fix conflicts with master

* chore(yarn): Copy latest from master

* chore(yarn): Readd offline-plugin

* refactor: Use save! and update!

* refactor: Send notifications async

* fix: Allow retry when push fails

* fix: Save track for failed pushes

* fix: Minify sw.js

* fix: Remove account_id from fabricator
2017-07-13 22:15:32 +02:00

86 lines
2.7 KiB
JavaScript

const handlePush = (event) => {
const options = event.data.json();
options.body = options.data.nsfw || options.data.content;
options.image = options.image || undefined; // Null results in a network request (404)
options.timestamp = options.timestamp && new Date(options.timestamp);
const expandAction = options.data.actions.find(action => action.todo === 'expand');
if (expandAction) {
options.actions = [expandAction];
options.hiddenActions = options.data.actions.filter(action => action !== expandAction);
options.data.hiddenImage = options.image;
options.image = undefined;
} else {
options.actions = options.data.actions;
}
event.waitUntil(self.registration.showNotification(options.title, options));
};
const cloneNotification = (notification) => {
const clone = { };
for(var k in notification) {
clone[k] = notification[k];
}
return clone;
};
const expandNotification = (notification) => {
const nextNotification = cloneNotification(notification);
nextNotification.body = notification.data.content;
nextNotification.image = notification.data.hiddenImage;
nextNotification.actions = notification.data.actions.filter(action => action.todo !== 'expand');
return self.registration.showNotification(nextNotification.title, nextNotification);
};
const makeRequest = (notification, action) =>
fetch(action.action, {
headers: {
'Authorization': `Bearer ${notification.data.access_token}`,
'Content-Type': 'application/json',
},
method: action.method,
credentials: 'include',
});
const removeActionFromNotification = (notification, action) => {
const actions = notification.actions.filter(act => act.action !== action.action);
const nextNotification = cloneNotification(notification);
nextNotification.actions = actions;
return self.registration.showNotification(nextNotification.title, nextNotification);
};
const handleNotificationClick = (event) => {
const reactToNotificationClick = new Promise((resolve, reject) => {
if (event.action) {
const action = event.notification.data.actions.find(({ action }) => action === event.action);
if (action.todo === 'expand') {
resolve(expandNotification(event.notification));
} else if (action.todo === 'request') {
resolve(makeRequest(event.notification, action)
.then(() => removeActionFromNotification(event.notification, action)));
} else {
reject(`Unknown action: ${action.todo}`);
}
} else {
event.notification.close();
resolve(self.clients.openWindow(event.notification.data.url));
}
});
event.waitUntil(reactToNotificationClick);
};
self.addEventListener('push', handlePush);
self.addEventListener('notificationclick', handleNotificationClick);