feat(push notification): add notification
This commit is contained in:
parent
0ae236d2ca
commit
d9df1e369b
@ -7,7 +7,9 @@
|
||||
use App\Models\OrderItem;
|
||||
use App\Models\Perfume;
|
||||
use App\Models\Purchase;
|
||||
use App\Models\PushNotification;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Component;
|
||||
|
||||
@ -248,6 +250,20 @@ protected function loadStats()
|
||||
]);
|
||||
}
|
||||
|
||||
#[On('notification:subscribe')]
|
||||
public function notificationSubscribe(string $subscription)
|
||||
{
|
||||
$userId = auth()->id();
|
||||
|
||||
PushNotification::updateOrCreate(
|
||||
[
|
||||
'user_id' => $userId,
|
||||
'subscriptions' => $subscription,
|
||||
],
|
||||
['subscriptions' => $subscription]
|
||||
);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.studio.dashboard.overview');
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
use App\Livewire\Forms\Studio\Master\FormulaForm;
|
||||
use App\Models\Bottle;
|
||||
use App\Models\Formula as FormulaModel;
|
||||
use App\Models\PushNotification;
|
||||
use App\Traits\WithAuthorization;
|
||||
use App\Traits\WithCloseModal;
|
||||
use App\Traits\WithConfirmation;
|
||||
@ -14,6 +15,8 @@
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Component;
|
||||
use Minishlink\WebPush\Subscription;
|
||||
use Minishlink\WebPush\WebPush;
|
||||
|
||||
#[Title('Rumus')]
|
||||
class Formula extends Component
|
||||
@ -93,6 +96,8 @@ public function create()
|
||||
|
||||
public function update()
|
||||
{
|
||||
$this->pushNotification();
|
||||
|
||||
$exists = FormulaModel::where('quality', $this->form->quality)
|
||||
->where('size', $this->form->size)
|
||||
->where('id', '!=', $this->form->formula->id)
|
||||
@ -149,6 +154,32 @@ public function delete(FormulaModel $formula)
|
||||
Flux::modals()->close();
|
||||
}
|
||||
|
||||
protected function pushNotification()
|
||||
{
|
||||
$auth = [
|
||||
'VAPID' => [
|
||||
'subject' => config('app.url'),
|
||||
'publicKey' => config('services.vapid.public_key'),
|
||||
'privateKey' => config('services.vapid.private_key'),
|
||||
],
|
||||
];
|
||||
|
||||
$webPush = new WebPush($auth);
|
||||
|
||||
$payload = json_encode([
|
||||
'title' => 'Penggajian',
|
||||
'body' => 'Penggajian baru telah ditambahkan.',
|
||||
]);
|
||||
|
||||
foreach (PushNotification::all() as $notification) {
|
||||
$webPush->sendOneNotification(
|
||||
Subscription::create(json_decode($notification->subscriptions, true)),
|
||||
$payload,
|
||||
['TTL' => 5000],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.studio.master.formulas', [
|
||||
|
||||
23
app/Models/PushNotification.php
Normal file
23
app/Models/PushNotification.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class PushNotification extends Model
|
||||
{
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'data' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@ -131,4 +131,9 @@ public function pointRecords(): HasMany
|
||||
{
|
||||
return $this->hasMany(PointRecord::class);
|
||||
}
|
||||
|
||||
public function pushNotifications(): HasMany
|
||||
{
|
||||
return $this->hasMany(PushNotification::class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
"livewire/flux": "2.2.5",
|
||||
"livewire/flux-pro": "2.2.5",
|
||||
"livewire/livewire": "^3.6",
|
||||
"minishlink/web-push": "^9.0",
|
||||
"rappasoft/laravel-livewire-tables": "^3.7",
|
||||
"spatie/laravel-activitylog": "^4.10",
|
||||
"spatie/laravel-medialibrary": "^11.15",
|
||||
|
||||
330
composer.lock
generated
330
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "5741a5b81c8f68f6d656467cf2e24651",
|
||||
"content-hash": "0dcdd0d151296be6bc124bd5e4d44961",
|
||||
"packages": [
|
||||
{
|
||||
"name": "archtechx/enums",
|
||||
@ -3595,6 +3595,71 @@
|
||||
],
|
||||
"time": "2025-01-27T12:07:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "minishlink/web-push",
|
||||
"version": "v9.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/web-push-libs/web-push-php.git",
|
||||
"reference": "5c185f78ee41f271e2ea7314c80760040465b713"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/web-push-libs/web-push-php/zipball/5c185f78ee41f271e2ea7314c80760040465b713",
|
||||
"reference": "5c185f78ee41f271e2ea7314c80760040465b713",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
"guzzlehttp/guzzle": "^7.4.5",
|
||||
"php": ">=8.1",
|
||||
"spomky-labs/base64url": "^2.0.4",
|
||||
"web-token/jwt-library": "^3.3.0|^4.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^v3.68.5",
|
||||
"phpstan/phpstan": "^2.1.2",
|
||||
"phpunit/phpunit": "^10.5.44|^11.5.6"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "Optional for performance.",
|
||||
"ext-gmp": "Optional for performance."
|
||||
},
|
||||
"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/web-push-libs/web-push-php",
|
||||
"keywords": [
|
||||
"Push API",
|
||||
"WebPush",
|
||||
"notifications",
|
||||
"push",
|
||||
"web"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/web-push-libs/web-push-php/issues",
|
||||
"source": "https://github.com/web-push-libs/web-push-php/tree/v9.0.3"
|
||||
},
|
||||
"time": "2025-11-13T17:14:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "3.9.0",
|
||||
@ -5779,6 +5844,180 @@
|
||||
],
|
||||
"time": "2025-01-13T13:04:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spomky-labs/base64url",
|
||||
"version": "v2.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Spomky-Labs/base64url.git",
|
||||
"reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/7752ce931ec285da4ed1f4c5aa27e45e097be61d",
|
||||
"reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^0.11|^0.12",
|
||||
"phpstan/phpstan-beberlei-assert": "^0.11|^0.12",
|
||||
"phpstan/phpstan-deprecation-rules": "^0.11|^0.12",
|
||||
"phpstan/phpstan-phpunit": "^0.11|^0.12",
|
||||
"phpstan/phpstan-strict-rules": "^0.11|^0.12"
|
||||
},
|
||||
"type": "library",
|
||||
"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"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Spomky-Labs/base64url/issues",
|
||||
"source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/Spomky",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/FlorentMorselli",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2020-11-03T09:10:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spomky-labs/pki-framework",
|
||||
"version": "1.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Spomky-Labs/pki-framework.git",
|
||||
"reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/bf6f55a9d9eb25b7781640221cb54f5c727850d7",
|
||||
"reference": "bf6f55a9d9eb25b7781640221cb54f5c727850d7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14",
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"ekino/phpstan-banned-code": "^1.0|^2.0|^3.0",
|
||||
"ext-gmp": "*",
|
||||
"ext-openssl": "*",
|
||||
"infection/infection": "^0.28|^0.29|^0.31",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.3",
|
||||
"phpstan/extension-installer": "^1.3|^2.0",
|
||||
"phpstan/phpstan": "^1.8|^2.0",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.0|^2.0",
|
||||
"phpstan/phpstan-phpunit": "^1.1|^2.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.3|^2.0",
|
||||
"phpunit/phpunit": "^10.1|^11.0|^12.0",
|
||||
"rector/rector": "^1.0|^2.0",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"symfony/string": "^6.4|^7.0|^8.0",
|
||||
"symfony/var-dumper": "^6.4|^7.0|^8.0",
|
||||
"symplify/easy-coding-standard": "^12.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "For better performance (or GMP)",
|
||||
"ext-gmp": "For better performance (or BCMath)",
|
||||
"ext-openssl": "For OpenSSL based cyphering"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SpomkyLabs\\Pki\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Joni Eskelinen",
|
||||
"email": "jonieske@gmail.com",
|
||||
"role": "Original developer"
|
||||
},
|
||||
{
|
||||
"name": "Florent Morselli",
|
||||
"email": "florent.morselli@spomky-labs.com",
|
||||
"role": "Spomky-Labs PKI Framework developer"
|
||||
}
|
||||
],
|
||||
"description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.",
|
||||
"homepage": "https://github.com/spomky-labs/pki-framework",
|
||||
"keywords": [
|
||||
"DER",
|
||||
"Private Key",
|
||||
"ac",
|
||||
"algorithm identifier",
|
||||
"asn.1",
|
||||
"asn1",
|
||||
"attribute certificate",
|
||||
"certificate",
|
||||
"certification request",
|
||||
"cryptography",
|
||||
"csr",
|
||||
"decrypt",
|
||||
"ec",
|
||||
"encrypt",
|
||||
"pem",
|
||||
"pkcs",
|
||||
"public key",
|
||||
"rsa",
|
||||
"sign",
|
||||
"signature",
|
||||
"verify",
|
||||
"x.509",
|
||||
"x.690",
|
||||
"x509",
|
||||
"x690"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Spomky-Labs/pki-framework/issues",
|
||||
"source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/Spomky",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/FlorentMorselli",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2025-10-22T08:24:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/clock",
|
||||
"version": "v7.3.0",
|
||||
@ -8618,6 +8857,95 @@
|
||||
],
|
||||
"time": "2024-11-21T01:49:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "web-token/jwt-library",
|
||||
"version": "4.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/web-token/jwt-library.git",
|
||||
"reference": "b05d01d4138b1e06328e29075a21a6da974935df"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/web-token/jwt-library/zipball/b05d01d4138b1e06328e29075a21a6da974935df",
|
||||
"reference": "b05d01d4138b1e06328e29075a21a6da974935df",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"brick/math": "^0.12|^0.13|^0.14",
|
||||
"php": ">=8.2",
|
||||
"psr/clock": "^1.0",
|
||||
"spomky-labs/pki-framework": "^1.2.1"
|
||||
},
|
||||
"conflict": {
|
||||
"spomky-labs/jose": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance",
|
||||
"ext-gmp": "GMP or BCMath is highly recommended to improve the library performance",
|
||||
"ext-openssl": "For key management (creation, optimization, etc.) and some algorithms (AES, RSA, ECDSA, etc.)",
|
||||
"ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys",
|
||||
"paragonie/sodium_compat": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys",
|
||||
"spomky-labs/aes-key-wrap": "For all Key Wrapping algorithms (AxxxKW, AxxxGCMKW, PBES2-HSxxx+AyyyKW...)",
|
||||
"symfony/console": "Needed to use console commands",
|
||||
"symfony/http-client": "To enable JKU/X5U support."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Jose\\Component\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Florent Morselli",
|
||||
"homepage": "https://github.com/Spomky"
|
||||
},
|
||||
{
|
||||
"name": "All contributors",
|
||||
"homepage": "https://github.com/web-token/jwt-framework/contributors"
|
||||
}
|
||||
],
|
||||
"description": "JWT library",
|
||||
"homepage": "https://github.com/web-token",
|
||||
"keywords": [
|
||||
"JOSE",
|
||||
"JWE",
|
||||
"JWK",
|
||||
"JWKSet",
|
||||
"JWS",
|
||||
"Jot",
|
||||
"RFC7515",
|
||||
"RFC7516",
|
||||
"RFC7517",
|
||||
"RFC7518",
|
||||
"RFC7519",
|
||||
"RFC7520",
|
||||
"bundle",
|
||||
"jwa",
|
||||
"jwt",
|
||||
"symfony"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/web-token/jwt-library/issues",
|
||||
"source": "https://github.com/web-token/jwt-library/tree/4.1.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/Spomky",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/FlorentMorselli",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2025-10-22T08:01:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
"version": "1.11.0",
|
||||
|
||||
@ -39,4 +39,9 @@
|
||||
'url' => env('ZAWA_API_URL'),
|
||||
],
|
||||
|
||||
'vapid' => [
|
||||
'public_key' => env('VAPID_PUBLIC_KEY'),
|
||||
'private_key' => env('VAPID_PRIVATE_KEY'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('push_notifications', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->json('subscriptions');
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('push_notifications');
|
||||
}
|
||||
};
|
||||
20
public/service-worker.js
Normal file
20
public/service-worker.js
Normal file
@ -0,0 +1,20 @@
|
||||
self.addEventListener('push', (event) => {
|
||||
const notification = event.data.json();
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(
|
||||
notification.title, {
|
||||
body: notification.body,
|
||||
icon: 'assets/images/dark-logo.png',
|
||||
data: {
|
||||
url: notification.url
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
event.waitUntil(
|
||||
clients.openWindow(event.notification.data.url)
|
||||
);
|
||||
})
|
||||
@ -23,6 +23,25 @@
|
||||
{{ $slot }}
|
||||
|
||||
@fluxScripts
|
||||
<script>
|
||||
document.addEventListener('livewire:navigated', () => {
|
||||
Notification.requestPermission().then((permission) => {
|
||||
if (permission === 'granted') {
|
||||
navigator.serviceWorker.ready.then((sw) => {
|
||||
sw.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: '{{ config('services.vapid.public_key') }}',
|
||||
}).then((subscription) => {
|
||||
const json = JSON.stringify(subscription)
|
||||
Livewire.dispatch('notification:subscribe', {
|
||||
subscription: json
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@ -148,3 +148,9 @@ class="font-bold {{ $item->type->value == \App\Enums\SalaryAdjustmentType::DEDUC
|
||||
</div>
|
||||
</flux:modal>
|
||||
</flux:main>
|
||||
|
||||
@script
|
||||
<script>
|
||||
navigator.serviceWorker.register('{{ URL::asset('service-worker.js') }}');
|
||||
</script>
|
||||
@endscript
|
||||
|
||||
Loading…
Reference in New Issue
Block a user