@modelnex/sdk 0.5.18 → 0.5.19

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
@@ -2719,6 +2719,7 @@ function createTourSocketPool({
2719
2719
  var tourSocketPool = createTourSocketPool();
2720
2720
 
2721
2721
  // src/utils/tour-playback-guards.ts
2722
+ var playbackOwners = /* @__PURE__ */ new Map();
2722
2723
  function shouldExecuteTourCommandBatch(isPlaybackActive) {
2723
2724
  return isPlaybackActive;
2724
2725
  }
@@ -2742,6 +2743,25 @@ function shouldLogTourDebugEntry(state) {
2742
2743
  }
2743
2744
  return true;
2744
2745
  }
2746
+ function createTourPlaybackOwnerKey(serverUrl, websiteId, experienceType) {
2747
+ return `${serverUrl}::${websiteId || "__default__"}::${experienceType}`;
2748
+ }
2749
+ function claimTourPlaybackOwnership(key, ownerId) {
2750
+ const currentOwner = playbackOwners.get(key);
2751
+ if (currentOwner && currentOwner !== ownerId) {
2752
+ return false;
2753
+ }
2754
+ playbackOwners.set(key, ownerId);
2755
+ return true;
2756
+ }
2757
+ function hasTourPlaybackOwnership(key, ownerId) {
2758
+ return playbackOwners.get(key) === ownerId;
2759
+ }
2760
+ function releaseTourPlaybackOwnership(key, ownerId) {
2761
+ if (playbackOwners.get(key) === ownerId) {
2762
+ playbackOwners.delete(key);
2763
+ }
2764
+ }
2745
2765
 
2746
2766
  // src/hooks/useTourPlayback.ts
2747
2767
  function resolveElement(step) {
@@ -3277,6 +3297,8 @@ function useTourPlayback({
3277
3297
  const runIdRef = (0, import_react12.useRef)(null);
3278
3298
  const turnIdRef = (0, import_react12.useRef)(null);
3279
3299
  const startRequestedRef = (0, import_react12.useRef)(false);
3300
+ const playbackOwnerIdRef = (0, import_react12.useRef)(`playback-owner-${Math.random().toString(36).slice(2, 10)}`);
3301
+ const claimedPlaybackOwnerKeyRef = (0, import_react12.useRef)(null);
3280
3302
  const socketRef = (0, import_react12.useRef)(null);
3281
3303
  const socketIdRef = (0, import_react12.useRef)(socketId);
3282
3304
  const commandUrlRef = (0, import_react12.useRef)(commandUrl);
@@ -3294,6 +3316,12 @@ function useTourPlayback({
3294
3316
  toursApiBaseRef.current = toursApiBase;
3295
3317
  pendingTourRef.current = pendingTour;
3296
3318
  showCaptionsRef.current = showCaptions;
3319
+ const releasePlaybackOwnership = (0, import_react12.useCallback)(() => {
3320
+ const claimedKey = claimedPlaybackOwnerKeyRef.current;
3321
+ if (!claimedKey) return;
3322
+ releaseTourPlaybackOwnership(claimedKey, playbackOwnerIdRef.current);
3323
+ claimedPlaybackOwnerKeyRef.current = null;
3324
+ }, []);
3297
3325
  (0, import_react12.useEffect)(() => {
3298
3326
  if (disabled) return;
3299
3327
  if (typeof window === "undefined") return;
@@ -3329,7 +3357,8 @@ function useTourPlayback({
3329
3357
  }
3330
3358
  };
3331
3359
  const handleCommand = async (payload) => {
3332
- if (!shouldExecuteTourCommandBatch(isActiveRef.current)) {
3360
+ const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, experienceTypeRef.current);
3361
+ if (!shouldExecuteTourCommandBatch(isActiveRef.current) || !hasTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
3333
3362
  const activeType = experienceTypeRef.current;
3334
3363
  console.log("[TourClient] Ignoring command batch for inactive playback hook:", activeType, payload.stepIndex);
3335
3364
  return;
@@ -3839,6 +3868,12 @@ function useTourPlayback({
3839
3868
  console.log(`[TourClient] Ignoring ${tour.type} start (this hook is for ${expType})`);
3840
3869
  return;
3841
3870
  }
3871
+ const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, expType);
3872
+ if (!claimTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
3873
+ console.log(`[TourClient] Ignoring ${expType} start because another hook already owns playback`);
3874
+ return;
3875
+ }
3876
+ claimedPlaybackOwnerKeyRef.current = ownerKey;
3842
3877
  skipRequestedRef.current = false;
3843
3878
  startRequestedRef.current = false;
3844
3879
  const total = tourData.totalSteps ?? tour?.steps?.length ?? 0;
@@ -3900,12 +3935,13 @@ function useTourPlayback({
3900
3935
  const handleDebugLog = (entry) => {
3901
3936
  const isDev = devModeRef.current || process.env.NODE_ENV === "development" || typeof window !== "undefined" && window.MODELNEX_DEBUG;
3902
3937
  const entryTourType = entry?.data?.tourContext?.type ?? tourRef.current?.type ?? null;
3938
+ const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, experienceTypeRef.current);
3903
3939
  if (!shouldLogTourDebugEntry({
3904
3940
  isPlaybackActive: isActiveRef.current,
3905
3941
  entryType: entry?.type,
3906
3942
  entryTourType,
3907
3943
  experienceType: experienceTypeRef.current
3908
- })) {
3944
+ }) || isActiveRef.current && !hasTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
3909
3945
  return;
3910
3946
  }
3911
3947
  if (isDev) {
@@ -3940,9 +3976,10 @@ function useTourPlayback({
3940
3976
  setServerState(null);
3941
3977
  runIdRef.current = null;
3942
3978
  turnIdRef.current = null;
3979
+ releasePlaybackOwnership();
3943
3980
  tourSocketPool.release(serverUrl, toClose);
3944
3981
  };
3945
- }, [serverUrl, disabled]);
3982
+ }, [serverUrl, disabled, releasePlaybackOwnership]);
3946
3983
  (0, import_react12.useEffect)(() => {
3947
3984
  if (disabled) return;
3948
3985
  const s = socketRef.current;
@@ -4014,6 +4051,7 @@ function useTourPlayback({
4014
4051
  skipRequestedRef.current = true;
4015
4052
  isActiveRef.current = false;
4016
4053
  startRequestedRef.current = false;
4054
+ releasePlaybackOwnership();
4017
4055
  activeExecutionTokenRef.current += 1;
4018
4056
  commandInFlightRef.current = false;
4019
4057
  activeCommandBatchIdRef.current = null;
@@ -4049,13 +4087,14 @@ function useTourPlayback({
4049
4087
  setIsReviewMode(false);
4050
4088
  pendingInputBufRef.current = null;
4051
4089
  onTourEnd?.();
4052
- }, [voice, onTourEnd, serverUrl, websiteId]);
4090
+ }, [voice, onTourEnd, serverUrl, websiteId, releasePlaybackOwnership]);
4053
4091
  const handleTourEnd = (0, import_react12.useCallback)(() => {
4054
4092
  const endingTourId = tourRef.current?.id;
4055
4093
  const endingPreviewRunId = previewRunIdRef.current;
4056
4094
  const endingStepOrder = stepIndexRef.current;
4057
4095
  isActiveRef.current = false;
4058
4096
  startRequestedRef.current = false;
4097
+ releasePlaybackOwnership();
4059
4098
  setPlaybackState("complete");
4060
4099
  removeHighlight();
4061
4100
  removeCaption();
@@ -4087,7 +4126,7 @@ function useTourPlayback({
4087
4126
  clearActiveDraftPreview(experienceType);
4088
4127
  }
4089
4128
  onTourEnd?.();
4090
- }, [experienceType, userProfile, serverUrl, voice, onTourEnd, websiteId]);
4129
+ }, [experienceType, userProfile, serverUrl, voice, onTourEnd, websiteId, releasePlaybackOwnership]);
4091
4130
  const runTour = (0, import_react12.useCallback)(async (tour, options) => {
4092
4131
  if (!shouldAcceptTourStart({
4093
4132
  isPlaybackActive: isActiveRef.current,
@@ -4107,6 +4146,12 @@ function useTourPlayback({
4107
4146
  console.warn("[TourClient] Cannot run tour, socket not connected.");
4108
4147
  return;
4109
4148
  }
4149
+ const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, tour.type ?? experienceTypeRef.current);
4150
+ if (!claimTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
4151
+ console.log("[TourClient] Ignoring duplicate start request because another hook already owns this experience:", tour.id);
4152
+ return;
4153
+ }
4154
+ claimedPlaybackOwnerKeyRef.current = ownerKey;
4110
4155
  startRequestedRef.current = true;
4111
4156
  const shouldReview = Boolean(options?.reviewMode);
4112
4157
  resetCaptionSuppression();
package/dist/index.mjs CHANGED
@@ -2509,6 +2509,7 @@ function createTourSocketPool({
2509
2509
  var tourSocketPool = createTourSocketPool();
2510
2510
 
2511
2511
  // src/utils/tour-playback-guards.ts
2512
+ var playbackOwners = /* @__PURE__ */ new Map();
2512
2513
  function shouldExecuteTourCommandBatch(isPlaybackActive) {
2513
2514
  return isPlaybackActive;
2514
2515
  }
@@ -2532,6 +2533,25 @@ function shouldLogTourDebugEntry(state) {
2532
2533
  }
2533
2534
  return true;
2534
2535
  }
2536
+ function createTourPlaybackOwnerKey(serverUrl, websiteId, experienceType) {
2537
+ return `${serverUrl}::${websiteId || "__default__"}::${experienceType}`;
2538
+ }
2539
+ function claimTourPlaybackOwnership(key, ownerId) {
2540
+ const currentOwner = playbackOwners.get(key);
2541
+ if (currentOwner && currentOwner !== ownerId) {
2542
+ return false;
2543
+ }
2544
+ playbackOwners.set(key, ownerId);
2545
+ return true;
2546
+ }
2547
+ function hasTourPlaybackOwnership(key, ownerId) {
2548
+ return playbackOwners.get(key) === ownerId;
2549
+ }
2550
+ function releaseTourPlaybackOwnership(key, ownerId) {
2551
+ if (playbackOwners.get(key) === ownerId) {
2552
+ playbackOwners.delete(key);
2553
+ }
2554
+ }
2535
2555
 
2536
2556
  // src/hooks/useTourPlayback.ts
2537
2557
  function resolveElement(step) {
@@ -3067,6 +3087,8 @@ function useTourPlayback({
3067
3087
  const runIdRef = useRef8(null);
3068
3088
  const turnIdRef = useRef8(null);
3069
3089
  const startRequestedRef = useRef8(false);
3090
+ const playbackOwnerIdRef = useRef8(`playback-owner-${Math.random().toString(36).slice(2, 10)}`);
3091
+ const claimedPlaybackOwnerKeyRef = useRef8(null);
3070
3092
  const socketRef = useRef8(null);
3071
3093
  const socketIdRef = useRef8(socketId);
3072
3094
  const commandUrlRef = useRef8(commandUrl);
@@ -3084,6 +3106,12 @@ function useTourPlayback({
3084
3106
  toursApiBaseRef.current = toursApiBase;
3085
3107
  pendingTourRef.current = pendingTour;
3086
3108
  showCaptionsRef.current = showCaptions;
3109
+ const releasePlaybackOwnership = useCallback7(() => {
3110
+ const claimedKey = claimedPlaybackOwnerKeyRef.current;
3111
+ if (!claimedKey) return;
3112
+ releaseTourPlaybackOwnership(claimedKey, playbackOwnerIdRef.current);
3113
+ claimedPlaybackOwnerKeyRef.current = null;
3114
+ }, []);
3087
3115
  useEffect11(() => {
3088
3116
  if (disabled) return;
3089
3117
  if (typeof window === "undefined") return;
@@ -3119,7 +3147,8 @@ function useTourPlayback({
3119
3147
  }
3120
3148
  };
3121
3149
  const handleCommand = async (payload) => {
3122
- if (!shouldExecuteTourCommandBatch(isActiveRef.current)) {
3150
+ const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, experienceTypeRef.current);
3151
+ if (!shouldExecuteTourCommandBatch(isActiveRef.current) || !hasTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
3123
3152
  const activeType = experienceTypeRef.current;
3124
3153
  console.log("[TourClient] Ignoring command batch for inactive playback hook:", activeType, payload.stepIndex);
3125
3154
  return;
@@ -3629,6 +3658,12 @@ function useTourPlayback({
3629
3658
  console.log(`[TourClient] Ignoring ${tour.type} start (this hook is for ${expType})`);
3630
3659
  return;
3631
3660
  }
3661
+ const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, expType);
3662
+ if (!claimTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
3663
+ console.log(`[TourClient] Ignoring ${expType} start because another hook already owns playback`);
3664
+ return;
3665
+ }
3666
+ claimedPlaybackOwnerKeyRef.current = ownerKey;
3632
3667
  skipRequestedRef.current = false;
3633
3668
  startRequestedRef.current = false;
3634
3669
  const total = tourData.totalSteps ?? tour?.steps?.length ?? 0;
@@ -3690,12 +3725,13 @@ function useTourPlayback({
3690
3725
  const handleDebugLog = (entry) => {
3691
3726
  const isDev = devModeRef.current || process.env.NODE_ENV === "development" || typeof window !== "undefined" && window.MODELNEX_DEBUG;
3692
3727
  const entryTourType = entry?.data?.tourContext?.type ?? tourRef.current?.type ?? null;
3728
+ const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, experienceTypeRef.current);
3693
3729
  if (!shouldLogTourDebugEntry({
3694
3730
  isPlaybackActive: isActiveRef.current,
3695
3731
  entryType: entry?.type,
3696
3732
  entryTourType,
3697
3733
  experienceType: experienceTypeRef.current
3698
- })) {
3734
+ }) || isActiveRef.current && !hasTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
3699
3735
  return;
3700
3736
  }
3701
3737
  if (isDev) {
@@ -3730,9 +3766,10 @@ function useTourPlayback({
3730
3766
  setServerState(null);
3731
3767
  runIdRef.current = null;
3732
3768
  turnIdRef.current = null;
3769
+ releasePlaybackOwnership();
3733
3770
  tourSocketPool.release(serverUrl, toClose);
3734
3771
  };
3735
- }, [serverUrl, disabled]);
3772
+ }, [serverUrl, disabled, releasePlaybackOwnership]);
3736
3773
  useEffect11(() => {
3737
3774
  if (disabled) return;
3738
3775
  const s = socketRef.current;
@@ -3804,6 +3841,7 @@ function useTourPlayback({
3804
3841
  skipRequestedRef.current = true;
3805
3842
  isActiveRef.current = false;
3806
3843
  startRequestedRef.current = false;
3844
+ releasePlaybackOwnership();
3807
3845
  activeExecutionTokenRef.current += 1;
3808
3846
  commandInFlightRef.current = false;
3809
3847
  activeCommandBatchIdRef.current = null;
@@ -3839,13 +3877,14 @@ function useTourPlayback({
3839
3877
  setIsReviewMode(false);
3840
3878
  pendingInputBufRef.current = null;
3841
3879
  onTourEnd?.();
3842
- }, [voice, onTourEnd, serverUrl, websiteId]);
3880
+ }, [voice, onTourEnd, serverUrl, websiteId, releasePlaybackOwnership]);
3843
3881
  const handleTourEnd = useCallback7(() => {
3844
3882
  const endingTourId = tourRef.current?.id;
3845
3883
  const endingPreviewRunId = previewRunIdRef.current;
3846
3884
  const endingStepOrder = stepIndexRef.current;
3847
3885
  isActiveRef.current = false;
3848
3886
  startRequestedRef.current = false;
3887
+ releasePlaybackOwnership();
3849
3888
  setPlaybackState("complete");
3850
3889
  removeHighlight();
3851
3890
  removeCaption();
@@ -3877,7 +3916,7 @@ function useTourPlayback({
3877
3916
  clearActiveDraftPreview(experienceType);
3878
3917
  }
3879
3918
  onTourEnd?.();
3880
- }, [experienceType, userProfile, serverUrl, voice, onTourEnd, websiteId]);
3919
+ }, [experienceType, userProfile, serverUrl, voice, onTourEnd, websiteId, releasePlaybackOwnership]);
3881
3920
  const runTour = useCallback7(async (tour, options) => {
3882
3921
  if (!shouldAcceptTourStart({
3883
3922
  isPlaybackActive: isActiveRef.current,
@@ -3897,6 +3936,12 @@ function useTourPlayback({
3897
3936
  console.warn("[TourClient] Cannot run tour, socket not connected.");
3898
3937
  return;
3899
3938
  }
3939
+ const ownerKey = createTourPlaybackOwnerKey(serverUrl, websiteIdRef.current, tour.type ?? experienceTypeRef.current);
3940
+ if (!claimTourPlaybackOwnership(ownerKey, playbackOwnerIdRef.current)) {
3941
+ console.log("[TourClient] Ignoring duplicate start request because another hook already owns this experience:", tour.id);
3942
+ return;
3943
+ }
3944
+ claimedPlaybackOwnerKeyRef.current = ownerKey;
3900
3945
  startRequestedRef.current = true;
3901
3946
  const shouldReview = Boolean(options?.reviewMode);
3902
3947
  resetCaptionSuppression();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelnex/sdk",
3
- "version": "0.5.18",
3
+ "version": "0.5.19",
4
4
  "description": "React SDK for natural language control of web apps via AI agents",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",