@omnikit-ai/sdk 2.0.9 → 2.2.0

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
@@ -527,10 +574,96 @@ var APIClient = class {
527
574
  this.userToken = detectedToken;
528
575
  }
529
576
  }
577
+ this.initializeBuiltInServices();
530
578
  this.ensureInitialized().catch((err) => {
531
579
  console.warn("[Omnikit SDK] Background initialization failed:", err);
532
580
  });
533
581
  }
582
+ /**
583
+ * Initialize built-in services with their definitions
584
+ * These services don't require backend schema - they're defined in the SDK
585
+ */
586
+ initializeBuiltInServices() {
587
+ const builtInServices = [
588
+ {
589
+ name: "SendEmail",
590
+ path: "/services/email",
591
+ description: "Send an email",
592
+ method: "POST",
593
+ params: { to: "string", subject: "string", body: "string" }
594
+ },
595
+ {
596
+ name: "InvokeLLM",
597
+ path: "/services/llm",
598
+ description: "Invoke a language model",
599
+ method: "POST",
600
+ params: { messages: "array", model: "string" }
601
+ },
602
+ {
603
+ name: "GenerateImage",
604
+ path: "/services/images",
605
+ description: "Generate an image from a prompt",
606
+ method: "POST",
607
+ async: true,
608
+ params: { prompt: "string" }
609
+ },
610
+ {
611
+ name: "GenerateSpeech",
612
+ path: "/services/speech",
613
+ description: "Generate speech from text",
614
+ method: "POST",
615
+ async: true,
616
+ params: { text: "string" }
617
+ },
618
+ {
619
+ name: "GenerateVideo",
620
+ path: "/services/video",
621
+ description: "Generate a video from a prompt",
622
+ method: "POST",
623
+ async: true,
624
+ params: { prompt: "string" }
625
+ },
626
+ {
627
+ name: "ExtractData",
628
+ path: "/services/extract-text",
629
+ description: "Extract structured data from text or files",
630
+ method: "POST",
631
+ async: true,
632
+ params: { file_url: "string" }
633
+ },
634
+ {
635
+ name: "SendSMS",
636
+ path: "/services/sms",
637
+ description: "Send an SMS message",
638
+ method: "POST",
639
+ params: { to: "string", body: "string" }
640
+ },
641
+ {
642
+ name: "UploadFile",
643
+ path: "/services/files",
644
+ description: "Upload a file (public)",
645
+ method: "POST",
646
+ params: { file: "File" }
647
+ },
648
+ {
649
+ name: "UploadPrivateFile",
650
+ path: "/services/files/private",
651
+ description: "Upload a private file",
652
+ method: "POST",
653
+ params: { file: "File" }
654
+ },
655
+ {
656
+ name: "CreateFileSignedUrl",
657
+ path: "/services/files/signed-url",
658
+ description: "Create a signed URL for a private file",
659
+ method: "POST",
660
+ params: { file_uri: "string" }
661
+ }
662
+ ];
663
+ builtInServices.forEach((service) => {
664
+ this._services[service.name] = this.createServiceMethod(service);
665
+ });
666
+ }
534
667
  /**
535
668
  * Load metadata from localStorage cache, falling back to initial config
536
669
  * Guards localStorage access for Deno/Node compatibility
@@ -670,13 +803,13 @@ var APIClient = class {
670
803
  return this.createAuthProxy();
671
804
  }
672
805
  /**
673
- * Lazy getter for service role operations
674
- * Only available when serviceToken is provided
806
+ * Service-level operations (elevated privileges).
807
+ * Only available when serviceToken is provided (e.g., via createServerClient).
675
808
  */
676
- get asServiceRole() {
809
+ get service() {
677
810
  if (!this._serviceToken) {
678
811
  throw new OmnikitError(
679
- "Service token is required to use asServiceRole. Provide serviceToken in config.",
812
+ "Service token is required. Use createServerClient(req) in backend functions.",
680
813
  403,
681
814
  "SERVICE_TOKEN_REQUIRED"
682
815
  );
@@ -689,12 +822,51 @@ var APIClient = class {
689
822
  // Service role collections
690
823
  services: this.createServicesProxy(true),
691
824
  // Service role services (new flat structure)
692
- integrations: this.createIntegrationsProxy(true)
825
+ integrations: this.createIntegrationsProxy(true),
693
826
  // Service role integrations (legacy)
827
+ connectors: this.connectors
828
+ // Connectors module (requires service token)
694
829
  // Note: auth not available in service role for security
695
830
  };
696
831
  return this._asServiceRole;
697
832
  }
833
+ /**
834
+ * @deprecated Use service instead
835
+ */
836
+ get asServiceRole() {
837
+ return this.service;
838
+ }
839
+ /**
840
+ * Lazy getter for connectors module
841
+ * Like Base44's connectors - provides access to external service tokens
842
+ *
843
+ * SECURITY: getAccessToken requires service token authentication.
844
+ * Only use in backend functions, not frontend code.
845
+ *
846
+ * @example
847
+ * ```typescript
848
+ * // In a backend function
849
+ * const { access_token } = await omnikit.connectors.getAccessToken('slack');
850
+ *
851
+ * // Make direct Slack API call
852
+ * const response = await fetch('https://slack.com/api/chat.postMessage', {
853
+ * method: 'POST',
854
+ * headers: { Authorization: `Bearer ${access_token}` },
855
+ * body: JSON.stringify({ channel: '#general', text: 'Hello!' })
856
+ * });
857
+ * ```
858
+ */
859
+ get connectors() {
860
+ if (!this._connectors) {
861
+ this._connectors = createConnectorsModule(
862
+ this.makeRequest.bind(this),
863
+ this.appId,
864
+ this.baseUrl,
865
+ () => this._serviceToken
866
+ );
867
+ }
868
+ return this._connectors;
869
+ }
698
870
  /**
699
871
  * Create auth proxy that auto-initializes
700
872
  */
@@ -1189,57 +1361,11 @@ Example: await ${collectionName}.list({ limit: 100, sort: '-created_at' })`,
1189
1361
  if (method) {
1190
1362
  response = await method(params, useServiceToken);
1191
1363
  } else {
1192
- const servicePathMap = {
1193
- "SendEmail": "services/email",
1194
- "InvokeLLM": "services/llm",
1195
- "GenerateImage": "services/images",
1196
- "GenerateSpeech": "services/speech",
1197
- "GenerateVideo": "services/video",
1198
- "ExtractData": "services/extract-text",
1199
- "SendSMS": "services/sms",
1200
- "UploadFile": "services/files",
1201
- "UploadPrivateFile": "services/files/private/init",
1202
- "CreateFileSignedUrl": "services/files/signed-url"
1203
- };
1204
- const servicePath = servicePathMap[normalizedName];
1205
- if (!servicePath) {
1206
- throw new OmnikitError(
1207
- `Service '${serviceName}' not found. Known services: ${Object.keys(servicePathMap).join(", ")} (camelCase also supported)`,
1208
- 404,
1209
- "SERVICE_NOT_FOUND"
1210
- );
1211
- }
1212
- const headers = {
1213
- "Content-Type": "application/json"
1214
- };
1215
- if (client._apiKey) {
1216
- headers["X-API-Key"] = client._apiKey;
1217
- } else if (client.userToken) {
1218
- headers["Authorization"] = `Bearer ${client.userToken}`;
1219
- }
1220
- if ((normalizedName === "UploadFile" || normalizedName === "uploadFile") && params?.file instanceof File) {
1221
- return client.handleFileUpload(params.file, servicePath, useServiceToken);
1222
- }
1223
- if ((normalizedName === "UploadPrivateFile" || normalizedName === "uploadPrivateFile") && params?.file instanceof File) {
1224
- return client.handleFileUpload(params.file, servicePath, useServiceToken);
1225
- }
1226
- const fetchResponse = await fetch(
1227
- `${client.baseUrl}/apps/${client.appId}/${servicePath}`,
1228
- {
1229
- method: "POST",
1230
- headers,
1231
- body: JSON.stringify(params || {})
1232
- }
1364
+ throw new OmnikitError(
1365
+ `Service '${serviceName}' not found. Known services: SendEmail, InvokeLLM, GenerateImage, GenerateSpeech, GenerateVideo, ExtractData, SendSMS, UploadFile, UploadPrivateFile, CreateFileSignedUrl (camelCase also supported)`,
1366
+ 404,
1367
+ "SERVICE_NOT_FOUND"
1233
1368
  );
1234
- if (!fetchResponse.ok) {
1235
- const error = await fetchResponse.json().catch(() => ({}));
1236
- throw new OmnikitError(
1237
- error.detail || `Service call failed: ${fetchResponse.statusText}`,
1238
- fetchResponse.status,
1239
- "SERVICE_CALL_FAILED"
1240
- );
1241
- }
1242
- response = await fetchResponse.json();
1243
1369
  }
1244
1370
  if (response && response.async && response.job_id) {
1245
1371
  if (asyncOptions?.returnJobId) {
@@ -2194,7 +2320,289 @@ Example: await ${collectionName}.list({ limit: 100, sort: '-created_at' })`,
2194
2320
  function createClient(config) {
2195
2321
  return new APIClient(config);
2196
2322
  }
2323
+ function createServerClient(request) {
2324
+ const getHeader = (name) => {
2325
+ if (typeof request.headers.get === "function") {
2326
+ return request.headers.get(name);
2327
+ }
2328
+ return request.headers[name] || request.headers[name.toLowerCase()] || null;
2329
+ };
2330
+ const authHeader = getHeader("Authorization") || getHeader("authorization");
2331
+ const userToken = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
2332
+ const serviceToken = getHeader("X-Omnikit-Service-Authorization") || getHeader("x-omnikit-service-authorization");
2333
+ const appId = getHeader("X-Omnikit-App-Id") || getHeader("x-omnikit-app-id");
2334
+ const serverUrl = getHeader("X-Omnikit-Server-Url") || getHeader("x-omnikit-server-url") || "https://omnikit.ai/api";
2335
+ if (!appId) {
2336
+ throw new OmnikitError(
2337
+ "X-Omnikit-App-Id header is required. This function should be invoked through the Omnikit platform.",
2338
+ 400,
2339
+ "MISSING_APP_ID"
2340
+ );
2341
+ }
2342
+ return createClient({
2343
+ appId,
2344
+ serverUrl,
2345
+ token: userToken || void 0,
2346
+ serviceToken: serviceToken || void 0,
2347
+ autoInitAuth: false
2348
+ // Don't auto-detect from localStorage in backend
2349
+ });
2350
+ }
2351
+ var createClientFromRequest = createServerClient;
2352
+
2353
+ // src/analytics.ts
2354
+ var STORAGE_KEY = "omnikit_session";
2355
+ var SESSION_TIMEOUT = 30 * 60 * 1e3;
2356
+ var FLUSH_INTERVAL = 5e3;
2357
+ var MAX_RETRIES = 3;
2358
+ var Analytics = class {
2359
+ constructor(config) {
2360
+ this.eventQueue = [];
2361
+ this.config = config;
2362
+ this.enabled = config.enabled !== false;
2363
+ this.sessionId = config.sessionId || this.initSession();
2364
+ this.userId = config.userId;
2365
+ if (this.enabled && typeof window !== "undefined") {
2366
+ this.startFlushTimer();
2367
+ window.addEventListener("beforeunload", () => this.flush());
2368
+ window.addEventListener("pagehide", () => this.flush());
2369
+ }
2370
+ }
2371
+ // ==========================================================================
2372
+ // Session Management
2373
+ // ==========================================================================
2374
+ initSession() {
2375
+ if (typeof window === "undefined" || typeof localStorage === "undefined") {
2376
+ return this.generateId();
2377
+ }
2378
+ try {
2379
+ const stored = localStorage.getItem(STORAGE_KEY);
2380
+ if (stored) {
2381
+ const { id, timestamp } = JSON.parse(stored);
2382
+ if (Date.now() - timestamp < SESSION_TIMEOUT) {
2383
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({
2384
+ id,
2385
+ timestamp: Date.now()
2386
+ }));
2387
+ return id;
2388
+ }
2389
+ }
2390
+ } catch {
2391
+ }
2392
+ const newId = this.generateId();
2393
+ this.saveSession(newId);
2394
+ return newId;
2395
+ }
2396
+ generateId() {
2397
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
2398
+ return crypto.randomUUID();
2399
+ }
2400
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
2401
+ const r = Math.random() * 16 | 0;
2402
+ const v = c === "x" ? r : r & 3 | 8;
2403
+ return v.toString(16);
2404
+ });
2405
+ }
2406
+ saveSession(id) {
2407
+ if (typeof localStorage === "undefined") return;
2408
+ try {
2409
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({
2410
+ id,
2411
+ timestamp: Date.now()
2412
+ }));
2413
+ } catch {
2414
+ }
2415
+ }
2416
+ /**
2417
+ * Get the current session ID
2418
+ */
2419
+ getSessionId() {
2420
+ return this.sessionId;
2421
+ }
2422
+ /**
2423
+ * Start a new session (e.g., after logout)
2424
+ */
2425
+ startNewSession() {
2426
+ this.sessionId = this.generateId();
2427
+ this.saveSession(this.sessionId);
2428
+ return this.sessionId;
2429
+ }
2430
+ // ==========================================================================
2431
+ // User Identification
2432
+ // ==========================================================================
2433
+ /**
2434
+ * Associate events with a user ID
2435
+ */
2436
+ setUserId(userId) {
2437
+ this.userId = userId;
2438
+ }
2439
+ /**
2440
+ * Clear user association (e.g., on logout)
2441
+ */
2442
+ clearUserId() {
2443
+ this.userId = void 0;
2444
+ }
2445
+ // ==========================================================================
2446
+ // Core Logging Methods
2447
+ // ==========================================================================
2448
+ /**
2449
+ * Log a custom event
2450
+ */
2451
+ async logEvent(eventType, payload = {}) {
2452
+ if (!this.enabled) return;
2453
+ this.eventQueue.push({
2454
+ eventType,
2455
+ payload,
2456
+ timestamp: Date.now()
2457
+ });
2458
+ if (eventType === "error" || eventType === "api_error") {
2459
+ await this.flush();
2460
+ }
2461
+ }
2462
+ /**
2463
+ * Log a page view event
2464
+ */
2465
+ async logPageView(pageName, metadata) {
2466
+ if (!this.enabled) return;
2467
+ const url = typeof window !== "undefined" ? window.location.href : "";
2468
+ const referrer = typeof document !== "undefined" ? document.referrer : "";
2469
+ await this.logEvent("page_view", {
2470
+ page_name: pageName,
2471
+ metadata: {
2472
+ url,
2473
+ referrer,
2474
+ ...metadata
2475
+ }
2476
+ });
2477
+ }
2478
+ /**
2479
+ * Log an error event
2480
+ */
2481
+ async logError(error, componentStack) {
2482
+ if (!this.enabled) return;
2483
+ const url = typeof window !== "undefined" ? window.location.href : "";
2484
+ await this.logEvent("error", {
2485
+ error_message: error.message,
2486
+ error_stack: error.stack,
2487
+ metadata: {
2488
+ name: error.name,
2489
+ component_stack: componentStack,
2490
+ url
2491
+ }
2492
+ });
2493
+ }
2494
+ /**
2495
+ * Log an API error event
2496
+ */
2497
+ async logApiError(endpoint, statusCode, errorMessage, metadata) {
2498
+ if (!this.enabled) return;
2499
+ await this.logEvent("api_error", {
2500
+ error_message: errorMessage,
2501
+ metadata: {
2502
+ endpoint,
2503
+ status_code: statusCode,
2504
+ ...metadata
2505
+ }
2506
+ });
2507
+ }
2508
+ // ==========================================================================
2509
+ // Event Batching & Flushing
2510
+ // ==========================================================================
2511
+ startFlushTimer() {
2512
+ if (this.flushTimer) return;
2513
+ this.flushTimer = setInterval(() => {
2514
+ if (this.eventQueue.length > 0) {
2515
+ this.flush();
2516
+ }
2517
+ }, FLUSH_INTERVAL);
2518
+ }
2519
+ /**
2520
+ * Flush all queued events to the server
2521
+ */
2522
+ async flush() {
2523
+ if (this.eventQueue.length === 0) return;
2524
+ const events = [...this.eventQueue];
2525
+ this.eventQueue = [];
2526
+ const sendPromises = events.map(
2527
+ ({ eventType, payload }) => this.sendEvent(eventType, payload)
2528
+ );
2529
+ await Promise.allSettled(sendPromises);
2530
+ }
2531
+ async sendEvent(eventType, payload) {
2532
+ const pageName = payload.page_name || (typeof window !== "undefined" ? window.location.pathname : "/");
2533
+ const url = `${this.config.apiUrl}/api/app-logs/${this.config.appId}/log-event`;
2534
+ const body = {
2535
+ session_id: this.sessionId,
2536
+ user_id: this.userId,
2537
+ event_type: eventType,
2538
+ page_name: pageName,
2539
+ action: payload.action,
2540
+ inputs: payload.inputs,
2541
+ metadata: payload.metadata,
2542
+ is_error: eventType === "error" || eventType === "api_error",
2543
+ error_message: payload.error_message,
2544
+ error_stack: payload.error_stack
2545
+ };
2546
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
2547
+ try {
2548
+ const response = await fetch(url, {
2549
+ method: "POST",
2550
+ headers: { "Content-Type": "application/json" },
2551
+ body: JSON.stringify(body),
2552
+ keepalive: true
2553
+ // Ensures request completes even on page unload
2554
+ });
2555
+ if (response.ok) {
2556
+ return;
2557
+ }
2558
+ if (response.status >= 400 && response.status < 500) {
2559
+ console.warn(`[Omnikit Analytics] Event rejected: ${response.status}`);
2560
+ return;
2561
+ }
2562
+ } catch (err) {
2563
+ }
2564
+ if (attempt < MAX_RETRIES - 1) {
2565
+ await new Promise((resolve) => setTimeout(resolve, Math.pow(2, attempt) * 1e3));
2566
+ }
2567
+ }
2568
+ console.warn("[Omnikit Analytics] Failed to send event after retries");
2569
+ }
2570
+ // ==========================================================================
2571
+ // Lifecycle
2572
+ // ==========================================================================
2573
+ /**
2574
+ * Enable or disable analytics
2575
+ */
2576
+ setEnabled(enabled) {
2577
+ this.enabled = enabled;
2578
+ if (enabled && typeof window !== "undefined") {
2579
+ this.startFlushTimer();
2580
+ } else if (this.flushTimer) {
2581
+ clearInterval(this.flushTimer);
2582
+ this.flushTimer = void 0;
2583
+ }
2584
+ }
2585
+ /**
2586
+ * Check if analytics is enabled
2587
+ */
2588
+ isEnabled() {
2589
+ return this.enabled;
2590
+ }
2591
+ /**
2592
+ * Clean up resources
2593
+ */
2594
+ destroy() {
2595
+ if (this.flushTimer) {
2596
+ clearInterval(this.flushTimer);
2597
+ this.flushTimer = void 0;
2598
+ }
2599
+ this.flush();
2600
+ }
2601
+ };
2602
+ function createAnalytics(config) {
2603
+ return new Analytics(config);
2604
+ }
2197
2605
 
2198
- export { APIClient, LiveVoiceSessionImpl, OmnikitError, cleanTokenFromUrl, createClient, getAccessToken, isTokenInUrl, removeAccessToken, saveAccessToken, setAccessToken };
2606
+ export { APIClient, Analytics, LiveVoiceSessionImpl, OmnikitError, cleanTokenFromUrl, createAnalytics, createClient, createClientFromRequest, createServerClient, getAccessToken, isTokenInUrl, removeAccessToken, saveAccessToken, setAccessToken };
2199
2607
  //# sourceMappingURL=index.mjs.map
2200
2608
  //# sourceMappingURL=index.mjs.map