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