obi-sdk 0.3.13 → 0.4.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/README.md CHANGED
@@ -21,6 +21,7 @@ The loader accepts the following configuration options:
21
21
  | `apiKey` | string | - | Your Obi API key (required for production) |
22
22
  | `user` | object | - | User information with `id` (required), `email` (optional) and `metadata` (optional) |
23
23
  | `isActive` | boolean | `true` | Whether to show the widget. Set to `false` to disable the widget for specific users |
24
+ | `linkOnlyAccess` | boolean | `false` | Hide the widget unless accessed via a session link. Perfect for trial periods where only customers with direct links should see the widget |
24
25
  | `primaryColor` | string | - | Custom primary color for the widget UI (CSS color value) |
25
26
  | `secondaryColor` | string | - | Custom secondary color for the widget UI (CSS color value) |
26
27
 
@@ -41,6 +42,30 @@ window.obiWidgetConfig = {
41
42
  }
42
43
  ```
43
44
 
45
+ ## Trial Mode / Link-Only Access
46
+
47
+ The `linkOnlyAccess` option is perfect for trial periods where you want to control who can see and interact with the widget. When enabled, the widget remains hidden until a session is started via a direct link.
48
+
49
+ ```js
50
+ window.obiWidgetConfig = {
51
+ apiKey: "YOUR_API_KEY",
52
+ linkOnlyAccess: true, // Widget only visible when accessed via session link
53
+ }
54
+ ```
55
+
56
+ This is ideal for:
57
+
58
+ - Trial periods where only specific customers should see the widget
59
+ - Beta testing with selected users
60
+ - Controlled rollouts using session-specific links
61
+ - Demo environments where widget access is link-based
62
+
63
+ When `linkOnlyAccess` is `true`:
64
+
65
+ - Widget is invisible to regular website visitors
66
+ - Widget becomes visible only when a session is started (e.g., via URL parameters)
67
+ - Once a session ends, the widget returns to hidden state
68
+
44
69
  ## Session Persistence
45
70
 
46
71
  The SDK now supports session persistence across page reloads and navigation. If a user refreshes the page or navigates to another page on your site, the session with Obi will be automatically restored when they return, providing a seamless experience.
package/dist/loader.d.ts CHANGED
@@ -10,9 +10,12 @@ interface ObiWidgetConfig {
10
10
  user?: {
11
11
  id: string;
12
12
  email?: string;
13
- metadata?: any;
14
- };
13
+ metadata?: Record<string, any>;
14
+ } | null;
15
15
  isActive?: boolean;
16
+ linkOnlyAccess?: boolean;
17
+ primaryColor?: string;
18
+ secondaryColor?: string;
16
19
  }
17
20
  declare class ObiWidgetLoader {
18
21
  private config;
@@ -534,6 +534,9 @@ class ObiSession {
534
534
  this._userAudioTimer = null;
535
535
  this.sessionId = sessionId;
536
536
  this.apiBaseUrl = apiBaseUrl || DEFAULT_API_BASE_URL;
537
+ this.client = new ObiClient({
538
+ baseUrl: this.apiBaseUrl
539
+ });
537
540
  this.emitter = new EventEmitter();
538
541
  }
539
542
  emit(event, data) {
@@ -590,7 +593,7 @@ class ObiSession {
590
593
  if (this.currentState === SDKState.RESEARCHING || this.currentState === SDKState.PAUSED)
591
594
  return;
592
595
  const state = attributes["lk.agent.state"];
593
- const newState = z$2(state).with("listening", () => SDKState.USER_SPEAKING).with("speaking", () => SDKState.AGENT_SPEAKING).with("thinking", () => SDKState.AGENT_SPEAKING).otherwise(() => void 0);
596
+ const newState = z$2(state).with("listening", () => SDKState.USER_SPEAKING).with("speaking", () => SDKState.AGENT_SPEAKING).with("thinking", () => SDKState.THINKING).otherwise(() => void 0);
594
597
  if (!newState)
595
598
  return;
596
599
  this.setState(newState);
@@ -660,10 +663,8 @@ class ObiSession {
660
663
  dynacast: true
661
664
  });
662
665
  this.setupRoomEventListeners();
663
- const params = new URLSearchParams({ token: this.sessionId, skip_intro: "true" });
664
- const joinEndpoint = `${this.apiBaseUrl}/join-token?${params.toString()}`;
665
- const joinToken = await fetch(joinEndpoint).then((res) => res.json());
666
- await this.room.connect(joinToken.url, joinToken.token);
666
+ const joinToken = await this.client.getJoinToken(this.sessionId, { skipIntro: true });
667
+ await this.room.connect(joinToken.data.url, joinToken.data.token);
667
668
  if (this.microphoneStream) {
668
669
  const micTrack = this.microphoneStream.getAudioTracks()[0];
669
670
  await this.room.localParticipant.publishTrack(micTrack, {
@@ -672,8 +673,8 @@ class ObiSession {
672
673
  });
673
674
  }
674
675
  return {
675
- url: joinToken.url,
676
- token: joinToken.token
676
+ url: joinToken.data.url,
677
+ token: joinToken.data.token
677
678
  };
678
679
  } catch (error) {
679
680
  console.error("Failed to connect to LiveKit:", error);
@@ -718,6 +719,9 @@ class ObiSession {
718
719
  this.currentState = newState;
719
720
  this.emitter.emit("stateChanged", newState);
720
721
  }
722
+ getCurrentState() {
723
+ return this.currentState;
724
+ }
721
725
  async requestMicrophone() {
722
726
  try {
723
727
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
@@ -11220,7 +11224,7 @@ SessionStartModal.styles = i$4`
11220
11224
  align-items: center;
11221
11225
  gap: 8px;
11222
11226
  aspect-ratio: 1/1;
11223
- border-radius: var(--border-radius-lg, 8px);
11227
+ border-radius: var(--border-radius-lg, 12px);
11224
11228
  background: var(--tailwind-colors-violet-600, #7c3aed);
11225
11229
  box-shadow:
11226
11230
  0px 0px 8px 0px rgba(168, 85, 247, 0.12),
@@ -11328,11 +11332,13 @@ var __decorateClass = (decorators, target, key, kind) => {
11328
11332
  __defProp(target, key, result);
11329
11333
  return result;
11330
11334
  };
11335
+ const WIDGET_PARAMS_KEY = "io.obi.widget-parameters";
11331
11336
  class ObiWidget extends i$1 {
11332
11337
  constructor() {
11333
11338
  super();
11334
11339
  this.apiKey = "";
11335
11340
  this.isActive = true;
11341
+ this.linkOnlyAccess = false;
11336
11342
  this.position = "bottom-right";
11337
11343
  this.user = null;
11338
11344
  this.state = SDKState.READY;
@@ -11354,6 +11360,7 @@ class ObiWidget extends i$1 {
11354
11360
  this.boundSaveSessionData = null;
11355
11361
  this.obiClient = null;
11356
11362
  this.closeNavTimeoutRef = null;
11363
+ this.researchingTimeoutRef = null;
11357
11364
  this.handleCourseSelectEvent = (event) => {
11358
11365
  const customEvent = event;
11359
11366
  this.selectedCourse = customEvent.detail;
@@ -11374,10 +11381,12 @@ class ObiWidget extends i$1 {
11374
11381
  const sessionWithPlan = matchingSession;
11375
11382
  this.selectedCourse = {
11376
11383
  id: sessionToken,
11377
- name: sessionWithPlan.onboarding_plan?.product?.name || "Session from URL",
11378
- description: sessionWithPlan.onboarding_plan?.product?.description || "Continue your session"
11384
+ name: sessionWithPlan.onboarding_plan?.name || "",
11385
+ description: sessionWithPlan.onboarding_plan?.description || ""
11379
11386
  };
11380
11387
  this.showSessionStartModal = true;
11388
+ } else {
11389
+ console.log("No session found with token:", sessionToken);
11381
11390
  }
11382
11391
  }
11383
11392
  } catch (error) {
@@ -11404,15 +11413,23 @@ class ObiWidget extends i$1 {
11404
11413
  if (window.obiWidgetConfig.isActive !== void 0) {
11405
11414
  this.isActive = window.obiWidgetConfig.isActive;
11406
11415
  }
11416
+ if (window.obiWidgetConfig.linkOnlyAccess !== void 0) {
11417
+ this.linkOnlyAccess = window.obiWidgetConfig.linkOnlyAccess;
11418
+ }
11407
11419
  this.style.setProperty("--obi-primary", window.obiWidgetConfig?.primaryColor || "#9500ff");
11408
11420
  this.style.setProperty("--obi-secondary", window.obiWidgetConfig?.secondaryColor || "#c4b5fd");
11409
11421
  }
11410
11422
  }
11411
- removeSessionFromUrl() {
11423
+ removeSessionUrlParams() {
11412
11424
  const url = new URL(window.location.href);
11413
11425
  url.searchParams.delete(SESSION_URL_PARAM);
11414
11426
  url.searchParams.delete(API_KEY_URL_PARAM);
11415
11427
  window.history.replaceState({}, "", url.toString());
11428
+ try {
11429
+ localStorage.removeItem(WIDGET_PARAMS_KEY);
11430
+ } catch (error) {
11431
+ console.warn("Failed to remove widget parameters from localStorage:", error);
11432
+ }
11416
11433
  }
11417
11434
  /**
11418
11435
  * Create a new ObiSession instance with common configuration
@@ -11438,9 +11455,27 @@ class ObiWidget extends i$1 {
11438
11455
  */
11439
11456
  setupSessionEventListeners(session, onError) {
11440
11457
  session.on("stateChanged", (newState) => {
11441
- this.state = newState;
11442
- if (newState !== SDKState.READY) {
11458
+ if (newState === SDKState.RESEARCHING) {
11459
+ if (this.researchingTimeoutRef) {
11460
+ window.clearTimeout(this.researchingTimeoutRef);
11461
+ }
11462
+ this.state = newState;
11463
+ this.researchingTimeoutRef = window.setTimeout(() => {
11464
+ this.researchingTimeoutRef = null;
11465
+ const currentSessionState = session.getCurrentState();
11466
+ this.state = currentSessionState;
11467
+ if (currentSessionState !== SDKState.READY) {
11468
+ this.storedActiveState = currentSessionState;
11469
+ }
11470
+ }, 1500);
11443
11471
  this.storedActiveState = newState;
11472
+ return;
11473
+ }
11474
+ if (this.researchingTimeoutRef === null) {
11475
+ this.state = newState;
11476
+ if (newState !== SDKState.READY) {
11477
+ this.storedActiveState = newState;
11478
+ }
11444
11479
  }
11445
11480
  });
11446
11481
  session.on("volume", ({ speaker, spectrum, volume }) => {
@@ -11473,10 +11508,10 @@ class ObiWidget extends i$1 {
11473
11508
  try {
11474
11509
  const session = this.createSession(sessionToken);
11475
11510
  if (!session) {
11476
- this.handleSessionCreationFailure(() => this.removeSessionFromUrl());
11511
+ this.handleSessionCreationFailure(() => this.removeSessionUrlParams());
11477
11512
  return;
11478
11513
  }
11479
- this.setupSessionEventListeners(session, () => this.removeSessionFromUrl());
11514
+ this.setupSessionEventListeners(session, () => this.removeSessionUrlParams());
11480
11515
  session.on("screenCaptureRequested", async () => {
11481
11516
  try {
11482
11517
  const canvas = await html2canvas(document.documentElement, {
@@ -11496,12 +11531,12 @@ class ObiWidget extends i$1 {
11496
11531
  this.sessionToken = sessionToken;
11497
11532
  this.roomToken = connectionInfo.token;
11498
11533
  this.roomUrl = connectionInfo.url;
11499
- this.removeSessionFromUrl();
11534
+ this.removeSessionUrlParams();
11500
11535
  }
11501
11536
  this.activeSession = session;
11502
11537
  } catch (error) {
11503
11538
  console.error("Failed to start session:", error);
11504
- this.handleSessionCreationFailure(() => this.removeSessionFromUrl());
11539
+ this.handleSessionCreationFailure(() => this.removeSessionUrlParams());
11505
11540
  }
11506
11541
  }
11507
11542
  async handleSessionStart(sessionToken) {
@@ -11582,9 +11617,28 @@ class ObiWidget extends i$1 {
11582
11617
  async sessionConnectionCheck() {
11583
11618
  await this.checkExistingSession();
11584
11619
  if (!this.activeSession) {
11585
- const urlParams = new URLSearchParams(window.location.search);
11586
- const sessionId = urlParams.get(SESSION_URL_PARAM);
11587
- this.apiKey = urlParams.get(API_KEY_URL_PARAM) || this.apiKey;
11620
+ let storedParams = {};
11621
+ try {
11622
+ const storedParamsJson = localStorage.getItem(WIDGET_PARAMS_KEY);
11623
+ if (storedParamsJson) {
11624
+ storedParams = JSON.parse(storedParamsJson);
11625
+ }
11626
+ } catch (error) {
11627
+ console.warn("Failed to parse stored widget parameters:", error);
11628
+ }
11629
+ if (Object.keys(storedParams).length === 0) {
11630
+ const urlParams = new URLSearchParams(window.location.search);
11631
+ urlParams.forEach((value, key) => {
11632
+ storedParams[key] = value;
11633
+ });
11634
+ }
11635
+ const sessionId = storedParams[SESSION_URL_PARAM];
11636
+ const urlApiKey = storedParams[API_KEY_URL_PARAM];
11637
+ if (urlApiKey) {
11638
+ this.apiKey = urlApiKey;
11639
+ } else if (!this.apiKey && window.obiWidgetConfig?.apiKey) {
11640
+ this.apiKey = window.obiWidgetConfig.apiKey;
11641
+ }
11588
11642
  if (sessionId && this.apiKey) {
11589
11643
  await this.handleUrlSessionEvent(sessionId);
11590
11644
  }
@@ -11603,11 +11657,15 @@ class ObiWidget extends i$1 {
11603
11657
  if (this.closeNavTimeoutRef !== null) {
11604
11658
  window.clearTimeout(this.closeNavTimeoutRef);
11605
11659
  }
11660
+ if (this.researchingTimeoutRef !== null) {
11661
+ window.clearTimeout(this.researchingTimeoutRef);
11662
+ this.researchingTimeoutRef = null;
11663
+ }
11606
11664
  if (this.boundSaveSessionData) {
11607
11665
  window.removeEventListener("beforeunload", this.boundSaveSessionData);
11608
11666
  window.removeEventListener("pagehide", this.boundSaveSessionData);
11609
11667
  }
11610
- this.removeSessionFromUrl();
11668
+ this.removeSessionUrlParams();
11611
11669
  super.disconnectedCallback();
11612
11670
  }
11613
11671
  handleMouseEnter() {
@@ -11635,6 +11693,10 @@ class ObiWidget extends i$1 {
11635
11693
  this.sessionToken = null;
11636
11694
  this.roomToken = null;
11637
11695
  this.roomUrl = null;
11696
+ if (this.researchingTimeoutRef !== null) {
11697
+ window.clearTimeout(this.researchingTimeoutRef);
11698
+ this.researchingTimeoutRef = null;
11699
+ }
11638
11700
  if (this.activeSession) {
11639
11701
  this.activeSession.disconnect();
11640
11702
  this.activeSession = null;
@@ -11659,13 +11721,26 @@ class ObiWidget extends i$1 {
11659
11721
  render() {
11660
11722
  if (!this.isActive)
11661
11723
  return E;
11724
+ if (this.linkOnlyAccess && this.state === SDKState.READY)
11725
+ return E;
11662
11726
  const stateRender = z$2(this.state).with(SDKState.LOADING, () => x`<obi-dot-loader></obi-dot-loader>`).with(SDKState.RESEARCHING, () => x`<obi-searching-loader></obi-searching-loader>`).with(
11663
11727
  N$1.union(SDKState.USER_SPEAKING, SDKState.AGENT_SPEAKING),
11664
11728
  () => x`<obi-audio-equalizer .volume=${this.volume}></obi-audio-equalizer>`
11665
- ).with(SDKState.PAUSED, () => obiIcon).otherwise(() => obiIcon);
11729
+ ).with(SDKState.THINKING, () => x`<obi-dot-loader></obi-dot-loader>`).with(SDKState.PAUSED, () => obiIcon).otherwise(() => obiIcon);
11730
+ const isPulseState = this.state === SDKState.USER_SPEAKING || this.state === SDKState.AGENT_SPEAKING;
11731
+ const isResearching = this.state === SDKState.RESEARCHING;
11732
+ const isUserSpeaking = this.state === SDKState.USER_SPEAKING;
11733
+ const isRotated = this.state !== SDKState.READY || this.navVisible;
11734
+ const containerClasses = [
11735
+ "widget-container",
11736
+ isRotated ? "rotated" : "",
11737
+ isPulseState ? "pulse" : "",
11738
+ isResearching ? "researching" : "",
11739
+ isUserSpeaking ? "user-speaking" : ""
11740
+ ].filter(Boolean).join(" ");
11666
11741
  return x`
11667
11742
  <div
11668
- class="widget-container ${this.state === SDKState.USER_SPEAKING || this.state === SDKState.AGENT_SPEAKING ? "pulse" : ""} ${this.state !== SDKState.READY || this.navVisible ? "rounded" : ""} ${this.state === SDKState.RESEARCHING ? "researching" : ""} ${this.state === SDKState.USER_SPEAKING ? "user-speaking" : ""}"
11743
+ class="${containerClasses}"
11669
11744
  @mouseenter=${this.handleMouseEnter}
11670
11745
  @mouseleave=${this.handleMouseLeave}
11671
11746
  >
@@ -11773,7 +11848,7 @@ ObiWidget.styles = i$4`
11773
11848
  position: fixed;
11774
11849
  width: 56px;
11775
11850
  height: 56px;
11776
- border-radius: 28px;
11851
+ border-radius: 12px;
11777
11852
  border-color: transparent;
11778
11853
  background-color: var(--obi-primary);
11779
11854
  display: flex;
@@ -11789,17 +11864,8 @@ ObiWidget.styles = i$4`
11789
11864
  linear-gradient(195.84deg, var(--obi-secondary) 00 11.05%, var(--obi-secondary) 117.01%);
11790
11865
  }
11791
11866
 
11792
- .widget-container:hover {
11793
- border-radius: 12px;
11794
- }
11795
-
11796
- .widget-container.rounded {
11797
- border-radius: 12px;
11798
- }
11799
-
11800
11867
  .widget-container.researching {
11801
11868
  width: 273px;
11802
- border-radius: 12px;
11803
11869
  }
11804
11870
 
11805
11871
  .widget-icon {
@@ -11808,7 +11874,7 @@ ObiWidget.styles = i$4`
11808
11874
  transition: transform 0.5s ease-in-out;
11809
11875
  }
11810
11876
 
11811
- .widget-container.rounded .widget-icon {
11877
+ .widget-container.rotated .widget-icon {
11812
11878
  transform: rotate(90deg);
11813
11879
  }
11814
11880
 
@@ -11834,6 +11900,9 @@ __decorateClass([
11834
11900
  __decorateClass([
11835
11901
  r$1()
11836
11902
  ], ObiWidget.prototype, "isActive", 2);
11903
+ __decorateClass([
11904
+ r$1()
11905
+ ], ObiWidget.prototype, "linkOnlyAccess", 2);
11837
11906
  __decorateClass([
11838
11907
  r$1()
11839
11908
  ], ObiWidget.prototype, "position", 2);
@@ -11894,4 +11963,4 @@ export {
11894
11963
  searchingLoader as s,
11895
11964
  x
11896
11965
  };
11897
- //# sourceMappingURL=obi-widget-d7e7c6bf.js.map
11966
+ //# sourceMappingURL=obi-widget-9ef6796a.js.map