├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── keys ├── private_key.pem ├── private_key.txt └── public_key.txt ├── router.php └── src ├── app.js ├── index.html ├── push_subscription.php ├── send_push_notification.php └── serviceWorker.js /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Louis Lagrange 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web Push example in PHP 2 | 3 | Navigating through the commits and files will help you build: 4 | - on the client 5 | - a user friendly opt-in push notification button 6 | - on the server 7 | - an endpoint for managing your push notification subscriptions 8 | - an endpoint that triggers push notification thanks to [web-push-php](https://github.com/web-push-libs/web-push-php) 9 | 10 | ## Requirements 11 | - Chrome or Firefox 12 | - [composer](https://getcomposer.org/) 13 | - PHP 8.0+ 14 | - gmp 15 | - mbstring 16 | - curl 17 | - openssl 18 | 19 | PHP 7.2 is no longer maintained, but you can checkout the `v3.x` tags. 20 | PHP 7.1 is no longer maintained, but you can checkout the `v2.x` tags. 21 | PHP 5.6+ is no longer maintained, but you can checkout the `v1.x` tags. 22 | 23 | ## Installation 24 | ```bash 25 | $ composer create-project minishlink/web-push-php-example 26 | $ cd web-push-php-example 27 | ``` 28 | 29 | You can change the VAPID keys in the [keys](./keys) folder with [this guide](https://github.com/web-push-libs/web-push-php#authentication-vapid). 30 | Don't forget to update the public key in [app.js](./src/app.js) too. 31 | 32 | ## Usage 33 | 34 | ```bash 35 | $ php -S localhost:8000 router.php 36 | ``` 37 | 38 | And open [localhost:8000](http://localhost:8000). 39 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minishlink/web-push-php-example", 3 | "description": "An example for sending Web Push notifications, using web-push-php", 4 | "type": "project", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Louis Lagrange", 9 | "email": "lagrange.louis@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "minishlink/web-push": "^8.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "318aa13021644c16a9c73ae62ca46f53", 8 | "packages": [ 9 | { 10 | "name": "brick/math", 11 | "version": "0.11.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/brick/math.git", 15 | "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/brick/math/zipball/0ad82ce168c82ba30d1c01ec86116ab52f589478", 20 | "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": "^8.0" 25 | }, 26 | "require-dev": { 27 | "php-coveralls/php-coveralls": "^2.2", 28 | "phpunit/phpunit": "^9.0", 29 | "vimeo/psalm": "5.0.0" 30 | }, 31 | "type": "library", 32 | "autoload": { 33 | "psr-4": { 34 | "Brick\\Math\\": "src/" 35 | } 36 | }, 37 | "notification-url": "https://packagist.org/downloads/", 38 | "license": [ 39 | "MIT" 40 | ], 41 | "description": "Arbitrary-precision arithmetic library", 42 | "keywords": [ 43 | "Arbitrary-precision", 44 | "BigInteger", 45 | "BigRational", 46 | "arithmetic", 47 | "bigdecimal", 48 | "bignum", 49 | "brick", 50 | "math" 51 | ], 52 | "support": { 53 | "issues": "https://github.com/brick/math/issues", 54 | "source": "https://github.com/brick/math/tree/0.11.0" 55 | }, 56 | "funding": [ 57 | { 58 | "url": "https://github.com/BenMorel", 59 | "type": "github" 60 | } 61 | ], 62 | "time": "2023-01-15T23:15:59+00:00" 63 | }, 64 | { 65 | "name": "guzzlehttp/guzzle", 66 | "version": "7.8.0", 67 | "source": { 68 | "type": "git", 69 | "url": "https://github.com/guzzle/guzzle.git", 70 | "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9" 71 | }, 72 | "dist": { 73 | "type": "zip", 74 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1110f66a6530a40fe7aea0378fe608ee2b2248f9", 75 | "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9", 76 | "shasum": "" 77 | }, 78 | "require": { 79 | "ext-json": "*", 80 | "guzzlehttp/promises": "^1.5.3 || ^2.0.1", 81 | "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", 82 | "php": "^7.2.5 || ^8.0", 83 | "psr/http-client": "^1.0", 84 | "symfony/deprecation-contracts": "^2.2 || ^3.0" 85 | }, 86 | "provide": { 87 | "psr/http-client-implementation": "1.0" 88 | }, 89 | "require-dev": { 90 | "bamarni/composer-bin-plugin": "^1.8.1", 91 | "ext-curl": "*", 92 | "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", 93 | "php-http/message-factory": "^1.1", 94 | "phpunit/phpunit": "^8.5.29 || ^9.5.23", 95 | "psr/log": "^1.1 || ^2.0 || ^3.0" 96 | }, 97 | "suggest": { 98 | "ext-curl": "Required for CURL handler support", 99 | "ext-intl": "Required for Internationalized Domain Name (IDN) support", 100 | "psr/log": "Required for using the Log middleware" 101 | }, 102 | "type": "library", 103 | "extra": { 104 | "bamarni-bin": { 105 | "bin-links": true, 106 | "forward-command": false 107 | } 108 | }, 109 | "autoload": { 110 | "files": [ 111 | "src/functions_include.php" 112 | ], 113 | "psr-4": { 114 | "GuzzleHttp\\": "src/" 115 | } 116 | }, 117 | "notification-url": "https://packagist.org/downloads/", 118 | "license": [ 119 | "MIT" 120 | ], 121 | "authors": [ 122 | { 123 | "name": "Graham Campbell", 124 | "email": "hello@gjcampbell.co.uk", 125 | "homepage": "https://github.com/GrahamCampbell" 126 | }, 127 | { 128 | "name": "Michael Dowling", 129 | "email": "mtdowling@gmail.com", 130 | "homepage": "https://github.com/mtdowling" 131 | }, 132 | { 133 | "name": "Jeremy Lindblom", 134 | "email": "jeremeamia@gmail.com", 135 | "homepage": "https://github.com/jeremeamia" 136 | }, 137 | { 138 | "name": "George Mponos", 139 | "email": "gmponos@gmail.com", 140 | "homepage": "https://github.com/gmponos" 141 | }, 142 | { 143 | "name": "Tobias Nyholm", 144 | "email": "tobias.nyholm@gmail.com", 145 | "homepage": "https://github.com/Nyholm" 146 | }, 147 | { 148 | "name": "Márk Sági-Kazár", 149 | "email": "mark.sagikazar@gmail.com", 150 | "homepage": "https://github.com/sagikazarmark" 151 | }, 152 | { 153 | "name": "Tobias Schultze", 154 | "email": "webmaster@tubo-world.de", 155 | "homepage": "https://github.com/Tobion" 156 | } 157 | ], 158 | "description": "Guzzle is a PHP HTTP client library", 159 | "keywords": [ 160 | "client", 161 | "curl", 162 | "framework", 163 | "http", 164 | "http client", 165 | "psr-18", 166 | "psr-7", 167 | "rest", 168 | "web service" 169 | ], 170 | "support": { 171 | "issues": "https://github.com/guzzle/guzzle/issues", 172 | "source": "https://github.com/guzzle/guzzle/tree/7.8.0" 173 | }, 174 | "funding": [ 175 | { 176 | "url": "https://github.com/GrahamCampbell", 177 | "type": "github" 178 | }, 179 | { 180 | "url": "https://github.com/Nyholm", 181 | "type": "github" 182 | }, 183 | { 184 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", 185 | "type": "tidelift" 186 | } 187 | ], 188 | "time": "2023-08-27T10:20:53+00:00" 189 | }, 190 | { 191 | "name": "guzzlehttp/promises", 192 | "version": "2.0.1", 193 | "source": { 194 | "type": "git", 195 | "url": "https://github.com/guzzle/promises.git", 196 | "reference": "111166291a0f8130081195ac4556a5587d7f1b5d" 197 | }, 198 | "dist": { 199 | "type": "zip", 200 | "url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", 201 | "reference": "111166291a0f8130081195ac4556a5587d7f1b5d", 202 | "shasum": "" 203 | }, 204 | "require": { 205 | "php": "^7.2.5 || ^8.0" 206 | }, 207 | "require-dev": { 208 | "bamarni/composer-bin-plugin": "^1.8.1", 209 | "phpunit/phpunit": "^8.5.29 || ^9.5.23" 210 | }, 211 | "type": "library", 212 | "extra": { 213 | "bamarni-bin": { 214 | "bin-links": true, 215 | "forward-command": false 216 | } 217 | }, 218 | "autoload": { 219 | "psr-4": { 220 | "GuzzleHttp\\Promise\\": "src/" 221 | } 222 | }, 223 | "notification-url": "https://packagist.org/downloads/", 224 | "license": [ 225 | "MIT" 226 | ], 227 | "authors": [ 228 | { 229 | "name": "Graham Campbell", 230 | "email": "hello@gjcampbell.co.uk", 231 | "homepage": "https://github.com/GrahamCampbell" 232 | }, 233 | { 234 | "name": "Michael Dowling", 235 | "email": "mtdowling@gmail.com", 236 | "homepage": "https://github.com/mtdowling" 237 | }, 238 | { 239 | "name": "Tobias Nyholm", 240 | "email": "tobias.nyholm@gmail.com", 241 | "homepage": "https://github.com/Nyholm" 242 | }, 243 | { 244 | "name": "Tobias Schultze", 245 | "email": "webmaster@tubo-world.de", 246 | "homepage": "https://github.com/Tobion" 247 | } 248 | ], 249 | "description": "Guzzle promises library", 250 | "keywords": [ 251 | "promise" 252 | ], 253 | "support": { 254 | "issues": "https://github.com/guzzle/promises/issues", 255 | "source": "https://github.com/guzzle/promises/tree/2.0.1" 256 | }, 257 | "funding": [ 258 | { 259 | "url": "https://github.com/GrahamCampbell", 260 | "type": "github" 261 | }, 262 | { 263 | "url": "https://github.com/Nyholm", 264 | "type": "github" 265 | }, 266 | { 267 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", 268 | "type": "tidelift" 269 | } 270 | ], 271 | "time": "2023-08-03T15:11:55+00:00" 272 | }, 273 | { 274 | "name": "guzzlehttp/psr7", 275 | "version": "2.6.1", 276 | "source": { 277 | "type": "git", 278 | "url": "https://github.com/guzzle/psr7.git", 279 | "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727" 280 | }, 281 | "dist": { 282 | "type": "zip", 283 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727", 284 | "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727", 285 | "shasum": "" 286 | }, 287 | "require": { 288 | "php": "^7.2.5 || ^8.0", 289 | "psr/http-factory": "^1.0", 290 | "psr/http-message": "^1.1 || ^2.0", 291 | "ralouphie/getallheaders": "^3.0" 292 | }, 293 | "provide": { 294 | "psr/http-factory-implementation": "1.0", 295 | "psr/http-message-implementation": "1.0" 296 | }, 297 | "require-dev": { 298 | "bamarni/composer-bin-plugin": "^1.8.1", 299 | "http-interop/http-factory-tests": "^0.9", 300 | "phpunit/phpunit": "^8.5.29 || ^9.5.23" 301 | }, 302 | "suggest": { 303 | "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" 304 | }, 305 | "type": "library", 306 | "extra": { 307 | "bamarni-bin": { 308 | "bin-links": true, 309 | "forward-command": false 310 | } 311 | }, 312 | "autoload": { 313 | "psr-4": { 314 | "GuzzleHttp\\Psr7\\": "src/" 315 | } 316 | }, 317 | "notification-url": "https://packagist.org/downloads/", 318 | "license": [ 319 | "MIT" 320 | ], 321 | "authors": [ 322 | { 323 | "name": "Graham Campbell", 324 | "email": "hello@gjcampbell.co.uk", 325 | "homepage": "https://github.com/GrahamCampbell" 326 | }, 327 | { 328 | "name": "Michael Dowling", 329 | "email": "mtdowling@gmail.com", 330 | "homepage": "https://github.com/mtdowling" 331 | }, 332 | { 333 | "name": "George Mponos", 334 | "email": "gmponos@gmail.com", 335 | "homepage": "https://github.com/gmponos" 336 | }, 337 | { 338 | "name": "Tobias Nyholm", 339 | "email": "tobias.nyholm@gmail.com", 340 | "homepage": "https://github.com/Nyholm" 341 | }, 342 | { 343 | "name": "Márk Sági-Kazár", 344 | "email": "mark.sagikazar@gmail.com", 345 | "homepage": "https://github.com/sagikazarmark" 346 | }, 347 | { 348 | "name": "Tobias Schultze", 349 | "email": "webmaster@tubo-world.de", 350 | "homepage": "https://github.com/Tobion" 351 | }, 352 | { 353 | "name": "Márk Sági-Kazár", 354 | "email": "mark.sagikazar@gmail.com", 355 | "homepage": "https://sagikazarmark.hu" 356 | } 357 | ], 358 | "description": "PSR-7 message implementation that also provides common utility methods", 359 | "keywords": [ 360 | "http", 361 | "message", 362 | "psr-7", 363 | "request", 364 | "response", 365 | "stream", 366 | "uri", 367 | "url" 368 | ], 369 | "support": { 370 | "issues": "https://github.com/guzzle/psr7/issues", 371 | "source": "https://github.com/guzzle/psr7/tree/2.6.1" 372 | }, 373 | "funding": [ 374 | { 375 | "url": "https://github.com/GrahamCampbell", 376 | "type": "github" 377 | }, 378 | { 379 | "url": "https://github.com/Nyholm", 380 | "type": "github" 381 | }, 382 | { 383 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", 384 | "type": "tidelift" 385 | } 386 | ], 387 | "time": "2023-08-27T10:13:57+00:00" 388 | }, 389 | { 390 | "name": "minishlink/web-push", 391 | "version": "v8.0.0", 392 | "source": { 393 | "type": "git", 394 | "url": "https://github.com/web-push-libs/web-push-php.git", 395 | "reference": "ec034f1e287cd1e74235e349bd017d71a61e9d8d" 396 | }, 397 | "dist": { 398 | "type": "zip", 399 | "url": "https://api.github.com/repos/web-push-libs/web-push-php/zipball/ec034f1e287cd1e74235e349bd017d71a61e9d8d", 400 | "reference": "ec034f1e287cd1e74235e349bd017d71a61e9d8d", 401 | "shasum": "" 402 | }, 403 | "require": { 404 | "ext-curl": "*", 405 | "ext-json": "*", 406 | "ext-mbstring": "*", 407 | "ext-openssl": "*", 408 | "guzzlehttp/guzzle": "^7.0.1|^6.2", 409 | "php": ">=8.0", 410 | "spomky-labs/base64url": "^2.0", 411 | "web-token/jwt-key-mgmt": "^2.0|^3.0.2", 412 | "web-token/jwt-signature": "^2.0|^3.0.2", 413 | "web-token/jwt-signature-algorithm-ecdsa": "^2.0|^3.0.2", 414 | "web-token/jwt-util-ecc": "^2.0|^3.0.2" 415 | }, 416 | "require-dev": { 417 | "friendsofphp/php-cs-fixer": "^v3.13.2", 418 | "phpstan/phpstan": "^1.9.8", 419 | "phpunit/phpunit": "^9.5.27" 420 | }, 421 | "suggest": { 422 | "ext-gmp": "Optional for performance." 423 | }, 424 | "type": "library", 425 | "autoload": { 426 | "psr-4": { 427 | "Minishlink\\WebPush\\": "src" 428 | } 429 | }, 430 | "notification-url": "https://packagist.org/downloads/", 431 | "license": [ 432 | "MIT" 433 | ], 434 | "authors": [ 435 | { 436 | "name": "Louis Lagrange", 437 | "email": "lagrange.louis@gmail.com", 438 | "homepage": "https://github.com/Minishlink" 439 | } 440 | ], 441 | "description": "Web Push library for PHP", 442 | "homepage": "https://github.com/web-push-libs/web-push-php", 443 | "keywords": [ 444 | "Push API", 445 | "WebPush", 446 | "notifications", 447 | "push", 448 | "web" 449 | ], 450 | "support": { 451 | "issues": "https://github.com/web-push-libs/web-push-php/issues", 452 | "source": "https://github.com/web-push-libs/web-push-php/tree/v8.0.0" 453 | }, 454 | "time": "2023-01-10T17:14:44+00:00" 455 | }, 456 | { 457 | "name": "paragonie/constant_time_encoding", 458 | "version": "v2.6.3", 459 | "source": { 460 | "type": "git", 461 | "url": "https://github.com/paragonie/constant_time_encoding.git", 462 | "reference": "58c3f47f650c94ec05a151692652a868995d2938" 463 | }, 464 | "dist": { 465 | "type": "zip", 466 | "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", 467 | "reference": "58c3f47f650c94ec05a151692652a868995d2938", 468 | "shasum": "" 469 | }, 470 | "require": { 471 | "php": "^7|^8" 472 | }, 473 | "require-dev": { 474 | "phpunit/phpunit": "^6|^7|^8|^9", 475 | "vimeo/psalm": "^1|^2|^3|^4" 476 | }, 477 | "type": "library", 478 | "autoload": { 479 | "psr-4": { 480 | "ParagonIE\\ConstantTime\\": "src/" 481 | } 482 | }, 483 | "notification-url": "https://packagist.org/downloads/", 484 | "license": [ 485 | "MIT" 486 | ], 487 | "authors": [ 488 | { 489 | "name": "Paragon Initiative Enterprises", 490 | "email": "security@paragonie.com", 491 | "homepage": "https://paragonie.com", 492 | "role": "Maintainer" 493 | }, 494 | { 495 | "name": "Steve 'Sc00bz' Thomas", 496 | "email": "steve@tobtu.com", 497 | "homepage": "https://www.tobtu.com", 498 | "role": "Original Developer" 499 | } 500 | ], 501 | "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", 502 | "keywords": [ 503 | "base16", 504 | "base32", 505 | "base32_decode", 506 | "base32_encode", 507 | "base64", 508 | "base64_decode", 509 | "base64_encode", 510 | "bin2hex", 511 | "encoding", 512 | "hex", 513 | "hex2bin", 514 | "rfc4648" 515 | ], 516 | "support": { 517 | "email": "info@paragonie.com", 518 | "issues": "https://github.com/paragonie/constant_time_encoding/issues", 519 | "source": "https://github.com/paragonie/constant_time_encoding" 520 | }, 521 | "time": "2022-06-14T06:56:20+00:00" 522 | }, 523 | { 524 | "name": "psr/http-client", 525 | "version": "1.0.3", 526 | "source": { 527 | "type": "git", 528 | "url": "https://github.com/php-fig/http-client.git", 529 | "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" 530 | }, 531 | "dist": { 532 | "type": "zip", 533 | "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", 534 | "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", 535 | "shasum": "" 536 | }, 537 | "require": { 538 | "php": "^7.0 || ^8.0", 539 | "psr/http-message": "^1.0 || ^2.0" 540 | }, 541 | "type": "library", 542 | "extra": { 543 | "branch-alias": { 544 | "dev-master": "1.0.x-dev" 545 | } 546 | }, 547 | "autoload": { 548 | "psr-4": { 549 | "Psr\\Http\\Client\\": "src/" 550 | } 551 | }, 552 | "notification-url": "https://packagist.org/downloads/", 553 | "license": [ 554 | "MIT" 555 | ], 556 | "authors": [ 557 | { 558 | "name": "PHP-FIG", 559 | "homepage": "https://www.php-fig.org/" 560 | } 561 | ], 562 | "description": "Common interface for HTTP clients", 563 | "homepage": "https://github.com/php-fig/http-client", 564 | "keywords": [ 565 | "http", 566 | "http-client", 567 | "psr", 568 | "psr-18" 569 | ], 570 | "support": { 571 | "source": "https://github.com/php-fig/http-client" 572 | }, 573 | "time": "2023-09-23T14:17:50+00:00" 574 | }, 575 | { 576 | "name": "psr/http-factory", 577 | "version": "1.0.2", 578 | "source": { 579 | "type": "git", 580 | "url": "https://github.com/php-fig/http-factory.git", 581 | "reference": "e616d01114759c4c489f93b099585439f795fe35" 582 | }, 583 | "dist": { 584 | "type": "zip", 585 | "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", 586 | "reference": "e616d01114759c4c489f93b099585439f795fe35", 587 | "shasum": "" 588 | }, 589 | "require": { 590 | "php": ">=7.0.0", 591 | "psr/http-message": "^1.0 || ^2.0" 592 | }, 593 | "type": "library", 594 | "extra": { 595 | "branch-alias": { 596 | "dev-master": "1.0.x-dev" 597 | } 598 | }, 599 | "autoload": { 600 | "psr-4": { 601 | "Psr\\Http\\Message\\": "src/" 602 | } 603 | }, 604 | "notification-url": "https://packagist.org/downloads/", 605 | "license": [ 606 | "MIT" 607 | ], 608 | "authors": [ 609 | { 610 | "name": "PHP-FIG", 611 | "homepage": "https://www.php-fig.org/" 612 | } 613 | ], 614 | "description": "Common interfaces for PSR-7 HTTP message factories", 615 | "keywords": [ 616 | "factory", 617 | "http", 618 | "message", 619 | "psr", 620 | "psr-17", 621 | "psr-7", 622 | "request", 623 | "response" 624 | ], 625 | "support": { 626 | "source": "https://github.com/php-fig/http-factory/tree/1.0.2" 627 | }, 628 | "time": "2023-04-10T20:10:41+00:00" 629 | }, 630 | { 631 | "name": "psr/http-message", 632 | "version": "2.0", 633 | "source": { 634 | "type": "git", 635 | "url": "https://github.com/php-fig/http-message.git", 636 | "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" 637 | }, 638 | "dist": { 639 | "type": "zip", 640 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", 641 | "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", 642 | "shasum": "" 643 | }, 644 | "require": { 645 | "php": "^7.2 || ^8.0" 646 | }, 647 | "type": "library", 648 | "extra": { 649 | "branch-alias": { 650 | "dev-master": "2.0.x-dev" 651 | } 652 | }, 653 | "autoload": { 654 | "psr-4": { 655 | "Psr\\Http\\Message\\": "src/" 656 | } 657 | }, 658 | "notification-url": "https://packagist.org/downloads/", 659 | "license": [ 660 | "MIT" 661 | ], 662 | "authors": [ 663 | { 664 | "name": "PHP-FIG", 665 | "homepage": "https://www.php-fig.org/" 666 | } 667 | ], 668 | "description": "Common interface for HTTP messages", 669 | "homepage": "https://github.com/php-fig/http-message", 670 | "keywords": [ 671 | "http", 672 | "http-message", 673 | "psr", 674 | "psr-7", 675 | "request", 676 | "response" 677 | ], 678 | "support": { 679 | "source": "https://github.com/php-fig/http-message/tree/2.0" 680 | }, 681 | "time": "2023-04-04T09:54:51+00:00" 682 | }, 683 | { 684 | "name": "ralouphie/getallheaders", 685 | "version": "3.0.3", 686 | "source": { 687 | "type": "git", 688 | "url": "https://github.com/ralouphie/getallheaders.git", 689 | "reference": "120b605dfeb996808c31b6477290a714d356e822" 690 | }, 691 | "dist": { 692 | "type": "zip", 693 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", 694 | "reference": "120b605dfeb996808c31b6477290a714d356e822", 695 | "shasum": "" 696 | }, 697 | "require": { 698 | "php": ">=5.6" 699 | }, 700 | "require-dev": { 701 | "php-coveralls/php-coveralls": "^2.1", 702 | "phpunit/phpunit": "^5 || ^6.5" 703 | }, 704 | "type": "library", 705 | "autoload": { 706 | "files": [ 707 | "src/getallheaders.php" 708 | ] 709 | }, 710 | "notification-url": "https://packagist.org/downloads/", 711 | "license": [ 712 | "MIT" 713 | ], 714 | "authors": [ 715 | { 716 | "name": "Ralph Khattar", 717 | "email": "ralph.khattar@gmail.com" 718 | } 719 | ], 720 | "description": "A polyfill for getallheaders.", 721 | "support": { 722 | "issues": "https://github.com/ralouphie/getallheaders/issues", 723 | "source": "https://github.com/ralouphie/getallheaders/tree/develop" 724 | }, 725 | "time": "2019-03-08T08:55:37+00:00" 726 | }, 727 | { 728 | "name": "spomky-labs/base64url", 729 | "version": "v2.0.4", 730 | "source": { 731 | "type": "git", 732 | "url": "https://github.com/Spomky-Labs/base64url.git", 733 | "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d" 734 | }, 735 | "dist": { 736 | "type": "zip", 737 | "url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/7752ce931ec285da4ed1f4c5aa27e45e097be61d", 738 | "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d", 739 | "shasum": "" 740 | }, 741 | "require": { 742 | "php": ">=7.1" 743 | }, 744 | "require-dev": { 745 | "phpstan/extension-installer": "^1.0", 746 | "phpstan/phpstan": "^0.11|^0.12", 747 | "phpstan/phpstan-beberlei-assert": "^0.11|^0.12", 748 | "phpstan/phpstan-deprecation-rules": "^0.11|^0.12", 749 | "phpstan/phpstan-phpunit": "^0.11|^0.12", 750 | "phpstan/phpstan-strict-rules": "^0.11|^0.12" 751 | }, 752 | "type": "library", 753 | "autoload": { 754 | "psr-4": { 755 | "Base64Url\\": "src/" 756 | } 757 | }, 758 | "notification-url": "https://packagist.org/downloads/", 759 | "license": [ 760 | "MIT" 761 | ], 762 | "authors": [ 763 | { 764 | "name": "Florent Morselli", 765 | "homepage": "https://github.com/Spomky-Labs/base64url/contributors" 766 | } 767 | ], 768 | "description": "Base 64 URL Safe Encoding/Decoding PHP Library", 769 | "homepage": "https://github.com/Spomky-Labs/base64url", 770 | "keywords": [ 771 | "base64", 772 | "rfc4648", 773 | "safe", 774 | "url" 775 | ], 776 | "support": { 777 | "issues": "https://github.com/Spomky-Labs/base64url/issues", 778 | "source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4" 779 | }, 780 | "funding": [ 781 | { 782 | "url": "https://github.com/Spomky", 783 | "type": "github" 784 | }, 785 | { 786 | "url": "https://www.patreon.com/FlorentMorselli", 787 | "type": "patreon" 788 | } 789 | ], 790 | "time": "2020-11-03T09:10:25+00:00" 791 | }, 792 | { 793 | "name": "spomky-labs/pki-framework", 794 | "version": "1.1.0", 795 | "source": { 796 | "type": "git", 797 | "url": "https://github.com/Spomky-Labs/pki-framework.git", 798 | "reference": "d3ba688bf40e7c6e0dabf065ee18fc210734e760" 799 | }, 800 | "dist": { 801 | "type": "zip", 802 | "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/d3ba688bf40e7c6e0dabf065ee18fc210734e760", 803 | "reference": "d3ba688bf40e7c6e0dabf065ee18fc210734e760", 804 | "shasum": "" 805 | }, 806 | "require": { 807 | "brick/math": "^0.10 || ^0.11", 808 | "ext-mbstring": "*", 809 | "php": ">=8.1" 810 | }, 811 | "require-dev": { 812 | "ekino/phpstan-banned-code": "^1.0", 813 | "ext-gmp": "*", 814 | "ext-openssl": "*", 815 | "infection/infection": "^0.26", 816 | "php-parallel-lint/php-parallel-lint": "^1.3", 817 | "phpstan/phpstan": "^1.8", 818 | "phpstan/phpstan-beberlei-assert": "^1.0", 819 | "phpstan/phpstan-deprecation-rules": "^1.0", 820 | "phpstan/phpstan-phpunit": "^1.1", 821 | "phpstan/phpstan-strict-rules": "^1.3", 822 | "phpunit/phpunit": "^10.0", 823 | "rector/rector": "^0.15", 824 | "roave/security-advisories": "dev-latest", 825 | "symfony/phpunit-bridge": "^6.1", 826 | "symfony/var-dumper": "^6.1", 827 | "symplify/easy-coding-standard": "^11.1", 828 | "thecodingmachine/phpstan-safe-rule": "^1.2" 829 | }, 830 | "suggest": { 831 | "ext-bcmath": "For better performance (or GMP)", 832 | "ext-gmp": "For better performance (or BCMath)", 833 | "ext-openssl": "For OpenSSL based cyphering" 834 | }, 835 | "type": "library", 836 | "autoload": { 837 | "psr-4": { 838 | "SpomkyLabs\\Pki\\": "src/" 839 | } 840 | }, 841 | "notification-url": "https://packagist.org/downloads/", 842 | "license": [ 843 | "MIT" 844 | ], 845 | "authors": [ 846 | { 847 | "name": "Joni Eskelinen", 848 | "email": "jonieske@gmail.com", 849 | "role": "Original developer" 850 | }, 851 | { 852 | "name": "Florent Morselli", 853 | "email": "florent.morselli@spomky-labs.com", 854 | "role": "Spomky-Labs PKI Framework developer" 855 | } 856 | ], 857 | "description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.", 858 | "homepage": "https://github.com/spomky-labs/pki-framework", 859 | "keywords": [ 860 | "DER", 861 | "Private Key", 862 | "ac", 863 | "algorithm identifier", 864 | "asn.1", 865 | "asn1", 866 | "attribute certificate", 867 | "certificate", 868 | "certification request", 869 | "cryptography", 870 | "csr", 871 | "decrypt", 872 | "ec", 873 | "encrypt", 874 | "pem", 875 | "pkcs", 876 | "public key", 877 | "rsa", 878 | "sign", 879 | "signature", 880 | "verify", 881 | "x.509", 882 | "x.690", 883 | "x509", 884 | "x690" 885 | ], 886 | "support": { 887 | "issues": "https://github.com/Spomky-Labs/pki-framework/issues", 888 | "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.1.0" 889 | }, 890 | "funding": [ 891 | { 892 | "url": "https://github.com/Spomky", 893 | "type": "github" 894 | }, 895 | { 896 | "url": "https://www.patreon.com/FlorentMorselli", 897 | "type": "patreon" 898 | } 899 | ], 900 | "time": "2023-02-13T17:21:24+00:00" 901 | }, 902 | { 903 | "name": "symfony/deprecation-contracts", 904 | "version": "v3.3.0", 905 | "source": { 906 | "type": "git", 907 | "url": "https://github.com/symfony/deprecation-contracts.git", 908 | "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" 909 | }, 910 | "dist": { 911 | "type": "zip", 912 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", 913 | "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", 914 | "shasum": "" 915 | }, 916 | "require": { 917 | "php": ">=8.1" 918 | }, 919 | "type": "library", 920 | "extra": { 921 | "branch-alias": { 922 | "dev-main": "3.4-dev" 923 | }, 924 | "thanks": { 925 | "name": "symfony/contracts", 926 | "url": "https://github.com/symfony/contracts" 927 | } 928 | }, 929 | "autoload": { 930 | "files": [ 931 | "function.php" 932 | ] 933 | }, 934 | "notification-url": "https://packagist.org/downloads/", 935 | "license": [ 936 | "MIT" 937 | ], 938 | "authors": [ 939 | { 940 | "name": "Nicolas Grekas", 941 | "email": "p@tchwork.com" 942 | }, 943 | { 944 | "name": "Symfony Community", 945 | "homepage": "https://symfony.com/contributors" 946 | } 947 | ], 948 | "description": "A generic function and convention to trigger deprecation notices", 949 | "homepage": "https://symfony.com", 950 | "support": { 951 | "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" 952 | }, 953 | "funding": [ 954 | { 955 | "url": "https://symfony.com/sponsor", 956 | "type": "custom" 957 | }, 958 | { 959 | "url": "https://github.com/fabpot", 960 | "type": "github" 961 | }, 962 | { 963 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 964 | "type": "tidelift" 965 | } 966 | ], 967 | "time": "2023-05-23T14:45:45+00:00" 968 | }, 969 | { 970 | "name": "web-token/jwt-core", 971 | "version": "3.2.8", 972 | "source": { 973 | "type": "git", 974 | "url": "https://github.com/web-token/jwt-core.git", 975 | "reference": "2bc6e99a60910d0f495682acd8b23d3eef9865a3" 976 | }, 977 | "dist": { 978 | "type": "zip", 979 | "url": "https://api.github.com/repos/web-token/jwt-core/zipball/2bc6e99a60910d0f495682acd8b23d3eef9865a3", 980 | "reference": "2bc6e99a60910d0f495682acd8b23d3eef9865a3", 981 | "shasum": "" 982 | }, 983 | "require": { 984 | "brick/math": "^0.9|^0.10|^0.11", 985 | "ext-json": "*", 986 | "ext-mbstring": "*", 987 | "paragonie/constant_time_encoding": "^2.4", 988 | "php": ">=8.1", 989 | "spomky-labs/pki-framework": "^1.0" 990 | }, 991 | "conflict": { 992 | "spomky-labs/jose": "*" 993 | }, 994 | "type": "library", 995 | "autoload": { 996 | "psr-4": { 997 | "Jose\\Component\\Core\\": "" 998 | } 999 | }, 1000 | "notification-url": "https://packagist.org/downloads/", 1001 | "license": [ 1002 | "MIT" 1003 | ], 1004 | "authors": [ 1005 | { 1006 | "name": "Florent Morselli", 1007 | "homepage": "https://github.com/Spomky" 1008 | }, 1009 | { 1010 | "name": "All contributors", 1011 | "homepage": "https://github.com/web-token/jwt-framework/contributors" 1012 | } 1013 | ], 1014 | "description": "Core component of the JWT Framework.", 1015 | "homepage": "https://github.com/web-token", 1016 | "keywords": [ 1017 | "JOSE", 1018 | "JWE", 1019 | "JWK", 1020 | "JWKSet", 1021 | "JWS", 1022 | "Jot", 1023 | "RFC7515", 1024 | "RFC7516", 1025 | "RFC7517", 1026 | "RFC7518", 1027 | "RFC7519", 1028 | "RFC7520", 1029 | "bundle", 1030 | "jwa", 1031 | "jwt", 1032 | "symfony" 1033 | ], 1034 | "support": { 1035 | "source": "https://github.com/web-token/jwt-core/tree/3.2.8" 1036 | }, 1037 | "funding": [ 1038 | { 1039 | "url": "https://www.patreon.com/FlorentMorselli", 1040 | "type": "patreon" 1041 | } 1042 | ], 1043 | "time": "2023-08-23T09:49:09+00:00" 1044 | }, 1045 | { 1046 | "name": "web-token/jwt-key-mgmt", 1047 | "version": "3.2.8", 1048 | "source": { 1049 | "type": "git", 1050 | "url": "https://github.com/web-token/jwt-key-mgmt.git", 1051 | "reference": "3b51eeeff38ac58ee86ec83d073b88b8294b1c7e" 1052 | }, 1053 | "dist": { 1054 | "type": "zip", 1055 | "url": "https://api.github.com/repos/web-token/jwt-key-mgmt/zipball/3b51eeeff38ac58ee86ec83d073b88b8294b1c7e", 1056 | "reference": "3b51eeeff38ac58ee86ec83d073b88b8294b1c7e", 1057 | "shasum": "" 1058 | }, 1059 | "require": { 1060 | "ext-openssl": "*", 1061 | "php": ">=8.1", 1062 | "psr/http-client": "^1.0", 1063 | "psr/http-factory": "^1.0", 1064 | "web-token/jwt-core": "^3.2" 1065 | }, 1066 | "suggest": { 1067 | "ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", 1068 | "php-http/httplug": "To enable JKU/X5U support.", 1069 | "php-http/message-factory": "To enable JKU/X5U support.", 1070 | "web-token/jwt-util-ecc": "To use EC key analyzers." 1071 | }, 1072 | "type": "library", 1073 | "autoload": { 1074 | "psr-4": { 1075 | "Jose\\Component\\KeyManagement\\": "" 1076 | } 1077 | }, 1078 | "notification-url": "https://packagist.org/downloads/", 1079 | "license": [ 1080 | "MIT" 1081 | ], 1082 | "authors": [ 1083 | { 1084 | "name": "Florent Morselli", 1085 | "homepage": "https://github.com/Spomky" 1086 | }, 1087 | { 1088 | "name": "All contributors", 1089 | "homepage": "https://github.com/web-token/jwt-key-mgmt/contributors" 1090 | } 1091 | ], 1092 | "description": "Key Management component of the JWT Framework.", 1093 | "homepage": "https://github.com/web-token", 1094 | "keywords": [ 1095 | "JOSE", 1096 | "JWE", 1097 | "JWK", 1098 | "JWKSet", 1099 | "JWS", 1100 | "Jot", 1101 | "RFC7515", 1102 | "RFC7516", 1103 | "RFC7517", 1104 | "RFC7518", 1105 | "RFC7519", 1106 | "RFC7520", 1107 | "bundle", 1108 | "jwa", 1109 | "jwt", 1110 | "symfony" 1111 | ], 1112 | "support": { 1113 | "source": "https://github.com/web-token/jwt-key-mgmt/tree/3.2.8" 1114 | }, 1115 | "funding": [ 1116 | { 1117 | "url": "https://www.patreon.com/FlorentMorselli", 1118 | "type": "patreon" 1119 | } 1120 | ], 1121 | "time": "2023-05-18T16:20:51+00:00" 1122 | }, 1123 | { 1124 | "name": "web-token/jwt-signature", 1125 | "version": "3.2.8", 1126 | "source": { 1127 | "type": "git", 1128 | "url": "https://github.com/web-token/jwt-signature.git", 1129 | "reference": "156e0b0ef534e53eecf23a32a92ee6d8cb4fdac4" 1130 | }, 1131 | "dist": { 1132 | "type": "zip", 1133 | "url": "https://api.github.com/repos/web-token/jwt-signature/zipball/156e0b0ef534e53eecf23a32a92ee6d8cb4fdac4", 1134 | "reference": "156e0b0ef534e53eecf23a32a92ee6d8cb4fdac4", 1135 | "shasum": "" 1136 | }, 1137 | "require": { 1138 | "php": ">=8.1", 1139 | "web-token/jwt-core": "^3.2" 1140 | }, 1141 | "suggest": { 1142 | "web-token/jwt-signature-algorithm-ecdsa": "ECDSA Based Signature Algorithms", 1143 | "web-token/jwt-signature-algorithm-eddsa": "EdDSA Based Signature Algorithms", 1144 | "web-token/jwt-signature-algorithm-experimental": "Experimental Signature Algorithms", 1145 | "web-token/jwt-signature-algorithm-hmac": "HMAC Based Signature Algorithms", 1146 | "web-token/jwt-signature-algorithm-none": "None Signature Algorithm", 1147 | "web-token/jwt-signature-algorithm-rsa": "RSA Based Signature Algorithms" 1148 | }, 1149 | "type": "library", 1150 | "autoload": { 1151 | "psr-4": { 1152 | "Jose\\Component\\Signature\\": "" 1153 | } 1154 | }, 1155 | "notification-url": "https://packagist.org/downloads/", 1156 | "license": [ 1157 | "MIT" 1158 | ], 1159 | "authors": [ 1160 | { 1161 | "name": "Florent Morselli", 1162 | "homepage": "https://github.com/Spomky" 1163 | }, 1164 | { 1165 | "name": "All contributors", 1166 | "homepage": "https://github.com/web-token/jwt-signature/contributors" 1167 | } 1168 | ], 1169 | "description": "Signature component of the JWT Framework.", 1170 | "homepage": "https://github.com/web-token", 1171 | "keywords": [ 1172 | "JOSE", 1173 | "JWE", 1174 | "JWK", 1175 | "JWKSet", 1176 | "JWS", 1177 | "Jot", 1178 | "RFC7515", 1179 | "RFC7516", 1180 | "RFC7517", 1181 | "RFC7518", 1182 | "RFC7519", 1183 | "RFC7520", 1184 | "bundle", 1185 | "jwa", 1186 | "jwt", 1187 | "symfony" 1188 | ], 1189 | "support": { 1190 | "source": "https://github.com/web-token/jwt-signature/tree/3.2.8" 1191 | }, 1192 | "funding": [ 1193 | { 1194 | "url": "https://www.patreon.com/FlorentMorselli", 1195 | "type": "patreon" 1196 | } 1197 | ], 1198 | "time": "2023-05-18T16:20:51+00:00" 1199 | }, 1200 | { 1201 | "name": "web-token/jwt-signature-algorithm-ecdsa", 1202 | "version": "3.2.8", 1203 | "source": { 1204 | "type": "git", 1205 | "url": "https://github.com/web-token/jwt-signature-algorithm-ecdsa.git", 1206 | "reference": "34b119d6b5eca53914ad3b96660e5bd7fb5538b9" 1207 | }, 1208 | "dist": { 1209 | "type": "zip", 1210 | "url": "https://api.github.com/repos/web-token/jwt-signature-algorithm-ecdsa/zipball/34b119d6b5eca53914ad3b96660e5bd7fb5538b9", 1211 | "reference": "34b119d6b5eca53914ad3b96660e5bd7fb5538b9", 1212 | "shasum": "" 1213 | }, 1214 | "require": { 1215 | "ext-openssl": "*", 1216 | "php": ">=8.1", 1217 | "web-token/jwt-signature": "^3.2" 1218 | }, 1219 | "type": "library", 1220 | "autoload": { 1221 | "psr-4": { 1222 | "Jose\\Component\\Signature\\Algorithm\\": "" 1223 | } 1224 | }, 1225 | "notification-url": "https://packagist.org/downloads/", 1226 | "license": [ 1227 | "MIT" 1228 | ], 1229 | "authors": [ 1230 | { 1231 | "name": "Florent Morselli", 1232 | "homepage": "https://github.com/Spomky" 1233 | }, 1234 | { 1235 | "name": "All contributors", 1236 | "homepage": "https://github.com/web-token/jwt-framework/contributors" 1237 | } 1238 | ], 1239 | "description": "ECDSA Based Signature Algorithms the JWT Framework.", 1240 | "homepage": "https://github.com/web-token", 1241 | "keywords": [ 1242 | "JOSE", 1243 | "JWE", 1244 | "JWK", 1245 | "JWKSet", 1246 | "JWS", 1247 | "Jot", 1248 | "RFC7515", 1249 | "RFC7516", 1250 | "RFC7517", 1251 | "RFC7518", 1252 | "RFC7519", 1253 | "RFC7520", 1254 | "bundle", 1255 | "jwa", 1256 | "jwt", 1257 | "symfony" 1258 | ], 1259 | "support": { 1260 | "source": "https://github.com/web-token/jwt-signature-algorithm-ecdsa/tree/3.2.8" 1261 | }, 1262 | "funding": [ 1263 | { 1264 | "url": "https://www.patreon.com/FlorentMorselli", 1265 | "type": "patreon" 1266 | } 1267 | ], 1268 | "time": "2023-05-18T16:20:51+00:00" 1269 | }, 1270 | { 1271 | "name": "web-token/jwt-util-ecc", 1272 | "version": "3.2.8", 1273 | "source": { 1274 | "type": "git", 1275 | "url": "https://github.com/web-token/jwt-util-ecc.git", 1276 | "reference": "b2337052dbee724d710c1fdb0d3609835a5f8609" 1277 | }, 1278 | "dist": { 1279 | "type": "zip", 1280 | "url": "https://api.github.com/repos/web-token/jwt-util-ecc/zipball/b2337052dbee724d710c1fdb0d3609835a5f8609", 1281 | "reference": "b2337052dbee724d710c1fdb0d3609835a5f8609", 1282 | "shasum": "" 1283 | }, 1284 | "require": { 1285 | "brick/math": "^0.9|^0.10|^0.11", 1286 | "php": ">=8.1" 1287 | }, 1288 | "suggest": { 1289 | "ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance", 1290 | "ext-gmp": "GMP or BCMath is highly recommended to improve the library performance" 1291 | }, 1292 | "type": "library", 1293 | "autoload": { 1294 | "psr-4": { 1295 | "Jose\\Component\\Core\\Util\\Ecc\\": "" 1296 | } 1297 | }, 1298 | "notification-url": "https://packagist.org/downloads/", 1299 | "license": [ 1300 | "MIT" 1301 | ], 1302 | "authors": [ 1303 | { 1304 | "name": "Florent Morselli", 1305 | "homepage": "https://github.com/Spomky" 1306 | }, 1307 | { 1308 | "name": "All contributors", 1309 | "homepage": "https://github.com/web-token/jwt-framework/contributors" 1310 | } 1311 | ], 1312 | "description": "ECC Tools for the JWT Framework.", 1313 | "homepage": "https://github.com/web-token", 1314 | "keywords": [ 1315 | "JOSE", 1316 | "JWE", 1317 | "JWK", 1318 | "JWKSet", 1319 | "JWS", 1320 | "Jot", 1321 | "RFC7515", 1322 | "RFC7516", 1323 | "RFC7517", 1324 | "RFC7518", 1325 | "RFC7519", 1326 | "RFC7520", 1327 | "bundle", 1328 | "jwa", 1329 | "jwt", 1330 | "symfony" 1331 | ], 1332 | "support": { 1333 | "source": "https://github.com/web-token/jwt-util-ecc/tree/3.2.8" 1334 | }, 1335 | "funding": [ 1336 | { 1337 | "url": "https://www.patreon.com/FlorentMorselli", 1338 | "type": "patreon" 1339 | } 1340 | ], 1341 | "time": "2023-02-02T13:35:41+00:00" 1342 | } 1343 | ], 1344 | "packages-dev": [], 1345 | "aliases": [], 1346 | "minimum-stability": "stable", 1347 | "stability-flags": [], 1348 | "prefer-stable": false, 1349 | "prefer-lowest": false, 1350 | "platform": [], 1351 | "platform-dev": [], 1352 | "plugin-api-version": "2.6.0" 1353 | } 1354 | -------------------------------------------------------------------------------- /keys/private_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BggqhkjOPQMBBw== 3 | -----END EC PARAMETERS----- 4 | -----BEGIN EC PRIVATE KEY----- 5 | MHcCAQEEIL6ZX5CE77tHMB6syvSo/g2ErWwh//QIRsfS6jMmniOsoAoGCCqGSM49 6 | AwEHoUQDQgAEwGWvpjOeFgxfc2BxYgNHFlezSyHu0K/v9gKxZzDDRkK/1YaD61EJ 7 | d8wQdOIE8KSxKp697W3CVuZUBkW/sZK6PQ== 8 | -----END EC PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /keys/private_key.txt: -------------------------------------------------------------------------------- 1 | vplfkITvu0cwHqzK9Kj-DYStbCH_9AhGx9LqMyaeI6w 2 | -------------------------------------------------------------------------------- /keys/public_key.txt: -------------------------------------------------------------------------------- 1 | BMBlr6YznhYMX3NgcWIDRxZXs0sh7tCv7_YCsWcww0ZCv9WGg-tRCXfMEHTiBPCksSqeve1twlbmVAZFv7GSuj0 2 | -------------------------------------------------------------------------------- /router.php: -------------------------------------------------------------------------------- 1 | { 2 | const applicationServerKey = 3 | 'BMBlr6YznhYMX3NgcWIDRxZXs0sh7tCv7_YCsWcww0ZCv9WGg-tRCXfMEHTiBPCksSqeve1twlbmVAZFv7GSuj0'; 4 | let isPushEnabled = false; 5 | 6 | const pushButton = document.querySelector('#push-subscription-button'); 7 | if (!pushButton) { 8 | return; 9 | } 10 | 11 | pushButton.addEventListener('click', function() { 12 | if (isPushEnabled) { 13 | push_unsubscribe(); 14 | } else { 15 | push_subscribe(); 16 | } 17 | }); 18 | 19 | if (!('serviceWorker' in navigator)) { 20 | console.warn('Service workers are not supported by this browser'); 21 | changePushButtonState('incompatible'); 22 | return; 23 | } 24 | 25 | if (!('PushManager' in window)) { 26 | console.warn('Push notifications are not supported by this browser'); 27 | changePushButtonState('incompatible'); 28 | return; 29 | } 30 | 31 | if (!('showNotification' in ServiceWorkerRegistration.prototype)) { 32 | console.warn('Notifications are not supported by this browser'); 33 | changePushButtonState('incompatible'); 34 | return; 35 | } 36 | 37 | // Check the current Notification permission. 38 | // If its denied, the button should appears as such, until the user changes the permission manually 39 | if (Notification.permission === 'denied') { 40 | console.warn('Notifications are denied by the user'); 41 | changePushButtonState('incompatible'); 42 | return; 43 | } 44 | 45 | navigator.serviceWorker.register('serviceWorker.js').then( 46 | () => { 47 | console.log('[SW] Service worker has been registered'); 48 | push_updateSubscription(); 49 | }, 50 | e => { 51 | console.error('[SW] Service worker registration failed', e); 52 | changePushButtonState('incompatible'); 53 | } 54 | ); 55 | 56 | function changePushButtonState(state) { 57 | switch (state) { 58 | case 'enabled': 59 | pushButton.disabled = false; 60 | pushButton.textContent = 'Disable Push notifications'; 61 | isPushEnabled = true; 62 | break; 63 | case 'disabled': 64 | pushButton.disabled = false; 65 | pushButton.textContent = 'Enable Push notifications'; 66 | isPushEnabled = false; 67 | break; 68 | case 'computing': 69 | pushButton.disabled = true; 70 | pushButton.textContent = 'Loading...'; 71 | break; 72 | case 'incompatible': 73 | pushButton.disabled = true; 74 | pushButton.textContent = 'Push notifications are not compatible with this browser'; 75 | break; 76 | default: 77 | console.error('Unhandled push button state', state); 78 | break; 79 | } 80 | } 81 | 82 | function urlBase64ToUint8Array(base64String) { 83 | const padding = '='.repeat((4 - (base64String.length % 4)) % 4); 84 | const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/'); 85 | 86 | const rawData = window.atob(base64); 87 | const outputArray = new Uint8Array(rawData.length); 88 | 89 | for (let i = 0; i < rawData.length; ++i) { 90 | outputArray[i] = rawData.charCodeAt(i); 91 | } 92 | return outputArray; 93 | } 94 | 95 | function checkNotificationPermission() { 96 | return new Promise((resolve, reject) => { 97 | if (Notification.permission === 'denied') { 98 | return reject(new Error('Push messages are blocked.')); 99 | } 100 | 101 | if (Notification.permission === 'granted') { 102 | return resolve(); 103 | } 104 | 105 | if (Notification.permission === 'default') { 106 | return Notification.requestPermission().then(result => { 107 | if (result !== 'granted') { 108 | reject(new Error('Bad permission result')); 109 | } else { 110 | resolve(); 111 | } 112 | }); 113 | } 114 | 115 | return reject(new Error('Unknown permission')); 116 | }); 117 | } 118 | 119 | function push_subscribe() { 120 | changePushButtonState('computing'); 121 | 122 | return checkNotificationPermission() 123 | .then(() => navigator.serviceWorker.ready) 124 | .then(serviceWorkerRegistration => 125 | serviceWorkerRegistration.pushManager.subscribe({ 126 | userVisibleOnly: true, 127 | applicationServerKey: urlBase64ToUint8Array(applicationServerKey), 128 | }) 129 | ) 130 | .then(subscription => { 131 | // Subscription was successful 132 | // create subscription on your server 133 | return push_sendSubscriptionToServer(subscription, 'POST'); 134 | }) 135 | .then(subscription => subscription && changePushButtonState('enabled')) // update your UI 136 | .catch(e => { 137 | if (Notification.permission === 'denied') { 138 | // The user denied the notification permission which 139 | // means we failed to subscribe and the user will need 140 | // to manually change the notification permission to 141 | // subscribe to push messages 142 | console.warn('Notifications are denied by the user.'); 143 | changePushButtonState('incompatible'); 144 | } else { 145 | // A problem occurred with the subscription; common reasons 146 | // include network errors or the user skipped the permission 147 | console.error('Impossible to subscribe to push notifications', e); 148 | changePushButtonState('disabled'); 149 | } 150 | }); 151 | } 152 | 153 | function push_updateSubscription() { 154 | navigator.serviceWorker.ready 155 | .then(serviceWorkerRegistration => serviceWorkerRegistration.pushManager.getSubscription()) 156 | .then(subscription => { 157 | changePushButtonState('disabled'); 158 | 159 | if (!subscription) { 160 | // We aren't subscribed to push, so set UI to allow the user to enable push 161 | return; 162 | } 163 | 164 | // Keep your server in sync with the latest endpoint 165 | return push_sendSubscriptionToServer(subscription, 'PUT'); 166 | }) 167 | .then(subscription => subscription && changePushButtonState('enabled')) // Set your UI to show they have subscribed for push messages 168 | .catch(e => { 169 | console.error('Error when updating the subscription', e); 170 | }); 171 | } 172 | 173 | function push_unsubscribe() { 174 | changePushButtonState('computing'); 175 | 176 | // To unsubscribe from push messaging, you need to get the subscription object 177 | navigator.serviceWorker.ready 178 | .then(serviceWorkerRegistration => serviceWorkerRegistration.pushManager.getSubscription()) 179 | .then(subscription => { 180 | // Check that we have a subscription to unsubscribe 181 | if (!subscription) { 182 | // No subscription object, so set the state 183 | // to allow the user to subscribe to push 184 | changePushButtonState('disabled'); 185 | return; 186 | } 187 | 188 | // We have a subscription, unsubscribe 189 | // Remove push subscription from server 190 | return push_sendSubscriptionToServer(subscription, 'DELETE'); 191 | }) 192 | .then(subscription => subscription.unsubscribe()) 193 | .then(() => changePushButtonState('disabled')) 194 | .catch(e => { 195 | // We failed to unsubscribe, this can lead to 196 | // an unusual state, so it may be best to remove 197 | // the users data from your data store and 198 | // inform the user that you have done so 199 | console.error('Error when unsubscribing the user', e); 200 | changePushButtonState('disabled'); 201 | }); 202 | } 203 | 204 | function push_sendSubscriptionToServer(subscription, method) { 205 | const key = subscription.getKey('p256dh'); 206 | const token = subscription.getKey('auth'); 207 | const contentEncoding = (PushManager.supportedContentEncodings || ['aesgcm'])[0]; 208 | 209 | return fetch('push_subscription.php', { 210 | method, 211 | body: JSON.stringify({ 212 | endpoint: subscription.endpoint, 213 | publicKey: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : null, 214 | authToken: token ? btoa(String.fromCharCode.apply(null, new Uint8Array(token))) : null, 215 | contentEncoding, 216 | }), 217 | }).then(() => subscription); 218 | } 219 | 220 | /** 221 | * START send_push_notification 222 | * this part handles the button that calls the endpoint that triggers a notification 223 | * in the real world, you wouldn't need this, because notifications are typically sent from backend logic 224 | */ 225 | 226 | const sendPushButton = document.querySelector('#send-push-button'); 227 | if (!sendPushButton) { 228 | return; 229 | } 230 | 231 | sendPushButton.addEventListener('click', () => 232 | navigator.serviceWorker.ready 233 | .then(serviceWorkerRegistration => serviceWorkerRegistration.pushManager.getSubscription()) 234 | .then(subscription => { 235 | if (!subscription) { 236 | alert('Please enable push notifications'); 237 | return; 238 | } 239 | 240 | const contentEncoding = (PushManager.supportedContentEncodings || ['aesgcm'])[0]; 241 | const jsonSubscription = subscription.toJSON(); 242 | fetch('send_push_notification.php', { 243 | method: 'POST', 244 | body: JSON.stringify(Object.assign(jsonSubscription, { contentEncoding })), 245 | }); 246 | }) 247 | ); 248 | /** 249 | * END send_push_notification 250 | */ 251 | }); 252 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Web Push talk @PWA Paris #2

4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/push_subscription.php: -------------------------------------------------------------------------------- 1 | array( 13 | 'subject' => 'https://github.com/Minishlink/web-push-php-example/', 14 | 'publicKey' => file_get_contents(__DIR__ . '/../keys/public_key.txt'), // don't forget that your public key also lives in app.js 15 | 'privateKey' => file_get_contents(__DIR__ . '/../keys/private_key.txt'), // in the real world, this would be in a secret file 16 | ), 17 | ); 18 | 19 | $webPush = new WebPush($auth); 20 | 21 | $report = $webPush->sendOneNotification( 22 | $subscription, 23 | '{"message":"Hello! 👋"}', 24 | ); 25 | 26 | // handle eventual errors here, and remove the subscription from your server if it is expired 27 | $endpoint = $report->getRequest()->getUri()->__toString(); 28 | 29 | if ($report->isSuccess()) { 30 | echo "[v] Message sent successfully for subscription {$endpoint}."; 31 | } else { 32 | echo "[x] Message failed to sent for subscription {$endpoint}: {$report->getReason()}"; 33 | } 34 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | self.addEventListener('push', function (event) { 2 | if (!(self.Notification && self.Notification.permission === 'granted')) { 3 | return; 4 | } 5 | 6 | const sendNotification = body => { 7 | // you could refresh a notification badge here with postMessage API 8 | const title = "Web Push example"; 9 | 10 | return self.registration.showNotification(title, { 11 | body, 12 | }); 13 | }; 14 | 15 | if (event.data) { 16 | const payload = event.data.json(); 17 | event.waitUntil(sendNotification(payload.message)); 18 | } 19 | }); 20 | --------------------------------------------------------------------------------