bliss/public/js/bliss.js

131 lines
3.9 KiB
JavaScript
Raw Permalink Normal View History

2026-08-21 11:35:38 -04:00
(function () {
if (window.Bliss) return;
function pageStructureId() {
return (
document.body?.dataset?.blissStructureId ||
document.querySelector("[data-bliss-structure-id]")?.dataset
?.blissStructureId ||
null
);
}
function clientId() {
const key = "bliss:client_id";
let id = localStorage.getItem(key);
if (!id) {
id =
crypto.randomUUID?.() ||
String(Date.now()) + "-" + Math.random().toString(36).slice(2);
localStorage.setItem(key, id);
}
return id;
}
function b64ToU8(s) {
const pad = "=".repeat((4 - (s.length % 4)) % 4);
const raw = atob((s + pad).replace(/-/g, "+").replace(/_/g, "/"));
const a = new Uint8Array(raw.length);
for (let i = 0; i < raw.length; i++) a[i] = raw.charCodeAt(i);
return a;
}
async function registration() {
if (!("serviceWorker" in navigator)) {
throw new Error("Service workers are not supported here.");
}
return navigator.serviceWorker.register("/_bliss/notification-sw.js");
}
async function postVisibility(state) {
if (!("serviceWorker" in navigator)) return;
const reg = await registration().catch(() => null);
reg?.active?.postMessage({
type: "bliss:visibility",
state,
structureId: pageStructureId(),
});
}
document.addEventListener("visibilitychange", () => {
postVisibility(document.visibilityState === "visible" ? "open" : "closed");
});
if (document.readyState === "loading") {
document.addEventListener(
"DOMContentLoaded",
() => postVisibility("open"),
{
once: true,
},
);
} else {
postVisibility("open");
}
window.Bliss = {
structureId: pageStructureId,
clientId,
notifications: {
supported() {
return (
"serviceWorker" in navigator &&
"PushManager" in window &&
"Notification" in window
);
},
permission() {
return "Notification" in window ? Notification.permission : "denied";
},
async register(options = {}) {
const structureId = options.structureId || pageStructureId();
if (!structureId) throw new Error("No Bliss structure is active.");
if (!this.supported()) {
throw new Error("Push notifications are not supported here.");
}
const reg = await registration();
if (Notification.permission !== "granted") {
const permission = await Notification.requestPermission();
if (permission !== "granted") {
throw new Error("Notification permission was not granted.");
}
}
const vapidResponse = await fetch("/_bliss/notifications/key");
if (!vapidResponse.ok) {
throw new Error("Could not load notification key.");
}
const { publicKey } = await vapidResponse.json();
const sub =
(await reg.pushManager.getSubscription()) ||
(await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: b64ToU8(publicKey),
}));
const ua =
String(navigator.userAgent || "") +
" " +
String(navigator.platform || "");
const isKaiOS = /kaios/i.test(ua) || !!navigator.b2g;
const response = await fetch("/_bliss/notifications/subscribe", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
structure_id: structureId,
client_id: clientId(),
subscription: sub,
content_encoding: isKaiOS ? "aesgcm" : "aes128gcm",
}),
});
if (!response.ok) {
const detail = await response.text().catch(() => "");
throw new Error(detail || "Could not register notifications.");
}
await postVisibility("open");
return true;
},
},
};
})();