PR: https://github.com/highspot/nutella/pull/66718
JIRA: HS-155885
Feature Flag: uat_improvements
- Activities are saved to
localStorageduring the session - A timer periodically uploads activities via
$.ajaxif they're "too old" - On
beforeunload, we save to localStorage but do not upload - When the page unloads, timers stop, data stays in localStorage
When users leave the page, data is saved locally but not uploaded.
- If user never returns → data never sent
- Backlog builds up → "large upload list" warnings (100+ keys)
- Analytics data is delayed or lost
When the user leaves the page:
- Save current activity to storage (already happens)
- Upload pending activities immediately before the page closes
const useVisibilityUpload = currentUser.features?.includes('uat_improvements');
if (useVisibilityUpload) {
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.saveActivity();
this.uploader.uploadWithBeacon();
}
});
} else {
$(window).on('beforeunload', () => {
this.saveActivity();
});
}uploadWithBeacon() {
const keys = this.handler.storage.loadActivityKeys();
if (!keys?.length) return;
const BEACON_LIMIT = 60000;
const url = Api.userActivity();
for (const key of keys) {
const payload = this.createPayload([key]);
if (!payload || !Object.keys(payload).length) continue;
const payloadString = JSON.stringify(payload);
if (new Blob([payloadString]).size > BEACON_LIMIT) continue; // Skip oversized
if (navigator.sendBeacon?.(url, new Blob([payloadString], { type: 'application/json' }))) {
this.handler.storage.removeKeys([key]);
}
}
}| Decision | Reasoning |
|---|---|
visibilitychange over beforeunload |
Fires earlier, gives time to act |
sendBeacon over $.ajax |
Designed for page unload, reliable |
New method over modifying uploadIfTooOld |
Keeps existing code unchanged |
| One key at a time | Simple, no batch logic, skips oversized keys |
| One feature flag | Easy to test and rollback |
| Aspect | Simplest Approach | PR |
|---|---|---|
| Event | visibilitychange |
visibilitychange |
| Upload | sendBeacon |
sendBeacon |
| Code structure | New uploadWithBeacon() |
Modifies uploadIfTooOld() with options object |
| Large payloads | Send one key at a time, skip if too large | Recursive splitting + AJAX fallback |
| Lines changed | ~30 | 661 |
- Handles edge cases (recursive splitting, AJAX fallback)
- Comprehensive test coverage
uploadIfTooOldbecomes overloaded (accepts number OR object)- AJAX fallback during unload is unreliable anyway
- More complex than needed if oversized payloads are rare
PR achieves the goal but with more complexity. The simpler approach covers 99% of cases with ~5% of the code.
- Does the backend deduplicate on
sessionId + activityNumber? (Affects whether we should keep keys after beacon) - How often are payloads > 60KB? (Determines if recursive splitting is necessary)
Last updated: Feb 4, 2026