@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.
- package/buildScripts/Jenkinsfile.deploy +317 -49
- package/clients/publicis/ps/README.md +84 -0
- package/clients/publicis/ps/package-lock.json +815 -0
- package/clients/publicis/ps/package.json +23 -0
- package/clients/publicis/ps/rollup.config.mjs +28 -0
- package/clients/publicis/ps/src/index.js +479 -0
- package/dist/infralytiqs.js +104 -1
- package/dist/infralytiqs.min.js +2 -2
- package/package.json +1 -1
- package/src/bootstrapLoader.ts +94 -0
- package/src/index.ts +15 -0
|
@@ -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
|
+
})();
|
package/dist/infralytiqs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! infralytiqs-sdk v1.0.
|
|
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;
|