@omnikit-ai/sdk 2.0.10 → 2.2.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/dist/index.mjs CHANGED
@@ -433,6 +433,53 @@ var LiveVoiceSessionImpl = class {
433
433
  }
434
434
  };
435
435
 
436
+ // src/connectors.ts
437
+ function createConnectorsModule(makeRequest, appId, baseUrl, getServiceToken) {
438
+ return {
439
+ async getAccessToken(connectorType) {
440
+ const serviceToken = getServiceToken();
441
+ if (!serviceToken) {
442
+ throw new Error(
443
+ "Service token is required to get connector access token. This method is only available in backend functions. Make sure you created the client with a serviceToken."
444
+ );
445
+ }
446
+ return makeRequest(
447
+ `${baseUrl}/apps/${appId}/connectors/${connectorType}/access-token`,
448
+ "GET",
449
+ null,
450
+ { useServiceToken: true }
451
+ );
452
+ },
453
+ async isConnected(connectorType) {
454
+ try {
455
+ const response = await makeRequest(
456
+ `${baseUrl}/apps/${appId}/connectors/${connectorType}`,
457
+ "GET"
458
+ );
459
+ return response?.connector?.status === "connected";
460
+ } catch {
461
+ return false;
462
+ }
463
+ },
464
+ async getStatus(connectorType) {
465
+ try {
466
+ const response = await makeRequest(
467
+ `${baseUrl}/apps/${appId}/connectors/${connectorType}`,
468
+ "GET"
469
+ );
470
+ return {
471
+ success: true,
472
+ connector: response?.connector
473
+ };
474
+ } catch (error) {
475
+ return {
476
+ success: false
477
+ };
478
+ }
479
+ }
480
+ };
481
+ }
482
+
436
483
  // src/client.ts
437
484
  var LLM_MODEL_MAP = {
438
485
  // Gemini 2.5 models
@@ -611,6 +658,20 @@ var APIClient = class {
611
658
  description: "Create a signed URL for a private file",
612
659
  method: "POST",
613
660
  params: { file_uri: "string" }
661
+ },
662
+ {
663
+ name: "DownloadPrivateFile",
664
+ path: "/services/files/private/download",
665
+ description: "Download a private file via backend proxy (solves CORS issues)",
666
+ method: "POST",
667
+ params: { file_uri: "string" }
668
+ },
669
+ {
670
+ name: "DownloadFile",
671
+ path: "/services/files/{file_id}/download",
672
+ description: "Download a public file via backend proxy (like Supabase ?download)",
673
+ method: "GET",
674
+ params: { file_id: "string", download: "boolean", filename: "string" }
614
675
  }
615
676
  ];
616
677
  builtInServices.forEach((service) => {
@@ -756,13 +817,13 @@ var APIClient = class {
756
817
  return this.createAuthProxy();
757
818
  }
758
819
  /**
759
- * Lazy getter for service role operations
760
- * Only available when serviceToken is provided
820
+ * Service-level operations (elevated privileges).
821
+ * Only available when serviceToken is provided (e.g., via createServerClient).
761
822
  */
762
- get asServiceRole() {
823
+ get service() {
763
824
  if (!this._serviceToken) {
764
825
  throw new OmnikitError(
765
- "Service token is required to use asServiceRole. Provide serviceToken in config.",
826
+ "Service token is required. Use createServerClient(req) in backend functions.",
766
827
  403,
767
828
  "SERVICE_TOKEN_REQUIRED"
768
829
  );
@@ -775,12 +836,70 @@ var APIClient = class {
775
836
  // Service role collections
776
837
  services: this.createServicesProxy(true),
777
838
  // Service role services (new flat structure)
778
- integrations: this.createIntegrationsProxy(true)
839
+ integrations: this.createIntegrationsProxy(true),
779
840
  // Service role integrations (legacy)
841
+ connectors: this.connectors
842
+ // Connectors module (requires service token)
780
843
  // Note: auth not available in service role for security
781
844
  };
782
845
  return this._asServiceRole;
783
846
  }
847
+ /**
848
+ * @deprecated Use service instead
849
+ */
850
+ get asServiceRole() {
851
+ return this.service;
852
+ }
853
+ /**
854
+ * Lazy getter for connectors module
855
+ * Like Base44's connectors - provides access to external service tokens
856
+ *
857
+ * SECURITY: getAccessToken requires service token authentication.
858
+ * Only use in backend functions, not frontend code.
859
+ *
860
+ * @example
861
+ * ```typescript
862
+ * // In a backend function
863
+ * const { access_token } = await omnikit.connectors.getAccessToken('slack');
864
+ *
865
+ * // Make direct Slack API call
866
+ * const response = await fetch('https://slack.com/api/chat.postMessage', {
867
+ * method: 'POST',
868
+ * headers: { Authorization: `Bearer ${access_token}` },
869
+ * body: JSON.stringify({ channel: '#general', text: 'Hello!' })
870
+ * });
871
+ * ```
872
+ */
873
+ get connectors() {
874
+ if (!this._connectors) {
875
+ this._connectors = createConnectorsModule(
876
+ this.makeRequest.bind(this),
877
+ this.appId,
878
+ this.baseUrl,
879
+ () => this._serviceToken
880
+ );
881
+ }
882
+ return this._connectors;
883
+ }
884
+ /**
885
+ * Resolve a return URL to an absolute URL.
886
+ * Handles relative paths like "/profile" by combining with current location.
887
+ * This fixes the OAuth redirect bug where relative URLs like "/profile" become
888
+ * "https://omnikit.ai/profile" instead of "https://omnikit.ai/app-builder/{id}/preview/profile"
889
+ */
890
+ _resolveReturnUrl(returnUrl) {
891
+ if (typeof window === "undefined") return returnUrl || "/";
892
+ if (!returnUrl) return window.location.href;
893
+ if (returnUrl.startsWith("http://") || returnUrl.startsWith("https://")) {
894
+ return returnUrl;
895
+ }
896
+ if (returnUrl.startsWith("/")) {
897
+ const basePath = window.location.pathname.endsWith("/") ? window.location.pathname.slice(0, -1) : window.location.pathname;
898
+ return window.location.origin + basePath + returnUrl;
899
+ }
900
+ const base = window.location.origin + window.location.pathname;
901
+ return base.endsWith("/") ? base + returnUrl : base + "/" + returnUrl;
902
+ }
784
903
  /**
785
904
  * Create auth proxy that auto-initializes
786
905
  */
@@ -808,31 +927,42 @@ var APIClient = class {
808
927
  client.emitUserChange(response);
809
928
  return response;
810
929
  },
811
- login(returnUrl) {
812
- const fullReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
813
- if (typeof window !== "undefined") {
814
- if (window.__omnikit_openLoginModal) {
815
- window.__omnikit_openLoginModal(fullReturnUrl);
816
- return;
817
- }
818
- const encodedReturnUrl = encodeURIComponent(fullReturnUrl);
819
- const currentPath = window.location.pathname;
820
- const apiSitesMatch = currentPath.match(/^\/api\/sites\/([^\/]+)/);
821
- if (apiSitesMatch) {
822
- window.location.href = `/api/sites/${client.appId}/login?return_url=${encodedReturnUrl}`;
823
- } else {
824
- window.location.href = `/login?return_url=${encodedReturnUrl}`;
930
+ async login(returnUrl) {
931
+ if (typeof window === "undefined") return;
932
+ const fullReturnUrl = client._resolveReturnUrl(returnUrl);
933
+ const token = getAccessToken();
934
+ if (token) {
935
+ try {
936
+ const isAuth = await this.isAuthenticated();
937
+ if (isAuth && returnUrl) {
938
+ window.location.href = fullReturnUrl;
939
+ return;
940
+ }
941
+ } catch {
825
942
  }
826
943
  }
944
+ if (window.__omnikit_openLoginModal) {
945
+ window.__omnikit_openLoginModal(fullReturnUrl);
946
+ return;
947
+ }
948
+ const encodedReturnUrl = encodeURIComponent(fullReturnUrl);
949
+ const currentPath = window.location.pathname;
950
+ const apiSitesMatch = currentPath.match(/^\/api\/sites\/([^\/]+)/);
951
+ if (apiSitesMatch) {
952
+ window.location.href = `/api/sites/${client.appId}/login?return_url=${encodedReturnUrl}`;
953
+ } else {
954
+ window.location.href = `/login?return_url=${encodedReturnUrl}`;
955
+ }
827
956
  },
828
957
  /**
829
958
  * Request a passwordless login code to email
830
959
  */
831
960
  async requestLoginCode(email, returnUrl) {
961
+ const fullReturnUrl = returnUrl ? client._resolveReturnUrl(returnUrl) : void 0;
832
962
  return await client.makeRequest(
833
963
  `${client.baseUrl}/auth/email/request-code`,
834
964
  "POST",
835
- { email, return_url: returnUrl }
965
+ { email, return_url: fullReturnUrl }
836
966
  );
837
967
  },
838
968
  /**
@@ -880,44 +1010,43 @@ var APIClient = class {
880
1010
  * Redirects to the backend OAuth endpoint for the specified provider
881
1011
  */
882
1012
  loginWithProvider(providerId, returnUrl) {
883
- const currentUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
884
- const redirectUrl = `${client.baseUrl}/auth/${providerId}?redirect_url=${encodeURIComponent(currentUrl)}`;
885
- if (typeof window !== "undefined") {
886
- const inIframe = window.self !== window.top;
887
- if (inIframe) {
888
- const width = 600;
889
- const height = 700;
890
- const left = window.screen.width / 2 - width / 2;
891
- const top = window.screen.height / 2 - height / 2;
892
- const popup = window.open(
893
- redirectUrl,
894
- "oauth_popup",
895
- `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes,status=yes`
896
- );
897
- if (popup) {
898
- const checkTimer = setInterval(() => {
899
- const token = getAccessToken();
900
- if (token) {
901
- clearInterval(checkTimer);
902
- popup.close();
1013
+ if (typeof window === "undefined") {
1014
+ throw new OmnikitError("loginWithProvider() can only be called in browser environment", 400, "NOT_BROWSER");
1015
+ }
1016
+ const fullReturnUrl = client._resolveReturnUrl(returnUrl);
1017
+ const redirectUrl = `${client.baseUrl}/auth/${providerId}?redirect_url=${encodeURIComponent(fullReturnUrl)}`;
1018
+ const inIframe = window.self !== window.top;
1019
+ if (inIframe) {
1020
+ const width = 600;
1021
+ const height = 700;
1022
+ const left = window.screen.width / 2 - width / 2;
1023
+ const top = window.screen.height / 2 - height / 2;
1024
+ const popup = window.open(
1025
+ redirectUrl,
1026
+ "oauth_popup",
1027
+ `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes,status=yes`
1028
+ );
1029
+ if (popup) {
1030
+ const checkTimer = setInterval(() => {
1031
+ const token = getAccessToken();
1032
+ if (token) {
1033
+ clearInterval(checkTimer);
1034
+ popup.close();
1035
+ window.location.reload();
1036
+ return;
1037
+ }
1038
+ if (popup.closed) {
1039
+ clearInterval(checkTimer);
1040
+ const finalToken = getAccessToken();
1041
+ if (finalToken) {
903
1042
  window.location.reload();
904
- return;
905
1043
  }
906
- if (popup.closed) {
907
- clearInterval(checkTimer);
908
- const finalToken = getAccessToken();
909
- if (finalToken) {
910
- window.location.reload();
911
- }
912
- }
913
- }, 1e3);
914
- return;
915
- }
1044
+ }
1045
+ }, 1e3);
1046
+ return;
916
1047
  }
917
- window.location.href = redirectUrl;
918
- } else {
919
- throw new OmnikitError("loginWithProvider() can only be called in browser environment", 400, "NOT_BROWSER");
920
1048
  }
1049
+ window.location.href = redirectUrl;
921
1050
  },
922
1051
  /**
923
1052
  * Initiate Google OAuth Login
@@ -1129,7 +1258,8 @@ Example: await ${collectionName}.list({ limit: 100, sort: '-created_at' })`,
1129
1258
  // Remove callback functions from request body
1130
1259
  onToken: void 0,
1131
1260
  onComplete: void 0,
1132
- onError: void 0
1261
+ onError: void 0,
1262
+ onToolCall: void 0
1133
1263
  };
1134
1264
  Object.keys(requestBody).forEach((key) => {
1135
1265
  if (requestBody[key] === void 0) {
@@ -1180,6 +1310,14 @@ Example: await ${collectionName}.list({ limit: 100, sort: '-created_at' })`,
1180
1310
  if (event.type === "token" && event.content) {
1181
1311
  fullResponse += event.content;
1182
1312
  params.onToken?.(event.content);
1313
+ } else if (event.type === "tool_call") {
1314
+ if (params.onToolCall && event.id && event.name) {
1315
+ params.onToolCall({
1316
+ id: event.id,
1317
+ name: event.name,
1318
+ arguments: event.arguments || {}
1319
+ });
1320
+ }
1183
1321
  } else if (event.type === "done") {
1184
1322
  params.onComplete?.({
1185
1323
  result: event.result || fullResponse,
@@ -1238,6 +1376,56 @@ Example: await ${collectionName}.list({ limit: 100, sort: '-created_at' })`,
1238
1376
  );
1239
1377
  };
1240
1378
  }
1379
+ if (normalizedName === "DownloadFile") {
1380
+ return async function(params) {
1381
+ await client.ensureInitialized();
1382
+ if (!params?.file_id) {
1383
+ throw new OmnikitError(
1384
+ "file_id is required for DownloadFile",
1385
+ 400,
1386
+ "MISSING_FILE_ID"
1387
+ );
1388
+ }
1389
+ const downloadUrl = new URL(`${client.baseUrl}/apps/${client.appId}/services/files/${params.file_id}/download`);
1390
+ if (params.download !== void 0) {
1391
+ downloadUrl.searchParams.set("download", String(params.download));
1392
+ }
1393
+ if (params.filename) {
1394
+ downloadUrl.searchParams.set("filename", params.filename);
1395
+ }
1396
+ const response = await fetch(downloadUrl.toString(), {
1397
+ method: "GET"
1398
+ });
1399
+ if (!response.ok) {
1400
+ const error = await response.json().catch(() => ({ detail: "Download failed" }));
1401
+ throw new OmnikitError(
1402
+ error.detail || "Download failed",
1403
+ response.status,
1404
+ "DOWNLOAD_FAILED"
1405
+ );
1406
+ }
1407
+ const blob = await response.blob();
1408
+ const blobUrl = URL.createObjectURL(blob);
1409
+ let filename = params.filename;
1410
+ if (!filename) {
1411
+ const contentDisposition = response.headers.get("Content-Disposition");
1412
+ if (contentDisposition) {
1413
+ const match = contentDisposition.match(/filename="?([^"]+)"?/);
1414
+ if (match) filename = match[1];
1415
+ }
1416
+ }
1417
+ if (!filename) {
1418
+ filename = `file-${params.file_id}`;
1419
+ }
1420
+ const link = document.createElement("a");
1421
+ link.href = blobUrl;
1422
+ link.download = filename;
1423
+ document.body.appendChild(link);
1424
+ link.click();
1425
+ document.body.removeChild(link);
1426
+ URL.revokeObjectURL(blobUrl);
1427
+ };
1428
+ }
1241
1429
  if (normalizedName === "DownloadPrivateFile") {
1242
1430
  return async function(params) {
1243
1431
  await client.ensureInitialized();
@@ -1248,16 +1436,47 @@ Example: await ${collectionName}.list({ limit: 100, sort: '-created_at' })`,
1248
1436
  "MISSING_FILE_URI"
1249
1437
  );
1250
1438
  }
1251
- const downloadUrl = new URL(`${client.baseUrl}/apps/${client.appId}/services/files/download`);
1252
- downloadUrl.searchParams.set("file_uri", params.file_uri);
1253
- if (params.filename) {
1254
- downloadUrl.searchParams.set("filename", params.filename);
1255
- }
1439
+ const downloadUrl = `${client.baseUrl}/apps/${client.appId}/services/files/private/download`;
1256
1440
  const token = client.getAuthToken();
1257
- if (token) {
1258
- downloadUrl.searchParams.set("token", token);
1441
+ const response = await fetch(downloadUrl, {
1442
+ method: "POST",
1443
+ headers: {
1444
+ "Content-Type": "application/json",
1445
+ ...token ? { "Authorization": `Bearer ${token}` } : {}
1446
+ },
1447
+ body: JSON.stringify({ file_uri: params.file_uri })
1448
+ });
1449
+ if (!response.ok) {
1450
+ const error = await response.json().catch(() => ({ detail: "Download failed" }));
1451
+ throw new OmnikitError(
1452
+ error.detail || "Download failed",
1453
+ response.status,
1454
+ "DOWNLOAD_FAILED"
1455
+ );
1456
+ }
1457
+ const blob = await response.blob();
1458
+ const blobUrl = URL.createObjectURL(blob);
1459
+ let filename = params.filename;
1460
+ if (!filename) {
1461
+ const contentDisposition = response.headers.get("Content-Disposition");
1462
+ if (contentDisposition) {
1463
+ const match = contentDisposition.match(/filename="?([^"]+)"?/);
1464
+ if (match) filename = match[1];
1465
+ }
1259
1466
  }
1260
- window.open(downloadUrl.toString(), "_blank");
1467
+ if (!filename) {
1468
+ const uriParts = params.file_uri.split("/");
1469
+ const lastPart = uriParts[uriParts.length - 1];
1470
+ const underscoreIdx = lastPart.indexOf("_");
1471
+ filename = underscoreIdx > 0 ? lastPart.slice(underscoreIdx + 1) : lastPart;
1472
+ }
1473
+ const link = document.createElement("a");
1474
+ link.href = blobUrl;
1475
+ link.download = filename;
1476
+ document.body.appendChild(link);
1477
+ link.click();
1478
+ document.body.removeChild(link);
1479
+ URL.revokeObjectURL(blobUrl);
1261
1480
  };
1262
1481
  }
1263
1482
  return async function(params, asyncOptions) {
@@ -1276,7 +1495,7 @@ Example: await ${collectionName}.list({ limit: 100, sort: '-created_at' })`,
1276
1495
  response = await method(params, useServiceToken);
1277
1496
  } else {
1278
1497
  throw new OmnikitError(
1279
- `Service '${serviceName}' not found. Known services: SendEmail, InvokeLLM, GenerateImage, GenerateSpeech, GenerateVideo, ExtractData, SendSMS, UploadFile, UploadPrivateFile, CreateFileSignedUrl (camelCase also supported)`,
1498
+ `Service '${serviceName}' not found. Known services: SendEmail, InvokeLLM, GenerateImage, GenerateSpeech, GenerateVideo, ExtractData, SendSMS, UploadFile, UploadPrivateFile, CreateFileSignedUrl, DownloadFile, DownloadPrivateFile (camelCase also supported)`,
1280
1499
  404,
1281
1500
  "SERVICE_NOT_FOUND"
1282
1501
  );
@@ -1792,6 +2011,8 @@ Example: await ${collectionName}.list({ limit: 100, sort: '-created_at' })`,
1792
2011
  "files": "UploadFile",
1793
2012
  "files/private": "UploadPrivateFile",
1794
2013
  "files/signed-url": "CreateFileSignedUrl",
2014
+ "files/private/download": "DownloadPrivateFile",
2015
+ "files/download": "DownloadFile",
1795
2016
  "images": "GenerateImage",
1796
2017
  "speech": "GenerateSpeech",
1797
2018
  "video": "GenerateVideo",
@@ -2234,7 +2455,289 @@ Example: await ${collectionName}.list({ limit: 100, sort: '-created_at' })`,
2234
2455
  function createClient(config) {
2235
2456
  return new APIClient(config);
2236
2457
  }
2458
+ function createServerClient(request) {
2459
+ const getHeader = (name) => {
2460
+ if (typeof request.headers.get === "function") {
2461
+ return request.headers.get(name);
2462
+ }
2463
+ return request.headers[name] || request.headers[name.toLowerCase()] || null;
2464
+ };
2465
+ const authHeader = getHeader("Authorization") || getHeader("authorization");
2466
+ const userToken = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
2467
+ const serviceToken = getHeader("X-Omnikit-Service-Authorization") || getHeader("x-omnikit-service-authorization");
2468
+ const appId = getHeader("X-Omnikit-App-Id") || getHeader("x-omnikit-app-id");
2469
+ const serverUrl = getHeader("X-Omnikit-Server-Url") || getHeader("x-omnikit-server-url") || "https://omnikit.ai/api";
2470
+ if (!appId) {
2471
+ throw new OmnikitError(
2472
+ "X-Omnikit-App-Id header is required. This function should be invoked through the Omnikit platform.",
2473
+ 400,
2474
+ "MISSING_APP_ID"
2475
+ );
2476
+ }
2477
+ return createClient({
2478
+ appId,
2479
+ serverUrl,
2480
+ token: userToken || void 0,
2481
+ serviceToken: serviceToken || void 0,
2482
+ autoInitAuth: false
2483
+ // Don't auto-detect from localStorage in backend
2484
+ });
2485
+ }
2486
+ var createClientFromRequest = createServerClient;
2487
+
2488
+ // src/analytics.ts
2489
+ var STORAGE_KEY = "omnikit_session";
2490
+ var SESSION_TIMEOUT = 30 * 60 * 1e3;
2491
+ var FLUSH_INTERVAL = 5e3;
2492
+ var MAX_RETRIES = 3;
2493
+ var Analytics = class {
2494
+ constructor(config) {
2495
+ this.eventQueue = [];
2496
+ this.config = config;
2497
+ this.enabled = config.enabled !== false;
2498
+ this.sessionId = config.sessionId || this.initSession();
2499
+ this.userId = config.userId;
2500
+ if (this.enabled && typeof window !== "undefined") {
2501
+ this.startFlushTimer();
2502
+ window.addEventListener("beforeunload", () => this.flush());
2503
+ window.addEventListener("pagehide", () => this.flush());
2504
+ }
2505
+ }
2506
+ // ==========================================================================
2507
+ // Session Management
2508
+ // ==========================================================================
2509
+ initSession() {
2510
+ if (typeof window === "undefined" || typeof localStorage === "undefined") {
2511
+ return this.generateId();
2512
+ }
2513
+ try {
2514
+ const stored = localStorage.getItem(STORAGE_KEY);
2515
+ if (stored) {
2516
+ const { id, timestamp } = JSON.parse(stored);
2517
+ if (Date.now() - timestamp < SESSION_TIMEOUT) {
2518
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({
2519
+ id,
2520
+ timestamp: Date.now()
2521
+ }));
2522
+ return id;
2523
+ }
2524
+ }
2525
+ } catch {
2526
+ }
2527
+ const newId = this.generateId();
2528
+ this.saveSession(newId);
2529
+ return newId;
2530
+ }
2531
+ generateId() {
2532
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
2533
+ return crypto.randomUUID();
2534
+ }
2535
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
2536
+ const r = Math.random() * 16 | 0;
2537
+ const v = c === "x" ? r : r & 3 | 8;
2538
+ return v.toString(16);
2539
+ });
2540
+ }
2541
+ saveSession(id) {
2542
+ if (typeof localStorage === "undefined") return;
2543
+ try {
2544
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({
2545
+ id,
2546
+ timestamp: Date.now()
2547
+ }));
2548
+ } catch {
2549
+ }
2550
+ }
2551
+ /**
2552
+ * Get the current session ID
2553
+ */
2554
+ getSessionId() {
2555
+ return this.sessionId;
2556
+ }
2557
+ /**
2558
+ * Start a new session (e.g., after logout)
2559
+ */
2560
+ startNewSession() {
2561
+ this.sessionId = this.generateId();
2562
+ this.saveSession(this.sessionId);
2563
+ return this.sessionId;
2564
+ }
2565
+ // ==========================================================================
2566
+ // User Identification
2567
+ // ==========================================================================
2568
+ /**
2569
+ * Associate events with a user ID
2570
+ */
2571
+ setUserId(userId) {
2572
+ this.userId = userId;
2573
+ }
2574
+ /**
2575
+ * Clear user association (e.g., on logout)
2576
+ */
2577
+ clearUserId() {
2578
+ this.userId = void 0;
2579
+ }
2580
+ // ==========================================================================
2581
+ // Core Logging Methods
2582
+ // ==========================================================================
2583
+ /**
2584
+ * Log a custom event
2585
+ */
2586
+ async logEvent(eventType, payload = {}) {
2587
+ if (!this.enabled) return;
2588
+ this.eventQueue.push({
2589
+ eventType,
2590
+ payload,
2591
+ timestamp: Date.now()
2592
+ });
2593
+ if (eventType === "error" || eventType === "api_error") {
2594
+ await this.flush();
2595
+ }
2596
+ }
2597
+ /**
2598
+ * Log a page view event
2599
+ */
2600
+ async logPageView(pageName, metadata) {
2601
+ if (!this.enabled) return;
2602
+ const url = typeof window !== "undefined" ? window.location.href : "";
2603
+ const referrer = typeof document !== "undefined" ? document.referrer : "";
2604
+ await this.logEvent("page_view", {
2605
+ page_name: pageName,
2606
+ metadata: {
2607
+ url,
2608
+ referrer,
2609
+ ...metadata
2610
+ }
2611
+ });
2612
+ }
2613
+ /**
2614
+ * Log an error event
2615
+ */
2616
+ async logError(error, componentStack) {
2617
+ if (!this.enabled) return;
2618
+ const url = typeof window !== "undefined" ? window.location.href : "";
2619
+ await this.logEvent("error", {
2620
+ error_message: error.message,
2621
+ error_stack: error.stack,
2622
+ metadata: {
2623
+ name: error.name,
2624
+ component_stack: componentStack,
2625
+ url
2626
+ }
2627
+ });
2628
+ }
2629
+ /**
2630
+ * Log an API error event
2631
+ */
2632
+ async logApiError(endpoint, statusCode, errorMessage, metadata) {
2633
+ if (!this.enabled) return;
2634
+ await this.logEvent("api_error", {
2635
+ error_message: errorMessage,
2636
+ metadata: {
2637
+ endpoint,
2638
+ status_code: statusCode,
2639
+ ...metadata
2640
+ }
2641
+ });
2642
+ }
2643
+ // ==========================================================================
2644
+ // Event Batching & Flushing
2645
+ // ==========================================================================
2646
+ startFlushTimer() {
2647
+ if (this.flushTimer) return;
2648
+ this.flushTimer = setInterval(() => {
2649
+ if (this.eventQueue.length > 0) {
2650
+ this.flush();
2651
+ }
2652
+ }, FLUSH_INTERVAL);
2653
+ }
2654
+ /**
2655
+ * Flush all queued events to the server
2656
+ */
2657
+ async flush() {
2658
+ if (this.eventQueue.length === 0) return;
2659
+ const events = [...this.eventQueue];
2660
+ this.eventQueue = [];
2661
+ const sendPromises = events.map(
2662
+ ({ eventType, payload }) => this.sendEvent(eventType, payload)
2663
+ );
2664
+ await Promise.allSettled(sendPromises);
2665
+ }
2666
+ async sendEvent(eventType, payload) {
2667
+ const pageName = payload.page_name || (typeof window !== "undefined" ? window.location.pathname : "/");
2668
+ const url = `${this.config.apiUrl}/api/app-logs/${this.config.appId}/log-event`;
2669
+ const body = {
2670
+ session_id: this.sessionId,
2671
+ user_id: this.userId,
2672
+ event_type: eventType,
2673
+ page_name: pageName,
2674
+ action: payload.action,
2675
+ inputs: payload.inputs,
2676
+ metadata: payload.metadata,
2677
+ is_error: eventType === "error" || eventType === "api_error",
2678
+ error_message: payload.error_message,
2679
+ error_stack: payload.error_stack
2680
+ };
2681
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
2682
+ try {
2683
+ const response = await fetch(url, {
2684
+ method: "POST",
2685
+ headers: { "Content-Type": "application/json" },
2686
+ body: JSON.stringify(body),
2687
+ keepalive: true
2688
+ // Ensures request completes even on page unload
2689
+ });
2690
+ if (response.ok) {
2691
+ return;
2692
+ }
2693
+ if (response.status >= 400 && response.status < 500) {
2694
+ console.warn(`[Omnikit Analytics] Event rejected: ${response.status}`);
2695
+ return;
2696
+ }
2697
+ } catch (err) {
2698
+ }
2699
+ if (attempt < MAX_RETRIES - 1) {
2700
+ await new Promise((resolve) => setTimeout(resolve, Math.pow(2, attempt) * 1e3));
2701
+ }
2702
+ }
2703
+ console.warn("[Omnikit Analytics] Failed to send event after retries");
2704
+ }
2705
+ // ==========================================================================
2706
+ // Lifecycle
2707
+ // ==========================================================================
2708
+ /**
2709
+ * Enable or disable analytics
2710
+ */
2711
+ setEnabled(enabled) {
2712
+ this.enabled = enabled;
2713
+ if (enabled && typeof window !== "undefined") {
2714
+ this.startFlushTimer();
2715
+ } else if (this.flushTimer) {
2716
+ clearInterval(this.flushTimer);
2717
+ this.flushTimer = void 0;
2718
+ }
2719
+ }
2720
+ /**
2721
+ * Check if analytics is enabled
2722
+ */
2723
+ isEnabled() {
2724
+ return this.enabled;
2725
+ }
2726
+ /**
2727
+ * Clean up resources
2728
+ */
2729
+ destroy() {
2730
+ if (this.flushTimer) {
2731
+ clearInterval(this.flushTimer);
2732
+ this.flushTimer = void 0;
2733
+ }
2734
+ this.flush();
2735
+ }
2736
+ };
2737
+ function createAnalytics(config) {
2738
+ return new Analytics(config);
2739
+ }
2237
2740
 
2238
- export { APIClient, LiveVoiceSessionImpl, OmnikitError, cleanTokenFromUrl, createClient, getAccessToken, isTokenInUrl, removeAccessToken, saveAccessToken, setAccessToken };
2741
+ export { APIClient, Analytics, LiveVoiceSessionImpl, OmnikitError, cleanTokenFromUrl, createAnalytics, createClient, createClientFromRequest, createServerClient, getAccessToken, isTokenInUrl, removeAccessToken, saveAccessToken, setAccessToken };
2239
2742
  //# sourceMappingURL=index.mjs.map
2240
2743
  //# sourceMappingURL=index.mjs.map