在本教程中,我们将使用WebPush在我们的Laravel应用程序中实现推送通知功能。我们将使用纯JavaScript,没有框架或库。推送通知是Service Workers的一个功能。Service Workers是在Web浏览器内运行的后台脚本。有许多功能,如缓存,后台同步,但本教程仅涉及推送通知。我们还将为访客用户实现推送通知。
注意:Service Workers使用HTTPS,除非你使用localhost。
入门
让我们使用composer创建一个新的Laravel项目:
composer create-project laravel/laravel webpush
在迁移表之前,在位于app\Providers的AppServiceProvider.php的**boot()**方法中添加以下代码:
<?php
\Schema::defaultStringLength(191);
以上代码将把字符串列的默认长度设置为191。由于Laravel默认使用utf8mb4字符集,其唯一键的最大长度为191。如果超过了限制,则Laravel将生成错误并且不会运行迁移。如果你想指定不同的字符集,那么可以在位于config目录中的database.php配置文件中设置它。现在设置数据库并将数据库凭据添加到**.env文件中并运行migrate**命令,该命令将创建用于身份验证的所有必要表:
php artisan migrate
并生成身份验证脚手架:
php artisan make:auth
我们将使用Laravel dev服务器(对于localhost),而不是使用带有apache的虚拟主机:
php artisan serve
打开Chrome并粘贴你在终端中看到的URL。(我不建议使用除Chrome或Firefox之外的浏览器)。
订阅用户
为了开始使用我们的通知,我们需要从浏览器订阅用户,我们可以使用JavaScript完成。订阅用户将涉及两个步骤,即:
- 从用户获取权限。
- 获取PushSubscription并将其发送到服务器。
在订阅用户之前,我们将需要生成应用程序服务器密钥,称为VAPID Keys。VAPID Keys是用于识别用户的公钥和私钥,这些密钥确保是同一服务器向用户触发推送消息。有几种方法可以生成这些密钥,你可以访问此网站web-push-codelab.glitch.me生成这些密钥,或使用npm安装web-push cli从命令行生成它们:
$ npm install -g web-push
$ web-push generate-vapid-keys
由于我们使用一个名为Webpush的Laravel包,因此我们可以使用Artisan命令生成这些密钥。Webpush库也可用于其他语言,请参见此链接以获取所有库的列表。让我们安装该软件包:
composer require laravel-notification-channels/webpush
Laravel 5.6或更高版本使用软件包发现功能,因此我们不需要添加软件包提供程序。让我们将HasPushSubscription特性添加到User模型:
<?php
use NotificationChannels\WebPush\HasPushSubscriptions; //import the trait
class User extends Model
{
use HasPushSubscriptions; // add the trait to your class
//your model code...
}
添加特性后,我们将需要发布将创建push_subscriptions表的迁移:
php artisan vendor:publish --provider="NotificationChannels\WebPush\WebPushServiceProvider" --tag="migrations"
当你运行迁移命令时,它将生成一个字符集错误,说指定的键太长,因此我们将为此表添加不同的字符集,并为端点列添加不同的长度。让我们更新**up()**方法:
<?php
public function up()
{
Schema::create('push_subscriptions', function (Blueprint $table) {
$table->charset ='utf8';
$table->collation = 'utf8_unicode_ci';
$table->increments('id');
$table->integer('guest_id')->unsigned()->index();
$table->string('endpoint', 255)->unique();
$table->string('public_key')->nullable();
$table->string('auth_token')->nullable();
$table->timestamps();
});
}
你还可以发布config文件(我们将不使用它):
php artisan vendor:publish --provider="NotificationChannels\WebPush\WebPushServiceProvider" --tag="config"
现在我们可以生成VAPID Keys:
php artisan webpush:vapid
此命令将生成VAPID公钥和私钥,并将它们存储到 .env 文件中。
让我们在public目录中创建一个名为sw.js的Service Worker文件,并在public/js目录中创建一个名为enable-push.js的JavaScript文件。将该脚本添加到位于resources/views/layouts的app.blade.php布局文件的</body>结束标记之前:
@auth
<script src="{{ asset('js/enable-push.js') }}" defer></script>
@endauth
仅在我们经过身份验证时包含此脚本。
让我们从客户端开始工作,打开enable-push.js文件。我们将通过定义一个名为 initSW() 的函数来注册我们的服务工作者:
function initSW() {
if (!"serviceWorker" in navigator) {
//service worker isn't supported
return;
}
//don't use it here if you use service worker
//for other stuff.
if (!"PushManager" in window) {
//push isn't supported
return;
}
//register the service worker
navigator.serviceWorker.register('../sw.js')
.then(() => {
console.log('serviceWorker installed!')
initPush();
})
.catch((err) => {
console.log(err)
});
}
在上述函数中,我们首先检查服务工作者和推送是否受支持,然后注册我们的服务工作者,该服务工作者在一个目录之外(你不必在此处检查推送,这将停止服务工作者的注册,但我们仅出于推送目的而使用服务工作者,因此我们将保留它。)在then()内,我们调用initPush(),它将负责订阅用户。不要忘记在我们的enable-push.js中调用initSW()。只需将此行推到脚本的顶部:
initSW();
让我们在initSW()之后定义initPush():
function initPush() {
if (!navigator.serviceWorker.ready) {
return;
}
new Promise(function (resolve, reject) {
const permissionResult = Notification.requestPermission(function (result) {
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
})
.then((permissionResult) => {
if (permissionResult !== 'granted') {
throw new Error('We weren\'t granted permission.');
}
subscribeUser();
});
}
在initPush()内,我们正在请求用户权限,如果用户点击允许,则可以获取PushSubscription。在我们被授予权限后,我们将调用subscribeUser(),该函数将订阅用户并将PushSubscrption发送到我们的服务器。让我们定义subscribeUser():
function subscribeUser() {
navigator.serviceWorker.ready
.then((registration) => {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'YOUR_VAPID_PUBLIC_KEY'
)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then((pushSubscription) => {
console.log('Received PushSubscription: ', JSON.stringify(pushSubscription));
storePushSubscription(pushSubscription);
});
}
navigator.serviceWorker.ready用于检查服务工作者是否已注册并处于活动状态。在第一个then()内,我们首先创建一个名为subscriptionOptions的对象,其中包含我们的VAPID public key和userVisibleOnly选项。使用你从上述命令生成的公钥。将userVisibleOnly设置为true将不允许在不让用户知道的情况下在后台进行静默推送(如果未定义,则会出现错误,请阅读更多)。urlBase64ToUint8Array()函数将将我们的公钥转换为适当的格式。订阅后。它将返回一个PushSubscription,我们可以将其发送到服务器。在第二个then()内,我们使用storePushSubscription()函数将PushSubscription发送到我们的服务器。在定义它之前,我们应该定义urlBase64ToUint8Array():
function urlBase64ToUint8Array(base64String) {
var padding = '='.repeat((4 - base64String.length % 4) % 4);
var base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
var rawData = window.atob(base64);
var outputArray = new Uint8Array(rawData.length);
for (var i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
我们的 storePushSubscription() 函数如下所示:
function storePushSubscription(pushSubscription) {
const token = document.querySelector('meta[name=csrf-token]').getAttribute('content');
fetch('/push', {
method: 'POST',
body: JSON.stringify(pushSubscription),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': token
}
})
.then((res) => {
return res.json();
})
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
});
}
上面的函数正在向 /push 路由发送 POST 请求。我们从 meta 标签中获取 CSRF Token,这对于我们的 ajax 请求非常重要。当我们创建身份验证脚手架时,就添加了该 meta 标签。
将 PushSubscription 存储到数据库中
让我们从路由开始(将其添加到你的 web.php 中,而不是 api.php,因为我们需要访问已验证的用户):
<?php
//store a push subscriber.
Route::post('/push','PushController@store');
让我们创建我们的 PushController:
php artisan make:controller PushController
现在打开 PushController 并添加 store 方法:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Notifications\PushDemo;
use App\User;
use Auth;
use Notification;
class PushController extends Controller
{
public function __construct(){
$this->middleware('auth');
}
/**
* Store the PushSubscription.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function store(Request $request){
$this->validate($request,[
'endpoint' => 'required',
'keys.auth' => 'required',
'keys.p256dh' => 'required'
]);
$endpoint = $request->endpoint;
$token = $request->keys['auth'];
$key = $request->keys['p256dh'];
$user = Auth::user();
$user->updatePushSubscription($endpoint, $key, $token);
return response()->json(['success' => true],200);
}
}
我们可以使用 updatePushSubscription() 来创建或更新 PushSubscriber(因为我们已经将其添加到我们的 User 模型中)。如果成功,我们将在控制台上获得成功消息。请注意,每当你访问 / 路由时,你将不会看到“服务工作程序已安装”消息,因为我们已经将脚本添加到了 app 布局文件中,而该文件不会被 welcome 视图使用。现在刷新页面,你会看到一个权限框:
当你点击 允许,你会在控制台看到 PushSubscription 和成功消息:
现在我们需要在 Laravel 中创建一个 Notification 来发送 Push 通知:
php artisan make:notification PushDemo
打开通知类,它位于 app/Notifcations 目录中,添加以下代码:
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use NotificationChannels\WebPush\WebPushMessage;
use NotificationChannels\WebPush\WebPushChannel;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class PushDemo extends Notification
{
use Queueable;
public function via($notifiable)
{
return [WebPushChannel::class];
}
public function toWebPush($notifiable, $notification)
{
return (new WebPushMessage)
->title('I\'m Notification Title')
->icon('/notification-icon.png')
->body('Great, Push Notifications work!')
->action('View App', 'notification_action');
}
}
在 toWebPush() 方法中,我们有一个通知标题作为通知的标题,正文将是额外的文本,图标是在通知上显示的图像,操作将显示一个带有通知的按钮(你可以添加更多的 选项,并了解这些 选项)。现在我们可以使用 Notification::send() 从我们的控制器发送通知。让我们在我们的控制器中定义一个 push 方法:
<?php
/**
* Send Push Notifications to all users.
*
* @return \Illuminate\Http\Response
*/
public function push(){
Notification::send(User::all(),new PushDemo);
return redirect()->back();
}
在 web.php 中添加一个新路由:
<?php
//make a push notification.
Route::get('/push','PushController@push')->name('push');
我们需要一个按钮来发送通知,所以让我们在我们的 home.blade.php 视图中添加它。将其放置在带有 card-body 类的 div 中:
<a href="{{route('push')}}" class="btn btn-outline-primary btn-block">Make a Push Notification!</a>
现在最后一步是添加一个事件监听器,它将监听我们的服务工作者文件 sw.js 中的 push event:
self.addEventListener('push', function (e) {
if (!(self.Notification && self.Notification.permission === 'granted')) {
//notifications aren't supported or permission not granted!
return;
}
if (e.data) {
var msg = e.data.json();
console.log(msg)
e.waitUntil(self.registration.showNotification(msg.title, {
body: msg.body,
icon: msg.icon,
actions: msg.actions
}));
}
});
在 push event 监听器中,我们首先检查是否允许并支持通知,然后使用 e.data 从 e 对象中访问通知数据,并使用 json() 方法将其转换为 json。在服务工作者中,waitUntil() 用于告诉浏览器任务正在运行,不应被终止。我们可以使用 serviceWorkerRegistration.showNotification(title,options) 显示通知。确保你在应用程序选项卡中检查了重新加载选项的更新。
现在我们可以通过点击该按钮来测试它。
每个浏览器和操作系统都有自己的通知显示方式。我在 Ubuntu 上使用 Chrome。Chrome 不显示图标,而 Firefox 则显示。
当你悬停在上面时,它会看起来像这样:
这是 Firefox 的显示方式(它不显示通知操作):
为访客用户提供通知(新增)
在实现访客通知之前,请阅读上面的教程
大多数时候,我们希望我们网站的所有访问者都能接收推送通知,因此让我们来实现这个功能。首先,我们需要更新迁移并创建一些新的迁移。打开 push_subscriptions 迁移并将 user_id 更新为 guest_id:
<?php
$table->integer('guest_id')->unsigned()->index();
删除外键约束:
<?php
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
让我们创建一个 Guest 模型及其迁移,以便与推送订阅相关联:
php artisan make:model Guest -m
在 guests 表迁移中,我们有一个唯一的列 endpoint,它将用于标识每个推送订阅:
<?php
public function up()
{
Schema::create('guests', function (Blueprint $table) {
$table->charset ='utf8';
$table->collation = 'utf8_unicode_ci';
$table->increments('id');
$table->string('endpoint',255)->unique();
$table->timestamps();
});
}
我们需要另一个迁移,它将在这些表之间创建外键约束:
php artisan make:migration add_foreign_to_guest_id_in_push_subscriptions_table --table=push_subscriptions
打开迁移并在 up() 方法中添加以下外键约束:
<?php
$table->foreign('guest_id')->references('id')->on('guests')->onDelete('cascade');
使用迁移命令创建所有表。现在打开 Guest 模型并添加以下内容:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use NotificationChannels\WebPush\HasPushSubscriptions;
class Guest extends Model
{
use Notifiable,
HasPushSubscriptions;
protected $fillable = [
'endpoint',
];
/**
* Determine if the given subscription belongs to this user.
*
* @param \NotificationChannels\WebPush\PushSubscription $subscription
* @return bool
*/
public function pushSubscriptionBelongsToUser($subscription){
return (int) $subscription->guest_id === (int) $this->id;
}
}
我们正在导入 Notifiable trait,以便我们可以使用 Laravel 通知(它已经被导入到 User 模型中)。重写 pushSubscriptionBelongsToUser() 方法非常重要,因为我们不使用经过身份验证的用户。
最后一步将修改我们的 PushController,因此打开控制器并首先将所有 User 模型用法替换为 Guest 模型。之后,将 store() 方法中的以下行替换为:
<?php
$user = Auth::user();
用以下内容替换:
<?php
$user = Guest::firstOrCreate([
'endpoint' => $endpoint
]);
当然,你需要删除阻止包含我们的 enable-push.js 脚本的 @auth 指令,然后我们就完成了。
你可能想将其部署到 heroku,它具有带有 https 的免费版本(如果你想在移动设备上测试它)。就用户体验而言,可能最好创建类似按钮的东西,它将触发 initPush()。一开始就要求用户授权通知并不是一个好主意,因为我们的用户不知道他们正在接收什么样的通知。上面是一个使用推送通知的基本示例。如果你想深入了解,可以在Google开发者文档中查找更多信息。
可以在这里找到源代码/Github仓库。访客通知的分支可以在这里找到。
译自:https://medium.com/@sagarmaheshwary31/push-notifications-with-laravel-and-webpush-446884265aaa
评论(0)