@clianta/sdk 1.6.3 → 1.6.5

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