1jQuery(function() { 2 if (document.getElementById("xmppsupport-chat-trigger")) return; 3 4 const path = (typeof DOKU_BASE !== 'undefined' ? DOKU_BASE : '/') + "lib/plugins/xmppsupport/"; 5 let conn = null, nick = "anon-client", op = ""; 6 7 const box = document.createElement("div"); box.id = "xmppsupport-chat-box"; 8 const header = document.createElement("div"); header.id = "xmppsupport-chat-header"; 9 const title = document.createElement("span"); title.id = "xmppsupport-header-title"; title.textContent = "Live Support Chat"; 10 const status = document.createElement("span"); status.id = "xmppsupport-header-status"; status.textContent = " (connecting...)"; 11 Object.assign(status.style, { fontSize: "11px", opacity: "0.8", marginLeft: "5px" }); 12 header.append(title, status); 13 14 const area = document.createElement("div"); area.id = "xmppsupport-chat-messages"; 15 const footer = document.createElement("div"); footer.id = "xmppsupport-chat-footer"; 16 const input = document.createElement("input"); Object.assign(input, { type: "text", id: "xmppsupport-chat-input", placeholder: "Type a message...", autocomplete: "off" }); 17 footer.appendChild(input); box.append(header, area, footer); document.body.appendChild(box); 18 19 function append(txt, type, save = true) { 20 const msg = document.createElement("div"); msg.className = `xmpp-msg ${type}`; msg.textContent = txt; 21 area.appendChild(msg); area.scrollTop = area.scrollHeight; 22 if (save) { 23 try { 24 let h = JSON.parse(localStorage.getItem("xmpp_chat_history")) || []; 25 h.push({ text: txt, type: type }); 26 localStorage.setItem("xmpp_chat_history", JSON.stringify(h)); 27 } catch(e) {} 28 } 29 } 30 31 try { 32 (JSON.parse(localStorage.getItem("xmpp_chat_history")) || []).forEach(i => append(i.text, i.type, false)); 33 } catch(e) {} 34 35 let cookies = `; ${document.cookie}`.split(`; xmpp_support_nick=`); 36 if (cookies.length === 2) nick = cookies.pop().split(';').shift(); 37 else { 38 const adjs = ["Swift", "Bright", "Clever", "Calm", "Bold", "Sly", "Wild", "Noble", "Eager", "Vast", "Iron", "Shadow", "Frost", "Flaring", "Silent"]; 39 const nns = ["Falcon", "Wizard", "Phoenix", "Wolf", "Knight", "Fox", "Raven", "Gargoyle", "Forge", "Golem", "Warden", "Specter", "Drake", "Sentinel", "Citadel"]; 40 nick = `${adjs[Math.floor(Math.random() * adjs.length)]}-${nns[Math.floor(Math.random() * nns.length)]}`.toLowerCase(); 41 let d = new Date(); d.setTime(d.getTime() + (365 * 24 * 3600 * 1000)); 42 document.cookie = `xmpp_support_nick=${nick}; expires=${d.toUTCString()}; path=/; SameSite=Lax`; 43 } 44 45 const scr = document.createElement("script"); scr.src = path + "strophe.min.js"; scr.charset = "utf-8"; 46 scr.onload = function() { 47 if (typeof Strophe === 'undefined') return; 48 49 function onMsg(st) { 50 const b = st.getElementsByTagName('body'); 51 if (b.length > 0 && (st.getAttribute('from') || '').toLowerCase().includes(op.toLowerCase())) append(Strophe.getText(b[0]), 'incoming'); 52 return true; 53 } 54 55 function onPres(st) { 56 if (!(st.getAttribute('from') || '').toLowerCase().includes(op.toLowerCase())) return true; 57 if (st.getAttribute('type') === 'unavailable') { status.textContent = " (offline)"; return true; } 58 const s = st.getElementsByTagName('show'), t = st.getElementsByTagName('status'); 59 const map = { away: " (away)", chat: " (free for chat)", dnd: " (do not disturb)", xa: " (extended away)", online: " (online)" }; 60 status.textContent = t.length > 0 ? ` (${Strophe.getText(t[0])})` : (map[s.length > 0 ? Strophe.getText(s[0]) : 'online'] || " (online)"); 61 return true; 62 } 63 64 function loop(st) { 65 if (st === Strophe.Status.CONNECTED) { 66 status.textContent = " (online)"; 67 conn.addHandler(onMsg, null, 'message', 'chat'); 68 conn.addHandler(onPres, null, 'presence'); 69 conn.send($pres().tree()); conn.send($pres({ to: op, type: 'subscribe' }).tree()); 70 } else if (st === Strophe.Status.DISCONNECTED) { 71 status.textContent = " (connecting...)"; setTimeout(() => connect(), 4000); 72 } 73 } 74 75 let cachedConfig = null; 76 function connect(cfg) { 77 if (cfg) cachedConfig = cfg; 78 if (!cachedConfig) return; 79 80 conn = new Strophe.Connection(cachedConfig.websocket); 81 op = cachedConfig.operator; 82 83 const targetDomain = cachedConfig.domain; 84 const jid = `${nick}@${targetDomain}`; 85 86 conn.connect(jid, nick, function(st) { 87 if (st === Strophe.Status.AUTHFAIL) { 88 const reg = $iq({ type: "set", to: targetDomain, id: "reg-1" }) 89 .c("query", { xmlns: "jabber:iq:register" }).c("username").t(nick).up().c("password").t(nick); 90 conn.sendIQ(reg, () => conn.connect(jid, nick, loop), (err) => { 91 const cond = err.getElementsByTagName("conflict"); 92 if (cond.length > 0) conn.connect(jid, nick, loop); 93 }); 94 } else loop(st); 95 }); 96 } 97 98 fetch(path + "config.php").then(r => r.json()).then(d => { 99 if (d?.title) title.textContent = d.title; 100 connect(d); 101 }).catch(() => {}); 102 }; 103 document.head.appendChild(scr); 104 105 const trig = document.createElement("div"); trig.id = "xmppsupport-chat-trigger"; 106 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" }); 107 const img = document.createElement("img"); Object.assign(img.style, { width: "24px", height: "24px", display: "block" }); img.src = path + "images/gate.png"; 108 trig.appendChild(img); document.body.appendChild(trig); 109 110 // FIX: Updated click target name handler hook properties to match trig explicitly 111 trig.addEventListener("click", () => { 112 box.style.display = window.getComputedStyle(box).display === 'none' ? 'flex' : 'none'; 113 if (box.style.display === 'flex') input.focus(); 114 }); 115 116 input.addEventListener("keydown", (e) => { 117 if (e.key === "Enter" && input.value.trim() !== "") { 118 const txt = input.value.trim(); 119 if (conn?.connected) { 120 conn.send($msg({ to: op, type: 'chat' }).c('body').t(txt).tree()); 121 append(txt, 'outgoing'); input.value = ""; 122 } else append("Connecting to support server...", "incoming", false); 123 } 124 }); 125}); 126