jQuery(function() { if (document.getElementById("xmppsupport-chat-trigger")) return; const path = (typeof DOKU_BASE !== 'undefined' ? DOKU_BASE : '/') + "lib/plugins/xmppsupport/"; let conn = null, nick = "anon-client", op = ""; const box = document.createElement("div"); box.id = "xmppsupport-chat-box"; const header = document.createElement("div"); header.id = "xmppsupport-chat-header"; const title = document.createElement("span"); title.id = "xmppsupport-header-title"; title.textContent = "Live Support Chat"; const status = document.createElement("span"); status.id = "xmppsupport-header-status"; status.textContent = " (connecting...)"; Object.assign(status.style, { fontSize: "11px", opacity: "0.8", marginLeft: "5px" }); header.append(title, status); const area = document.createElement("div"); area.id = "xmppsupport-chat-messages"; const footer = document.createElement("div"); footer.id = "xmppsupport-chat-footer"; const input = document.createElement("input"); Object.assign(input, { type: "text", id: "xmppsupport-chat-input", placeholder: "Type a message...", autocomplete: "off" }); footer.appendChild(input); box.append(header, area, footer); document.body.appendChild(box); function append(txt, type, save = true) { const msg = document.createElement("div"); msg.className = `xmpp-msg ${type}`; msg.textContent = txt; area.appendChild(msg); area.scrollTop = area.scrollHeight; if (save) { try { let h = JSON.parse(localStorage.getItem("xmpp_chat_history")) || []; h.push({ text: txt, type: type }); localStorage.setItem("xmpp_chat_history", JSON.stringify(h)); } catch(e) {} } } try { (JSON.parse(localStorage.getItem("xmpp_chat_history")) || []).forEach(i => append(i.text, i.type, false)); } catch(e) {} let cookies = `; ${document.cookie}`.split(`; xmpp_support_nick=`); if (cookies.length === 2) nick = cookies.pop().split(';').shift(); else { const adjs = ["Swift", "Bright", "Clever", "Calm", "Bold", "Sly", "Wild", "Noble", "Eager", "Vast", "Iron", "Shadow", "Frost", "Flaring", "Silent"]; const nns = ["Falcon", "Wizard", "Phoenix", "Wolf", "Knight", "Fox", "Raven", "Gargoyle", "Forge", "Golem", "Warden", "Specter", "Drake", "Sentinel", "Citadel"]; nick = `${adjs[Math.floor(Math.random() * adjs.length)]}-${nns[Math.floor(Math.random() * nns.length)]}`.toLowerCase(); let d = new Date(); d.setTime(d.getTime() + (365 * 24 * 3600 * 1000)); document.cookie = `xmpp_support_nick=${nick}; expires=${d.toUTCString()}; path=/; SameSite=Lax`; } const scr = document.createElement("script"); scr.src = path + "strophe.min.js"; scr.charset = "utf-8"; scr.onload = function() { if (typeof Strophe === 'undefined') return; function onMsg(st) { const b = st.getElementsByTagName('body'); if (b.length > 0 && (st.getAttribute('from') || '').toLowerCase().includes(op.toLowerCase())) append(Strophe.getText(b[0]), 'incoming'); return true; } function onPres(st) { if (!(st.getAttribute('from') || '').toLowerCase().includes(op.toLowerCase())) return true; if (st.getAttribute('type') === 'unavailable') { status.textContent = " (offline)"; return true; } const s = st.getElementsByTagName('show'), t = st.getElementsByTagName('status'); const map = { away: " (away)", chat: " (free for chat)", dnd: " (do not disturb)", xa: " (extended away)", online: " (online)" }; status.textContent = t.length > 0 ? ` (${Strophe.getText(t[0])})` : (map[s.length > 0 ? Strophe.getText(s[0]) : 'online'] || " (online)"); return true; } function loop(st) { if (st === Strophe.Status.CONNECTED) { status.textContent = " (online)"; conn.addHandler(onMsg, null, 'message', 'chat'); conn.addHandler(onPres, null, 'presence'); conn.send($pres().tree()); conn.send($pres({ to: op, type: 'subscribe' }).tree()); } else if (st === Strophe.Status.DISCONNECTED) { status.textContent = " (connecting...)"; setTimeout(() => connect(), 4000); } } let cachedConfig = null; function connect(cfg) { if (cfg) cachedConfig = cfg; if (!cachedConfig) return; conn = new Strophe.Connection(cachedConfig.websocket); op = cachedConfig.operator; const targetDomain = cachedConfig.domain; const jid = `${nick}@${targetDomain}`; conn.connect(jid, nick, function(st) { if (st === Strophe.Status.AUTHFAIL) { const reg = $iq({ type: "set", to: targetDomain, id: "reg-1" }) .c("query", { xmlns: "jabber:iq:register" }).c("username").t(nick).up().c("password").t(nick); conn.sendIQ(reg, () => conn.connect(jid, nick, loop), (err) => { const cond = err.getElementsByTagName("conflict"); if (cond.length > 0) conn.connect(jid, nick, loop); }); } else loop(st); }); } fetch(path + "config.php").then(r => r.json()).then(d => { if (d?.title) title.textContent = d.title; connect(d); }).catch(() => {}); }; document.head.appendChild(scr); const trig = document.createElement("div"); trig.id = "xmppsupport-chat-trigger"; Object.assign(trig.style, { position: "fixed", bottom: "20px", right: "20px", width: "44px", height: "44px", backgroundColor: "#fff", boxShadow: "0 3px 10px rgba(0,0,0,0.22)", cursor: "pointer", zIndex: "2147483647", display: "flex", alignItems: "center", justifyContent: "center", border: "1px solid #ccc" }); const img = document.createElement("img"); Object.assign(img.style, { width: "24px", height: "24px", display: "block" }); img.src = path + "images/gate.png"; trig.appendChild(img); document.body.appendChild(trig); // FIX: Updated click target name handler hook properties to match trig explicitly trig.addEventListener("click", () => { box.style.display = window.getComputedStyle(box).display === 'none' ? 'flex' : 'none'; if (box.style.display === 'flex') input.focus(); }); input.addEventListener("keydown", (e) => { if (e.key === "Enter" && input.value.trim() !== "") { const txt = input.value.trim(); if (conn?.connected) { conn.send($msg({ to: op, type: 'chat' }).c('body').t(txt).tree()); append(txt, 'outgoing'); input.value = ""; } else append("Connecting to support server...", "incoming", false); } }); });