saltfish 0.3.71 → 0.3.74
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/core/services/PlaylistOrchestrator.d.ts.map +1 -1
- package/dist/managers/AnalyticsManager.d.ts +11 -0
- package/dist/managers/AnalyticsManager.d.ts.map +1 -1
- package/dist/player.js +3 -3
- package/dist/player.min.js +3 -3
- package/dist/saltfish-playlist-player.es.js +199 -46
- package/dist/saltfish-playlist-player.umd.js +1 -1
- package/dist/updaters/EventUpdater.d.ts.map +1 -1
- package/dist/utils/domSnapshotCapture.d.ts +23 -0
- package/dist/utils/domSnapshotCapture.d.ts.map +1 -0
- package/dist/utils/elementErrorReporter.d.ts +7 -0
- package/dist/utils/elementErrorReporter.d.ts.map +1 -1
- package/package.json +2 -1
|
@@ -2983,7 +2983,6 @@ class PlaylistOrchestrator {
|
|
|
2983
2983
|
});
|
|
2984
2984
|
}
|
|
2985
2985
|
}
|
|
2986
|
-
this.managers.analyticsManager.trackPlaylistStart(playlistId);
|
|
2987
2986
|
this.managers.cursorManager.resetFirstAnimation();
|
|
2988
2987
|
log(`[PlaylistOrchestrator.startPlaylist] Using validated manifest path: ${manifestPathToLoad}`);
|
|
2989
2988
|
await this.managers.playlistManager.load(manifestPathToLoad, { ...finalOptions, persistence: playlistPersistence });
|
|
@@ -3750,6 +3749,39 @@ function setupEventUpdater(eventManager) {
|
|
|
3750
3749
|
let prevPreviousState = null;
|
|
3751
3750
|
let prevIsMinimized = null;
|
|
3752
3751
|
let prevStepId = null;
|
|
3752
|
+
let pendingStartEvents = { playlistData: null, stepData: null };
|
|
3753
|
+
let autoplayConfirmationTimer = null;
|
|
3754
|
+
const firePendingEvents = () => {
|
|
3755
|
+
if (autoplayConfirmationTimer) {
|
|
3756
|
+
clearTimeout(autoplayConfirmationTimer);
|
|
3757
|
+
autoplayConfirmationTimer = null;
|
|
3758
|
+
}
|
|
3759
|
+
if (pendingStartEvents.playlistData) {
|
|
3760
|
+
eventManager.trigger("playlistStarted", {
|
|
3761
|
+
timestamp: Date.now(),
|
|
3762
|
+
playlist: pendingStartEvents.playlistData
|
|
3763
|
+
});
|
|
3764
|
+
}
|
|
3765
|
+
if (pendingStartEvents.stepData) {
|
|
3766
|
+
eventManager.trigger("stepStarted", {
|
|
3767
|
+
timestamp: Date.now(),
|
|
3768
|
+
step: { id: pendingStartEvents.stepData.id, title: pendingStartEvents.stepData.title },
|
|
3769
|
+
playlist: { id: pendingStartEvents.stepData.playlistId, title: pendingStartEvents.stepData.playlistTitle }
|
|
3770
|
+
});
|
|
3771
|
+
}
|
|
3772
|
+
pendingStartEvents = { playlistData: null, stepData: null };
|
|
3773
|
+
};
|
|
3774
|
+
const startAutoplayConfirmationTimer = () => {
|
|
3775
|
+
if (autoplayConfirmationTimer) {
|
|
3776
|
+
clearTimeout(autoplayConfirmationTimer);
|
|
3777
|
+
}
|
|
3778
|
+
autoplayConfirmationTimer = setTimeout(() => {
|
|
3779
|
+
if (pendingStartEvents.playlistData || pendingStartEvents.stepData) {
|
|
3780
|
+
firePendingEvents();
|
|
3781
|
+
}
|
|
3782
|
+
autoplayConfirmationTimer = null;
|
|
3783
|
+
}, 500);
|
|
3784
|
+
};
|
|
3753
3785
|
const unsubscribe = useSaltfishStore.subscribe(
|
|
3754
3786
|
(state) => {
|
|
3755
3787
|
var _a;
|
|
@@ -3774,30 +3806,55 @@ function setupEventUpdater(eventManager) {
|
|
|
3774
3806
|
log(`EventUpdater: Processing state change from '${eventData.previousState}' to '${eventData.currentState}'`, {
|
|
3775
3807
|
manifestId: (_a = state.manifest) == null ? void 0 : _a.id
|
|
3776
3808
|
});
|
|
3777
|
-
|
|
3809
|
+
if (eventData.previousState === "playing") {
|
|
3810
|
+
if (eventData.currentState === "autoplayBlocked" || eventData.currentState === "idleMode") {
|
|
3811
|
+
if (autoplayConfirmationTimer) {
|
|
3812
|
+
clearTimeout(autoplayConfirmationTimer);
|
|
3813
|
+
autoplayConfirmationTimer = null;
|
|
3814
|
+
}
|
|
3815
|
+
if (pendingStartEvents.playlistData || pendingStartEvents.stepData) {
|
|
3816
|
+
pendingStartEvents = { playlistData: null, stepData: null };
|
|
3817
|
+
}
|
|
3818
|
+
} else if (pendingStartEvents.playlistData || pendingStartEvents.stepData) {
|
|
3819
|
+
firePendingEvents();
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
handleStateTransitionEvents(eventData, state, eventManager, pendingStartEvents);
|
|
3778
3823
|
handleMinimizeEvents(eventData, state, eventManager, actualPrevMinimized);
|
|
3779
|
-
handleStepEvents(eventData, state, eventManager, actualPrevStepId);
|
|
3824
|
+
handleStepEvents(eventData, state, eventManager, actualPrevStepId, pendingStartEvents);
|
|
3780
3825
|
handleErrorEvents(eventData, state, eventManager);
|
|
3826
|
+
if ((pendingStartEvents.playlistData || pendingStartEvents.stepData) && !autoplayConfirmationTimer) {
|
|
3827
|
+
startAutoplayConfirmationTimer();
|
|
3828
|
+
}
|
|
3781
3829
|
}
|
|
3782
3830
|
);
|
|
3783
3831
|
return unsubscribe;
|
|
3784
3832
|
}
|
|
3785
|
-
function handleStateTransitionEvents(eventData, store, eventManager) {
|
|
3833
|
+
function handleStateTransitionEvents(eventData, store, eventManager, pendingStartEvents) {
|
|
3786
3834
|
const { prevPreviousState, previousState, currentState, currentStepId } = eventData;
|
|
3787
3835
|
const isNormalPlaylistStart = currentState === "playing" && previousState === "paused" && prevPreviousState === "loading";
|
|
3788
|
-
const
|
|
3789
|
-
if (
|
|
3836
|
+
const isUserInitiatedPlaylistStart = currentState === "playing" && (previousState === "autoplayBlocked" || previousState === "idleMode");
|
|
3837
|
+
if (store.manifest && currentStepId) {
|
|
3790
3838
|
const isStartingNode = currentStepId === store.manifest.startStep;
|
|
3791
3839
|
if (isStartingNode) {
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3840
|
+
if (isUserInitiatedPlaylistStart) {
|
|
3841
|
+
log(`EventUpdater: Triggering playlistStarted event for ${store.manifest.id} (user-initiated from ${previousState})`);
|
|
3842
|
+
eventManager.trigger("playlistStarted", {
|
|
3843
|
+
timestamp: Date.now(),
|
|
3844
|
+
playlist: {
|
|
3845
|
+
id: store.manifest.id,
|
|
3846
|
+
title: store.manifest.name
|
|
3847
|
+
}
|
|
3848
|
+
});
|
|
3849
|
+
return;
|
|
3850
|
+
} else if (isNormalPlaylistStart) {
|
|
3851
|
+
log(`EventUpdater: Deferring playlistStarted event for ${store.manifest.id} (pending autoplay confirmation)`);
|
|
3852
|
+
pendingStartEvents.playlistData = {
|
|
3796
3853
|
id: store.manifest.id,
|
|
3797
3854
|
title: store.manifest.name
|
|
3798
|
-
}
|
|
3799
|
-
|
|
3800
|
-
|
|
3855
|
+
};
|
|
3856
|
+
return;
|
|
3857
|
+
}
|
|
3801
3858
|
}
|
|
3802
3859
|
}
|
|
3803
3860
|
if (previousState === "paused" && currentState === "playing") {
|
|
@@ -3831,7 +3888,7 @@ function handleMinimizeEvents(eventData, _store, eventManager, prevIsMinimized)
|
|
|
3831
3888
|
});
|
|
3832
3889
|
}
|
|
3833
3890
|
}
|
|
3834
|
-
function handleStepEvents(eventData, store, eventManager, prevStepId) {
|
|
3891
|
+
function handleStepEvents(eventData, store, eventManager, prevStepId, pendingStartEvents) {
|
|
3835
3892
|
var _a, _b, _c;
|
|
3836
3893
|
const { prevPreviousState, previousState, currentStepId, currentState } = eventData;
|
|
3837
3894
|
const currentStep = store.currentStepId ? (((_a = store.manifest) == null ? void 0 : _a.steps) || []).find((s) => s.id === store.currentStepId) : null;
|
|
@@ -3865,26 +3922,38 @@ function handleStepEvents(eventData, store, eventManager, prevStepId) {
|
|
|
3865
3922
|
}
|
|
3866
3923
|
}
|
|
3867
3924
|
const isStepChange = currentStepId !== prevStepId && currentState === "playing";
|
|
3868
|
-
const
|
|
3869
|
-
const
|
|
3870
|
-
const
|
|
3925
|
+
const isUserInitiatedStepStart = (previousState === "autoplayBlocked" || previousState === "idleMode") && currentState === "playing" && prevStepId === currentStepId;
|
|
3926
|
+
const isFirstPlayOfPlaylist = prevPreviousState === "loading" && previousState === "paused" && currentState === "playing" && currentStepId;
|
|
3927
|
+
const isStartingNode = store.manifest && currentStepId === store.manifest.startStep;
|
|
3928
|
+
const isFirstStepAutoplayAttempt = isStartingNode && isFirstPlayOfPlaylist;
|
|
3929
|
+
const isNonStartingNodeFirstPlay = !isStartingNode && isFirstPlayOfPlaylist;
|
|
3871
3930
|
log("EventUpdater.handleStepEvents: Step started check", {
|
|
3872
3931
|
currentStep: currentStep == null ? void 0 : currentStep.id,
|
|
3873
3932
|
hasManifest: !!store.manifest
|
|
3874
3933
|
});
|
|
3875
|
-
if (
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3934
|
+
if (currentStep && store.manifest) {
|
|
3935
|
+
if (isStepChange || isUserInitiatedStepStart || isNonStartingNodeFirstPlay) {
|
|
3936
|
+
log(`EventUpdater: Triggering stepStarted for ${currentStep.id}`);
|
|
3937
|
+
eventManager.trigger("stepStarted", {
|
|
3938
|
+
timestamp: Date.now(),
|
|
3939
|
+
step: {
|
|
3940
|
+
id: currentStep.id,
|
|
3941
|
+
title: currentStep.title || currentStep.id
|
|
3942
|
+
},
|
|
3943
|
+
playlist: {
|
|
3944
|
+
id: store.manifest.id,
|
|
3945
|
+
title: store.manifest.name
|
|
3946
|
+
}
|
|
3947
|
+
});
|
|
3948
|
+
} else if (isFirstStepAutoplayAttempt) {
|
|
3949
|
+
log(`EventUpdater: Deferring stepStarted for ${currentStep.id} (pending autoplay confirmation)`);
|
|
3950
|
+
pendingStartEvents.stepData = {
|
|
3880
3951
|
id: currentStep.id,
|
|
3881
|
-
title: currentStep.title || currentStep.id
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
}
|
|
3887
|
-
});
|
|
3952
|
+
title: currentStep.title || currentStep.id,
|
|
3953
|
+
playlistId: store.manifest.id,
|
|
3954
|
+
playlistTitle: store.manifest.name
|
|
3955
|
+
};
|
|
3956
|
+
}
|
|
3888
3957
|
}
|
|
3889
3958
|
}
|
|
3890
3959
|
function handleErrorEvents(eventData, store, eventManager) {
|
|
@@ -6956,6 +7025,32 @@ function isElementValid(element, expectedElement, expectedSize, config = DEFAULT
|
|
|
6956
7025
|
}
|
|
6957
7026
|
return true;
|
|
6958
7027
|
}
|
|
7028
|
+
const RRWEB_SNAPSHOT_URL = "https://storage.saltfish.ai/libs/rrweb-snapshot-2.0.0-alpha.18.js";
|
|
7029
|
+
async function captureDOMSnapshot() {
|
|
7030
|
+
try {
|
|
7031
|
+
const rrwebSnapshot = await import(
|
|
7032
|
+
/* webpackIgnore: true */
|
|
7033
|
+
RRWEB_SNAPSHOT_URL
|
|
7034
|
+
);
|
|
7035
|
+
const domSnapshot = rrwebSnapshot.snapshot(document);
|
|
7036
|
+
return domSnapshot;
|
|
7037
|
+
} catch (error2) {
|
|
7038
|
+
console.warn("Failed to capture DOM snapshot:", error2);
|
|
7039
|
+
return null;
|
|
7040
|
+
}
|
|
7041
|
+
}
|
|
7042
|
+
async function captureDOMSnapshotAsString() {
|
|
7043
|
+
const snapshot = await captureDOMSnapshot();
|
|
7044
|
+
if (!snapshot) {
|
|
7045
|
+
return null;
|
|
7046
|
+
}
|
|
7047
|
+
try {
|
|
7048
|
+
return JSON.stringify(snapshot);
|
|
7049
|
+
} catch (error2) {
|
|
7050
|
+
console.warn("Failed to serialize DOM snapshot:", error2);
|
|
7051
|
+
return null;
|
|
7052
|
+
}
|
|
7053
|
+
}
|
|
6959
7054
|
const API_URL = "https://player.saltfish.ai/element-errors";
|
|
6960
7055
|
function determineFailureReason(selector, expectedElement, expectedSize) {
|
|
6961
7056
|
const elements = document.querySelectorAll(selector);
|
|
@@ -6978,21 +7073,29 @@ function determineFailureReason(selector, expectedElement, expectedSize) {
|
|
|
6978
7073
|
}
|
|
6979
7074
|
function reportElementError(playlistId, stepId, selector, expectedElement, expectedSize) {
|
|
6980
7075
|
const failureReason = determineFailureReason(selector, expectedElement, expectedSize);
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
7076
|
+
(async () => {
|
|
7077
|
+
try {
|
|
7078
|
+
const payload = {
|
|
7079
|
+
playlistId,
|
|
7080
|
+
stepId,
|
|
7081
|
+
failureReason,
|
|
7082
|
+
selector
|
|
7083
|
+
};
|
|
7084
|
+
const domSnapshot = await captureDOMSnapshotAsString();
|
|
7085
|
+
if (domSnapshot) {
|
|
7086
|
+
payload.domSnapshot = domSnapshot;
|
|
7087
|
+
}
|
|
7088
|
+
await fetch(API_URL, {
|
|
7089
|
+
method: "POST",
|
|
7090
|
+
headers: {
|
|
7091
|
+
"Content-Type": "application/json"
|
|
7092
|
+
},
|
|
7093
|
+
body: JSON.stringify(payload)
|
|
7094
|
+
});
|
|
7095
|
+
} catch (error2) {
|
|
7096
|
+
console.warn("Failed to report element error:", error2);
|
|
7097
|
+
}
|
|
7098
|
+
})();
|
|
6996
7099
|
}
|
|
6997
7100
|
class CursorManager {
|
|
6998
7101
|
constructor() {
|
|
@@ -8960,7 +9063,6 @@ class EventSubscriberManager {
|
|
|
8960
9063
|
}
|
|
8961
9064
|
}
|
|
8962
9065
|
class AnalyticsManager extends EventSubscriberManager {
|
|
8963
|
-
// Default to enabled
|
|
8964
9066
|
/**
|
|
8965
9067
|
* Creates a new AnalyticsManager
|
|
8966
9068
|
* @param eventManager - Optional event manager to subscribe to events
|
|
@@ -8974,6 +9076,8 @@ class AnalyticsManager extends EventSubscriberManager {
|
|
|
8974
9076
|
__publicField(this, "flushInterval", null);
|
|
8975
9077
|
__publicField(this, "sessionId", null);
|
|
8976
9078
|
__publicField(this, "analyticsEnabled", true);
|
|
9079
|
+
// Default to enabled
|
|
9080
|
+
__publicField(this, "boundVisibilityHandler", null);
|
|
8977
9081
|
}
|
|
8978
9082
|
/**
|
|
8979
9083
|
* Subscribe to relevant player events for analytics tracking
|
|
@@ -9060,6 +9164,10 @@ class AnalyticsManager extends EventSubscriberManager {
|
|
|
9060
9164
|
});
|
|
9061
9165
|
}
|
|
9062
9166
|
});
|
|
9167
|
+
this.eventManager.on("playlistStarted", (event) => {
|
|
9168
|
+
log(`AnalyticsManager: Playlist started event received - ${event.playlist.id}`);
|
|
9169
|
+
this.trackPlaylistStart(event.playlist.id);
|
|
9170
|
+
});
|
|
9063
9171
|
}
|
|
9064
9172
|
/**
|
|
9065
9173
|
* Initializes the analytics manager
|
|
@@ -9076,6 +9184,8 @@ class AnalyticsManager extends EventSubscriberManager {
|
|
|
9076
9184
|
this.flushInterval = window.setInterval(() => {
|
|
9077
9185
|
this.flushEvents();
|
|
9078
9186
|
}, ANALYTICS.FLUSH_INTERVAL_MS);
|
|
9187
|
+
this.boundVisibilityHandler = this.handleVisibilityChange.bind(this);
|
|
9188
|
+
document.addEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
9079
9189
|
}
|
|
9080
9190
|
}
|
|
9081
9191
|
/**
|
|
@@ -9272,6 +9382,45 @@ class AnalyticsManager extends EventSubscriberManager {
|
|
|
9272
9382
|
this.isSending = false;
|
|
9273
9383
|
}
|
|
9274
9384
|
}
|
|
9385
|
+
/**
|
|
9386
|
+
* Handles visibility change events to flush analytics when page is hidden
|
|
9387
|
+
* Uses sendBeacon for reliable delivery during page unload
|
|
9388
|
+
*/
|
|
9389
|
+
handleVisibilityChange() {
|
|
9390
|
+
if (document.visibilityState === "hidden") {
|
|
9391
|
+
this.flushWithBeacon();
|
|
9392
|
+
}
|
|
9393
|
+
}
|
|
9394
|
+
/**
|
|
9395
|
+
* Flushes events using sendBeacon API for reliable delivery during page unload
|
|
9396
|
+
* sendBeacon is fire-and-forget and survives page navigation/close
|
|
9397
|
+
*/
|
|
9398
|
+
flushWithBeacon() {
|
|
9399
|
+
if (this.eventQueue.length === 0 || !this.config || !this.analyticsEnabled) {
|
|
9400
|
+
return;
|
|
9401
|
+
}
|
|
9402
|
+
const events = [...this.eventQueue];
|
|
9403
|
+
this.eventQueue = [];
|
|
9404
|
+
const payload = {
|
|
9405
|
+
token: this.config.token,
|
|
9406
|
+
sessionId: this.sessionId,
|
|
9407
|
+
user: this.user,
|
|
9408
|
+
events
|
|
9409
|
+
};
|
|
9410
|
+
try {
|
|
9411
|
+
fetch("https://player.saltfish.ai/analytics", {
|
|
9412
|
+
method: "POST",
|
|
9413
|
+
headers: {
|
|
9414
|
+
"Content-Type": "application/json"
|
|
9415
|
+
},
|
|
9416
|
+
body: JSON.stringify(payload),
|
|
9417
|
+
keepalive: true
|
|
9418
|
+
});
|
|
9419
|
+
} catch (error2) {
|
|
9420
|
+
console.warn("AnalyticsManager: fetch keepalive failed, events returned to queue");
|
|
9421
|
+
this.eventQueue = [...events, ...this.eventQueue];
|
|
9422
|
+
}
|
|
9423
|
+
}
|
|
9275
9424
|
/**
|
|
9276
9425
|
* Cleans up resources used by the analytics manager
|
|
9277
9426
|
*/
|
|
@@ -9283,6 +9432,10 @@ class AnalyticsManager extends EventSubscriberManager {
|
|
|
9283
9432
|
clearInterval(this.flushInterval);
|
|
9284
9433
|
this.flushInterval = null;
|
|
9285
9434
|
}
|
|
9435
|
+
if (this.boundVisibilityHandler) {
|
|
9436
|
+
document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
9437
|
+
this.boundVisibilityHandler = null;
|
|
9438
|
+
}
|
|
9286
9439
|
if (this.eventManager) {
|
|
9287
9440
|
this.eventManager = null;
|
|
9288
9441
|
}
|
|
@@ -12590,7 +12743,7 @@ const SaltfishPlayer$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.de
|
|
|
12590
12743
|
__proto__: null,
|
|
12591
12744
|
SaltfishPlayer
|
|
12592
12745
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
12593
|
-
const version = "0.3.
|
|
12746
|
+
const version = "0.3.74";
|
|
12594
12747
|
const packageJson = {
|
|
12595
12748
|
version
|
|
12596
12749
|
};
|