@enjoys/pinglet
Version:
Lightweight Customizable Web & CustomPush Notification SDK for modern web apps. Supports customizable layouts, secure delivery, and real-time updates via SSE.
556 lines (555 loc) • 16.1 kB
JavaScript
!(() => {
((e) => {
!(() => {
const e = document.createElement("link");
(e.rel = "stylesheet"),
(e.href =
"https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap"),
document.head.appendChild(e);
const t = document.createElement("style");
(t.innerHTML =
'\n [class^="pinglet-"],\n [class*=" pinglet-"] {\n font-family: \'Inter\', sans-serif !important;\n }\n '),
document.head.appendChild(t);
})();
const t = {
style: { color: "#fff", backgroundColor: "#000" },
position: "bottom-left",
branding: {
html: 'Powered by <a href="https://pinglet.enjoys.in" style="color:#4da6ff;text-decoration:none;" target="_blank">Pinglet</a> - Enjoys',
},
duration: 2e3,
};
const n = "pinglet-wrapper";
let o = 0;
let i = null;
const a = {
createMediaElement(e, t = "Pinglet Notification") {
let n;
switch (e.type) {
default:
(n = document.createElement("img")), (n.src = e.src), (n.alt = t);
break;
case "video":
(n = document.createElement("video")),
(n.src = e.src),
(n.controls = !0),
n.setAttribute("playsinline", ""),
n.setAttribute("muted", "");
break;
case "audio":
(n = document.createElement("audio")),
(n.src = e.src),
(n.controls = !0);
break;
case "icon":
(n = document.createElement("span")),
(n.className = `icon-${e.src}`),
n.setAttribute("aria-label", t);
}
return (
["image", "video", "audio"].includes(e.type) &&
(Object.assign(n.style, {
width: "100%",
maxWidth: "320px",
height: "auto",
borderRadius: "10px",
marginBottom: "8px",
display: "block",
}),
"audio" === e.type && (n.style.height = "40px"),
("video" !== e.type && "image" !== e.type) ||
((n.style.height = "180px"), (n.style.objectFit = "cover"))),
n
);
},
createWrapper(e = "bottom-right") {
let t = document.getElementById(n);
if (!t) {
(t = document.createElement("div")),
(t.id = n),
Object.assign(t.style, {
position: "fixed",
zIndex: 99999,
display: "flex",
flexDirection: "column",
gap: "10px",
maxWidth: "calc(100vw - 20px)",
padding: "12px",
pointerEvents: "none",
});
const o = {
"top-left": { top: "0", left: "0", alignItems: "flex-start" },
"top-right": { top: "0", right: "0", alignItems: "flex-end" },
"bottom-left": {
bottom: "0",
left: "0",
alignItems: "flex-start",
},
"bottom-right": {
bottom: "0",
right: "0",
alignItems: "flex-end",
},
"top-center": {
top: "0",
left: "50%",
transform: "translateX(-50%)",
alignItems: "center",
},
"bottom-center": {
bottom: "0",
left: "50%",
transform: "translateX(-50%)",
alignItems: "center",
},
};
Object.assign(t.style, o[e] || o["bottom-right"]),
document.body.appendChild(t);
}
return t;
},
playNotificationSound({ play: e, src: t }) {
if (!e || !t) return;
new Audio(t).play().catch((e) => {});
},
showToast(e) {
const {
title: n = "",
description: a = "",
media: s,
buttons: r = [],
} = e;
const { style: l = {}, position: c, duration: d, branding: p } = t;
const m = this.createWrapper(c);
const f = document.createElement("div");
(f.className = "pinglet-toast"),
o++,
Object.assign(f.style, {
background: "#1f1f1f",
color: "#fff",
padding: "16px 20px",
borderRadius: "12px",
boxShadow: "0 6px 18px rgba(0,0,0,0.25)",
fontFamily: "'Inter', sans-serif",
fontSize: "14px",
lineHeight: "1.5",
maxWidth: "360px",
minWidth: "280px",
pointerEvents: "auto",
position: "relative",
opacity: "0",
transform: "none",
transition: "all 0.4s ease",
...(o >= 4 ? { opacity: 0.8, scale: 0.95 } : {}),
...l,
});
const g = document.createElement("span");
if (
((g.innerHTML = "×"),
Object.assign(g.style, {
position: "absolute",
top: "6px",
right: "10px",
cursor: "pointer",
fontSize: "18px",
color: "#aaa",
}),
(g.onclick = () => this.removeToast(f)),
f.appendChild(g),
s)
) {
if ("icon" === s.type) {
const e = document.createElement("div");
(e.innerHTML = `<span style="font-size:14px;margin-right:8px;">${s.src}</span><strong>${n}</strong>`),
f.appendChild(e);
} else if ("image" === s.type || "video" === s.type) {
const e = this.createMediaElement(s);
f.appendChild(e);
const t = document.createElement("div");
(t.innerHTML = `<strong>${n}</strong>`), f.appendChild(t);
}
} else {
const e = document.createElement("div");
(e.innerHTML = `<strong>${n}</strong>`), f.appendChild(e);
}
if (a) {
const e = document.createElement("div");
(e.textContent = a), (e.style.color = "#ccc"), f.appendChild(e);
}
if (r.length) {
const e = document.createElement("div");
Object.assign(e.style, {
display: "flex",
gap: "10px",
marginTop: "8px",
flexWrap: "wrap",
}),
r.forEach((t) => {
const n = document.createElement("button");
if (
((n.type = "button"),
(n.className = `pinglet-toast-btn-${o}`),
(n.textContent = t.text),
Object.assign(n.style, {
padding: "6px 12px",
background: "#333",
border: "1px solid #555",
color: "#fff",
borderRadius: "6px",
cursor: "pointer",
fontSize: "13px",
}),
t.onClick)
) {
if ("string" === typeof t.onClick)
try {
t.onClick = new Function(`return (${t.onClick})`)();
} catch (e) {
t.onClick = null;
}
"function" === typeof t.onClick &&
n.addEventListener("click", t.onClick);
}
e.appendChild(n);
}),
f.appendChild(e);
}
i ||
((i = document.createElement("div")),
(i.innerHTML = p.html),
Object.assign(i.style, {
fontSize: "11px",
color: "#888",
textAlign: "right",
fontFamily: "'Inter', sans-serif",
marginTop: "8px",
})),
m.appendChild(f),
i && m.appendChild(i),
setTimeout(() => {
(f.style.opacity = "1"), (f.style.transform = "translateX(0)");
}, 10);
let u = setTimeout(() => this.removeToast(f), d);
f.addEventListener("mouseenter", () => clearTimeout(u)),
f.addEventListener("mouseleave", () => {
u = setTimeout(() => removeToast(f), 2e3);
});
},
removeToast(e) {
e &&
((e.style.opacity = "0"),
(e.style.transform = "translateX(100%)"),
setTimeout(() => {
e.remove(), o--, 0 === o && i && (i.remove(), (i = null));
}, 400));
},
};
const s = {
init({ endpoint: e, configuredDomain: t, projectIds: n = [] }) {
const o = location.hostname.replace(/^www\./, "");
if (0 === t.length)
return void this._showPopup(
"Domain Configuration Error ",
"Please ensure you are running Pinglet on a valid domain.",
);
if (0 === n.length)
return void this._showPopup(
"Project Configuration Error ",
"Please add at least one project to your Pinglet configuration.",
);
t === o
? n.forEach((t) => {
new EventSource(
`${e}/sse?projectId=${encodeURIComponent(t)}`,
).onmessage = (e) => {
try {
const t = JSON.parse(e.data);
a.showToast({
title: t.title,
description: t.description,
media: t.media,
buttons: t.buttons,
});
} catch (e) {}
};
})
: this._showPopup(
"Domain Configuration Error ",
`Your current domain is not allowed. Please ensure it matches one of the following:\n ${t}`,
);
},
_showNotification(e) {
const {
title: t = "",
description: n = "",
buttons: o = [],
media: i = null,
style: a = {},
branding: s = {
show: !0,
html: 'Powered by <a href="https://pinglet.enjoys.in" style="color:#4da6ff;text-decoration:none;" target="_blank">Pinglet</a> - Enjoys',
},
} = e;
const r = this._getWrapper();
const l = document.createElement("div");
if (
((l.className = "pinglet-toast"),
Object.assign(l.style, {
background: "#1f1f1f",
color: "#fff",
padding: "16px 20px",
borderRadius: "12px",
boxShadow: "0 6px 18px rgba(0, 0, 0, 0.25)",
fontFamily: "'Inter', sans-serif",
minWidth: "260px",
maxWidth: "360px",
opacity: "0",
transform: "translateX(100%)",
transition: "opacity 0.3s ease, transform 0.3s ease",
display: "flex",
flexDirection: "column",
gap: "12px",
...a,
}),
i)
) {
if ("icon" === i.type) {
const e = document.createElement("div");
Object.assign(e.style, {
display: "flex",
alignItems: "center",
gap: "10px",
fontSize: "16px",
fontWeight: "600",
});
const n = document.createElement("span");
n.textContent = i.src;
const o = document.createElement("span");
(o.textContent = t),
e.appendChild(n),
e.appendChild(o),
l.appendChild(e);
} else if ("image" === i.type || "video" === i.type) {
const e = document.createElement(i.type);
Object.assign(e.style, { maxWidth: "100%", borderRadius: "8px" }),
(e.src = i.src),
"video" === i.type && (e.controls = !0),
l.appendChild(e);
const n = document.createElement("div");
(n.textContent = t),
Object.assign(n.style, { fontSize: "16px", fontWeight: "600" }),
l.appendChild(n);
}
} else {
const e = document.createElement("div");
(e.textContent = t),
Object.assign(e.style, { fontSize: "16px", fontWeight: "600" }),
l.appendChild(e);
}
if (n) {
const e = document.createElement("div");
(e.textContent = n),
Object.assign(e.style, {
fontSize: "13.5px",
color: "#ccc",
lineHeight: "1.5",
}),
l.appendChild(e);
}
if (Array.isArray(o) && o.length) {
const e = document.createElement("div");
Object.assign(e.style, {
display: "flex",
gap: "10px",
marginTop: "6px",
});
for (const t of o) {
const n = document.createElement("button");
(n.textContent = t.text || "Click"),
Object.assign(n.style, {
padding: "8px 14px",
background: "#333",
color: "#fff",
border: "1px solid #444",
borderRadius: "6px",
fontSize: "13px",
cursor: "pointer",
transition: "background 0.3s",
}),
(n.onmouseover = () => (n.style.background = "#444")),
(n.onmouseout = () => (n.style.background = "#333")),
"function" === typeof t.onClick && (n.onclick = t.onClick),
e.appendChild(n);
}
l.appendChild(e);
}
if ((r.appendChild(l), !1 !== s?.show)) {
const e = document.createElement("div");
(e.innerHTML = s?.html || ""),
Object.assign(e.style, {
fontSize: "11px",
color: "#999",
marginTop: "4px",
textAlign: "right",
fontFamily: "'Inter', sans-serif",
}),
r.appendChild(e),
setTimeout(() => {
l.remove(), e.remove();
}, 5e3);
} else setTimeout(() => l.remove(), 5e3);
requestAnimationFrame(() => {
(l.style.opacity = "1"), (l.style.transform = "translateX(0)");
});
},
_showPopup(
e,
t,
n = [
{
text: "See Docs",
onClick: () =>
window.open("https://pinglet.enjoys.in/docs", "_blank"),
},
],
o = "⚠️",
) {
const i = "toastContainer";
let a = document.getElementById(i);
a ||
((a = document.createElement("div")),
(a.id = i),
Object.assign(a.style, {
position: "fixed",
bottom: "24px",
right: "24px",
zIndex: "9999",
display: "flex",
flexDirection: "column",
gap: "4px",
alignItems: "flex-end",
}),
document.body.appendChild(a));
const s = document.createElement("div");
Object.assign(s.style, {
background: "#1f1f1f",
color: "#fff",
padding: "16px 20px",
borderRadius: "12px",
boxShadow: "0 6px 18px rgba(0, 0, 0, 0.25)",
fontFamily: "'Inter', sans-serif",
minWidth: "260px",
maxWidth: "340px",
opacity: "0",
transform: "translateX(100%)",
transition: "opacity 0.3s ease, transform 0.3s ease",
display: "flex",
flexDirection: "column",
gap: "10px",
});
const r = document.createElement("div");
Object.assign(r.style, {
display: "flex",
alignItems: "center",
gap: "10px",
fontSize: "16px",
fontWeight: "600",
});
const l = document.createElement("div");
l.textContent = o;
const c = document.createElement("div");
if (
((c.textContent = e),
r.appendChild(l),
r.appendChild(c),
s.appendChild(r),
t)
) {
const e = document.createElement("div");
(e.textContent = t),
Object.assign(e.style, {
fontSize: "13.5px",
fontWeight: "400",
color: "#ddd",
lineHeight: "1.5",
}),
s.appendChild(e);
}
if (Array.isArray(n) && n.length) {
const e = document.createElement("div");
Object.assign(e.style, {
marginTop: "8px",
display: "flex",
gap: "10px",
justifyContent: "flex-start",
});
for (const t of n) {
const n = document.createElement("button");
(n.textContent = t.text || "Click"),
Object.assign(n.style, {
padding: "8px 14px",
background: "#333",
color: "#fff",
border: "1px solid #444",
borderRadius: "6px",
fontSize: "13px",
cursor: "pointer",
transition: "background 0.3s",
}),
(n.onmouseover = () => (n.style.background = "#444")),
(n.onmouseout = () => (n.style.background = "#333")),
"function" === typeof t.onClick &&
n.addEventListener("click", t.onClick),
e.appendChild(n);
}
s.appendChild(e);
}
a.appendChild(s);
const d = document.createElement("div");
(d.innerHTML =
'Powered by <a href="https://pinglet.enjoys.in" target="_blank" style="color:#4da6ff;text-decoration:none;">Pinglet</a> - Enjoys'),
Object.assign(d.style, {
fontSize: "11px",
color: "#999",
marginTop: "4px",
textAlign: "right",
fontFamily: "'Inter', sans-serif",
}),
a.appendChild(d),
requestAnimationFrame(() => {
(s.style.opacity = "1"), (s.style.transform = "translateX(0)");
}),
setTimeout(() => {
(s.style.opacity = "0"),
(s.style.transform = "translateX(100%)"),
setTimeout(() => {
s.remove(), d.remove();
}, 500);
}, 5e3);
},
_getWrapper() {
let e = document.getElementById("pinglet-wrapper");
return (
e ||
((e = document.createElement("div")),
(e.id = "pinglet-wrapper"),
(e.style =
"position:fixed;bottom:20px;right:20px;z-index:99999;display:flex;flex-direction:column;gap:10px;max-width:300px;font-family:sans-serif"),
document.body.appendChild(e)),
e
);
},
_addBranding(e) {
if (!document.getElementById("pinglet-brand")) {
const t = document.createElement("div");
(t.id = "pinglet-brand"),
(t.style =
"font-size:10px;text-align:right;color:#aaa;margin-top:8px"),
(t.innerHTML = "Powered by <strong>Pinglet</strong> – Enjoys"),
e.appendChild(t);
}
},
};
e.PingletWidget = s;
})(window);
})();