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