From 0c7c188c459117770ac1f74f70a9e65ed2be606f Mon Sep 17 00:00:00 2001 From: Sorin Davidoi Date: Thu, 13 Jul 2017 22:15:32 +0200 Subject: 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 --- config/environments/development.rb | 5 +++++ config/environments/test.rb | 5 +++++ config/initializers/vapid.rb | 17 +++++++++++++++++ config/locales/en.yml | 15 +++++++++++++++ config/locales/pl.yml | 15 +++++++++++++++ config/routes.rb | 5 +++++ config/webpack/production.js | 14 ++++++++++++++ 7 files changed, 76 insertions(+) create mode 100644 config/initializers/vapid.rb (limited to 'config') diff --git a/config/environments/development.rb b/config/environments/development.rb index 406fa970b..4c60965c8 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -31,6 +31,11 @@ Rails.application.configure do config.logger = ActiveSupport::TaggedLogging.new(logger) end + # Generate random VAPID keys + vapid_key = Webpush.generate_key + config.x.vapid_private_key = vapid_key.private_key + config.x.vapid_public_key = vapid_key.public_key + # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false diff --git a/config/environments/test.rb b/config/environments/test.rb index bde69eba1..e68cb156d 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -40,6 +40,11 @@ Rails.application.configure do # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr + # Generate random VAPID keys + vapid_key = Webpush.generate_key + config.x.vapid_private_key = vapid_key.private_key + config.x.vapid_public_key = vapid_key.public_key + # Raises error for missing translations # config.action_view.raise_on_missing_translations = true end diff --git a/config/initializers/vapid.rb b/config/initializers/vapid.rb new file mode 100644 index 000000000..74e07377c --- /dev/null +++ b/config/initializers/vapid.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +Rails.application.configure do + + # You can generate the keys using the following command (first is the private key, second is the public one) + # You should only generate this once per instance. If you later decide to change it, all push subscription will + # be invalidated, requiring the users to access the website again to resubscribe. + # + # ruby -e "require 'webpush'; vapid_key = Webpush.generate_key; puts vapid_key.private_key; puts vapid_key.public_key;" + # + # For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html + + if Rails.env.production? + config.x.vapid_private_key = ENV['VAPID_PRIVATE_KEY'] + config.x.vapid_public_key = ENV['VAPID_PUBLIC_KEY'] + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index c9b5d9ab8..79efddfad 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -335,6 +335,21 @@ en: next: Next prev: Prev truncate: "…" + push_notifications: + favourite: + title: "%{name} favourited your status" + follow: + title: "%{name} is now following you" + mention: + action_boost: 'Boost' + action_expand: 'Show more' + action_favourite: 'Favourite' + title: "%{name} mentioned you" + reblog: + title: "%{name} boosted your status" + subscribed: + body: "You can now receive push notifications." + title: "Subscription registered!" remote_follow: acct: Enter your username@domain you want to follow from missing_resource: Could not find the required redirect URL for your account diff --git a/config/locales/pl.yml b/config/locales/pl.yml index dc5aa716b..f9d69745f 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -339,6 +339,21 @@ pl: next: Następna prev: Poprzednia truncate: "…" + push_notifications: + favourite: + title: "%{name} dodał Twój status do ulubionych" + follow: + title: "%{name} zaczął Cię śledzić" + mention: + action_boost: 'Podbij' + action_expand: 'Pokaż więcej' + action_favourite: 'Dodaj do ulubionych' + title: "%{name} wspomniał o Tobie" + reblog: + title: "%{name} podbił Twój status" + subscribed: + body: "Otrzymujesz teraz powiadomienia push." + title: "Zarejestrowano subskrypcję!" remote_follow: acct: Podaj swój adres (nazwa@domena), z którego chcesz śledzić missing_resource: Nie udało się znaleźć adresu przekierowania z Twojej domeny diff --git a/config/routes.rb b/config/routes.rb index 963fedcb4..9171d02d4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -206,6 +206,11 @@ Rails.application.routes.draw do namespace :web do resource :settings, only: [:update] + resources :push_subscriptions, only: [:create] do + member do + put :update + end + end end end diff --git a/config/webpack/production.js b/config/webpack/production.js index 303fca81b..4592db89e 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -5,6 +5,9 @@ const merge = require('webpack-merge'); const CompressionPlugin = require('compression-webpack-plugin'); const sharedConfig = require('./shared.js'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const OfflinePlugin = require('offline-plugin'); +const { publicPath } = require('./configuration.js'); +const path = require('path'); module.exports = merge(sharedConfig, { output: { filename: '[name]-[chunkhash].js' }, @@ -39,5 +42,16 @@ module.exports = merge(sharedConfig, { openAnalyzer: false, logLevel: 'silent', // do not bother Webpacker, who runs with --json and parses stdout }), + new OfflinePlugin({ + publicPath: publicPath, // sw.js must be served from the root to avoid scope issues + caches: { }, // do not cache things, we only use it for push notifications for now + ServiceWorker: { + entry: path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'), + cacheName: 'mastodon', + output: '../sw.js', + publicPath: '/sw.js', + minify: true, + }, + }), ], }); -- cgit