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