diff --git a/app/Http/Controllers/Api/Web/NotificationsController.php b/app/Http/Controllers/Api/Web/NotificationsController.php index badec332..5843b3b3 100644 --- a/app/Http/Controllers/Api/Web/NotificationsController.php +++ b/app/Http/Controllers/Api/Web/NotificationsController.php @@ -25,6 +25,8 @@ use Input; use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase; use Poniverse\Ponyfm\Models\Notification; use Poniverse\Ponyfm\Models\Subscription; +use Poniverse\Ponyfm\Models\Track; +use Poniverse\Ponyfm\Models\User; use Minishlink\WebPush\WebPush; class NotificationsController extends ApiControllerBase @@ -84,9 +86,25 @@ class NotificationsController extends ApiControllerBase 'auth' => $input->keys->auth ]); - return $subscription->id; + return ['id' => $subscription->id]; } else { - return $existing->id; + return ['id' => $existing->id]; } } + + /** + * Removes a user's notification subscription + * + * @return string + */ + public function postUnsubscribe() + { + $input = json_decode(Input::json('subscription')); + + $existing = Subscription::where('endpoint', '=', $input->endpoint) + ->where('user_id', '=', Auth::user()->id) + ->delete(); + + return ['result' => 'success']; + } } diff --git a/app/Http/routes.php b/app/Http/routes.php index a59b82bc..a7e09d50 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -138,6 +138,7 @@ Route::group(['prefix' => 'api/web'], function() { Route::get('/notifications', 'Api\Web\NotificationsController@getNotifications'); Route::put('/notifications/mark-as-read', 'Api\Web\NotificationsController@putMarkAsRead'); Route::post('/notifications/subscribe', 'Api\Web\NotificationsController@postSubscribe'); + Route::post('/notifications/unsubscribe', 'Api\Web\NotificationsController@postUnsubscribe'); Route::get('/tracks/edit/{id}', 'Api\Web\TracksController@getEdit'); diff --git a/app/Jobs/SendNotifications.php b/app/Jobs/SendNotifications.php index 53d1080d..9d71b30e 100644 --- a/app/Jobs/SendNotifications.php +++ b/app/Jobs/SendNotifications.php @@ -62,7 +62,7 @@ class SendNotifications extends Job implements SelfHandling, ShouldQueue // tries (and fails) to serialize static fields. $drivers = [ PonyfmDriver::class, - //NativeDriver::class + NativeDriver::class ]; foreach ($drivers as $driver) { diff --git a/app/Library/Notifications/Drivers/NativeDriver.php b/app/Library/Notifications/Drivers/NativeDriver.php index 4243a4a0..1690d402 100644 --- a/app/Library/Notifications/Drivers/NativeDriver.php +++ b/app/Library/Notifications/Drivers/NativeDriver.php @@ -21,31 +21,48 @@ namespace Poniverse\Ponyfm\Library\Notifications\Drivers; -use Carbon\Carbon; use Poniverse\Ponyfm\Contracts\Favouritable; use Poniverse\Ponyfm\Models\Activity; use Poniverse\Ponyfm\Models\Comment; -use Poniverse\Ponyfm\Models\Notification; use Poniverse\Ponyfm\Models\Playlist; use Poniverse\Ponyfm\Models\Track; use Poniverse\Ponyfm\Models\User; +use Minishlink\WebPush\WebPush; -class NativeDriver extends AbstractDriver { +class NativeDriver extends AbstractDriver { /** - * A helper method for bulk insertion of notification records. + * Method for sending notifications to devices * - * @param int $activityId + * @param Activity $activity * @param User[] $recipients collection of {@link User} objects */ - private function pushNotifications(int $activityId, $recipients) { - $notifications = []; + private function pushNotifications(Activity $activity, $recipients) { + $apiKeys = array( + 'GCM' => 'AIzaSyCLmCVIgASWL280rHyPz8OP7il3pf8SrGg', + ); + + $webPush = new WebPush($apiKeys); + + $data = [ + 'id' => $activity->id, + 'text' => $activity->getTextAttribute(), + 'title' => $activity->getTitleFromActivityType(), + 'image' => $activity->getThumbnailUrlAttribute(), + 'url' => $activity->url + ]; + + $jsonData = json_encode($data); + foreach ($recipients as $recipient) { - $notifications[] = [ - 'activity_id' => $activityId, - 'user_id' => $recipient->id - ]; + $webPush->sendNotification( + $recipient->endpoint, + $jsonData, + $recipient->p256dh, + $recipient->auth + ); } - Notification::insert($notifications); + + $webPush->flush(); } /** @@ -58,7 +75,7 @@ class NativeDriver extends AbstractDriver { ->get()[0]; - $this->pushNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args())); + $this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args())); } /** @@ -70,16 +87,16 @@ class NativeDriver extends AbstractDriver { ->where('resource_id', $playlist->id) ->get()[0]; - $this->pushNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args())); + $this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args())); } public function newFollower(User $userBeingFollowed, User $follower) { - $activity = Activity::where('user_id', $follower->user_id) + $activity = Activity::where('user_id', $follower->id) ->where('activity_type', Activity::TYPE_NEW_FOLLOWER) ->where('resource_id', $userBeingFollowed->id) ->get()[0]; - $this->pushNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args())); + $this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args())); } /** @@ -91,18 +108,18 @@ class NativeDriver extends AbstractDriver { ->where('resource_id', $comment->id) ->get()[0]; - $this->pushNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args())); + $this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args())); } /** * @inheritdoc */ public function newFavourite(Favouritable $entityBeingFavourited, User $favouriter) { - $activity = Activity::where('user_id', $favouriter->user_id) + $activity = Activity::where('user_id', $favouriter->id) ->where('activity_type', Activity::TYPE_CONTENT_FAVOURITED) ->where('resource_id', $entityBeingFavourited->id) ->get()[0]; - $this->pushNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args())); + $this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args())); } } diff --git a/app/Library/Notifications/RecipientFinder.php b/app/Library/Notifications/RecipientFinder.php index e92aef7c..94f8e022 100644 --- a/app/Library/Notifications/RecipientFinder.php +++ b/app/Library/Notifications/RecipientFinder.php @@ -25,9 +25,11 @@ use Illuminate\Foundation\Bus\DispatchesJobs; use Poniverse\Ponyfm\Contracts\Favouritable; use Poniverse\Ponyfm\Contracts\NotificationHandler; use Poniverse\Ponyfm\Jobs\SendNotifications; +use Poniverse\Ponyfm\Library\Notifications\Drivers\NativeDriver; use Poniverse\Ponyfm\Library\Notifications\Drivers\PonyfmDriver; use Poniverse\Ponyfm\Models\Comment; use Poniverse\Ponyfm\Models\Playlist; +use Poniverse\Ponyfm\Models\Subscription; use Poniverse\Ponyfm\Models\Track; use Poniverse\Ponyfm\Models\User; @@ -59,6 +61,21 @@ class RecipientFinder implements NotificationHandler { switch ($this->notificationDriver) { case PonyfmDriver::class: return $track->user->followers; + case NativeDriver::class: + $followerIds = []; + $subIds = []; + $rawSubIds = Subscription::select('id')->get(); + + foreach ($track->user->followers as $follower) { + array_push($followerIds, $follower->id); + } + + foreach ($rawSubIds as $sub) { + array_push($subIds, $sub->id); + } + + $targetIds = array_intersect($followerIds, $subIds); + return Subscription::whereIn('user_id', $targetIds)->get(); default: return $this->fail(); } @@ -71,6 +88,21 @@ class RecipientFinder implements NotificationHandler { switch ($this->notificationDriver) { case PonyfmDriver::class: return $playlist->user->followers; + case NativeDriver::class: + $followerIds = []; + $subIds = []; + $rawSubIds = Subscription::select('id')->get(); + + foreach ($playlist->user->followers as $follower) { + array_push($followerIds, $follower->id); + } + + foreach ($rawSubIds as $sub) { + array_push($subIds, $sub->id); + } + + $targetIds = array_intersect($followerIds, $subIds); + return Subscription::whereIn('user_id', $targetIds)->get(); default: return $this->fail(); } @@ -83,6 +115,8 @@ class RecipientFinder implements NotificationHandler { switch ($this->notificationDriver) { case PonyfmDriver::class: return [$userBeingFollowed]; + case NativeDriver::class: + return Subscription::where('user_id', '=', $userBeingFollowed->id)->get(); default: return $this->fail(); } @@ -98,6 +132,8 @@ class RecipientFinder implements NotificationHandler { $comment->user->id === $comment->resource->user->id ? [] : [$comment->resource->user]; + case NativeDriver::class: + return Subscription::where('user_id', '=', $comment->resource->user->id)->get(); default: return $this->fail(); } @@ -113,6 +149,8 @@ class RecipientFinder implements NotificationHandler { $favouriter->id === $entityBeingFavourited->user->id ? [] : [$entityBeingFavourited->user]; + case NativeDriver::class: + return Subscription::where('user_id', '=', $entityBeingFavourited->user->id)->get(); default: return $this->fail(); } diff --git a/app/Models/Activity.php b/app/Models/Activity.php index fdb91215..5e661d36 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -165,6 +165,24 @@ class Activity extends Model { } } + public function getTitleFromActivityType() { + switch($this->activity_type) { + case static::TYPE_PUBLISHED_TRACK: + return "Pony.fm - New track"; + case static::TYPE_PUBLISHED_PLAYLIST: + return "Pony.fm - New playlist"; + case static::TYPE_NEW_FOLLOWER: + return "Pony.fm - New follower"; + case static::TYPE_NEW_COMMENT: + return "Pony.fm - New comment"; + case static::TYPE_CONTENT_FAVOURITED: + return "Pony.fm - Favourited"; + + default: + return "Pony.fm - Unknown"; + } + } + /** * @return string human-readable Markdown string describing this notification * @throws \Exception diff --git a/public/service-worker.js b/public/service-worker.js index dd410e97..2d3a8f0f 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -23,6 +23,14 @@ var urlsToCache = [ var CACHE_NAME = 'pfm-offline-v1'; +var notifUrlCache = {}; + +function getChromeVersion () { + var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); + + return raw ? parseInt(raw[2], 10) : false; +} + // Set the callback for the install step self.addEventListener('install', function(event) { // Perform install steps @@ -60,5 +68,41 @@ self.addEventListener('fetch', function(event) { }); self.addEventListener('push', function(event) { - console.log('Push message', event); + console.log(event); + var data = {}; + + if (event.data) { + console.log(event.data.json()); + data = JSON.parse(event.data.text()); + } + + notifUrlCache['pfm-' + data.id] = data.url; + + self.registration.showNotification(data.title, { + body: data.text, + icon: data.image, + tag: 'pfm-' + data.id + }) +}); + +self.addEventListener('notificationclick', function(event) { + event.notification.close(); + + event.waitUntil( + clients.matchAll({ + type: "window" + }) + .then(function(clientList) { + var url = notifUrlCache[event.notification.tag]; + for (var i = 0; i < clientList.length; i++) { + var client = clientList[i]; + if (client.url == url && 'focus' in client) + return client.focus(); + } + + if (clients.openWindow) { + return clients.openWindow(url); + } + }) + ); }); \ No newline at end of file diff --git a/public/templates/directives/notification-list.html b/public/templates/directives/notification-list.html index 036b2352..1dac7b41 100644 --- a/public/templates/directives/notification-list.html +++ b/public/templates/directives/notification-list.html @@ -1,7 +1,14 @@