8. Carpeta « srv »

Versión para imprimir.

A. srv / Bd.php

1<?php
2
3class Bd
4{
5
6 private static ?PDO $conexion = null;
7
8 static function getConexion(): PDO
9 {
10 if (self::$conexion === null) {
11
12 self::$conexion = new PDO(
13 // cadena de conexión
14 "sqlite:notificacionespush.db",
15 // usuario
16 null,
17 // contraseña
18 null,
19 // Opciones: conexiones persistentes y lanza excepciones.
20 [PDO::ATTR_PERSISTENT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
21 );
22
23 self::$conexion->exec(
24 'CREATE TABLE IF NOT EXISTS SUSCRIPCION (
25 SUS_ENDPOINT TEXT NOT NULL,
26 SUS_PUB_KEY TEXT NOT NULL,
27 SUS_AUT_TOK TEXT NOT NULL,
28 SUS_CONT_ENCOD TEXT NOT NULL,
29 CONSTRAINT SUS_PK
30 PRIMARY KEY(SUS_ENDPOINT),
31 CONSTRAINT SUS_ENDPNT_NV
32 CHECK(LENGTH(SUS_ENDPOINT) > 0)
33 )'
34 );
35 }
36
37 return self::$conexion;
38 }
39}
40

B. srv / genera-llaves.php

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width">
8
9 <title>Llaves VAPID</title>
10
11</head>
12
13<body>
14
15 <h1>Llaves VAPID</h1>
16
17 <pre>
18 <?php
19
20 require __DIR__ . "/../vendor/autoload.php";
21
22 use Minishlink\WebPush\VAPID;
23
24 var_dump(VAPID::createVapidKeys());
25
26 ?>
27 </pre>
28
29</body>
30
31</html>

C. srv / notifica.php

1<?php
2
3require_once __DIR__ . "/../vendor/autoload.php";
4require_once __DIR__ . "/../lib/php/fetchAll.php";
5require_once __DIR__ . "/../lib/php/ProblemDetails.php";
6require_once __DIR__ . "/../lib/php/devuelveJson.php";
7require_once __DIR__ . "/../lib/php/devuelveProblemDetails.php";
8require_once __DIR__ . "/../lib/php/devuelveErrorInterno.php";
9require_once __DIR__ . "/Bd.php";
10require_once __DIR__ . "/Suscripcion.php";
11require_once __DIR__ . "/suscripcionElimina.php";
12
13use Minishlink\WebPush\WebPush;
14
15const AUTH = [
16 "VAPID" => [
17 "subject" => "https://notificacionesphp.gilbertopachec2.repl.co/",
18 "publicKey" => "BMBlr6YznhYMX3NgcWIDRxZXs0sh7tCv7_YCsWcww0ZCv9WGg-tRCXfMEHTiBPCksSqeve1twlbmVAZFv7GSuj0",
19 "privateKey" => "vplfkITvu0cwHqzK9Kj-DYStbCH_9AhGx9LqMyaeI6w"
20 ]
21];
22
23try {
24
25 $webPush = new WebPush(AUTH);
26 $mensaje = "Hola! 👋";
27
28 // Envia el mensaje a todas las suscripciones.
29
30 $conexion = Bd::getConexion();
31
32 $suscripciones = fetchAll(
33 $conexion->query(
34 "SELECT
35 SUS_ENDPOINT as endpoint,
36 SUS_PUB_KEY as publicKey,
37 SUS_AUT_TOK as authToken,
38 SUS_CONT_ENCOD as contentEncoding
39 FROM SUSCRIPCION"
40 ),
41 [],
42 PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE,
43 Suscripcion::class
44 );
45
46 foreach ($suscripciones as $suscripcion) {
47 $webPush->queueNotification($suscripcion, $mensaje);
48 }
49 $reportes = $webPush->flush();
50
51 // Genera el reporte de envio a cada suscripcion.
52 $reporteDeEnvios = "";
53 foreach ($reportes as $reporte) {
54 $endpoint = htmlentities($reporte->getRequest()->getUri());
55 if ($reporte->isSuccess()) {
56 // Reporte de éxito.
57 $reporteDeEnvios .= "<dt>$endpoint</dt><dd>Éxito</dd>";
58 } else {
59 if ($reporte->isSubscriptionExpired()) {
60 suscripcionElimina($endpoint);
61 }
62 // Reporte de fallo.
63 $explicacion = htmlentities($reporte->getReason());
64 $reporteDeEnvios .= "<dt>$endpoint</dt><dd>Fallo: $explicacion</dd>";
65 }
66 }
67
68 devuelveJson(["reporte" => ["innerHTML" => $reporteDeEnvios]]);
69} catch (ProblemDetails $details) {
70
71 devuelveProblemDetails($details);
72} catch (Throwable $error) {
73
74 devuelveErrorInterno($error);
75}
76

D. srv / suscripcion-elimina.php

1<?php
2
3require_once __DIR__ . "/../lib/php/recuperaJson.php";
4require_once __DIR__ . "/../lib/php/devuelveNoContent.php";
5require_once __DIR__ . "/../lib/php/ProblemDetails.php";
6require_once __DIR__ . "/../lib/php/devuelveProblemDetails.php";
7require_once __DIR__ . "/../lib/php/devuelveErrorInterno.php";
8require_once __DIR__ . "/Bd.php";
9require_once __DIR__ . "/suscripcionRecupera.php";
10require_once __DIR__ . "/suscripcionElimina.php";
11
12try {
13
14 $objeto = recuperaJson();
15 $modelo = suscripcionRecupera($objeto);
16 suscripcionElimina($modelo->endpoint);
17 devuelveNoContent();
18} catch (ProblemDetails $details) {
19
20 devuelveProblemDetails($details);
21} catch (Throwable $error) {
22
23 devuelveErrorInterno($error);
24}
25

E. srv / suscripcion-modifica.php

1<?php
2
3require_once __DIR__ . "/../lib/php/recuperaJson.php";
4require_once __DIR__ . "/../lib/php/devuelveCreated.php";
5require_once __DIR__ . "/../lib/php/devuelveJson.php";
6require_once __DIR__ . "/../lib/php/ProblemDetails.php";
7require_once __DIR__ . "/../lib/php/devuelveProblemDetails.php";
8require_once __DIR__ . "/../lib/php/devuelveErrorInterno.php";
9require_once __DIR__ . "/Bd.php";
10require_once __DIR__ . "/Suscripcion.php";
11require_once __DIR__ . "/suscripcionRecupera.php";
12
13try {
14
15 $objeto = recuperaJson();
16 $modelo = suscripcionRecupera($objeto);
17
18 $conexion = Bd::getConexion();
19
20 $statement = $conexion->prepare(
21 "SELECT
22 SUS_ENDPOINT
23 FROM SUSCRIPCION
24 WHERE SUS_ENDPOINT = :endpoint"
25 );
26 $statement->execute([":endpoint" => $modelo->endpoint]);
27
28 if ($statement->fetchColumn() === false) {
29
30 $conexion->prepare(
31 "INSERT INTO SUSCRIPCION
32 (SUS_ENDPOINT, SUS_PUB_KEY, SUS_AUT_TOK, SUS_CONT_ENCOD)
33 VALUES
34 (:endpoint, :publicKey, :authToken, :contentEncoding)"
35 )
36 ->execute([
37 ":endpoint" => $modelo->endpoint,
38 ":publicKey" => $modelo->publicKey,
39 ":authToken" => $modelo->authToken,
40 ":contentEncoding" => $modelo->contentEncoding
41 ]);
42
43 devuelveCreated("", $modelo);
44 } else {
45
46 $conexion->prepare(
47 "UPDATE SUSCRIPCION
48 SET
49 SUS_PUB_KEY = :publicKey,
50 SUS_AUT_TOK = :authToken,
51 SUS_CONT_ENCOD = :contentEncoding
52 WHERE SUS_ENDPOINT = :endpoint"
53 )
54 ->execute([
55 ":endpoint" => $modelo->endpoint,
56 ":publicKey" => $modelo->publicKey,
57 ":authToken" => $modelo->authToken,
58 ":contentEncoding" => $modelo->contentEncoding
59 ]);
60
61 devuelveJson($modelo);
62 }
63} catch (ProblemDetails $details) {
64
65 devuelveProblemDetails($details);
66} catch (Throwable $error) {
67
68 devuelveErrorInterno($error);
69}
70

F. srv / Suscripcion.php

1<?php
2
3require_once __DIR__ . "/../vendor/autoload.php";
4
5use Minishlink\WebPush\SubscriptionInterface;
6
7class Suscripcion implements SubscriptionInterface
8{
9
10 public string $endpoint;
11 public string $publicKey;
12 public string $authToken;
13 public string $contentEncoding;
14
15 public function __construct(
16 string $endpoint = "",
17 string $publicKey = "",
18 string $authToken = "",
19 string $contentEncoding = ""
20 ) {
21 $this->endpoint = $endpoint;
22 $this->publicKey = $publicKey;
23 $this->authToken = $authToken;
24 $this->contentEncoding = $contentEncoding;
25 }
26
27 public function getEndpoint(): string
28 {
29 return $this->endpoint;
30 }
31
32 public function getPublicKey(): ?string
33 {
34 return $this->publicKey;
35 }
36
37 public function getAuthToken(): ?string
38 {
39 return $this->authToken;
40 }
41
42 public function getContentEncoding(): ?string
43 {
44 return $this->contentEncoding;
45 }
46}
47

G. srv / suscripcionConsulta.php

1<?php
2
3require_once __DIR__ . "/../lib/php/fetchAll.php";
4require_once __DIR__ . "/Bd.php";
5require_once __DIR__ . "/Suscripcion.php";
6
7function suscripcionConsulta()
8{
9 $conexion = Bd::getConexion();
10 return fetchAll(
11 $conexion->query(
12 "SELECT
13 SUS_ENDPOINT as endpoint,
14 SUS_PUB_KEY as publicKey,
15 SUS_AUT_TOK as authToken,
16 SUS_CONT_ENCOD as contentEncoding
17 FROM SUSCRIPCION"
18 ),
19 [],
20 PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE,
21 Suscripcion::class
22 );
23}
24

H. srv / suscripcionElimina.php

1<?php
2
3require_once __DIR__ . "/Bd.php";
4
5function suscripcionElimina(string $endpoint)
6{
7 $con = Bd::getConexion();
8 $stmt = $con->prepare(
9 "DELETE FROM SUSCRIPCION
10 WHERE SUS_ENDPOINT = :endpoint"
11 );
12 $stmt->execute([":endpoint" => $endpoint]);
13}
14

I. srv / suscripcionRecupera.php

1<?php
2
3require_once __DIR__ . "/../lib/php/BAD_REQUEST.php";
4require_once __DIR__ . "/../lib/php/validaJson.php";
5require_once __DIR__ . "/../lib/php/ProblemDetails.php";
6require_once __DIR__ . "/Suscripcion.php";
7
8function suscripcionRecupera($objeto)
9{
10
11 $objeto = validaJson($objeto);
12
13 if (!isset($objeto->publicKey) || !is_string($objeto->publicKey))
14 throw new ProblemDetails(
15 type: "/error/publickeyincorrecta.html",
16 status: BAD_REQUEST,
17 title: "La publicKey debe ser texto.",
18 );
19
20 if (!isset($objeto->authToken) || !is_string($objeto->authToken))
21 throw new ProblemDetails(
22 type: "/error/authtokenincorrecto.html",
23 status: BAD_REQUEST,
24 title: "El authToken debe ser texto.",
25 );
26
27 if (!isset($objeto->contentEncoding) || !is_string($objeto->contentEncoding))
28 throw new ProblemDetails(
29 type: "/error/contentencodingincorrecta.html",
30 status: BAD_REQUEST,
31 title: "La contentEncoding debe ser texto.",
32 );
33
34 return new Suscripcion(
35 endpoint: $objeto->endpoint,
36 publicKey: $objeto->publicKey,
37 authToken: $objeto->authToken,
38 contentEncoding: $objeto->contentEncoding
39 );
40}
41