diff --git a/app/Http/Controllers/Api/Web/NotificationsController.php b/app/Http/Controllers/Api/Web/NotificationsController.php index c0a0d710..10a25a37 100644 --- a/app/Http/Controllers/Api/Web/NotificationsController.php +++ b/app/Http/Controllers/Api/Web/NotificationsController.php @@ -21,10 +21,13 @@ namespace Poniverse\Ponyfm\Http\Controllers\Api\Web; use Auth; -use Carbon\Carbon; 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 { @@ -59,4 +62,52 @@ class NotificationsController extends ApiControllerBase return ['notifications_updated' => $numberOfUpdatedRows]; } + + /** + * Subscribe a user to native push notifications. Takes an endpoint and + * encryption keys from the client and stores them in the database + * for future use. + * + * @return string + */ + public function postSubscribe() + { + $input = json_decode(Input::json('subscription')); + if ($input != 'null') { + $existing = Subscription::where('endpoint', '=', $input->endpoint) + ->where('user_id', '=', Auth::user()->id) + ->first(); + + if ($existing === null) { + $subscription = Subscription::create([ + 'user_id' => Auth::user()->id, + 'endpoint' => $input->endpoint, + 'p256dh' => $input->keys->p256dh, + 'auth' => $input->keys->auth + ]); + + return ['id' => $subscription->id]; + } else { + return ['id' => $existing->id]; + } + } else { + return ['error' => 'No data']; + } + } + + /** + * 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 976c50ee..a7e09d50 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -134,8 +134,11 @@ Route::group(['prefix' => 'api/web'], function() { Route::post('/dashboard/read-news', 'Api\Web\DashboardController@postReadNews'); Route::get('/account/settings/{slug}', 'Api\Web\AccountController@getSettings'); + 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 f586834e..9d71b30e 100644 --- a/app/Jobs/SendNotifications.php +++ b/app/Jobs/SendNotifications.php @@ -25,6 +25,7 @@ use Illuminate\Queue\InteractsWithQueue; use Poniverse\Ponyfm\Jobs\Job; use Illuminate\Contracts\Bus\SelfHandling; use Poniverse\Ponyfm\Library\Notifications\Drivers\AbstractDriver; +use Poniverse\Ponyfm\Library\Notifications\Drivers\NativeDriver; use Poniverse\Ponyfm\Library\Notifications\Drivers\PonyfmDriver; use Poniverse\Ponyfm\Models\User; use SerializesModels; @@ -60,7 +61,8 @@ class SendNotifications extends Job implements SelfHandling, ShouldQueue // to work around a Laravel bug - namely, the SerializesModels trait // tries (and fails) to serialize static fields. $drivers = [ - PonyfmDriver::class + PonyfmDriver::class, + NativeDriver::class ]; foreach ($drivers as $driver) { diff --git a/app/Library/Notifications/Drivers/NativeDriver.php b/app/Library/Notifications/Drivers/NativeDriver.php new file mode 100644 index 00000000..c340b5e9 --- /dev/null +++ b/app/Library/Notifications/Drivers/NativeDriver.php @@ -0,0 +1,129 @@ +. + */ + +namespace Poniverse\Ponyfm\Library\Notifications\Drivers; + + +use Config; +use Poniverse\Ponyfm\Contracts\Favouritable; +use Poniverse\Ponyfm\Models\Activity; +use Poniverse\Ponyfm\Models\Comment; +use Poniverse\Ponyfm\Models\Playlist; +use Poniverse\Ponyfm\Models\Track; +use Poniverse\Ponyfm\Models\User; +use Minishlink\WebPush\WebPush; + +class NativeDriver extends AbstractDriver { + /** + * Method for sending notifications to devices + * + * @param Activity $activity + * @param User[] $recipients collection of {@link User} objects + */ + private function pushNotifications(Activity $activity, $recipients) + { + if (Config::get('ponyfm.gcm_key') != 'default') { + $apiKeys = array( + 'GCM' => Config::get('ponyfm.gcm_key'), + ); + + $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) { + $webPush->sendNotification( + $recipient->endpoint, + $jsonData, + $recipient->p256dh, + $recipient->auth + ); + } + + $webPush->flush(); + } + } + + /** + * @inheritdoc + */ + public function publishedNewTrack(Track $track) { + $activity = Activity::where('user_id', $track->user_id) + ->where('activity_type', Activity::TYPE_PUBLISHED_TRACK) + ->where('resource_id', $track->id) + ->get()[0]; + + + $this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args())); + } + + /** + * @inheritdoc + */ + public function publishedNewPlaylist(Playlist $playlist) { + $activity = Activity::where('user_id', $playlist->user_id) + ->where('activity_type', Activity::TYPE_PUBLISHED_PLAYLIST) + ->where('resource_id', $playlist->id) + ->get()[0]; + + $this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args())); + } + + public function newFollower(User $userBeingFollowed, User $follower) { + $activity = Activity::where('user_id', $follower->id) + ->where('activity_type', Activity::TYPE_NEW_FOLLOWER) + ->where('resource_id', $userBeingFollowed->id) + ->get()[0]; + + $this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args())); + } + + /** + * @inheritdoc + */ + public function newComment(Comment $comment) { + $activity = Activity::where('user_id', $comment->user_id) + ->where('activity_type', Activity::TYPE_NEW_COMMENT) + ->where('resource_id', $comment->id) + ->get()[0]; + + $this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args())); + } + + /** + * @inheritdoc + */ + public function newFavourite(Favouritable $entityBeingFavourited, User $favouriter) { + $activity = Activity::where('user_id', $favouriter->id) + ->where('activity_type', Activity::TYPE_CONTENT_FAVOURITED) + ->where('resource_id', $entityBeingFavourited->id) + ->get()[0]; + + $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/app/Models/Subscription.php b/app/Models/Subscription.php new file mode 100644 index 00000000..eaffdd76 --- /dev/null +++ b/app/Models/Subscription.php @@ -0,0 +1,49 @@ +. + */ + +namespace Poniverse\Ponyfm\Models; + +use Illuminate\Database\Eloquent\Model; + +/** + * Poniverse\Ponyfm\Models\Subscription + * + * @property integer $id + * @property integer $user_id + * @property string $endpoint + * @property string $p256dh + * @property string $auth + * @property-read \Poniverse\Ponyfm\Models\User $user + */ +class Subscription extends Model { + public $timestamps = false; + protected $fillable = ['user_id', 'endpoint', 'p256dh', 'auth']; + protected $casts = [ + 'id' => 'integer', + 'user_id' => 'integer', + 'endpoint' => 'string', + 'p256dh' => 'string', + 'auth' => 'string' + ]; + + public function user() { + return $this->belongsTo(User::class, 'user_id', 'id'); + } +} diff --git a/composer.json b/composer.json index f8fcedae..e1a15e3c 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "barryvdh/laravel-debugbar": "^2.2", "predis/predis": "^1.0", "ksubileau/color-thief-php": "^1.3", - "graham-campbell/exceptions": "^8.6" + "graham-campbell/exceptions": "^8.6", + "minishlink/web-push": "^1.0" }, "require-dev": { "fzaninotto/faker": "~1.4", diff --git a/composer.lock b/composer.lock index da69a173..49ff6f90 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "5c9f86045c835b8964e1eb198a7727ea", - "content-hash": "a09f15b4c222efb8e676a4efdd979b4f", + "hash": "070553e4e21387213808a4cb779e5f16", + "content-hash": "98c97b7ca37abf031e353edaf4ac2ae3", "packages": [ { "name": "barryvdh/laravel-debugbar", @@ -124,6 +124,59 @@ ], "time": "2016-03-03 08:45:00" }, + { + "name": "beberlei/assert", + "version": "v2.5", + "source": { + "type": "git", + "url": "https://github.com/beberlei/assert.git", + "reference": "91e2690c4ecc8a4e3e2d333430069f6a0c694a7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/assert/zipball/91e2690c4ecc8a4e3e2d333430069f6a0c694a7a", + "reference": "91e2690c4ecc8a4e3e2d333430069f6a0c694a7a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "@stable" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Assert": "lib/" + }, + "files": [ + "lib/Assert/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + } + ], + "description": "Thin assertion library for input validation in business models.", + "keywords": [ + "assert", + "assertion", + "validation" + ], + "time": "2016-03-22 14:34:51" + }, { "name": "classpreloader/classpreloader", "version": "3.0.0", @@ -883,6 +936,58 @@ ], "time": "2016-03-18 16:31:37" }, + { + "name": "fgrosse/phpasn1", + "version": "1.3.2", + "source": { + "type": "git", + "url": "https://github.com/fgrosse/PHPASN1.git", + "reference": "ee6d1abd18f8bcbaf0b55563ba87e5ed16cd0c98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/ee6d1abd18f8bcbaf0b55563ba87e5ed16cd0c98", + "reference": "ee6d1abd18f8bcbaf0b55563ba87e5ed16cd0c98", + "shasum": "" + }, + "require": { + "ext-gmp": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "FG\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Friedrich Große", + "email": "friedrich.grosse@gmail.com", + "homepage": "https://github.com/FGrosse", + "role": "Author" + }, + { + "name": "All contributors", + "homepage": "https://github.com/FGrosse/PHPASN1/contributors" + } + ], + "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", + "homepage": "https://github.com/FGrosse/PHPASN1", + "time": "2015-07-15 21:26:40" + }, { "name": "graham-campbell/exceptions", "version": "v8.6.1", @@ -1454,6 +1559,54 @@ ], "time": "2015-12-05 17:17:57" }, + { + "name": "kriswallsmith/buzz", + "version": "v0.15", + "source": { + "type": "git", + "url": "https://github.com/kriswallsmith/Buzz.git", + "reference": "d4041666c3ffb379af02a92dabe81c904b35fab8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kriswallsmith/Buzz/zipball/d4041666c3ffb379af02a92dabe81c904b35fab8", + "reference": "d4041666c3ffb379af02a92dabe81c904b35fab8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "suggest": { + "ext-curl": "*" + }, + "type": "library", + "autoload": { + "psr-0": { + "Buzz": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kris Wallsmith", + "email": "kris.wallsmith@gmail.com", + "homepage": "http://kriswallsmith.net/" + } + ], + "description": "Lightweight HTTP client", + "homepage": "https://github.com/kriswallsmith/Buzz", + "keywords": [ + "curl", + "http client" + ], + "time": "2015-06-25 17:26:56" + }, { "name": "ksubileau/color-thief-php", "version": "v1.3.0", @@ -1780,6 +1933,118 @@ ], "time": "2016-01-22 12:22:23" }, + { + "name": "mdanter/ecc", + "version": "v0.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpecc/phpecc.git", + "reference": "8b588fc094ba743d8f8c84980bcc6b470c4baed7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpecc/phpecc/zipball/8b588fc094ba743d8f8c84980bcc6b470c4baed7", + "reference": "8b588fc094ba743d8f8c84980bcc6b470c4baed7", + "shasum": "" + }, + "require": { + "ext-gmp": "*", + "ext-mcrypt": "*", + "fgrosse/phpasn1": "~1.3.1", + "php": ">=5.4.0", + "symfony/console": "~2.6" + }, + "require-dev": { + "phpunit/phpunit": "~4.1", + "squizlabs/php_codesniffer": "~2", + "symfony/yaml": "~2.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mdanter\\Ecc\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matyas Danter", + "homepage": "http://matejdanter.com/", + "role": "Author" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io", + "homepage": "http://aztech.io", + "role": "Maintainer" + }, + { + "name": "Drak", + "email": "drak@zikula.org", + "homepage": "http://zikula.org", + "role": "Maintainer" + } + ], + "description": "PHP Elliptic Curve Cryptography library", + "homepage": "https://github.com/mdanter/phpecc", + "time": "2014-07-07 12:44:15" + }, + { + "name": "minishlink/web-push", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/Minishlink/web-push.git", + "reference": "ad407ca84e87595760ff87a836a55ad107fa8245" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Minishlink/web-push/zipball/ad407ca84e87595760ff87a836a55ad107fa8245", + "reference": "ad407ca84e87595760ff87a836a55ad107fa8245", + "shasum": "" + }, + "require": { + "kriswallsmith/buzz": ">=0.6", + "lib-openssl": "*", + "mdanter/ecc": "^0.3.0", + "php": ">=5.4", + "spomky-labs/base64url": "^1.0", + "spomky-labs/php-aes-gcm": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Minishlink\\WebPush\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Louis Lagrange", + "email": "lagrange.louis@gmail.com", + "homepage": "https://github.com/Minishlink" + } + ], + "description": "Web Push library for PHP", + "homepage": "https://github.com/Minishlink/web-push", + "keywords": [ + "Push API", + "WebPush", + "notifications", + "push", + "web" + ], + "time": "2016-05-13 21:21:54" + }, { "name": "monolog/monolog", "version": "1.19.0", @@ -2400,6 +2665,115 @@ "description": "A lightweight implementation of CommonJS Promises/A for PHP", "time": "2016-05-03 17:50:52" }, + { + "name": "spomky-labs/base64url", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/base64url.git", + "reference": "ef6d5fb93894063d9cee996022259fd08d6646ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/ef6d5fb93894063d9cee996022259fd08d6646ea", + "reference": "ef6d5fb93894063d9cee996022259fd08d6646ea", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0|^5.0", + "satooshi/php-coveralls": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Base64Url\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky-Labs/base64url/contributors" + } + ], + "description": "Base 64 URL Safe Encoding/decoding PHP Library", + "homepage": "https://github.com/Spomky-Labs/base64url", + "keywords": [ + "base64", + "rfc4648", + "safe", + "url" + ], + "time": "2016-01-21 19:50:30" + }, + { + "name": "spomky-labs/php-aes-gcm", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/php-aes-gcm.git", + "reference": "7d415c6eeb5133804a38451d6ed93b5af76872ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/php-aes-gcm/zipball/7d415c6eeb5133804a38451d6ed93b5af76872ce", + "reference": "7d415c6eeb5133804a38451d6ed93b5af76872ce", + "shasum": "" + }, + "require": { + "beberlei/assert": "^2.0", + "lib-openssl": "*", + "php": ">=5.4", + "symfony/polyfill-mbstring": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.5|^5.0", + "satooshi/php-coveralls": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "AESGCM\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/php-aes-gcm/contributors" + } + ], + "description": "AES GCM (Galois Counter Mode) PHP implementation.", + "homepage": "https://github.com/Spomky-Labs/php-aes-gcm", + "keywords": [ + "Galois Counter Mode", + "gcm" + ], + "time": "2016-04-28 13:58:02" + }, { "name": "swiftmailer/swiftmailer", "version": "v5.4.2", diff --git a/config/ponyfm.php b/config/ponyfm.php index 6d7ae949..a0d0ff2b 100644 --- a/config/ponyfm.php +++ b/config/ponyfm.php @@ -115,5 +115,18 @@ return [ | */ - 'user_slug_minimum_length' => 3 + 'user_slug_minimum_length' => 3, + + /* + |-------------------------------------------------------------------------- + | Indexing queue name + |-------------------------------------------------------------------------- + | + | Google Cloud Messaging API key. Needs to be generated in the Google Cloud + | Console as a browser key. This is used to send notifications to users + | with push notifications enabled. + | + */ + + 'gcm_key' => env('GCM_KEY', 'default'), ]; diff --git a/database/migrations/2016_06_10_010314_create_subscription_table.php b/database/migrations/2016_06_10_010314_create_subscription_table.php new file mode 100644 index 00000000..3d4b5bd3 --- /dev/null +++ b/database/migrations/2016_06_10_010314_create_subscription_table.php @@ -0,0 +1,36 @@ +bigIncrements('id'); + $table->unsignedInteger('user_id')->index(); + $table->string('endpoint'); + $table->string('p256dh'); + $table->string('auth'); + $table->timestamps(); + + $table->foreign('user_id')->references('id')->on('users'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('subscriptions'); + } +} diff --git a/public/manifest.json b/public/manifest.json index 133c8434..5c2f814b 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -22,5 +22,6 @@ "background_color": "#EEE", "start_url": "/", "display": "standalone", - "orientation": "portrait" + "orientation": "portrait", + "gcm_sender_id": "628116355343" } diff --git a/public/service-worker.js b/public/service-worker.js index 0e24768e..58eaa9db 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 @@ -48,13 +56,58 @@ self.addEventListener('activate', function (event) { // Basic offline mode // Just respond with an offline error page for now self.addEventListener('fetch', function(event) { - event.respondWith( - caches.match(event.request).then(function(response) { - return response || fetch(event.request); - }).catch(function () { - if (event.request.mode == 'navigate') { - return caches.match('/offline.html'); + if (event.request.url.indexOf('stage.pony.fm') > -1 || event.request.url.indexOf('upload') > -1) { + // Ignore some requests + return; + } else { + event.respondWith( + caches.match(event.request).then(function (response) { + return response || fetch(event.request); + }).catch(function () { + if (event.request.mode == 'navigate') { + return caches.match('/offline.html'); + } + }) + ) + } +}); + +self.addEventListener('push', function(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 @@