@sharadtech/infralytiqs-sdk 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@sharadtech/infralytiqs-client-publicis-ps",
3
+ "version": "1.0.0-SNAPSHOT",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "Infralytiqs client-specific bootstrap for Publicis Sapient (psassets.publicissapient.com). Reads window.IL_* configuration injected by the host page, initializes the Infralytiqs SDK loaded from the CDN, and hooks the login-page user journey: anonymous page view, credential sign-in submit, Lion Login SSO click, and Terms & Conditions click.",
7
+ "main": "dist/infralytiqs-bootstrap.min.js",
8
+ "files": [
9
+ "dist",
10
+ "src",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "rollup -c rollup.config.mjs",
15
+ "dev": "rollup -c rollup.config.mjs --watch",
16
+ "clean": "rm -rf dist"
17
+ },
18
+ "devDependencies": {
19
+ "@rollup/plugin-node-resolve": "^16.0.1",
20
+ "@rollup/plugin-terser": "^0.4.4",
21
+ "rollup": "^4.34.8"
22
+ }
23
+ }
@@ -0,0 +1,28 @@
1
+ import terser from '@rollup/plugin-terser';
2
+ import resolve from '@rollup/plugin-node-resolve';
3
+ import { readFileSync } from 'node:fs';
4
+
5
+ const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
6
+ const banner = `/*! ${pkg.name} v${pkg.version} | (c) ${new Date().getUTCFullYear()} sharadtech | Built: ${new Date().toISOString()} */`;
7
+
8
+ export default {
9
+ input: 'src/index.js',
10
+ output: [
11
+ {
12
+ file: 'dist/infralytiqs-bootstrap.js',
13
+ format: 'iife',
14
+ name: 'InfralytiqsBootstrapPublicisPS',
15
+ sourcemap: false,
16
+ banner,
17
+ },
18
+ {
19
+ file: 'dist/infralytiqs-bootstrap.min.js',
20
+ format: 'iife',
21
+ name: 'InfralytiqsBootstrapPublicisPS',
22
+ sourcemap: false,
23
+ banner,
24
+ plugins: [terser({ format: { comments: /^!/ } })],
25
+ },
26
+ ],
27
+ plugins: [resolve()],
28
+ };
@@ -0,0 +1,479 @@
1
+ /**
2
+ * Infralytiqs client-specific bootstrap — Publicis Sapient (psassets.publicissapient.com)
3
+ *
4
+ * Pipeline:
5
+ * 1. The host page embeds the Infralytiqs SDK from the CDN:
6
+ * <script src="https://assets.infralytiqs.com/cdn/infralytiqs.min.js"></script>
7
+ * 2. The host page injects per-tenant configuration as `window.IL_*` globals
8
+ * (server-side rendered via the Sightly `Infralytiqs.html` fragment).
9
+ * 3. This bootstrap is loaded after the SDK + config and:
10
+ * a. Reads window.IL_* and Infralytiqs.init() with browser-side enrichment.
11
+ * b. Tracks the four key login-page events:
12
+ * • login_page_view — anonymous landing on the login page
13
+ * • login_attempt — Sign-in button submit (captures typed username,
14
+ * NEVER the password)
15
+ * • sso_click — "Login with Lion Login" SSO button click
16
+ * • terms_click — "Terms and Conditions" link click
17
+ *
18
+ * Selector strategy
19
+ * -----------------
20
+ * The page DOM may evolve, so every selector is overridable via window globals
21
+ * (set BEFORE this bootstrap runs):
22
+ *
23
+ * window.IL_PS_SELECTORS = {
24
+ * loginForm: '#loginForm',
25
+ * signInButton: 'button[type="submit"]',
26
+ * userField: 'input[name="username"]',
27
+ * ssoButton: '[data-il-sso-lion]',
28
+ * termsLink: 'a[href*="terms"]'
29
+ * };
30
+ *
31
+ * If none are provided we fall back to a robust default set that matches by
32
+ * id, name, role, data-attribute, and visible text — so the bootstrap is
33
+ * essentially zero-config for the common case.
34
+ */
35
+ (function () {
36
+ 'use strict';
37
+
38
+ if (window.__ilPsBootstrapped) {
39
+ return;
40
+ }
41
+ window.__ilPsBootstrapped = true;
42
+
43
+ // ─── 1. Read host-page configuration ──────────────────────
44
+ var SERVER_URL = window.IL_SERVER_URL;
45
+ var TENANT_ID = window.IL_TENANT_ID;
46
+ var SITE_ID = window.IL_SITE_ID;
47
+ var DB_NAME = window.IL_DB_NAME;
48
+
49
+ if (!SERVER_URL || !TENANT_ID || !SITE_ID) {
50
+ return;
51
+ }
52
+
53
+ var USER_OVERRIDES = (window.IL_PS_SELECTORS && typeof window.IL_PS_SELECTORS === 'object')
54
+ ? window.IL_PS_SELECTORS
55
+ : {};
56
+
57
+ // ─── 2. Default selectors ────────────────────────────────
58
+ // Each is a comma-separated list of CSS selectors tried in order. The first
59
+ // element that matches the page wins. Text-based fallback below handles
60
+ // pages whose Sign-in / Terms / SSO controls don't carry stable IDs.
61
+ var SELECTORS = {
62
+ loginForm: USER_OVERRIDES.loginForm
63
+ || 'form#loginForm, form[name="loginForm"], form[action*="login"], form[action*="signin"]',
64
+ signInButton: USER_OVERRIDES.signInButton
65
+ || '#signInButton, #signin, #signInBtn, #loginButton, button[name="signin"], button[data-action="signin"], button[type="submit"]',
66
+ userField: USER_OVERRIDES.userField
67
+ || '#username, #userId, #email, input[name="username"], input[name="userId"], input[name="email"], input[type="email"]',
68
+ ssoButton: USER_OVERRIDES.ssoButton
69
+ || '#lionLogin, [data-il-sso-lion], [data-sso="lion-login"], button[name="lion-login"], a[href*="lion-login"], a[href*="lionlogin"]',
70
+ termsLink: USER_OVERRIDES.termsLink
71
+ || 'a[data-il-terms], a#termsLink, a[href*="terms-and-conditions"], a[href*="terms_and_conditions"], a[href*="/terms"], a[href*="terms.html"]'
72
+ };
73
+
74
+ // Visible-text regexes used as a last-resort fallback when none of the CSS
75
+ // selectors above match. Intentionally case-insensitive and tolerant of
76
+ // surrounding whitespace / punctuation.
77
+ var TEXT_PATTERNS = {
78
+ signIn: /\b(sign[- ]?in|log[- ]?in|login)\b/i,
79
+ lionSso: /\blion\s*login\b/i,
80
+ terms: /\bterms?\s*(?:&|and)\s*conditions?\b/i
81
+ };
82
+
83
+ // ─── 3. SDK access + safe wrappers ───────────────────────
84
+ function getApi() {
85
+ var raw = window.Infralytiqs;
86
+ if (!raw) return null;
87
+ return raw && raw.default && typeof raw.default.init === 'function' ? raw.default : raw;
88
+ }
89
+
90
+ function track(eventType, dims, metrics, subtype) {
91
+ var api = getApi();
92
+ if (!api) return;
93
+ try {
94
+ api.track(eventType, dims || {}, metrics || {}, subtype);
95
+ } catch (e) {
96
+ console.error('[IL-PS] track failed', eventType, e);
97
+ }
98
+ }
99
+
100
+ function identify(userId) {
101
+ var api = getApi();
102
+ if (!api) return;
103
+ try { api.identify(userId); } catch (e) { /* noop */ }
104
+ }
105
+
106
+ function flush() {
107
+ var api = getApi();
108
+ if (!api) return Promise.resolve();
109
+ try { return api.flush(); } catch (e) { return Promise.resolve(); }
110
+ }
111
+
112
+ // ─── 4. Browser-side enrichment ──────────────────────────
113
+ // Each event picks up these as `custom_dimensions` so reports can break
114
+ // traffic down by browser, screen, timezone, etc. without per-event work.
115
+ function detectBrowser() {
116
+ var ua = (navigator && navigator.userAgent) || '';
117
+ var rules = [
118
+ { name: 'Edge', re: /\bEdg(?:e|A|iOS)?\/([\d\.]+)/i },
119
+ { name: 'Opera', re: /\bOPR\/([\d\.]+)/i },
120
+ { name: 'Samsung', re: /\bSamsungBrowser\/([\d\.]+)/i },
121
+ { name: 'Firefox', re: /\bFirefox\/([\d\.]+)/i },
122
+ { name: 'Chrome', re: /\bChrom(?:e|ium)\/([\d\.]+)/i },
123
+ { name: 'Safari', re: /\bVersion\/([\d\.]+).*Safari\b/i },
124
+ { name: 'IE', re: /\bMSIE\s([\d\.]+)|\bTrident\/.*\brv:([\d\.]+)/i }
125
+ ];
126
+ for (var i = 0; i < rules.length; i++) {
127
+ var m = ua.match(rules[i].re);
128
+ if (m) {
129
+ return { name: rules[i].name, version: (m[1] || m[2] || '').split('.')[0] };
130
+ }
131
+ }
132
+ return { name: 'Other', version: '' };
133
+ }
134
+
135
+ function safeStr(v) {
136
+ return (v === undefined || v === null) ? '' : String(v);
137
+ }
138
+
139
+ function getViewportSize() {
140
+ try {
141
+ var w = window.innerWidth || (document.documentElement && document.documentElement.clientWidth) || 0;
142
+ var h = window.innerHeight || (document.documentElement && document.documentElement.clientHeight) || 0;
143
+ return w && h ? (w + 'x' + h) : '';
144
+ } catch (e) { return ''; }
145
+ }
146
+
147
+ function getScreenResolution() {
148
+ try {
149
+ var s = window.screen || {};
150
+ var w = s.width || 0;
151
+ var h = s.height || 0;
152
+ return w && h ? (w + 'x' + h) : '';
153
+ } catch (e) { return ''; }
154
+ }
155
+
156
+ function getIanaTimezone() {
157
+ try {
158
+ if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
159
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || '';
160
+ }
161
+ } catch (e) { /* noop */ }
162
+ return '';
163
+ }
164
+
165
+ function getColorScheme() {
166
+ try {
167
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark';
168
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) return 'light';
169
+ } catch (e) { /* noop */ }
170
+ return '';
171
+ }
172
+
173
+ function buildEnrichmentDims() {
174
+ var browser = detectBrowser();
175
+ var dims = {
176
+ client_app: 'publicis-ps',
177
+ client_origin: location.origin,
178
+ client_tenant: TENANT_ID,
179
+ client_site: SITE_ID,
180
+ browser_name: browser.name,
181
+ browser_version: browser.version,
182
+ viewport_size: getViewportSize(),
183
+ screen_resolution: getScreenResolution(),
184
+ timezone_iana: getIanaTimezone(),
185
+ language_full: safeStr(navigator && navigator.language),
186
+ color_scheme: getColorScheme()
187
+ };
188
+ var clean = {};
189
+ for (var k in dims) {
190
+ if (dims.hasOwnProperty(k) && dims[k] !== '' && dims[k] !== undefined) {
191
+ clean[k] = dims[k];
192
+ }
193
+ }
194
+ return clean;
195
+ }
196
+
197
+ // ─── 5. SDK initialization ───────────────────────────────
198
+ function initSdk() {
199
+ var api = getApi();
200
+ if (!api) return false;
201
+ try {
202
+ api.init({
203
+ serverUrl: SERVER_URL,
204
+ tenantId: TENANT_ID,
205
+ siteId: SITE_ID,
206
+ dbName: DB_NAME,
207
+ debug: !!window.IL_DEBUG,
208
+ captureLocation: window.IL_CAPTURE_LOCATION === true,
209
+ globalDimensions: buildEnrichmentDims()
210
+ });
211
+ } catch (e) {
212
+ console.error('[IL-PS] init failed', e);
213
+ return false;
214
+ }
215
+ return true;
216
+ }
217
+
218
+ // ─── 6. DOM helpers ──────────────────────────────────────
219
+ function $(selectorList) {
220
+ if (!selectorList) return null;
221
+ try {
222
+ return document.querySelector(selectorList);
223
+ } catch (e) {
224
+ return null;
225
+ }
226
+ }
227
+
228
+ function visibleText(el) {
229
+ if (!el) return '';
230
+ var t = el.textContent || el.value || el.getAttribute('aria-label') || '';
231
+ return t.replace(/\s+/g, ' ').trim();
232
+ }
233
+
234
+ function closestMatchByText(root, tag, pattern) {
235
+ var nodes = root.querySelectorAll(tag);
236
+ for (var i = 0; i < nodes.length; i++) {
237
+ if (pattern.test(visibleText(nodes[i]))) {
238
+ return nodes[i];
239
+ }
240
+ }
241
+ return null;
242
+ }
243
+
244
+ /**
245
+ * Login-page heuristic: explicit URL hint, or DOM signals (password input
246
+ * inside a form). Anything that matches counts — false positives are
247
+ * harmless (just adds a `login_page` subtype to the page-view).
248
+ */
249
+ function isLoginPage() {
250
+ var href = (location.pathname + location.search + location.hash).toLowerCase();
251
+ if (/(^|\/)login(\.|\/|$|\?)/.test(href)) return true;
252
+ if (/signin/.test(href)) return true;
253
+ if (document.querySelector('input[type="password"]')) return true;
254
+ return false;
255
+ }
256
+
257
+ // ─── 7. Event-specific hooks ─────────────────────────────
258
+
259
+ /**
260
+ * EVENT 1: Anonymous landing on the Login page.
261
+ *
262
+ * The SDK already auto-fires a `page_view` on every load (including this
263
+ * one), but we add an explicit `login_page_view` event so the Reports
264
+ * "login funnel" panel can distinguish login-page traffic from generic
265
+ * site traffic and segment anonymous visitors before identify() fires.
266
+ */
267
+ function trackLoginPageView() {
268
+ if (!isLoginPage()) return;
269
+ track('login_page_view', {
270
+ page_type: 'login',
271
+ auth_state: 'anonymous',
272
+ page_url: location.href,
273
+ referrer: document.referrer || ''
274
+ }, {}, 'anonymous');
275
+ }
276
+
277
+ /**
278
+ * EVENT 2: Submit username + password via Sign-in button.
279
+ *
280
+ * Captures the typed username (used as a soft identifier) but NEVER the
281
+ * password. The submit listener on the form also covers Enter-key
282
+ * submissions; the button click listener covers buttons that intercept
283
+ * the submit and POST via JS.
284
+ */
285
+ function hookSignInSubmit() {
286
+ var form = $(SELECTORS.loginForm);
287
+ var btn = $(SELECTORS.signInButton);
288
+
289
+ // Last-resort fallback: walk the page for a button/<a>/role=button
290
+ // whose visible text matches /sign[- ]?in|log[- ]?in|login/.
291
+ if (!btn) {
292
+ btn = closestMatchByText(document, 'button, a, [role="button"], input[type="submit"]', TEXT_PATTERNS.signIn);
293
+ }
294
+
295
+ function fire(reason) {
296
+ var userInput = $(SELECTORS.userField);
297
+ var typedUser = (userInput && (userInput.value || '').trim()) || '';
298
+ if (typedUser) {
299
+ identify(typedUser);
300
+ }
301
+ track('login_attempt', {
302
+ method: 'credentials',
303
+ attempt_user: typedUser,
304
+ submit_reason: reason || 'click',
305
+ page_url: location.href
306
+ });
307
+ flush();
308
+ }
309
+
310
+ if (btn) {
311
+ btn.addEventListener('click', function () { fire('click'); }, { capture: true });
312
+ }
313
+ if (form) {
314
+ form.addEventListener('submit', function () { fire('submit'); }, { capture: true });
315
+ }
316
+ }
317
+
318
+ /**
319
+ * EVENT 3: Click on the SSO "Login with Lion Login" button.
320
+ */
321
+ function hookSsoClick() {
322
+ var btn = $(SELECTORS.ssoButton);
323
+ if (!btn) {
324
+ btn = closestMatchByText(document, 'button, a, [role="button"]', TEXT_PATTERNS.lionSso);
325
+ }
326
+ if (!btn) return;
327
+ btn.addEventListener('click', function () {
328
+ track('sso_click', {
329
+ method: 'lion_login',
330
+ sso_provider: 'lion',
331
+ button_text: visibleText(btn).substring(0, 120),
332
+ page_url: location.href
333
+ });
334
+ flush();
335
+ }, { capture: true });
336
+ }
337
+
338
+ /**
339
+ * EVENT 4: Click on the "Terms and Conditions" link.
340
+ */
341
+ function hookTermsClick() {
342
+ var link = $(SELECTORS.termsLink);
343
+ if (!link) {
344
+ link = closestMatchByText(document, 'a, button, [role="link"]', TEXT_PATTERNS.terms);
345
+ }
346
+ if (!link) return;
347
+ link.addEventListener('click', function () {
348
+ track('terms_click', {
349
+ link_url: (link.getAttribute && link.getAttribute('href')) || '',
350
+ link_text: visibleText(link).substring(0, 120),
351
+ page_url: location.href
352
+ });
353
+ flush();
354
+ }, { capture: true });
355
+ }
356
+
357
+ // ─── 8. Orchestration ────────────────────────────────────
358
+
359
+ /**
360
+ * The login page on psassets.publicissapient.com is a single-page
361
+ * application — controls may render after initial DOMContentLoaded. We
362
+ * try once on ready, then re-attempt via a short MutationObserver to
363
+ * catch late-mounted buttons / forms. Hooks are idempotent thanks to
364
+ * per-element flags below.
365
+ */
366
+ var HOOK_FLAG = '__il_ps_hooked__';
367
+
368
+ function safeHookAll() {
369
+ function once(el, name, fn) {
370
+ if (!el || el[HOOK_FLAG + name]) return;
371
+ el[HOOK_FLAG + name] = true;
372
+ fn();
373
+ }
374
+ // Re-resolve every cycle because the SPA can swap nodes underneath us.
375
+ var form = $(SELECTORS.loginForm);
376
+ var signBtn = $(SELECTORS.signInButton)
377
+ || closestMatchByText(document, 'button, a, [role="button"], input[type="submit"]', TEXT_PATTERNS.signIn);
378
+ var ssoBtn = $(SELECTORS.ssoButton)
379
+ || closestMatchByText(document, 'button, a, [role="button"]', TEXT_PATTERNS.lionSso);
380
+ var terms = $(SELECTORS.termsLink)
381
+ || closestMatchByText(document, 'a, button, [role="link"]', TEXT_PATTERNS.terms);
382
+
383
+ once(form, 'form', function () {
384
+ form.addEventListener('submit', function () {
385
+ var userInput = $(SELECTORS.userField);
386
+ var typedUser = (userInput && (userInput.value || '').trim()) || '';
387
+ if (typedUser) identify(typedUser);
388
+ track('login_attempt', {
389
+ method: 'credentials',
390
+ attempt_user: typedUser,
391
+ submit_reason: 'submit',
392
+ page_url: location.href
393
+ });
394
+ flush();
395
+ }, { capture: true });
396
+ });
397
+
398
+ once(signBtn, 'sign', function () {
399
+ signBtn.addEventListener('click', function () {
400
+ var userInput = $(SELECTORS.userField);
401
+ var typedUser = (userInput && (userInput.value || '').trim()) || '';
402
+ if (typedUser) identify(typedUser);
403
+ track('login_attempt', {
404
+ method: 'credentials',
405
+ attempt_user: typedUser,
406
+ submit_reason: 'click',
407
+ page_url: location.href
408
+ });
409
+ flush();
410
+ }, { capture: true });
411
+ });
412
+
413
+ once(ssoBtn, 'sso', function () {
414
+ ssoBtn.addEventListener('click', function () {
415
+ track('sso_click', {
416
+ method: 'lion_login',
417
+ sso_provider: 'lion',
418
+ button_text: visibleText(ssoBtn).substring(0, 120),
419
+ page_url: location.href
420
+ });
421
+ flush();
422
+ }, { capture: true });
423
+ });
424
+
425
+ once(terms, 'terms', function () {
426
+ terms.addEventListener('click', function () {
427
+ track('terms_click', {
428
+ link_url: (terms.getAttribute && terms.getAttribute('href')) || '',
429
+ link_text: visibleText(terms).substring(0, 120),
430
+ page_url: location.href
431
+ });
432
+ flush();
433
+ }, { capture: true });
434
+ });
435
+ }
436
+
437
+ function start() {
438
+ if (!initSdk()) {
439
+ console.warn('[IL-PS] Infralytiqs SDK not loaded — bootstrap aborted');
440
+ return;
441
+ }
442
+ trackLoginPageView();
443
+ safeHookAll();
444
+
445
+ if (typeof MutationObserver === 'function') {
446
+ var mo = new MutationObserver(function () { safeHookAll(); });
447
+ mo.observe(document.documentElement || document.body, {
448
+ childList: true,
449
+ subtree: true
450
+ });
451
+ // Stop watching after 20 seconds — by then the SPA has settled and
452
+ // anything we missed isn't going to appear from a page-load mutation.
453
+ setTimeout(function () { mo.disconnect(); }, 20000);
454
+ }
455
+ }
456
+
457
+ // ─── 9. Entry point ──────────────────────────────────────
458
+ function waitForSdkAndStart() {
459
+ var attempts = 0;
460
+ var max = 50;
461
+ (function tick() {
462
+ if (getApi()) {
463
+ start();
464
+ return;
465
+ }
466
+ if (++attempts >= max) {
467
+ console.warn('[IL-PS] Infralytiqs SDK never appeared after ' + (max * 100) + 'ms');
468
+ return;
469
+ }
470
+ setTimeout(tick, 100);
471
+ })();
472
+ }
473
+
474
+ if (document.readyState === 'loading') {
475
+ document.addEventListener('DOMContentLoaded', waitForSdkAndStart, { once: true });
476
+ } else {
477
+ waitForSdkAndStart();
478
+ }
479
+ })();
@@ -1,4 +1,4 @@
1
- /*! infralytiqs-sdk v1.0.0 | (c) 2026 sharadtech | License: See LICENSE | Built: 2026-05-24T02:39:05.253Z */
1
+ /*! infralytiqs-sdk v1.0.1 | (c) 2026 sharadtech | License: See LICENSE | Built: 2026-05-27T12:40:33.717Z */
2
2
  var Infralytiqs = (function (exports) {
3
3
  'use strict';
4
4
 
@@ -820,6 +820,95 @@ var Infralytiqs = (function (exports) {
820
820
  }
821
821
  }
822
822
 
823
+ /**
824
+ * Client-bootstrap auto-loader.
825
+ *
826
+ * When the SDK script first executes on a host page, it looks for a
827
+ * `window.IL_CLIENT_BOOTSTRAP_LIB` global declared by the host (in AEM this
828
+ * is injected by the `Infralytiqs.html` Sightly fragment). If present, the
829
+ * SDK fetches that client-specific bootstrap JavaScript from the CDN and
830
+ * appends it to <head>. The bootstrap is then responsible for calling
831
+ * `Infralytiqs.init({...})` with tenant/site configuration and wiring any
832
+ * site-specific event hooks.
833
+ *
834
+ * Path resolution
835
+ * ---------------
836
+ * `IL_CLIENT_BOOTSTRAP_LIB` may be either:
837
+ * • A fully qualified URL ("https://…/bootstrap.min.js") — used as-is.
838
+ * • A path starting with "/" (e.g. "/cl/publicis/ps/bootstrap.min.js") —
839
+ * resolved against the SDK's own origin (the CDN host that served this
840
+ * script). This lets every client bootstrap live in the same S3 bucket
841
+ * as the SDK without the host page needing to know the CDN hostname.
842
+ * • A relative path — resolved against the SDK's own URL.
843
+ *
844
+ * The SDK script URL is captured at module-evaluation time via
845
+ * `document.currentScript`, which is only reliable during the SDK's
846
+ * synchronous IIFE execution. A defensive fallback scans existing <script>
847
+ * tags for one whose src matches the SDK bundle naming convention.
848
+ */
849
+ const SDK_SCRIPT_URL = (() => {
850
+ try {
851
+ if (typeof document === 'undefined')
852
+ return '';
853
+ const current = document.currentScript;
854
+ if (current && current.src)
855
+ return current.src;
856
+ const scripts = document.getElementsByTagName('script');
857
+ for (let i = scripts.length - 1; i >= 0; i--) {
858
+ const src = scripts[i].src;
859
+ if (src && /infralytiqs(\.min)?\.js(\?.*)?$/.test(src))
860
+ return src;
861
+ }
862
+ }
863
+ catch (_a) {
864
+ /* noop — fall through to empty */
865
+ }
866
+ return '';
867
+ })();
868
+ const BOOTSTRAP_FLAG = '__il_bootstrap_loaded__';
869
+ function resolveBootstrapUrl(rawPath) {
870
+ if (/^https?:\/\//i.test(rawPath))
871
+ return rawPath;
872
+ if (!SDK_SCRIPT_URL)
873
+ return rawPath;
874
+ try {
875
+ return new URL(rawPath, SDK_SCRIPT_URL).href;
876
+ }
877
+ catch (_a) {
878
+ return rawPath;
879
+ }
880
+ }
881
+ /**
882
+ * Injects a <script src="…"> tag for the client bootstrap declared on the
883
+ * host page. Idempotent — a window sentinel guards against double injection
884
+ * if the SDK is somehow evaluated twice.
885
+ */
886
+ function loadClientBootstrap() {
887
+ if (typeof window === 'undefined' || typeof document === 'undefined')
888
+ return;
889
+ const w = window;
890
+ if (w[BOOTSTRAP_FLAG])
891
+ return;
892
+ const lib = w.IL_CLIENT_BOOTSTRAP_LIB;
893
+ if (!lib || typeof lib !== 'string')
894
+ return;
895
+ const absoluteUrl = resolveBootstrapUrl(lib);
896
+ w[BOOTSTRAP_FLAG] = true;
897
+ const s = document.createElement('script');
898
+ s.src = absoluteUrl;
899
+ s.async = true;
900
+ s.setAttribute('data-il-bootstrap', 'client');
901
+ s.onerror = () => {
902
+ if (typeof console !== 'undefined') {
903
+ console.error('[Infralytiqs] failed to load client bootstrap from', absoluteUrl);
904
+ }
905
+ };
906
+ const parent = document.head || document.documentElement;
907
+ if (parent) {
908
+ parent.appendChild(s);
909
+ }
910
+ }
911
+
823
912
  /**
824
913
  * st-ck-server license module id for Infralytiqs (Companies.productPlans[].module,
825
914
  * Users.moduleAndRole[].module). Kept here for documentation and optional host-app use.
@@ -897,6 +986,20 @@ var Infralytiqs = (function (exports) {
897
986
  tracker.destroy();
898
987
  },
899
988
  };
989
+ // ─── Auto-load the client-specific bootstrap ────────────────────────────
990
+ // When the SDK script tag executes, look for `window.IL_CLIENT_BOOTSTRAP_LIB`
991
+ // declared by the host page (e.g. by the AEM Infralytiqs.html Sightly
992
+ // fragment) and inject a <script> tag for it. The bootstrap is then
993
+ // responsible for calling `Infralytiqs.init({...})` and wiring up any
994
+ // site-specific event hooks. Resolution against the SDK's own CDN origin
995
+ // is handled inside the loader, so host pages can use absolute-path
996
+ // shortcuts like "/cl/publicis/ps/infralytiqs-bootstrap.min.js".
997
+ try {
998
+ loadClientBootstrap();
999
+ }
1000
+ catch (_a) {
1001
+ /* never throw out of SDK script-tag evaluation */
1002
+ }
900
1003
 
901
1004
  exports.LICENSE_MODULE_ID = LICENSE_MODULE_ID;
902
1005
  exports.default = Infralytiqs;