Add structure-scoped notifications
This commit is contained in:
parent
e7064a7c6a
commit
464774ef4b
4 changed files with 460 additions and 1 deletions
130
public/js/bliss.js
Normal file
130
public/js/bliss.js
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
(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;
|
||||
},
|
||||
},
|
||||
};
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue