|
16 | 16 |
|
17 | 17 | var Audience = window.ImmutableAudience.Audience; |
18 | 18 | var IdentityType = window.ImmutableAudience.IdentityType; |
| 19 | + var SDK_VERSION = (window.ImmutableAudience && window.ImmutableAudience.version) || 'unknown'; |
19 | 20 |
|
20 | 21 | // State |
21 | 22 | var audience = null; |
|
24 | 25 | var logEntries = []; |
25 | 26 | var MAX_LOG_ENTRIES = 500; |
26 | 27 |
|
| 28 | + // Track whether the user has scrolled up inside the event log. When true, |
| 29 | + // renderLog stops auto-pinning to the bottom so new entries don't yank the |
| 30 | + // user away from whatever they were reading. |
| 31 | + var logAutoScroll = true; |
| 32 | + var LOG_BOTTOM_THRESHOLD = 20; // px from bottom still counts as "at the bottom" |
| 33 | + |
27 | 34 | // DOM helpers |
28 | 35 | function $(id) { return document.getElementById(id); } |
29 | 36 |
|
|
35 | 42 | return el; |
36 | 43 | } |
37 | 44 |
|
| 45 | + function isLogAtBottom() { |
| 46 | + var el = $('log'); |
| 47 | + if (!el) return true; |
| 48 | + return el.scrollHeight - el.scrollTop - el.clientHeight <= LOG_BOTTOM_THRESHOLD; |
| 49 | + } |
| 50 | + |
38 | 51 | function getRadio(name) { |
39 | 52 | var radios = document.querySelectorAll('input[name="' + name + '"]'); |
40 | 53 | for (var i = 0; i < radios.length; i++) { |
|
211 | 224 |
|
212 | 225 | container.appendChild(entry); |
213 | 226 | } |
214 | | - container.scrollTop = container.scrollHeight; |
| 227 | + if (logAutoScroll) { |
| 228 | + container.scrollTop = container.scrollHeight; |
| 229 | + } |
215 | 230 | var countText = logEntries.length + ' entries'; |
216 | 231 | if (logEntries.length >= MAX_LOG_ENTRIES) { |
217 | 232 | countText += ' (capped at ' + MAX_LOG_ENTRIES + ')'; |
218 | 233 | } |
219 | 234 | text($('log-count'), countText); |
220 | 235 | } |
221 | 236 |
|
| 237 | + function onCopyLog() { |
| 238 | + var btn = $('btn-copy-log'); |
| 239 | + if (!btn) return; |
| 240 | + var originalText = btn.textContent; |
| 241 | + function flashLabel(msg) { |
| 242 | + btn.textContent = msg; |
| 243 | + setTimeout(function () { |
| 244 | + btn.textContent = originalText; |
| 245 | + }, 1500); |
| 246 | + } |
| 247 | + |
| 248 | + var payload; |
| 249 | + try { |
| 250 | + payload = JSON.stringify(logEntries, null, 2); |
| 251 | + } catch (err) { |
| 252 | + log('WARN', 'Copy session failed: ' + String(err && err.message || err), 'warn'); |
| 253 | + flashLabel('Copy failed'); |
| 254 | + return; |
| 255 | + } |
| 256 | + |
| 257 | + if (!navigator.clipboard || !navigator.clipboard.writeText) { |
| 258 | + log('WARN', 'Clipboard API unavailable in this browser', 'warn'); |
| 259 | + flashLabel('Not supported'); |
| 260 | + return; |
| 261 | + } |
| 262 | + |
| 263 | + navigator.clipboard.writeText(payload).then(function () { |
| 264 | + flashLabel('Copied!'); |
| 265 | + }).catch(function (err) { |
| 266 | + log('WARN', 'Copy session failed: ' + String(err && err.message || err), 'warn'); |
| 267 | + flashLabel('Copy failed'); |
| 268 | + }); |
| 269 | + } |
| 270 | + |
222 | 271 | function clearLog() { |
223 | 272 | logEntries = []; |
| 273 | + logAutoScroll = true; |
224 | 274 | renderLog(); |
225 | 275 | } |
226 | 276 |
|
|
260 | 310 | initBtn.disabled = pkInput.value.trim().length === 0; |
261 | 311 | } |
262 | 312 |
|
| 313 | + // Sync the Alias button's enabled state based on from/to inputs. |
| 314 | + // Mirrors core's isAliasValid: button is disabled if the SDK is not initialised, |
| 315 | + // if either ID is empty, or if (fromId, fromType) === (toId, toType). |
| 316 | + function syncAliasButton() { |
| 317 | + var btn = $('btn-alias'); |
| 318 | + if (!audience) { btn.disabled = true; return; } |
| 319 | + var fromId = $('alias-from-id').value.trim(); |
| 320 | + var toId = $('alias-to-id').value.trim(); |
| 321 | + var fromType = $('alias-from-type').value; |
| 322 | + var toType = $('alias-to-type').value; |
| 323 | + if (!fromId || !toId) { btn.disabled = true; return; } |
| 324 | + if (fromId === toId && fromType === toType) { btn.disabled = true; return; } |
| 325 | + btn.disabled = false; |
| 326 | + } |
| 327 | + |
263 | 328 | // Enable/disable controls based on init state |
264 | 329 | function setInitState(on) { |
265 | 330 | $('btn-init').disabled = on; |
|
279 | 344 | var consentRadios = document.querySelectorAll('input[name="initial-consent"]'); |
280 | 345 | for (var j = 0; j < consentRadios.length; j++) consentRadios[j].disabled = on; |
281 | 346 | if (!on) syncInitEnabled(); |
| 347 | + // Alias button needs the finer-grained check (inputs + equality). Called |
| 348 | + // unconditionally because syncAliasButton handles both the enabled and |
| 349 | + // disabled cases internally. |
| 350 | + syncAliasButton(); |
282 | 351 | } |
283 | 352 |
|
284 | 353 | // onError handler passed to Audience.init |
|
518 | 587 | $('btn-shutdown').addEventListener('click', onShutdown); |
519 | 588 | $('btn-reset').addEventListener('click', onReset); |
520 | 589 | $('btn-flush').addEventListener('click', onFlush); |
| 590 | + $('btn-copy-log').addEventListener('click', onCopyLog); |
521 | 591 | $('btn-clear-log').addEventListener('click', clearLog); |
| 592 | + $('log').addEventListener('scroll', function () { |
| 593 | + logAutoScroll = isLogAtBottom(); |
| 594 | + }); |
522 | 595 |
|
523 | 596 | $('btn-consent-none').addEventListener('click', function () { onSetConsent('none'); }); |
524 | 597 | $('btn-consent-anon').addEventListener('click', function () { onSetConsent('anonymous'); }); |
|
527 | 600 | $('btn-page').addEventListener('click', onPage); |
528 | 601 | $('btn-track').addEventListener('click', onTrack); |
529 | 602 |
|
| 603 | + var versionEl = $('sdk-version'); |
| 604 | + if (versionEl) versionEl.textContent = SDK_VERSION; |
| 605 | + |
530 | 606 | populateIdentityDropdowns(); |
531 | 607 | initDemoGutter(); |
532 | 608 | $('btn-identify').addEventListener('click', onIdentify); |
533 | 609 | $('btn-identify-traits').addEventListener('click', onIdentifyTraits); |
534 | 610 | $('btn-alias').addEventListener('click', onAlias); |
535 | 611 |
|
| 612 | + // Real-time alias validity: disable the button when either ID is empty or |
| 613 | + // when (fromId, fromType) === (toId, toType). Matches the design spec and |
| 614 | + // mirrors the SDK's isAliasValid() — user gets immediate feedback instead |
| 615 | + // of discovering the problem only after clicking. |
| 616 | + $('alias-from-id').addEventListener('input', syncAliasButton); |
| 617 | + $('alias-to-id').addEventListener('input', syncAliasButton); |
| 618 | + $('alias-from-type').addEventListener('change', syncAliasButton); |
| 619 | + $('alias-to-type').addEventListener('change', syncAliasButton); |
| 620 | + |
536 | 621 | // Enable Init only when the publishable key input has non-whitespace content. |
537 | 622 | $('pk').addEventListener('input', syncInitEnabled); |
538 | 623 | syncInitEnabled(); |
|
0 commit comments