UNPKG

@zencemarketing/web-sdk

Version:

ZenceMarketing Web SDK for push notifications, popups, and custom event tracking.

432 lines (388 loc) 15 kB
import apiRequest from "../api.js"; import { getSdkData } from "./init.js"; import { logger } from "../index.js"; import { getWebSocketInstance } from "../websocket.js"; import { SDKData } from "../types/types.js"; type Position = | "Top Left" | "Top Right" | "Top Middle" | "Bottom Left" | "Bottom Right" | "Bottom Middle"; function urlBase64ToUint8Array(base64String: string): Uint8Array { const padding = "=".repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/"); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } async function subscribeUserMain(swFilePath:string, vapidPublicKey:string, websiteUrl:string) { if ('serviceWorker' in navigator && 'PushManager' in window) { try { const registration : ServiceWorkerRegistration = await navigator.serviceWorker.register(swFilePath); await new Promise(resolve => setTimeout(resolve, 1000)); if (registration.active) { // logger.info(`A service worker is active: ${registration.active}`); // const vapidPublicKey = publicVapidKey; const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey); const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: convertedVapidKey, }); // logger.info("User is subscribed:", subscription); const userID = "gaId"; await fetch("https://qa-ndjs-appwebnotification.erzqa.com/api/subscription/subscribe", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ subscription, userID, websiteUrl }), }); return subscription; } else { throw new Error("Service worker is not active yet."); } } catch (error) { logger.error("Error in subscribing user:", error); return null; } } else { return null; } } async function checkAndSubscribe(currentDomain:string, swFilePath:string, vapidPublicKey:string, sdkData:SDKData) { let subNew: PushSubscription | null = null; await navigator.serviceWorker.getRegistrations().then(async (registrations) => { if (registrations.length === 0) { // logger.info("No service workers registered."); subNew = await subscribeUserMain(swFilePath, vapidPublicKey, sdkData.website_url); return subNew; } else { let foundSubscription = false; for (const reg of registrations) { let subscription = await reg.pushManager.getSubscription(); if (subscription) { // logger.info("Subscription details retrieved:", subscription); const regDomain = new URL(reg.scope).hostname; // const currentDomain = window.location.hostname; const isMatchingDomain = regDomain === currentDomain || regDomain.endsWith(`.${currentDomain}`); if (isMatchingDomain) { subNew = subscription; foundSubscription = true; } // logger.info("Domain Match:", isMatchingDomain, "Scope:", reg.scope); } } if (foundSubscription) { // logger.info("Subscription found for service worker...", subNew); return subNew; } else { // logger.info("No subscription found for any service worker. Subscribing..."); subNew = await subscribeUserMain(swFilePath, vapidPublicKey, sdkData.website_url); return subNew; } } }); return subNew; } async function handleNotificationSubscription(sdkData: SDKData, swFilePath: string, triggerSource: string) { const gaId = sdkData.userID; if (triggerSource === 'Box' || triggerSource === 'Bell') { const notificationPopup = document.querySelector('.popup-notification-er') as HTMLElement | null;; if (notificationPopup) { notificationPopup.style.display = 'none'; } } let subscription = null; if ('serviceWorker' in navigator && 'PushManager' in window) { subscription = await checkAndSubscribe(sdkData.website_url, swFilePath, sdkData.public_vapid_key, sdkData); } else { // logger.info("Service workers or push notifications are not supported in this browser."); } let webSocketInstance = getWebSocketInstance(gaId); webSocketInstance.send("subscribedNotification", { gaId: gaId, website_url: sdkData.website_url, permission: Notification.permission, subscription: subscription ?? null, send_web_push: true, }); } async function showPopupNotificationER(sdkData: SDKData, swFilePath: string) { const config = sdkData.opt_in_prompt; // Define default config if not passed const defaultConfig = { OptPromptType: "Box", ShowOverlyHint: true, Message: "Bee the first to know! Join us for instant updates on limited time offers, exclusive styles, and more.", Position: "Top Right", AllowButtonText: "Stay connected", DontAllowButtonText: "I'll do this later", BackgroundColor: "#fff", TextColor: "#000", UserClickDontAllow: "", Minutes: "", ShowOverlyBox: "true", TextOnHover: "" }; // logger.info(config); // Merge the provided config with the default one const settings = { ...defaultConfig, ...config }; if (settings.OptPromptType === "Native") { handleNotificationSubscription(sdkData, swFilePath, "Native"); return; } if (settings.OptPromptType === "Bell") { const bellIcon = document.createElement("div") as HTMLElement; bellIcon.className = "popup-bell-icon"; bellIcon.innerHTML = "🔔"; document.body.appendChild(bellIcon); const positionStyles: Record<Position, Partial<CSSStyleDeclaration>> = { "Top Left": { top: "20px", left: "20px" }, "Top Right": { top: "20px", right: "20px" }, "Top Middle": { top: "20px", left: "50%", transform: "translateX(-50%)" }, "Bottom Left": { bottom: "20px", left: "20px" }, "Bottom Right": { bottom: "20px", right: "20px" }, "Bottom Middle": { bottom: "20px", left: "50%", transform: "translateX(-50%)" } }; const positionKey = settings.Position as Position; // Apply position styles Object.assign(bellIcon.style, { position: "fixed", // Ensure the bell icon is fixed BackgroundColor: settings.BackgroundColor, color: settings.TextColor || "#fff", fontSize: "24px", padding: "10px", borderRadius: "50%", cursor: "pointer", boxShadow: "0px 4px 12px rgba(0, 0, 0, 0.2)", zIndex: "1000", display: "flex", alignItems: "center", justifyContent: "center", width: "50px", height: "50px", ...positionStyles[positionKey] }); bellIcon.addEventListener("mouseover", () => { bellIcon.style.backgroundColor = settings.TextOnHover; }); bellIcon.addEventListener("mouseout", () => { bellIcon.style.backgroundColor = settings.BackgroundColor; }); bellIcon.addEventListener("click", () => { handleNotificationSubscription(sdkData, swFilePath, "Bell"); bellIcon.style.display = "none"; }); return; } // Create the notification popup const notificationPopup = document.createElement("div") as HTMLElement; notificationPopup.className = "popup-notification-er"; notificationPopup.innerHTML = ` <div class="popup-notification-er-message">${settings.Message}</div> <div class="popup-notification-er-div"> <button class="popup-notification-er-button" id="requestPermissionButton-notification-er-doLaterButton">${settings.DontAllowButtonText}</button> <button class="popup-notification-er-button" id="requestPermissionButton-notification-er-allow">${settings.AllowButtonText}</button> </div> `; // Define styles for the popup const styleElement = document.createElement("style") as HTMLElement; styleElement.innerHTML = ` .popup-notification-er { position: fixed; background-color: ${settings.BackgroundColor}; color: ${settings.TextColor}; border-radius: 8px; box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.2); padding: 15px; max-width: 300px; z-index: 1000; display: flex; flex-direction: column; justify-content: space-between; min-width: 220px; min-height: 80px; } /* Position Handling */ .popup-notification-er.top-left { top: 20px; left: 20px; } .popup-notification-er.top-right { top: 20px; right: 20px; } .popup-notification-er.top-middle { top: 20px; left: 50%; transform: translateX(-50%); } .popup-notification-er.bottom-left { bottom: 20px; left: 20px; } .popup-notification-er.bottom-right { bottom: 20px; right: 20px; } .popup-notification-er.bottom-middle { bottom: 20px; left: 50%; transform: translateX(-50%); } .popup-notification-er-message { color: ${settings.TextColor}; font-size: 14px; margin-bottom: 15px; text-align: center; } .popup-notification-er-div { display: flex; justify-content: space-between; gap: 5px; } .popup-notification-er-button { background-color: #ffffff; color: teal; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; flex: 1; } .popup-notification-er-button:focus { outline: none; } .popup-notification-er-button:hover { background-color: ${settings.TextOnHover}; } `; document.head.appendChild(styleElement); document.body.appendChild(notificationPopup); notificationPopup.style.display = 'block'; notificationPopup.classList.add(settings.Position.toLowerCase().replace(" ", "-")); const allowBtn = document.getElementById('requestPermissionButton-notification-er-allow'); if (allowBtn) { allowBtn.addEventListener('click', async function () { handleNotificationSubscription(sdkData, swFilePath, "Box"); }); } const doLaterBtn = document.getElementById('requestPermissionButton-notification-er-doLaterButton'); if (doLaterBtn) { doLaterBtn.addEventListener('click', () => { notificationPopup.style.display = 'none'; }); } } function showNotification(data: any) { const { title, body, icon, badge, image, data: notificationData, } = data; const notificationOptions: any = {}; if (title) { notificationOptions.title = title; } if (body) { notificationOptions.body = body; } if (icon) { notificationOptions.icon = icon; } if (badge) { notificationOptions.badge = badge; } if (image) { notificationOptions.image = image; } if (notificationData) { notificationOptions.data = notificationData; } if (Notification.permission === "granted") { new Notification(title || '', notificationOptions); } else if (Notification.permission !== "denied") { Notification.requestPermission().then(function (permission) { if (permission === "granted") { new Notification(title || '', notificationOptions); } }); } } async function subscribeUserPush(swFilePath: string, gaId: string) { const sdkData = getSdkData(); sdkData.userID = gaId; if (sdkData.show_web_push === "1") { logger.info("✅ Web Push is enabled"); if (Notification.permission === "default") { showPopupNotificationER(sdkData, swFilePath); } else if (Notification.permission === "granted") { let sub: any = null; if ("serviceWorker" in navigator && "PushManager" in window) { sub = await checkAndSubscribe(sdkData.website_url, swFilePath, sdkData.public_vapid_key, sdkData); } else { // logger.info("❌ Service workers or push notifications are not supported in this browser."); } // logger.info("📩 Subscription Data:", sub); let webSocketInstance = getWebSocketInstance(gaId); webSocketInstance.send("subscribedNotification", { gaId: gaId, website_url: sdkData.website_url, permission: Notification.permission, subscription: sub ?? null, send_web_push: true, }); if(sub && sub.endpoint){ sdkData.pushSubscribed = true; sdkData.sub = sub; } } } else { logger.info("❌ Web Push is not enabled"); } } async function sendNotification(notificationData: any) { const sdkData = getSdkData(); if (sdkData.pushSubscribed) { return apiRequest("sendNotificationToUser", "POST", { "subscription": sdkData.sub, ...notificationData }); } else{ return { "success": false, "data": null, "message": "Please Subscribe", } } // return apiRequest("sendNotificationToUser", "POST", { notificationData }); } // async function unsubscribeUser(gaId: string) { // return apiRequest("subscription/unsubscribe", "POST", { gaId }); // } async function getSubscriptionStatus() { const sdkData = getSdkData(); if (sdkData.pushSubscribed) { return { "subscribed": true, "sub": sdkData.sub, } } else{ return { "subscribed": false, "sub": null, } } } export { subscribeUserPush, sendNotification, // unsubscribeUser, getSubscriptionStatus, showNotification };