@clianta/sdk 1.6.4 → 1.6.6

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Clianta SDK v1.6.4
2
+ * Clianta SDK v1.6.6
3
3
  * (c) 2026 Clianta
4
4
  * Released under the MIT License.
5
5
  */
@@ -13,7 +13,7 @@ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentS
13
13
  * @see SDK_VERSION in core/config.ts
14
14
  */
15
15
  /** SDK Version */
16
- const SDK_VERSION = '1.6.4';
16
+ const SDK_VERSION = '1.6.6';
17
17
  /** Default API endpoint — reads from env or falls back to localhost */
18
18
  const getDefaultApiEndpoint = () => {
19
19
  // Next.js (process.env)
@@ -52,6 +52,7 @@ const DEFAULT_PLUGINS = [
52
52
  'exitIntent',
53
53
  'errors',
54
54
  'performance',
55
+ 'autoIdentify',
55
56
  ];
56
57
  /** Default configuration values */
57
58
  const DEFAULT_CONFIG = {
@@ -2244,6 +2245,316 @@ class PopupFormsPlugin extends BasePlugin {
2244
2245
  }
2245
2246
  }
2246
2247
 
2248
+ /**
2249
+ * Clianta SDK - Auto-Identify Plugin
2250
+ * Automatically detects logged-in users by checking JWT tokens in
2251
+ * cookies, localStorage, and sessionStorage. Works with any auth provider:
2252
+ * Clerk, Firebase, Auth0, Supabase, NextAuth, Passport, custom JWT, etc.
2253
+ *
2254
+ * How it works:
2255
+ * 1. On init + periodically, scans for JWT tokens
2256
+ * 2. Decodes the JWT payload (base64, no secret needed)
2257
+ * 3. Extracts email/name from standard JWT claims
2258
+ * 4. Calls tracker.identify() automatically
2259
+ *
2260
+ * @see SDK_VERSION in core/config.ts
2261
+ */
2262
+ /** Known auth cookie patterns and their JWT locations */
2263
+ const AUTH_COOKIE_PATTERNS = [
2264
+ // Clerk
2265
+ '__session',
2266
+ '__clerk_db_jwt',
2267
+ // NextAuth
2268
+ 'next-auth.session-token',
2269
+ '__Secure-next-auth.session-token',
2270
+ // Supabase
2271
+ 'sb-access-token',
2272
+ // Auth0
2273
+ 'auth0.is.authenticated',
2274
+ // Firebase — uses localStorage, handled separately
2275
+ // Generic patterns
2276
+ 'token',
2277
+ 'jwt',
2278
+ 'access_token',
2279
+ 'session_token',
2280
+ 'auth_token',
2281
+ 'id_token',
2282
+ ];
2283
+ /** localStorage/sessionStorage key patterns for auth tokens */
2284
+ const STORAGE_KEY_PATTERNS = [
2285
+ // Supabase
2286
+ 'sb-',
2287
+ 'supabase.auth.',
2288
+ // Firebase
2289
+ 'firebase:authUser:',
2290
+ // Auth0
2291
+ 'auth0spajs',
2292
+ '@@auth0spajs@@',
2293
+ // Generic
2294
+ 'token',
2295
+ 'jwt',
2296
+ 'auth',
2297
+ 'user',
2298
+ 'session',
2299
+ ];
2300
+ /** Standard JWT claim fields for email */
2301
+ const EMAIL_CLAIMS = ['email', 'sub', 'preferred_username', 'user_email', 'mail'];
2302
+ const NAME_CLAIMS = ['name', 'full_name', 'display_name', 'given_name'];
2303
+ const FIRST_NAME_CLAIMS = ['given_name', 'first_name', 'firstName'];
2304
+ const LAST_NAME_CLAIMS = ['family_name', 'last_name', 'lastName'];
2305
+ class AutoIdentifyPlugin extends BasePlugin {
2306
+ constructor() {
2307
+ super(...arguments);
2308
+ this.name = 'autoIdentify';
2309
+ this.checkInterval = null;
2310
+ this.identifiedEmail = null;
2311
+ this.checkCount = 0;
2312
+ this.MAX_CHECKS = 30; // Stop checking after ~5 minutes
2313
+ this.CHECK_INTERVAL_MS = 10000; // Check every 10 seconds
2314
+ }
2315
+ init(tracker) {
2316
+ super.init(tracker);
2317
+ if (typeof window === 'undefined')
2318
+ return;
2319
+ // First check after 2 seconds (give auth providers time to init)
2320
+ setTimeout(() => {
2321
+ try {
2322
+ this.checkForAuthUser();
2323
+ }
2324
+ catch { /* silently fail */ }
2325
+ }, 2000);
2326
+ // Then check periodically
2327
+ this.checkInterval = setInterval(() => {
2328
+ this.checkCount++;
2329
+ if (this.checkCount >= this.MAX_CHECKS) {
2330
+ if (this.checkInterval) {
2331
+ clearInterval(this.checkInterval);
2332
+ this.checkInterval = null;
2333
+ }
2334
+ return;
2335
+ }
2336
+ try {
2337
+ this.checkForAuthUser();
2338
+ }
2339
+ catch { /* silently fail */ }
2340
+ }, this.CHECK_INTERVAL_MS);
2341
+ }
2342
+ destroy() {
2343
+ if (this.checkInterval) {
2344
+ clearInterval(this.checkInterval);
2345
+ this.checkInterval = null;
2346
+ }
2347
+ super.destroy();
2348
+ }
2349
+ /**
2350
+ * Main check — scan all sources for auth tokens
2351
+ */
2352
+ checkForAuthUser() {
2353
+ if (!this.tracker || this.identifiedEmail)
2354
+ return;
2355
+ try {
2356
+ // 1. Check cookies for JWTs
2357
+ const cookieUser = this.checkCookies();
2358
+ if (cookieUser) {
2359
+ this.identifyUser(cookieUser);
2360
+ return;
2361
+ }
2362
+ }
2363
+ catch { /* cookie access blocked */ }
2364
+ try {
2365
+ // 2. Check localStorage
2366
+ if (typeof localStorage !== 'undefined') {
2367
+ const localUser = this.checkStorage(localStorage);
2368
+ if (localUser) {
2369
+ this.identifyUser(localUser);
2370
+ return;
2371
+ }
2372
+ }
2373
+ }
2374
+ catch { /* localStorage access blocked */ }
2375
+ try {
2376
+ // 3. Check sessionStorage
2377
+ if (typeof sessionStorage !== 'undefined') {
2378
+ const sessionUser = this.checkStorage(sessionStorage);
2379
+ if (sessionUser) {
2380
+ this.identifyUser(sessionUser);
2381
+ return;
2382
+ }
2383
+ }
2384
+ }
2385
+ catch { /* sessionStorage access blocked */ }
2386
+ }
2387
+ /**
2388
+ * Identify the user and stop checking
2389
+ */
2390
+ identifyUser(user) {
2391
+ if (!this.tracker || this.identifiedEmail === user.email)
2392
+ return;
2393
+ this.identifiedEmail = user.email;
2394
+ this.tracker.identify(user.email, {
2395
+ firstName: user.firstName,
2396
+ lastName: user.lastName,
2397
+ });
2398
+ // Stop interval — we found the user
2399
+ if (this.checkInterval) {
2400
+ clearInterval(this.checkInterval);
2401
+ this.checkInterval = null;
2402
+ }
2403
+ }
2404
+ /**
2405
+ * Scan cookies for JWT tokens
2406
+ */
2407
+ checkCookies() {
2408
+ if (typeof document === 'undefined')
2409
+ return null;
2410
+ try {
2411
+ const cookies = document.cookie.split(';').map(c => c.trim());
2412
+ for (const cookie of cookies) {
2413
+ const [name, ...valueParts] = cookie.split('=');
2414
+ const value = valueParts.join('=');
2415
+ const cookieName = name.trim().toLowerCase();
2416
+ // Check if this cookie matches known auth patterns
2417
+ const isAuthCookie = AUTH_COOKIE_PATTERNS.some(pattern => cookieName.includes(pattern.toLowerCase()));
2418
+ if (isAuthCookie && value) {
2419
+ const user = this.extractUserFromToken(decodeURIComponent(value));
2420
+ if (user)
2421
+ return user;
2422
+ }
2423
+ }
2424
+ }
2425
+ catch {
2426
+ // Cookie access may fail in some environments
2427
+ }
2428
+ return null;
2429
+ }
2430
+ /**
2431
+ * Scan localStorage or sessionStorage for auth tokens
2432
+ */
2433
+ checkStorage(storage) {
2434
+ try {
2435
+ for (let i = 0; i < storage.length; i++) {
2436
+ const key = storage.key(i);
2437
+ if (!key)
2438
+ continue;
2439
+ const keyLower = key.toLowerCase();
2440
+ const isAuthKey = STORAGE_KEY_PATTERNS.some(pattern => keyLower.includes(pattern.toLowerCase()));
2441
+ if (isAuthKey) {
2442
+ const value = storage.getItem(key);
2443
+ if (!value)
2444
+ continue;
2445
+ // Try as direct JWT
2446
+ const user = this.extractUserFromToken(value);
2447
+ if (user)
2448
+ return user;
2449
+ // Try as JSON containing a token
2450
+ try {
2451
+ const json = JSON.parse(value);
2452
+ const user = this.extractUserFromJson(json);
2453
+ if (user)
2454
+ return user;
2455
+ }
2456
+ catch {
2457
+ // Not JSON, skip
2458
+ }
2459
+ }
2460
+ }
2461
+ }
2462
+ catch {
2463
+ // Storage access may fail (iframe, security restrictions)
2464
+ }
2465
+ return null;
2466
+ }
2467
+ /**
2468
+ * Try to extract user info from a JWT token string
2469
+ */
2470
+ extractUserFromToken(token) {
2471
+ // JWT format: header.payload.signature
2472
+ const parts = token.split('.');
2473
+ if (parts.length !== 3)
2474
+ return null;
2475
+ try {
2476
+ const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
2477
+ return this.extractUserFromClaims(payload);
2478
+ }
2479
+ catch {
2480
+ return null;
2481
+ }
2482
+ }
2483
+ /**
2484
+ * Extract user info from a JSON object (e.g., Firebase auth user stored in localStorage)
2485
+ */
2486
+ extractUserFromJson(data) {
2487
+ if (!data || typeof data !== 'object')
2488
+ return null;
2489
+ // Direct user object
2490
+ const user = this.extractUserFromClaims(data);
2491
+ if (user)
2492
+ return user;
2493
+ // Nested: { user: { email } } or { data: { user: { email } } }
2494
+ for (const key of ['user', 'data', 'session', 'currentUser', 'authUser', 'access_token', 'token']) {
2495
+ if (data[key]) {
2496
+ if (typeof data[key] === 'string') {
2497
+ // Might be a JWT inside JSON
2498
+ const tokenUser = this.extractUserFromToken(data[key]);
2499
+ if (tokenUser)
2500
+ return tokenUser;
2501
+ }
2502
+ else if (typeof data[key] === 'object') {
2503
+ const nestedUser = this.extractUserFromClaims(data[key]);
2504
+ if (nestedUser)
2505
+ return nestedUser;
2506
+ }
2507
+ }
2508
+ }
2509
+ return null;
2510
+ }
2511
+ /**
2512
+ * Extract user from JWT claims or user object
2513
+ */
2514
+ extractUserFromClaims(claims) {
2515
+ if (!claims || typeof claims !== 'object')
2516
+ return null;
2517
+ // Find email
2518
+ let email = null;
2519
+ for (const claim of EMAIL_CLAIMS) {
2520
+ const value = claims[claim];
2521
+ if (value && typeof value === 'string' && value.includes('@') && value.includes('.')) {
2522
+ email = value;
2523
+ break;
2524
+ }
2525
+ }
2526
+ if (!email)
2527
+ return null;
2528
+ // Find name
2529
+ let firstName;
2530
+ let lastName;
2531
+ for (const claim of FIRST_NAME_CLAIMS) {
2532
+ if (claims[claim] && typeof claims[claim] === 'string') {
2533
+ firstName = claims[claim];
2534
+ break;
2535
+ }
2536
+ }
2537
+ for (const claim of LAST_NAME_CLAIMS) {
2538
+ if (claims[claim] && typeof claims[claim] === 'string') {
2539
+ lastName = claims[claim];
2540
+ break;
2541
+ }
2542
+ }
2543
+ // If no first/last name, try full name
2544
+ if (!firstName) {
2545
+ for (const claim of NAME_CLAIMS) {
2546
+ if (claims[claim] && typeof claims[claim] === 'string') {
2547
+ const parts = claims[claim].split(' ');
2548
+ firstName = parts[0];
2549
+ lastName = lastName || parts.slice(1).join(' ') || undefined;
2550
+ break;
2551
+ }
2552
+ }
2553
+ }
2554
+ return { email, firstName, lastName };
2555
+ }
2556
+ }
2557
+
2247
2558
  /**
2248
2559
  * Clianta SDK - Plugins Index
2249
2560
  * Version is defined in core/config.ts as SDK_VERSION
@@ -2273,6 +2584,8 @@ function getPlugin(name) {
2273
2584
  return new PerformancePlugin();
2274
2585
  case 'popupForms':
2275
2586
  return new PopupFormsPlugin();
2587
+ case 'autoIdentify':
2588
+ return new AutoIdentifyPlugin();
2276
2589
  default:
2277
2590
  throw new Error(`Unknown plugin: ${name}`);
2278
2591
  }