saltfish 0.2.73 → 0.2.75

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.
@@ -1525,6 +1525,36 @@ const useSaltfishStore = {
1525
1525
  subscribe: saltfishStore.subscribe,
1526
1526
  destroy: saltfishStore.destroy
1527
1527
  };
1528
+ const MAX_PROGRESS_AGE_MS = 6e3;
1529
+ function parseProgressTimestamp(progressData) {
1530
+ if (!progressData) {
1531
+ return null;
1532
+ }
1533
+ if (progressData.lastProgressAt && typeof progressData.lastProgressAt === "object" && "_seconds" in progressData.lastProgressAt && "_nanoseconds" in progressData.lastProgressAt) {
1534
+ const firestoreTimestamp = progressData.lastProgressAt;
1535
+ return firestoreTimestamp._seconds * 1e3 + Math.floor(firestoreTimestamp._nanoseconds / 1e6);
1536
+ }
1537
+ if (progressData.timestamp && typeof progressData.timestamp === "number") {
1538
+ return progressData.timestamp;
1539
+ }
1540
+ if (progressData.lastProgressAt && typeof progressData.lastProgressAt === "number") {
1541
+ return progressData.lastProgressAt;
1542
+ }
1543
+ return null;
1544
+ }
1545
+ function isProgressRecent(progressData, maxAgeMs = MAX_PROGRESS_AGE_MS) {
1546
+ const timestampMs = parseProgressTimestamp(progressData);
1547
+ if (!timestampMs) {
1548
+ return { isValid: false, ageMs: null, timestampMs: null };
1549
+ }
1550
+ const currentTime = Date.now();
1551
+ const ageMs = currentTime - timestampMs;
1552
+ return {
1553
+ isValid: ageMs <= maxAgeMs,
1554
+ ageMs,
1555
+ timestampMs
1556
+ };
1557
+ }
1528
1558
  class ErrorHandler {
1529
1559
  /**
1530
1560
  * Handles an error with consistent logging, reporting, and response
@@ -2042,40 +2072,19 @@ class PlayerInitializationService {
2042
2072
  if (currentManifestId && currentState !== "idle" && currentState !== "error") {
2043
2073
  return false;
2044
2074
  }
2045
- const currentTime = Date.now();
2046
- const MAX_AGE_MS = 6e3;
2047
2075
  const inProgressEntry = Object.entries(watchedPlaylists).find(([playlistId, status]) => {
2048
2076
  if ((status == null ? void 0 : status.status) !== "in_progress") {
2049
2077
  return false;
2050
2078
  }
2051
- let timestampMs = null;
2052
- if (status.lastProgressAt && typeof status.lastProgressAt === "object" && "_seconds" in status.lastProgressAt && "_nanoseconds" in status.lastProgressAt) {
2053
- const firestoreTimestamp = status.lastProgressAt;
2054
- timestampMs = firestoreTimestamp._seconds * 1e3 + Math.floor(firestoreTimestamp._nanoseconds / 1e6);
2055
- } else if (status.timestamp && typeof status.timestamp === "number") {
2056
- timestampMs = status.timestamp;
2057
- } else if (status.lastProgressAt && typeof status.lastProgressAt === "number") {
2058
- timestampMs = status.lastProgressAt;
2059
- }
2060
- if (!timestampMs) {
2061
- return false;
2062
- }
2063
- const ageMs = currentTime - timestampMs;
2064
- if (ageMs > MAX_AGE_MS) {
2079
+ const { isValid, ageMs } = isProgressRecent(status);
2080
+ if (!isValid) {
2065
2081
  return false;
2066
2082
  }
2067
2083
  return true;
2068
2084
  });
2069
2085
  if (inProgressEntry) {
2070
2086
  const [playlistId, status] = inProgressEntry;
2071
- if ((status == null ? void 0 : status.lastProgressAt) && typeof status.lastProgressAt === "object" && "_seconds" in status.lastProgressAt && "_nanoseconds" in status.lastProgressAt) {
2072
- const firestoreTimestamp = status.lastProgressAt;
2073
- firestoreTimestamp._seconds * 1e3 + Math.floor(firestoreTimestamp._nanoseconds / 1e6);
2074
- } else if ((status == null ? void 0 : status.timestamp) && typeof status.timestamp === "number") {
2075
- status.timestamp;
2076
- } else if ((status == null ? void 0 : status.lastProgressAt) && typeof status.lastProgressAt === "number") {
2077
- status.lastProgressAt;
2078
- }
2087
+ parseProgressTimestamp(status);
2079
2088
  try {
2080
2089
  if (this.playlistOrchestrator) {
2081
2090
  await this.playlistOrchestrator.startPlaylist(playlistId);
@@ -2997,9 +3006,10 @@ class StateMachineActionHandler {
2997
3006
  const completionPolicy = hasSpecialTransitions ? "manual" : "auto";
2998
3007
  if (hasSpecialTransitions) {
2999
3008
  log("StateMachineActionHandler: Setting up transitions immediately for step with special transitions");
3000
- this.managers.transitionManager.setupTransitions(currentStep, false);
3009
+ this.managers.transitionManager.setupTransitions(currentStep, false, true);
3001
3010
  }
3002
3011
  this.managers.videoManager.setCompletionPolicy(completionPolicy, () => {
3012
+ var _a;
3003
3013
  const store = useSaltfishStore.getState();
3004
3014
  if (!hasSpecialTransitions) {
3005
3015
  log("StateMachineActionHandler: Setting up transitions after video ended");
@@ -3014,10 +3024,38 @@ class StateMachineActionHandler {
3014
3024
  });
3015
3025
  }
3016
3026
  } else {
3017
- store.sendStateMachineEvent({
3018
- type: "VIDEO_FINISHED_WAIT",
3019
- step: currentStep
3020
- });
3027
+ const timeoutTransition = currentStep.transitions.find((t) => t.type === "timeout");
3028
+ const hasStepTransitionButtons = (_a = currentStep.buttons) == null ? void 0 : _a.some(
3029
+ (button) => button.action.type === "goto" || button.action.type === "next"
3030
+ );
3031
+ if (timeoutTransition && !hasStepTransitionButtons) {
3032
+ const timeout = timeoutTransition.timeout || 0;
3033
+ log(`StateMachineActionHandler: Video ended, setting up timeout transition to ${timeoutTransition.nextStep} with ${timeout}ms delay`);
3034
+ setTimeout(() => {
3035
+ var _a2;
3036
+ const currentStore = useSaltfishStore.getState();
3037
+ if (currentStore.currentStepId === currentStep.id && (currentStore.currentState === "waitingForInteraction" || currentStore.currentState === "playing")) {
3038
+ log(`StateMachineActionHandler: Timeout expired (${timeout}ms), transitioning to ${timeoutTransition.nextStep}`);
3039
+ (_a2 = currentStore.goToStep) == null ? void 0 : _a2.call(currentStore, timeoutTransition.nextStep);
3040
+ } else {
3041
+ log(`StateMachineActionHandler: Timeout cancelled - step changed or state is ${currentStore.currentState}`);
3042
+ }
3043
+ }, timeout);
3044
+ store.sendStateMachineEvent({
3045
+ type: "VIDEO_FINISHED_WAIT",
3046
+ step: currentStep
3047
+ });
3048
+ } else {
3049
+ if (hasStepTransitionButtons) {
3050
+ log("StateMachineActionHandler: Video ended, has step-transition button (goto/next) - waiting for user to click button");
3051
+ } else {
3052
+ log("StateMachineActionHandler: Video ended, waiting for user interaction");
3053
+ }
3054
+ store.sendStateMachineEvent({
3055
+ type: "VIDEO_FINISHED_WAIT",
3056
+ step: currentStep
3057
+ });
3058
+ }
3021
3059
  }
3022
3060
  });
3023
3061
  log("StateMachineActionHandler: Starting async video load");
@@ -7209,6 +7247,8 @@ class InteractionManager {
7209
7247
  state.progress[state.manifest.id] = {
7210
7248
  ...state.progress[state.manifest.id],
7211
7249
  lastStepId: buttonConfig.action.target,
7250
+ lastProgressAt: Date.now(),
7251
+ // Use timestamp for 6-second rule
7212
7252
  lastVisited: (/* @__PURE__ */ new Date()).toISOString()
7213
7253
  };
7214
7254
  }
@@ -7962,17 +8002,20 @@ class TransitionManager {
7962
8002
  * Sets up transitions for a step
7963
8003
  * @param step - The step to set up transitions for
7964
8004
  * @param triggerImmediately - Whether to immediately trigger non-interaction transitions
8005
+ * @param skipTimeouts - Whether to skip setting up timeout transitions (useful for manual policy steps)
7965
8006
  */
7966
- setupTransitions(step, triggerImmediately = false) {
8007
+ setupTransitions(step, triggerImmediately = false, skipTimeouts = false) {
7967
8008
  this.cleanupTransitions();
7968
- log(`TransitionManager: Setting up transitions for step ${step.id}`);
8009
+ log(`TransitionManager: Setting up transitions for step ${step.id}${skipTimeouts ? " (skipping timeouts)" : ""}`);
7969
8010
  step.transitions.forEach((transition) => {
7970
8011
  switch (transition.type) {
7971
8012
  case "dom-click":
7972
8013
  this.setupDOMClickTransition(transition);
7973
8014
  break;
7974
8015
  case "timeout":
7975
- this.setupTimeoutTransition(transition, triggerImmediately);
8016
+ if (!skipTimeouts) {
8017
+ this.setupTimeoutTransition(transition, triggerImmediately);
8018
+ }
7976
8019
  break;
7977
8020
  case "url-path":
7978
8021
  this.setupURLPathTransition(transition);
@@ -8078,7 +8121,7 @@ class TransitionManager {
8078
8121
  let timeoutId = null;
8079
8122
  if (triggerImmediately) {
8080
8123
  this.triggerTransition(nextStepId);
8081
- } else if (timeout > 0) {
8124
+ } else {
8082
8125
  timeoutId = window.setTimeout(() => {
8083
8126
  this.triggerTransition(nextStepId);
8084
8127
  }, timeout);
@@ -9382,11 +9425,16 @@ class PlaylistLoader {
9382
9425
  if (progressData.status === "completed") {
9383
9426
  startStepId = manifest.startStep;
9384
9427
  } else {
9385
- const lastStepId = progressData.currentStepId || progressData.lastStepId;
9386
- if (lastStepId) {
9387
- const savedStep = manifest.steps.find((step) => step.id === lastStepId);
9388
- if (savedStep) {
9389
- startStepId = lastStepId;
9428
+ const { isValid, ageMs } = isProgressRecent(progressData);
9429
+ if (!isValid) {
9430
+ startStepId = manifest.startStep;
9431
+ } else {
9432
+ const lastStepId = progressData.currentStepId || progressData.lastStepId;
9433
+ if (lastStepId) {
9434
+ const savedStep = manifest.steps.find((step) => step.id === lastStepId);
9435
+ if (savedStep) {
9436
+ startStepId = lastStepId;
9437
+ }
9390
9438
  }
9391
9439
  }
9392
9440
  }
@@ -10748,7 +10796,7 @@ const SaltfishPlayer$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.de
10748
10796
  __proto__: null,
10749
10797
  SaltfishPlayer
10750
10798
  }, Symbol.toStringTag, { value: "Module" }));
10751
- const version = "0.2.73";
10799
+ const version = "0.2.75";
10752
10800
  const packageJson = {
10753
10801
  version
10754
10802
  };