Skip to content

Instantly share code, notes, and snippets.

@mendelB
Created February 25, 2026 13:33
Show Gist options
  • Select an option

  • Save mendelB/5702e99aad8f420a8d904cba1971204e to your computer and use it in GitHub Desktop.

Select an option

Save mendelB/5702e99aad8f420a8d904cba1971204e to your computer and use it in GitHub Desktop.
Zendesk Messaging conversationFields diagnostic script — 21Casino/WHG
/**
* Zendesk Messaging Diagnostic Script — 21Casino
* ================================================
*
* PURPOSE: Diagnose why conversationFields may not be flowing through
* to Zendesk tickets on 21Casino (branders.koek.link).
*
* USAGE:
* 1. Open the 21Casino site in Chrome
* 2. Open DevTools (F12 → Console)
* 3. Paste this ENTIRE script and press Enter
* 4. Click the chat button on the site
* 5. Review the diagnostic output in the console
*
* WHAT IT DOES:
* - Intercepts ALL zE() calls with precise timestamps
* - Tracks widget lifecycle: load → conversationFields → open
* - Detects race conditions (fields set after conversation starts)
* - Monitors Zendesk network requests for field delivery
* - Checks conversation state (new vs existing)
* - Validates field IDs against known WHG configuration
* - Prints a diagnostic summary when you send a message
*
* SAFE: Uses Object.defineProperty to wrap zE without breaking it.
* All original zE behavior is preserved.
*
* Author: Cevro Engineering (for WHG/Branders debugging)
* Date: 2026-02-25
*/
(function() {
'use strict';
// ─── Configuration ───────────────────────────────────────────────
// Known WHG custom field IDs (for validation)
var KNOWN_FIELDS = {
'29575162993042': 'Player ID',
'30809643209618': 'Full Name',
'30809684417554': 'Email Address',
'33311874639250': 'Country',
'33311899437330': 'VIP',
'33311891744018': 'Flags'
};
// ─── State Tracking ──────────────────────────────────────────────
var state = {
scriptInstalledAt: Date.now(),
widgetLoadedAt: null,
conversationFieldsCalls: [], // every call to conversationFields
messengerOpenCalls: [], // every call to messenger open
messengerCloseCalls: [], // every call to messenger close
allZeCalls: [], // every zE() call
networkRequests: [], // Zendesk API requests
errors: [], // any errors caught
widgetState: 'not-loaded', // not-loaded | loaded | open | closed
existingConversation: null, // true/false/null (unknown)
callbacksReceived: [] // callbacks from conversationFields
};
var T0 = state.scriptInstalledAt;
function ts() {
return '+' + (Date.now() - T0) + 'ms';
}
function log(tag, msg, data) {
var prefix = '[ZE-DIAG ' + ts() + ']';
var color = {
'LOAD': 'color: #00aa00; font-weight: bold',
'FIELDS': 'color: #ff8800; font-weight: bold',
'OPEN': 'color: #0088ff; font-weight: bold',
'CLOSE': 'color: #888888; font-weight: bold',
'CALL': 'color: #666666',
'NET': 'color: #aa00aa; font-weight: bold',
'WARN': 'color: #ff0000; font-weight: bold',
'INFO': 'color: #0066cc',
'OK': 'color: #00aa00; font-weight: bold',
'RACE': 'background: #ff0000; color: white; font-weight: bold; padding: 2px 6px',
'SUMMARY':'background: #0066cc; color: white; font-weight: bold; padding: 2px 8px'
}[tag] || 'color: #333';
if (data !== undefined) {
console.log('%c' + prefix + ' [' + tag + '] ' + msg, color, data);
} else {
console.log('%c' + prefix + ' [' + tag + '] ' + msg, color);
}
}
// ─── Network Monitoring ──────────────────────────────────────────
//
// Intercept fetch() and XMLHttpRequest to capture Zendesk API calls.
// This shows whether conversationFields data actually leaves the browser.
// Wrap fetch
var _origFetch = window.fetch;
window.fetch = function() {
var url = (arguments[0] && arguments[0].url) ? arguments[0].url : String(arguments[0]);
var method = 'GET';
var body = null;
if (arguments[1]) {
method = arguments[1].method || 'GET';
body = arguments[1].body || null;
}
if (url.indexOf('zendesk') !== -1 || url.indexOf('zdassets') !== -1) {
var entry = {
timestamp: Date.now(),
elapsed: ts(),
method: method,
url: url,
bodySnippet: null
};
// Try to extract field-related data from request body
if (body) {
try {
var bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
if (bodyStr.indexOf('field') !== -1 || bodyStr.indexOf('metadata') !== -1) {
entry.bodySnippet = bodyStr.substring(0, 500);
log('NET', 'Zendesk request with field/metadata data: ' + method + ' ' + url);
log('NET', 'Body snippet:', entry.bodySnippet);
}
} catch(e) { /* ignore */ }
}
state.networkRequests.push(entry);
// Flag conversation-starting requests
if (url.indexOf('/conversations') !== -1 || url.indexOf('/messages') !== -1) {
log('NET', 'Conversation/message request: ' + method + ' ' + url);
}
}
return _origFetch.apply(this, arguments);
};
// Wrap XMLHttpRequest
var _origXHROpen = XMLHttpRequest.prototype.open;
var _origXHRSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url) {
this._zeMethod = method;
this._zeUrl = url;
return _origXHROpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(body) {
if (this._zeUrl && (this._zeUrl.indexOf('zendesk') !== -1 || this._zeUrl.indexOf('zdassets') !== -1)) {
var entry = {
timestamp: Date.now(),
elapsed: ts(),
method: this._zeMethod,
url: this._zeUrl,
bodySnippet: null
};
if (body) {
try {
var bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
if (bodyStr.indexOf('field') !== -1 || bodyStr.indexOf('metadata') !== -1) {
entry.bodySnippet = bodyStr.substring(0, 500);
log('NET', 'XHR to Zendesk with field data: ' + this._zeMethod + ' ' + this._zeUrl);
}
} catch(e) { /* ignore */ }
}
state.networkRequests.push(entry);
}
return _origXHRSend.apply(this, arguments);
};
// ─── zE Interceptor ──────────────────────────────────────────────
//
// Uses Object.defineProperty to catch the moment Zendesk creates
// window.zE, then wraps it to log all calls.
var _origZE = null;
var _installed = false;
function wrapZE(realZE) {
return function wrappedZE() {
var args = Array.prototype.slice.call(arguments);
var cmd = args[0] || '';
var sub = args[1] || '';
var now = Date.now();
// Record every call
state.allZeCalls.push({
timestamp: now,
elapsed: ts(),
command: cmd,
subcommand: sub,
args: args
});
// ── conversationFields ──
if (cmd === 'messenger:set' && sub === 'conversationFields') {
var fields = args[2];
var callback = args[3];
var hasCallback = typeof callback === 'function';
log('FIELDS', 'conversationFields called' + (hasCallback ? ' (WITH callback)' : ' (NO callback)'));
var callEntry = {
timestamp: now,
elapsed: ts(),
fields: [],
hasCallback: hasCallback,
widgetStateAtCall: state.widgetState,
messengerAlreadyOpened: state.messengerOpenCalls.length > 0
};
// Validate each field
if (Array.isArray(fields)) {
fields.forEach(function(f) {
var knownName = KNOWN_FIELDS[String(f.id)] || 'UNKNOWN FIELD';
var valuePreview = String(f.value).substring(0, 50);
var valid = f.id && f.value !== undefined && f.value !== null && f.value !== '';
callEntry.fields.push({
id: String(f.id),
name: knownName,
value: valuePreview,
valid: valid,
type: typeof f.value
});
var statusIcon = valid ? 'OK' : 'WARN';
log(statusIcon, ' Field ' + f.id + ' (' + knownName + ') = "' + valuePreview + '"' +
' [type: ' + typeof f.value + ']' + (valid ? '' : ' — EMPTY OR MISSING VALUE'));
});
} else {
log('WARN', ' fields argument is NOT an array:', fields);
}
// ── RACE CONDITION DETECTION ──
if (callEntry.messengerAlreadyOpened) {
log('RACE', 'TIMING ISSUE: conversationFields called AFTER messenger was already opened!');
log('RACE', 'Messenger opened at ' + state.messengerOpenCalls[0].elapsed +
', fields set at ' + ts());
log('RACE', 'Zendesk docs say fields are applied when "end users start a conversation ' +
'or send a message". If the conversation already started, these fields may NOT apply.');
} else {
log('OK', 'Good: conversationFields set BEFORE messenger open');
}
// If there IS a callback, wrap it to track when Zendesk confirms
if (hasCallback) {
args[3] = function() {
log('OK', 'conversationFields CALLBACK fired — Zendesk confirmed fields are stored');
state.callbacksReceived.push({ timestamp: Date.now(), elapsed: ts() });
return callback.apply(this, arguments);
};
} else {
log('WARN', 'No callback provided. Zendesk recommends using a callback to confirm ' +
'fields are stored. This is a potential reliability issue.');
}
state.conversationFieldsCalls.push(callEntry);
}
// ── messenger open ──
else if (cmd === 'messenger' && sub === 'open') {
log('OPEN', 'messenger OPEN called');
state.messengerOpenCalls.push({ timestamp: now, elapsed: ts() });
state.widgetState = 'open';
// Check if fields were set before this
if (state.conversationFieldsCalls.length === 0) {
log('WARN', 'Messenger opened but NO conversationFields have been set yet');
} else {
var lastFieldsCall = state.conversationFieldsCalls[state.conversationFieldsCalls.length - 1];
var timeSinceFields = now - lastFieldsCall.timestamp;
log('INFO', 'conversationFields were set ' + timeSinceFields + 'ms before open — ' +
(timeSinceFields > 50 ? 'good gap' : 'very tight, could race'));
}
}
// ── messenger close ──
else if (cmd === 'messenger' && sub === 'close') {
log('CLOSE', 'messenger CLOSE called');
state.messengerCloseCalls.push({ timestamp: now, elapsed: ts() });
state.widgetState = 'closed';
}
// ── locale ──
else if (cmd === 'messenger:set' && sub === 'locale') {
log('INFO', 'Locale set to: ' + args[2]);
}
// ── loginUser (JWT auth) ──
else if (cmd === 'messenger' && sub === 'loginUser') {
log('INFO', 'loginUser called — JWT authentication active');
}
// ── logoutUser ──
else if (cmd === 'messenger' && sub === 'logoutUser') {
log('INFO', 'logoutUser called');
}
// ── anything else ──
else {
log('CALL', cmd + ' ' + sub, args.length > 2 ? args.slice(2) : '');
}
// Pass through to real zE
if (realZE) {
try {
return realZE.apply(this, args);
} catch(e) {
log('WARN', 'zE() threw an error: ' + e.message);
state.errors.push({ timestamp: now, error: e.message, command: cmd + ' ' + sub });
throw e;
}
}
};
}
// Check if zE is already defined (widget already loaded)
if (typeof window.zE !== 'undefined') {
log('INFO', 'Zendesk widget already loaded — wrapping existing zE');
_origZE = window.zE;
window.zE = wrapZE(_origZE);
state.widgetLoadedAt = 'before-script';
state.widgetState = 'loaded';
_installed = true;
} else {
// Widget not loaded yet — use defineProperty to intercept when it gets created
log('INFO', 'Zendesk widget not loaded yet — installing property trap');
Object.defineProperty(window, 'zE', {
configurable: true,
enumerable: true,
get: function() {
if (_origZE && !_installed) {
_installed = true;
// Return the wrapper
var wrapped = wrapZE(_origZE);
// Replace ourselves with a normal property now
Object.defineProperty(window, 'zE', {
configurable: true,
enumerable: true,
writable: true,
value: wrapped
});
return wrapped;
}
return _origZE ? wrapZE(_origZE) : undefined;
},
set: function(val) {
if (val && !_origZE) {
state.widgetLoadedAt = Date.now();
state.widgetState = 'loaded';
log('LOAD', 'Zendesk widget loaded! (took ' + (state.widgetLoadedAt - T0) + 'ms from script install)');
}
_origZE = val;
// Process any queued commands (Zendesk queues calls made before load)
if (val && val.q && Array.isArray(val.q)) {
log('INFO', 'Found ' + val.q.length + ' queued zE commands from before widget load');
val.q.forEach(function(queuedArgs, i) {
if (queuedArgs[0] === 'messenger:set' && queuedArgs[1] === 'conversationFields') {
log('FIELDS', 'Queued command #' + i + ' is a conversationFields call — will be processed on load');
}
});
}
}
});
}
// ─── Conversation State Detection ─────────────────────────────────
//
// After widget loads, check if there is an existing conversation.
// An existing conversation means conversationFields may not apply
// (they only apply at ticket creation or when a new message is sent).
function checkConversationState() {
try {
// Look for Zendesk's internal conversation storage
var iframes = document.querySelectorAll('iframe[title*="messaging"], iframe[title*="Messaging"], iframe[data-product="messaging"]');
if (iframes.length > 0) {
log('INFO', 'Found ' + iframes.length + ' Zendesk messaging iframe(s)');
}
// Check localStorage for existing conversation markers
var conversationKeys = [];
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (key && (key.indexOf('zendesk') !== -1 || key.indexOf('zE') !== -1 || key.indexOf('messaging') !== -1)) {
conversationKeys.push(key);
}
}
if (conversationKeys.length > 0) {
log('INFO', 'Zendesk localStorage keys found: ' + conversationKeys.join(', '));
state.existingConversation = true;
log('WARN', 'Existing conversation data found in localStorage. If the player has ' +
'chatted before, conversationFields may only apply to the NEXT message, not retroactively.');
} else {
state.existingConversation = false;
log('OK', 'No existing conversation data found — this looks like a fresh session');
}
} catch(e) {
log('WARN', 'Could not check conversation state: ' + e.message);
}
}
// Run the check after a delay (widget needs time to initialize)
setTimeout(checkConversationState, 2000);
// ─── Diagnostic Summary ──────────────────────────────────────────
//
// Call window.zeDiagSummary() at any time to get the full report.
// Also auto-prints 10 seconds after messenger opens.
function printSummary() {
var now = Date.now();
console.log('');
log('SUMMARY', '═══════════════════════════════════════════════════');
log('SUMMARY', ' ZENDESK DIAGNOSTIC SUMMARY');
log('SUMMARY', '═══════════════════════════════════════════════════');
console.log('');
// 1. Widget Load
console.log('%c1. Widget Load', 'font-weight: bold; font-size: 13px');
if (state.widgetLoadedAt === 'before-script') {
console.log(' Widget was already loaded when script was installed');
} else if (state.widgetLoadedAt) {
console.log(' Loaded at ' + (state.widgetLoadedAt - T0) + 'ms after script install');
} else {
console.log(' %cWidget has NOT loaded yet', 'color: red');
}
console.log(' Current state: ' + state.widgetState);
console.log('');
// 2. conversationFields Calls
console.log('%c2. conversationFields Calls (' + state.conversationFieldsCalls.length + ' total)',
'font-weight: bold; font-size: 13px');
if (state.conversationFieldsCalls.length === 0) {
console.log(' %cNO conversationFields calls detected!', 'color: red; font-weight: bold');
console.log(' This means branders.chat.setUser() either did not run,');
console.log(' or zE was not defined when it ran.');
} else {
state.conversationFieldsCalls.forEach(function(call, i) {
console.log(' Call #' + (i + 1) + ' at ' + call.elapsed);
console.log(' Widget state: ' + call.widgetStateAtCall);
console.log(' Messenger already opened: ' + call.messengerAlreadyOpened);
console.log(' Has callback: ' + call.hasCallback);
console.log(' Fields:');
call.fields.forEach(function(f) {
var status = f.valid ? 'OK' : 'EMPTY';
console.log(' [' + status + '] ' + f.id + ' (' + f.name + ') = "' + f.value + '"');
});
});
}
console.log('');
// 3. Timing Analysis
console.log('%c3. Timing Analysis', 'font-weight: bold; font-size: 13px');
if (state.conversationFieldsCalls.length > 0 && state.messengerOpenCalls.length > 0) {
var firstFields = state.conversationFieldsCalls[0].timestamp;
var firstOpen = state.messengerOpenCalls[0].timestamp;
var delta = firstFields - firstOpen;
if (delta < 0) {
// Fields were set BEFORE open — correct order
console.log(' %cFields set ' + Math.abs(delta) + 'ms BEFORE messenger open — CORRECT ORDER',
'color: green; font-weight: bold');
} else if (delta === 0) {
console.log(' %cFields set at SAME TIME as messenger open — RISKY (race condition possible)',
'color: orange; font-weight: bold');
} else {
console.log(' %cFields set ' + delta + 'ms AFTER messenger open — WRONG ORDER!',
'color: red; font-weight: bold');
console.log(' Zendesk docs: fields are "only applied when end users start a conversation');
console.log(' or send a message in an existing conversation." If the conversation starts');
console.log(' before fields are set, they may be lost.');
}
if (!state.conversationFieldsCalls[0].hasCallback) {
console.log(' %cNo callback used — Zendesk recommends callbacks for reliable delivery',
'color: orange');
console.log(' Recommended pattern:');
console.log(' zE("messenger:set", "conversationFields", fields, function() {');
console.log(' zE("messenger", "open");');
console.log(' });');
}
} else {
if (state.conversationFieldsCalls.length === 0) {
console.log(' Cannot analyze — no conversationFields calls detected');
}
if (state.messengerOpenCalls.length === 0) {
console.log(' Cannot analyze — messenger has not been opened');
}
}
console.log('');
// 4. Existing Conversation Check
console.log('%c4. Conversation State', 'font-weight: bold; font-size: 13px');
if (state.existingConversation === true) {
console.log(' %cExisting conversation detected in localStorage', 'color: orange');
console.log(' conversationFields apply at ticket CREATION time or on next MESSAGE.');
console.log(' If the player already has an open conversation, the widget may resume it');
console.log(' and fields set via the API would only appear on the next Support ticket.');
} else if (state.existingConversation === false) {
console.log(' %cNo existing conversation — fresh session (good)', 'color: green');
} else {
console.log(' Could not determine conversation state');
}
console.log('');
// 5. All zE Calls (timeline)
console.log('%c5. Full zE Call Timeline (' + state.allZeCalls.length + ' calls)',
'font-weight: bold; font-size: 13px');
state.allZeCalls.forEach(function(call) {
console.log(' ' + call.elapsed + ' ' + call.command + ' ' + call.subcommand);
});
console.log('');
// 6. Network Requests
console.log('%c6. Zendesk Network Requests (' + state.networkRequests.length + ' captured)',
'font-weight: bold; font-size: 13px');
var fieldRequests = state.networkRequests.filter(function(r) { return r.bodySnippet; });
if (fieldRequests.length > 0) {
console.log(' Requests containing field/metadata data:');
fieldRequests.forEach(function(r) {
console.log(' ' + r.elapsed + ' ' + r.method + ' ' + r.url);
console.log(' Body: ' + r.bodySnippet);
});
} else {
console.log(' No requests with field/metadata data captured');
console.log(' (Fields may be queued client-side and sent with the first message)');
}
console.log('');
// 7. Errors
if (state.errors.length > 0) {
console.log('%c7. Errors (' + state.errors.length + ')', 'font-weight: bold; font-size: 13px; color: red');
state.errors.forEach(function(e) {
console.log(' ' + e.command + ': ' + e.error);
});
} else {
console.log('%c7. No errors detected', 'font-weight: bold; font-size: 13px; color: green');
}
console.log('');
// 8. Verdict
console.log('%c8. VERDICT', 'font-weight: bold; font-size: 14px');
var issues = [];
if (state.conversationFieldsCalls.length === 0) {
issues.push('conversationFields was NEVER called — check if branders.account.user is populated');
}
state.conversationFieldsCalls.forEach(function(call) {
if (call.messengerAlreadyOpened) {
issues.push('RACE CONDITION: conversationFields called after messenger open');
}
if (!call.hasCallback) {
issues.push('No callback used — async delivery not confirmed');
}
call.fields.forEach(function(f) {
if (!f.valid) {
issues.push('Empty/missing value for field ' + f.id + ' (' + f.name + ')');
}
if (f.name === 'UNKNOWN FIELD') {
issues.push('Unknown field ID: ' + f.id + ' — verify this exists in Zendesk Admin Center');
}
});
});
if (state.existingConversation) {
issues.push('Existing conversation found — fields may not apply to resumed conversation');
}
if (issues.length === 0) {
console.log(' %cNo issues detected from client-side. If fields still missing on tickets,',
'color: green; font-weight: bold');
console.log(' %ccheck Zendesk Admin Center: field must be "editable in portal" and not hidden.',
'color: green');
} else {
console.log(' %cIssues found:', 'color: red; font-weight: bold');
issues.forEach(function(issue, i) {
console.log(' ' + (i + 1) + '. %c' + issue, 'color: red');
});
}
console.log('');
log('SUMMARY', '═══════════════════════════════════════════════════');
console.log('');
console.log('Raw state object available at: window._zeDiagState');
console.log('Run again anytime: window.zeDiagSummary()');
}
// Export for manual invocation
window._zeDiagState = state;
window.zeDiagSummary = printSummary;
// Auto-print summary 10 seconds after messenger opens
var _autoSummaryScheduled = false;
var _origSetInterval = setInterval;
_origSetInterval(function() {
if (state.messengerOpenCalls.length > 0 && !_autoSummaryScheduled) {
_autoSummaryScheduled = true;
log('INFO', 'Messenger opened — diagnostic summary will print in 10 seconds...');
log('INFO', 'Send a message in the chat, then check the summary.');
log('INFO', 'Or run window.zeDiagSummary() at any time.');
setTimeout(printSummary, 10000);
}
}, 1000);
// ─── Ready ───────────────────────────────────────────────────────
console.log('');
console.log('%c╔══════════════════════════════════════════════════════════╗', 'color: #0066cc');
console.log('%c║ Zendesk Diagnostic Script Installed ║', 'color: #0066cc; font-weight: bold');
console.log('%c║ ║', 'color: #0066cc');
console.log('%c║ 1. Click the chat button on the site ║', 'color: #0066cc');
console.log('%c║ 2. Watch the console for intercepted zE calls ║', 'color: #0066cc');
console.log('%c║ 3. Send a test message in the chat ║', 'color: #0066cc');
console.log('%c║ 4. Summary prints automatically after 10 seconds ║', 'color: #0066cc');
console.log('%c║ — or run window.zeDiagSummary() anytime ║', 'color: #0066cc');
console.log('%c║ ║', 'color: #0066cc');
console.log('%c║ State: window._zeDiagState ║', 'color: #0066cc');
console.log('%c╚══════════════════════════════════════════════════════════╝', 'color: #0066cc');
console.log('');
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment