A. Carpeta « js / lib »

Versión para imprimir.

1. js / lib / activaNotificacionesPush.js

1
/**
2
 * @param { string | URL } urlDeServiceWorkerQueRecibeNotificaciones
3
 */
4
export async function activaNotificacionesPush(
5
6
 urlDeServiceWorkerQueRecibeNotificaciones) {
7
8
 // Valida que el navegador soporte notificaciones push.
9
 if (!('PushManager' in window))
10
  throw new Error("Este navegador no soporta notificaciones push.")
11
12
 // Valida que el navegador soporte notificaciones,
13
 if (!("Notification" in window))
14
  throw new Error("Este navegador no soporta notificaciones push.")
15
16
 // Valida que el navegador soporte service workers,
17
 if (!("serviceWorker" in navigator))
18
  throw new Error("Este navegador no soporta service workers.")
19
20
 // Recupera el permiso para usar notificaciones
21
 let permiso = Notification.permission
22
 if (permiso === "default") {
23
  // Permiso no asignado. Pide al usuario su autorización.
24
  permiso = await Notification.requestPermission()
25
 }
26
27
 // Valida que el usuario haya permitido usar notificaciones..
28
 if (permiso === "denied")
29
  throw new Error("Notificaciones bloqueadas.")
30
31
 const registro = await navigator.serviceWorker.register(
32
  urlDeServiceWorkerQueRecibeNotificaciones)
33
 console.log(urlDeServiceWorkerQueRecibeNotificaciones, "registrado.")
34
 console.log(registro)
35
36
 if (!("showNotification" in registro))
37
  throw new Error("Este navegador no soporta notificaciones.")
38
}

2. js / lib / calculaDtoParaSuscripcion.js

1
/**
2
 * Devuelve una literal de objeto que se puede usar para enviar
3
 * en formato JSON al servidor.
4
 * DTO es un acrónimo para Data Transder Object, u
5
 * objeto para transferencia de datos.
6
 * @param { PushSubscription } suscripcion
7
 */
8
export function calculaDtoParaSuscripcion(suscripcion) {
9
 const key = suscripcion.getKey("p256dh")
10
 const token = suscripcion.getKey("auth")
11
 const supported = PushManager.supportedContentEncodings
12
 const encodings = Array.isArray(supported) && supported.length > 0
13
  ? supported
14
  : ["aesgcm"]
15
 const endpoint = suscripcion.endpoint
16
 const publicKey = key === null
17
  ? null
18
  : btoa(String.fromCharCode.apply(null, new Uint8Array(key)))
19
 const authToken = token === null
20
  ? null
21
  : btoa(String.fromCharCode.apply(null, new Uint8Array(token)))
22
 const contentEncoding = encodings[0]
23
 return {
24
  endpoint,
25
  publicKey,
26
  authToken,
27
  contentEncoding
28
 }
29
}
30

3. js / lib / cancelaSuscripcionPush.js

1
import { getSuscripcionPush } from "./getSuscripcionPush.js"
2
3
export async function cancelaSuscripcionPush() {
4
 const suscripcion = await getSuscripcionPush()
5
 const resultado = suscripcion === null
6
  ? false
7
  : await suscripcion.unsubscribe()
8
 return resultado === true ? suscripcion : null
9
}

4. js / lib / consume.js

1
import { ProblemDetailsError } from "./ProblemDetailsError.js"
2
3
/**
4
 * Espera a que la promesa de un fetch termine. Si
5
 * hay error, lanza una excepción.
6
 * 
7
 * @param {Promise<Response> } servicio
8
 */
9
export async function consume(servicio) {
10
 const respuesta = await servicio
11
 if (respuesta.ok) {
12
  return respuesta
13
 } else {
14
  const contentType = respuesta.headers.get("Content-Type")
15
  if (contentType === "application/problem+json; charset=utf-8")
16
   throw new ProblemDetailsError(await respuesta.json())
17
  else
18
   throw new Error(respuesta.statusText)
19
 }
20
}

5. js / lib / descargaVista.js

1
import { consume } from "./consume.js"
2
import { muestraObjeto } from "./muestraObjeto.js"
3
import { recibeJson } from "./recibeJson.js"
4
5
/**
6
 * @param {string} url
7
 * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS"
8
 *  | "CONNECT" | "HEAD" } metodoHttp
9
 */
10
export async function descargaVista(url, metodoHttp = "GET") {
11
 const respuesta = await consume(recibeJson(url, metodoHttp))
12
 const json = await respuesta.json()
13
 muestraObjeto(document, json)
14
 return json
15
}

6. js / lib / enviaJsonRecibeJson.js

1
2
/**
3
 * @param { string } url
4
 * @param { Object } body
5
 * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS"
6
 *  | "CONNECT" | "HEAD" } metodoHttp
7
 */
8
export async function enviaJsonRecibeJson(url, body, metodoHttp = "POST") {
9
 return fetch(
10
  url,
11
  {
12
   method: metodoHttp,
13
   headers: {
14
    "Content-Type": "application/json",
15
    "Accept": "application/json, application/problem+json"
16
   },
17
   body: JSON.stringify(body)
18
  }
19
 )
20
}

7. js / lib / getSuscripcionPush.js

1
export async function getSuscripcionPush() {
2
 // Recupera el service worker registrado.
3
 const registro = await navigator.serviceWorker.ready
4
 return registro.pushManager.getSubscription()
5
}

8. js / lib / manejaErrores.js

1
import { muestraError } from "./muestraError.js"
2
3
/**
4
 * Intercepta Response.prototype.json para capturar errores de parseo
5
 * y asegurar que se reporten correctamente en navegadores Chromium.
6
 */
7
{
8
 const originalJson = Response.prototype.json
9
10
 Response.prototype.json = function () {
11
  // Llamamos al método original usando el contexto (this) de la respuesta
12
  return originalJson.apply(this, arguments)
13
   .catch((/** @type {any} */ error) => {
14
    // Corrige un error de Chrome que evita el manejo correcto de errores.
15
    throw new Error(error)
16
   })
17
 }
18
}
19
20
window.onerror = function (
21
  /** @type {string} */ _message,
22
  /** @type {string} */ _url,
23
  /** @type {number} */ _line,
24
  /** @type {number} */ _column,
25
  /** @type {Error} */ errorObject
26
) {
27
 muestraError(errorObject)
28
 return true
29
}
30
31
window.addEventListener('unhandledrejection', event => {
32
 muestraError(event.reason)
33
 event.preventDefault()
34
})
35

9. js / lib / muestraError.js

1
import { ProblemDetailsError } from "./ProblemDetailsError.js"
2
3
/**
4
 * Muestra los datos de una Error en la consola y en un cuadro de alerta.
5
 * @param { ProblemDetailsError | Error | null } error descripción del error.
6
 */
7
export function muestraError(error) {
8
9
 if (error === null) {
10
11
  console.error("Error")
12
  alert("Error")
13
14
 } else if (error instanceof ProblemDetailsError) {
15
16
  const problemDetails = error.problemDetails
17
18
  let mensaje =
19
   typeof problemDetails["title"] === "string" ? problemDetails["title"] : ""
20
  if (typeof problemDetails["detail"] === "string") {
21
   if (mensaje !== "") {
22
    mensaje += "\n\n"
23
   }
24
   mensaje += problemDetails["detail"]
25
  }
26
  if (mensaje === "") {
27
   mensaje = "Error"
28
  }
29
  console.error(error, problemDetails)
30
  alert(mensaje)
31
32
 } else {
33
34
  console.error(error)
35
  alert(error.message)
36
37
 }
38
39
}

10. js / lib / muestraObjeto.js

1
/**
2
 * @param {Document | HTMLElement | ShadowRoot} raizHtml
3
 * @param { any } objeto
4
 */
5
export function muestraObjeto(raizHtml, objeto) {
6
 for (const [nombre, definiciones] of Object.entries(objeto)) {
7
  if (Array.isArray(definiciones)) {
8
   muestraArray(raizHtml, nombre, definiciones)
9
  } else if (definiciones !== undefined && definiciones !== null) {
10
   muestraElemento(raizHtml, nombre, definiciones)
11
  }
12
 }
13
}
14
15
/**
16
 * @param { string } nombre
17
 */
18
export function selectorDeNombre(nombre) {
19
 return `[id="${nombre}"],[name="${nombre}"],[data-name="${nombre}"]`
20
}
21
22
/**
23
 * @param { Document | HTMLElement | ShadowRoot } raizHtml
24
 * @param { string } propiedad
25
 * @param {any[]} valores
26
 */
27
function muestraArray(raizHtml, propiedad, valores) {
28
 const conjunto = new Set(valores)
29
 const elementos = raizHtml.querySelectorAll(selectorDeNombre(propiedad))
30
 if (elementos.length === 1 && elementos[0] instanceof HTMLSelectElement) {
31
  muestraOptions(elementos[0], conjunto)
32
 } else {
33
  muestraInputs(elementos, conjunto)
34
 }
35
36
}
37
38
/**
39
 * @param {HTMLSelectElement} select
40
 * @param {Set<any>} conjunto
41
 */
42
function muestraOptions(select, conjunto) {
43
 for (let i = 0, options = select.options, len = options.length; i < len; i++) {
44
  const option = options[i]
45
  option.selected = conjunto.has(option.value)
46
 }
47
}
48
49
/**
50
 * @param {NodeListOf<Element>} elementos
51
 * @param {Set<any>} conjunto
52
 */
53
function muestraInputs(elementos, conjunto) {
54
 for (let i = 0, len = elementos.length; i < len; i++) {
55
  const elemento = elementos[i]
56
  if (elemento instanceof HTMLInputElement) {
57
   elemento.checked = conjunto.has(elemento.value)
58
  }
59
 }
60
}
61
62
const data_ = "data-"
63
const data_Length = data_.length
64
65
/**
66
 * @param {Document | HTMLElement | ShadowRoot} raizHtml
67
 * @param {string} nombre
68
 * @param {{ [s: string]: any; } } definiciones
69
 */
70
function muestraElemento(raizHtml, nombre, definiciones) {
71
 const elemento = raizHtml.querySelector(selectorDeNombre(nombre))
72
 if (elemento !== null) {
73
  for (const [propiedad, valor] of Object.entries(definiciones)) {
74
   if (propiedad in elemento) {
75
    elemento[propiedad] = valor
76
   } else if (
77
    propiedad.length > data_Length
78
    && propiedad.startsWith(data_)
79
    && elemento instanceof HTMLElement
80
   ) {
81
    elemento.dataset[propiedad.substring(data_Length)] = valor
82
   }
83
  }
84
 }
85
}

11. js / lib / ProblemDetailsError.js

1
export class ProblemDetailsError extends Error {
2
3
 /**
4
  * Detalle de los errores devueltos por un servicio.
5
  * Crea una instancia de ProblemDetailsError.
6
  * @param {object} problemDetails Objeto con la descripcipon del error.
7
  */
8
 constructor(problemDetails) {
9
10
  super(typeof problemDetails["detail"] === "string"
11
   ? problemDetails["detail"]
12
   : (typeof problemDetails["title"] === "string"
13
    ? problemDetails["title"]
14
    : "Error"))
15
16
  this.problemDetails = problemDetails
17
18
 }
19
20
}

12. js / lib / recibeJson.js

1
2
/**
3
 * @param {string} url
4
 * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS"
5
 *  | "CONNECT" | "HEAD" } metodoHttp
6
 */
7
export async function recibeJson(url, metodoHttp = "GET") {
8
 return fetch(
9
  url,
10
  {
11
   method: metodoHttp,
12
   headers: { "Accept": "application/json, application/problem+json" }
13
  }
14
 )
15
}

13. js / lib / suscribeAPush.js

1
/**
2
 * @param { ArrayBuffer } applicationServerKey
3
 */
4
export async function suscribeAPush(applicationServerKey) {
5
 // Recupera el service worker registrado.
6
 const registro = await navigator.serviceWorker.ready
7
 return registro.pushManager.subscribe({
8
  userVisibleOnly: true,
9
  applicationServerKey
10
 })
11
}

14. js / lib / urlBase64ToUint8Array.js

1
/**
2
 * @param { string } base64String
3
 */
4
export function urlBase64ToUint8Array(base64String) {
5
 const padding = "=".repeat((4 - (base64String.length % 4)) % 4)
6
 const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
7
 const rawData = atob(base64)
8
 const outputArray = new Uint8Array(rawData.length)
9
 for (let i = 0; i < rawData.length; ++i) {
10
  outputArray[i] = rawData.charCodeAt(i)
11
 }
12
 return outputArray
13
}