Skip to content

Commit 7a8f5d5

Browse files
committed
Merge pull request #5116 from wled/add-report-version-feature
Add report version feature
1 parent d85ef56 commit 7a8f5d5

File tree

2 files changed

+192
-1
lines changed

2 files changed

+192
-1
lines changed

wled00/data/index.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ input[type=range]::-moz-range-thumb {
793793
/* buttons */
794794
.btn {
795795
padding: 8px;
796-
/*margin: 10px 4px;*/
796+
margin: 10px 4px;
797797
width: 230px;
798798
font-size: 19px;
799799
color: var(--c-d);

wled00/data/index.js

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,8 @@ function parseInfo(i) {
688688
// gId("filterVol").classList.add("hide"); hideModes(" ♪"); // hide volume reactive effects
689689
// gId("filterFreq").classList.add("hide"); hideModes(" ♫"); // hide frequency reactive effects
690690
// }
691+
// Check for version upgrades on page load
692+
checkVersionUpgrade(i);
691693
}
692694

693695
//https://stackoverflow.com/questions/2592092/executing-script-elements-inserted-with-innerhtml
@@ -3248,6 +3250,195 @@ function simplifyUI() {
32483250
gId("btns").style.display = "none";
32493251
}
32503252

3253+
// Version reporting feature
3254+
var versionCheckDone = false;
3255+
3256+
function checkVersionUpgrade(info) {
3257+
// Only check once per page load
3258+
if (versionCheckDone) return;
3259+
versionCheckDone = true;
3260+
3261+
// Suppress feature if in AP mode (no internet connection available)
3262+
if (info.wifi && info.wifi.ap) return;
3263+
3264+
// Fetch version-info.json using existing /edit endpoint
3265+
fetch(getURL('/edit?func=edit&path=/version-info.json'), {
3266+
method: 'get'
3267+
})
3268+
.then(res => {
3269+
if (res.status === 404) {
3270+
// File doesn't exist - first install, show install prompt
3271+
showVersionUpgradePrompt(info, null, info.ver);
3272+
return null;
3273+
}
3274+
if (!res.ok) {
3275+
throw new Error('Failed to fetch version-info.json');
3276+
}
3277+
return res.json();
3278+
})
3279+
.then(versionInfo => {
3280+
if (!versionInfo) return; // 404 case already handled
3281+
3282+
// Check if user opted out
3283+
if (versionInfo.neverAsk) return;
3284+
3285+
// Check if version has changed
3286+
const currentVersion = info.ver;
3287+
const storedVersion = versionInfo.version || '';
3288+
3289+
if (storedVersion && storedVersion !== currentVersion) {
3290+
// Version has changed, show upgrade prompt
3291+
showVersionUpgradePrompt(info, storedVersion, currentVersion);
3292+
} else if (!storedVersion) {
3293+
// Empty version in file, show install prompt
3294+
showVersionUpgradePrompt(info, null, currentVersion);
3295+
}
3296+
})
3297+
.catch(e => {
3298+
console.log('Failed to load version-info.json', e);
3299+
// On error, save current version for next time
3300+
if (info && info.ver) {
3301+
updateVersionInfo(info.ver, false);
3302+
}
3303+
});
3304+
}
3305+
3306+
function showVersionUpgradePrompt(info, oldVersion, newVersion) {
3307+
// Determine if this is an install or upgrade
3308+
const isInstall = !oldVersion;
3309+
3310+
// Create overlay and dialog
3311+
const overlay = d.createElement('div');
3312+
overlay.id = 'versionUpgradeOverlay';
3313+
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);z-index:10000;display:flex;align-items:center;justify-content:center;';
3314+
3315+
const dialog = d.createElement('div');
3316+
dialog.style.cssText = 'background:var(--c-1);border-radius:10px;padding:25px;max-width:500px;margin:20px;box-shadow:0 4px 6px rgba(0,0,0,0.3);';
3317+
3318+
// Build contextual message based on install vs upgrade
3319+
const title = isInstall
3320+
? '🎉 Thank you for installing WLED!'
3321+
: '🎉 WLED Upgrade Detected!';
3322+
3323+
const description = isInstall
3324+
? `You are now running WLED <strong style="text-wrap: nowrap">${newVersion}</strong>.`
3325+
: `Your WLED has been upgraded from <strong style="text-wrap: nowrap">${oldVersion}</strong> to <strong style="text-wrap: nowrap">${newVersion}</strong>.`;
3326+
3327+
const question = 'Help make WLED better with a one-time hardware report? It includes only device details like chip type, LED count, etc. — never personal data or your activities.'
3328+
3329+
dialog.innerHTML = `
3330+
<h2 style="margin-top:0;color:var(--c-f);">${title}</h2>
3331+
<p style="color:var(--c-f);">${description}</p>
3332+
<p style="color:var(--c-f);">${question}</p>
3333+
<p style="color:var(--c-f);font-size:0.9em;">
3334+
<a href="https://kno.wled.ge/about/privacy-policy/" target="_blank" style="color:var(--c-6);">Learn more about what data is collected and why</a>
3335+
</p>
3336+
<div style="margin-top:20px;">
3337+
<button id="versionReportYes" class="btn">Yes</button>
3338+
<button id="versionReportNo" class="btn">Not Now</button>
3339+
<button id="versionReportNever" class="btn">Never Ask</button>
3340+
</div>
3341+
`;
3342+
3343+
overlay.appendChild(dialog);
3344+
d.body.appendChild(overlay);
3345+
3346+
// Add event listeners
3347+
gId('versionReportYes').addEventListener('click', () => {
3348+
reportUpgradeEvent(info, oldVersion);
3349+
d.body.removeChild(overlay);
3350+
});
3351+
3352+
gId('versionReportNo').addEventListener('click', () => {
3353+
// Don't update version, will ask again on next load
3354+
d.body.removeChild(overlay);
3355+
});
3356+
3357+
gId('versionReportNever').addEventListener('click', () => {
3358+
updateVersionInfo(newVersion, true);
3359+
d.body.removeChild(overlay);
3360+
showToast('You will not be asked again.');
3361+
});
3362+
}
3363+
3364+
function reportUpgradeEvent(info, oldVersion) {
3365+
showToast('Reporting upgrade...');
3366+
3367+
// Fetch fresh data from /json/info endpoint as requested
3368+
fetch(getURL('/json/info'), {
3369+
method: 'get'
3370+
})
3371+
.then(res => res.json())
3372+
.then(infoData => {
3373+
// Map to UpgradeEventRequest structure per OpenAPI spec
3374+
// Required fields: deviceId, version, previousVersion, releaseName, chip, ledCount, isMatrix, bootloaderSHA256
3375+
const upgradeData = {
3376+
deviceId: infoData.deviceId, // Use anonymous unique device ID
3377+
version: infoData.ver || '', // Current version string
3378+
previousVersion: oldVersion || '', // Previous version from version-info.json
3379+
releaseName: infoData.release || '', // Release name (e.g., "WLED 0.15.0")
3380+
chip: infoData.arch || '', // Chip architecture (esp32, esp8266, etc)
3381+
ledCount: infoData.leds ? infoData.leds.count : 0, // Number of LEDs
3382+
isMatrix: !!(infoData.leds && infoData.leds.matrix), // Whether it's a 2D matrix setup
3383+
bootloaderSHA256: infoData.bootloaderSHA256 || '', // Bootloader SHA256 hash
3384+
brand: infoData.brand, // Device brand (always present)
3385+
product: infoData.product, // Product name (always present)
3386+
flashSize: infoData.flash // Flash size (always present)
3387+
};
3388+
3389+
// Add optional fields if available
3390+
if (infoData.psram !== undefined) upgradeData.psramSize = infoData.psram;
3391+
// Note: partitionSizes not currently available in /json/info endpoint
3392+
3393+
// Make AJAX call to postUpgradeEvent API
3394+
return fetch('https://usage.wled.me/api/usage/upgrade', {
3395+
method: 'POST',
3396+
headers: {
3397+
'Content-Type': 'application/json'
3398+
},
3399+
body: JSON.stringify(upgradeData)
3400+
});
3401+
})
3402+
.then(res => {
3403+
if (res.ok) {
3404+
showToast('Thank you for reporting!');
3405+
updateVersionInfo(info.ver, false);
3406+
} else {
3407+
showToast('Report failed. Please try again later.', true);
3408+
// Do NOT update version info on failure - user will be prompted again
3409+
}
3410+
})
3411+
.catch(e => {
3412+
console.log('Failed to report upgrade', e);
3413+
showToast('Report failed. Please try again later.', true);
3414+
// Do NOT update version info on error - user will be prompted again
3415+
});
3416+
}
3417+
3418+
function updateVersionInfo(version, neverAsk) {
3419+
const versionInfo = {
3420+
version: version,
3421+
neverAsk: neverAsk
3422+
};
3423+
3424+
// Create a Blob with JSON content and use /upload endpoint
3425+
const blob = new Blob([JSON.stringify(versionInfo)], {type: 'application/json'});
3426+
const formData = new FormData();
3427+
formData.append('data', blob, 'version-info.json');
3428+
3429+
fetch(getURL('/upload'), {
3430+
method: 'POST',
3431+
body: formData
3432+
})
3433+
.then(res => res.text())
3434+
.then(data => {
3435+
console.log('Version info updated', data);
3436+
})
3437+
.catch(e => {
3438+
console.log('Failed to update version-info.json', e);
3439+
});
3440+
}
3441+
32513442
size();
32523443
_C.style.setProperty('--n', N);
32533444

0 commit comments

Comments
 (0)