Laravel Notification Channels FCM (Firebase Cloud Messaging)

初始化專案

laravel new fcm_test

使用 laravel/ui 創建身分認證

使用Laravel Breeze也可以

composer require laravel/ui

創建 auth

php artisan ui bootstrap --auth

npm install

npm run dev

新增 user 欄位,儲存要發送裝置 token

php artisan make:migration add_column_device_token_to_users

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddColumnDeviceTokenToUsers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('device_token')->nullable();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('device_token');
});
}
}

更改 user model

將 device_token 欄位設成可更新

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;

/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
'device_token',
];

/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];

/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}

遷移

php artisan migrate

安裝 laravel-notification-channels/fcm

composer require laravel-notification-channels/fcm:~2.0

申請 Firebase 專案

firebase

申請專案

點選專案設定

選擇網頁應用

初始化的 SDK

產生 json 檔案

配置

下載服務帳戶 JSON 文件後,您可以通過在.env 文件中指定以 FIREBASE_開頭的環境變量來配置包。通常,包的工作需要以下條件

# 服務帳戶 JSON 文件的相對或完整路徑
FIREBASE_CREDENTIALS=
# 您可以在以下位置找到項目的數據庫 URL
# https://console.firebase.google.com/project/_/database
FIREBASE_DATABASE_URL=https://<your-project>.firebaseio.com

以剛剛創建的專案為例

將 JOSN 檔案放在專案根目錄
\填入專案 ID

.env檔案配置

FIREBASE_CREDENTIALS=firebase-adminsdk.json
FIREBASE_DATABASE_URL=https://laravel-test-974f3.firebaseio.com

配置查看laravel-firebase#configuration

創建通知

php artisan make:notification HelloNotification

每個通知類別都有一個 via 方法用於判別要將通知寄送哪個頻道。

toFcm 會接收一個 $notifiable 實體並且返回一個 NotificationChannels\Fcm\FcmMessage 實例

<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;
use NotificationChannels\Fcm\FcmChannel;
use NotificationChannels\Fcm\FcmMessage;
use NotificationChannels\Fcm\Resources\AndroidConfig;
use NotificationChannels\Fcm\Resources\AndroidFcmOptions;
use NotificationChannels\Fcm\Resources\AndroidNotification;
use NotificationChannels\Fcm\Resources\ApnsConfig;
use NotificationChannels\Fcm\Resources\ApnsFcmOptions;

class HelloNotification extends Notification
{
public function via($notifiable)
{
return [FcmChannel::class];
}

public function toFcm($notifiable)
{
return FcmMessage::create()
->setData(['data1' => 'value', 'data2' => 'value2'])
->setNotification(\NotificationChannels\Fcm\Resources\Notification::create()
->setTitle('你好')
->setBody('測試通知.')
->setImage('https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Laravel.svg/1200px-Laravel.svg.png'))
->setAndroid(
AndroidConfig::create()
->setFcmOptions(AndroidFcmOptions::create()->setAnalyticsLabel('analytics'))
->setNotification(AndroidNotification::create()->setColor('#0A0A0A'))
)->setApns(
ApnsConfig::create()
->setFcmOptions(ApnsFcmOptions::create()->setAnalyticsLabel('analytics_ios'))
);
}

// optional method when using kreait/laravel-firebase:^3.0, this method can be omitted, defaults to the default project
public function fcmProject($notifiable, $message)
{
// $message is what is returned by `toFcm`
return 'app'; // name of the firebase project to use
}
}

創建推播的 controller

php artisan make:controller PushController

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Notifications\HelloNotification;
use Illuminate\Support\Facades\Auth;

class PushController extends Controller
{
public function push()
{
$user = Auth::user();
$user->notify(new HelloNotification());
return redirect()->back();
}

public function fcmToken(Request $request)
{
try {
if (Auth::user()->device_token == $request->token) {
return response()->json(['success' => true], 200);
} else {
Auth::user()->update(['device_token' => $request->token]);
return response()->json(['success' => true], 200);
}
} catch (\Throwable $th) {
throw $th;
}
}
}

新增路由

web.php新增路由

// 測試通知push
Route::get('/push', [App\Http\Controllers\PushController::class, 'push'])->name('push')->middleware('auth');
// 取得fcm token
Route::post('/fcm-token', [App\Http\Controllers\PushController::class, 'fcmToken'])->name('fcm.token')->middleware('auth');

在通知模型中設置一個方法

class User extends Authenticatable
{
use Notifiable;

....

/**
* Specifies the user's FCM token
*
* @return string|array
*/
public function routeNotificationForFcm()
{
return $this->device_token;
}
}

更新home.blade.php

@extends('layouts.app') @section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Dashboard') }}</div>

<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif {{ __('You are logged in!') }}

<a href="{{route('push')}}">測試通知!</a>
</div>
</div>
</div>
</div>
</div>
<script
src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous"
></script>
<script src="https://www.gstatic.com/firebasejs/8.3.2/firebase.js"></script>
<script>
var firebaseConfig = {
apiKey: "XXXX",
authDomain: "XXXX.firebaseapp.com",
projectId: "XXXX",
storageBucket: "XXXX",
messagingSenderId: "XXXX",
appId: "XXXX",
};

firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();

function notifyMe() {
// 讓我們檢查瀏覽器是否支持通知
if (!("Notification" in window)) {
console.log("This browser does not support desktop notification");
}

// 讓我們檢查是否已經授予通知權限
else if (Notification.permission === "granted") {
// 如果同意取得通知權限,則可啟動通知
startFCM();
}

// 否則,我們需要徵求用戶的許可
else if (
Notification.permission !== "denied" ||
Notification.permission === "default"
) {
Notification.requestPermission(function (permission) {
// If the user accepts, let's create a notification
if (permission === "granted") {
startFCM();
}
});
}

// 最後,如果用戶拒絕通知,而你想要尊重他們就沒有必要再打擾他們了
}

function startFCM() {
messaging
.requestPermission()
.then(function () {
return messaging.getToken();
})
.then(function (request) {
$.ajaxSetup({
headers: {
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
},
});
$.ajax({
url: '{{ route("fcm.token") }}',
type: "POST",
// 將 token 傳送到 /fcm-token 進行處理
data: {
token: request,
},
dataType: "JSON",
success: function (request) {
console.log("取得token成功");
},
error: function (error) {
console.log(error);
},
});
})
.catch(function (error) {
console.log(error);
});
}

messaging.onMessage(function (payload) {
const title = payload.notification.title;
const options = {
body: payload.notification.body,
icon: payload.notification.image,
};
new Notification(title, options);
});

notifyMe();
</script>
@endsection

創建firebase-messaging-sw.js

為 Web 推送通知設置 firebase 配置文件,並將提供的代碼放在public/firebase-messaging-sw.js

// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here. Other Firebase libraries
// are not available in the service worker.importScripts('https://www.gstatic.com/firebasejs/7.23.0/firebase-app.js');
importScripts("https://www.gstatic.com/firebasejs/8.3.2/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.3.2/firebase-messaging.js");
/*
Initialize the Firebase app in the service worker by passing in the messagingSenderId.
*/
firebase.initializeApp({
apiKey: "XXXX",
authDomain: "XXXX.firebaseapp.com",
projectId: "XXXX",
storageBucket: "XXXX",
messagingSenderId: "XXXX",
appId: "XXXX",
});

const messaging = firebase.messaging();

messaging.setBackgroundMessageHandler(function (payload) {
console.log("Message received.", payload);

const title = "Hello world is awesome";
const options = {
body: "Your notificaiton message .",
icon: "/firebase-logo.png",
};

return self.registration.showNotification(title, options);
});

展示

cURL error 60: SSL certificate: unable to get local issuer certificate

如在本地端運行發生cURL error 60: SSL certificate: unable to get local issuer certificate

請至caextract下載證書包

更改php.ini,找到curl.cainfo取消註解,並指向剛剛下載的檔案C:/cacert.pem

如還是有誤請參考stackoverflow