Clarify analytics is hosted only
All checks were successful
Build and test project / build-and-test (push) Successful in 1m29s
All checks were successful
Build and test project / build-and-test (push) Successful in 1m29s
This commit is contained in:
23
README.md
23
README.md
@@ -231,27 +231,12 @@ The WASM patcher performs several checks on each patched binary before including
|
||||
|
||||
## Analytics (optional)
|
||||
|
||||
The app supports optional, privacy-focused analytics via [Umami](https://umami.is). Analytics are disabled by default and only activate when two environment variables are set on the server:
|
||||
The hosted version at [kp.nicoverbruggen.be](https://kp.nicoverbruggen.be) uses optional, privacy-focused analytics via [Umami](https://umami.is) to understand how the tool is used. No personal identifiers are collected. See the "Privacy" link in the footer for details.
|
||||
|
||||
Analytics are disabled for local and self-hosted installs. They activate only when `UMAMI_WEBSITE_ID` and `UMAMI_SCRIPT_URL` environment variables are set on the server. To test the analytics UI locally without sending any data:
|
||||
|
||||
```bash
|
||||
UMAMI_WEBSITE_ID=your-website-id
|
||||
UMAMI_SCRIPT_URL=https://your-umami-instance/script.js
|
||||
```
|
||||
|
||||
When enabled, the server injects the Umami tracking script into `index.html` at runtime. A "Privacy" link appears in the footer with a modal explaining what is tracked.
|
||||
|
||||
**What is tracked** (no personal identifiers):
|
||||
|
||||
- **Flow start** — whether the user connected a Kobo directly (`connect`) or chose manual download (`manual`)
|
||||
- **NickelMenu option** — which option was selected (`sample`, `nickelmenu-only`, or `remove`)
|
||||
- **Flow end** — how the process completed (`nm-write`, `nm-download`, `nm-remove`, `patches-write`, `patches-download`, `restore-write`, `restore-download`)
|
||||
|
||||
**What is not tracked**: device model, serial number, firmware version, IP address, browsing behaviour. Umami is cookie-free and GDPR/CCPA/PECR compliant.
|
||||
|
||||
For local installs via `./serve-locally.sh`, analytics is disabled unless the environment variables are set:
|
||||
|
||||
```bash
|
||||
UMAMI_WEBSITE_ID=... UMAMI_SCRIPT_URL=... ./serve-locally.sh
|
||||
./serve-locally.sh --fake-analytics
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${1:-}" == "--fake-analytics" ]]; then
|
||||
export UMAMI_WEBSITE_ID="fake"
|
||||
export UMAMI_SCRIPT_URL="data:,"
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
WEB_DIR="$SCRIPT_DIR/web"
|
||||
SRC_DIR="$WEB_DIR/src"
|
||||
|
||||
@@ -35,7 +35,7 @@ test.describe('NickelMenu', () => {
|
||||
await expect(page.locator('#btn-nm-next')).toBeDisabled();
|
||||
|
||||
// Select "Install NickelMenu and configure"
|
||||
await page.click('input[name="nm-option"][value="sample"]');
|
||||
await page.click('input[name="nm-option"][value="preset"]');
|
||||
await expect(page.locator('#nm-config-options')).not.toBeHidden();
|
||||
|
||||
// Verify default checkbox states
|
||||
@@ -106,7 +106,7 @@ test.describe('NickelMenu', () => {
|
||||
|
||||
// NickelMenu configure step — select "Install NickelMenu with preset"
|
||||
await expect(page.locator('#step-nickelmenu')).not.toBeHidden();
|
||||
await page.click('input[name="nm-option"][value="sample"]');
|
||||
await page.click('input[name="nm-option"][value="preset"]');
|
||||
await expect(page.locator('#nm-config-options')).not.toBeHidden();
|
||||
|
||||
// KOReader checkbox should be visible and unchecked by default
|
||||
@@ -150,7 +150,7 @@ test.describe('NickelMenu', () => {
|
||||
await page.click('#btn-mode-next');
|
||||
|
||||
// Select "Install NickelMenu with preset"
|
||||
await page.click('input[name="nm-option"][value="sample"]');
|
||||
await page.click('input[name="nm-option"][value="preset"]');
|
||||
|
||||
// Enable KOReader
|
||||
await page.check('input[name="nm-cfg-koreader"]');
|
||||
@@ -243,7 +243,7 @@ test.describe('NickelMenu', () => {
|
||||
await expect(page.locator('#nm-option-remove')).toHaveClass(/nm-option-disabled/);
|
||||
|
||||
// Select "Install NickelMenu and configure"
|
||||
await page.click('input[name="nm-option"][value="sample"]');
|
||||
await page.click('input[name="nm-option"][value="preset"]');
|
||||
await expect(page.locator('#nm-config-options')).not.toBeHidden();
|
||||
|
||||
// Enable all options for testing
|
||||
|
||||
@@ -1016,6 +1016,22 @@ button:focus-visible {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.site-footer a.site-footer-link {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.site-footer p {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.site-footer p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.site-footer-attribution {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.site-footer a:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
<p>Choose what to do with your Kobo.</p>
|
||||
<div class="nm-options">
|
||||
<label class="nm-option">
|
||||
<input type="radio" name="nm-option" value="sample">
|
||||
<input type="radio" name="nm-option" value="preset">
|
||||
<div class="nm-option-body">
|
||||
<div class="nm-option-title">Install NickelMenu with preset</div>
|
||||
<div class="nm-option-desc">Installs NickelMenu with a curated set of menu options. You get to decide which optional features you'd like to enable.</div>
|
||||
@@ -384,27 +384,27 @@
|
||||
|
||||
<footer class="site-footer">
|
||||
<p>
|
||||
<a href="#" id="btn-how-it-works">How does this work?</a>
|
||||
This project is not affiliated with Rakuten Kobo Inc.
|
||||
</p>
|
||||
<p>
|
||||
<a href="#" id="btn-how-it-works" class="site-footer-link">Disclaimer</a>
|
||||
<span id="privacy-link-separator" hidden> · </span>
|
||||
<a href="#" id="btn-privacy" hidden>Privacy</a>
|
||||
<a href="#" id="btn-privacy" class="site-footer-link" hidden>Privacy</a>
|
||||
·
|
||||
<a id="commit-link" href="https://github.com/nicoverbruggen/kobopatch-webui" target="_blank">Version <span id="commit-hash"></span></a>
|
||||
<br/>
|
||||
<br/>
|
||||
<a id="commit-link" class="site-footer-link" href="https://github.com/nicoverbruggen/kobopatch-webui" target="_blank"><span id="commit-hash"></span></a>
|
||||
</p>
|
||||
<p class="site-footer-attribution">
|
||||
Created by <a href="https://nicoverbruggen.be" target="_blank">Nico Verbruggen</a>.
|
||||
Readerly is part of my <a href="https://ebook-fonts.nicoverbruggen.be" target="_blank">curated font collection</a>.
|
||||
<br/>
|
||||
Built on <a href="https://github.com/pgaskin/kobopatch" target="_blank">kobopatch</a> and <a href="https://pgaskin.net/NickelMenu/" target="_blank">NickelMenu</a> by Patrick Gaskin.
|
||||
</p>
|
||||
<p>
|
||||
This project is not affiliated with Rakuten Kobo Inc.
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<dialog id="how-it-works-dialog" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>How does this work?</h2>
|
||||
<h2>Disclaimer</h2>
|
||||
<button id="btn-close-dialog" class="modal-close" aria-label="Close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -472,9 +472,8 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
This website uses <a href="https://umami.is" target="_blank">Umami</a>, a privacy-focused
|
||||
analytics tool, to help improve the experience. Analytics are only enabled on the hosted
|
||||
version of this site, not on local or self-hosted installs.
|
||||
This hosted version of the website uses <a href="https://umami.is" target="_blank">Umami</a>, a privacy-focused
|
||||
analytics tool, to help improve the experience. Data is only used to further improve this website, and no personal information is collected.
|
||||
</p>
|
||||
|
||||
<h3>What is tracked</h3>
|
||||
@@ -483,11 +482,11 @@
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>How the flow was started</strong> — whether you connected a Kobo
|
||||
directly or chose the manual download option.</li>
|
||||
directly or chose the manual download option. This helps the author decide whether to keep the manual download option, or remove it (if it is barely used).</li>
|
||||
<li><strong>Which NickelMenu option was selected</strong> — preset installation,
|
||||
NickelMenu only, or removal.</li>
|
||||
<li><strong>How the flow ended</strong> — whether files were written to the
|
||||
device or downloaded as a ZIP.</li>
|
||||
device or downloaded as a ZIP. This helps the author understand how many people have actually used the complete flow successfully.</li>
|
||||
</ul>
|
||||
|
||||
<h3>What is not tracked</h3>
|
||||
|
||||
@@ -22,7 +22,7 @@ import JSZip from 'jszip';
|
||||
let isRestore = false;
|
||||
let availablePatches = null;
|
||||
let selectedMode = null; // 'nickelmenu' | 'patches'
|
||||
let nickelMenuOption = null; // 'sample' | 'nickelmenu-only' | 'remove'
|
||||
let nickelMenuOption = null; // 'preset' | 'nickelmenu-only' | 'remove'
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
@@ -477,7 +477,7 @@ import JSZip from 'jszip';
|
||||
// Show/hide config checkboxes based on radio selection, enable Continue
|
||||
for (const radio of $qa('input[name="nm-option"]', stepNickelMenu)) {
|
||||
radio.addEventListener('change', () => {
|
||||
nmConfigOptions.hidden = radio.value !== 'sample' || !radio.checked;
|
||||
nmConfigOptions.hidden = radio.value !== 'preset' || !radio.checked;
|
||||
btnNmNext.disabled = false;
|
||||
});
|
||||
}
|
||||
@@ -523,7 +523,7 @@ import JSZip from 'jszip';
|
||||
function goToNickelMenuConfig() {
|
||||
checkNickelMenuInstalled();
|
||||
const currentOption = $q('input[name="nm-option"]:checked', stepNickelMenu);
|
||||
nmConfigOptions.hidden = !currentOption || currentOption.value !== 'sample';
|
||||
nmConfigOptions.hidden = !currentOption || currentOption.value !== 'preset';
|
||||
btnNmNext.disabled = !currentOption;
|
||||
setNavStep(3);
|
||||
showStep(stepNickelMenu);
|
||||
@@ -613,7 +613,7 @@ import JSZip from 'jszip';
|
||||
return;
|
||||
}
|
||||
|
||||
const cfg = nickelMenuOption === 'sample' ? getNmConfig() : null;
|
||||
const cfg = nickelMenuOption === 'preset' ? getNmConfig() : null;
|
||||
|
||||
if (writeToDevice && device.directoryHandle) {
|
||||
await nmInstaller.installToDevice(device, nickelMenuOption, cfg, (msg) => {
|
||||
@@ -653,7 +653,7 @@ import JSZip from 'jszip';
|
||||
triggerDownload(resultNmZip, 'NickelMenu-install.zip', 'application/zip');
|
||||
$('nm-download-instructions').hidden = false;
|
||||
// Show eReader.conf + reboot steps only when sample config is included
|
||||
const showConfStep = nickelMenuOption === 'sample';
|
||||
const showConfStep = nickelMenuOption === 'preset';
|
||||
$('nm-download-conf-step').hidden = !showConfStep;
|
||||
$('nm-download-reboot-step').hidden = !showConfStep;
|
||||
track('flow-end', { result: 'nm-download' });
|
||||
|
||||
@@ -9,9 +9,9 @@ import JSZip from 'jszip';
|
||||
*
|
||||
* Options:
|
||||
* 'nickelmenu-only' — just NickelMenu (KoboRoot.tgz)
|
||||
* 'sample' — NickelMenu + config based on cfg flags
|
||||
* 'preset' — NickelMenu + config based on cfg flags
|
||||
*
|
||||
* Config flags (when option is 'sample'):
|
||||
* Config flags (when option is 'preset'):
|
||||
* fonts: bool — include Readerly fonts
|
||||
* screensaver: bool — include custom screensaver
|
||||
* simplifyTabs: bool — comment out experimental tab items in config
|
||||
@@ -162,8 +162,8 @@ class NickelMenuInstaller {
|
||||
/**
|
||||
* Install to a connected Kobo device via File System Access API.
|
||||
* @param {KoboDevice} device
|
||||
* @param {string} option - 'sample' or 'nickelmenu-only'
|
||||
* @param {object|null} cfg - config flags (when option is 'sample')
|
||||
* @param {string} option - 'preset' or 'nickelmenu-only'
|
||||
* @param {object|null} cfg - config flags (when option is 'preset')
|
||||
* @param {function} progressFn
|
||||
*/
|
||||
async installToDevice(device, option, cfg, progressFn) {
|
||||
@@ -233,8 +233,8 @@ class NickelMenuInstaller {
|
||||
|
||||
/**
|
||||
* Build a zip for manual download containing all files to copy to the Kobo.
|
||||
* @param {string} option - 'sample' or 'nickelmenu-only'
|
||||
* @param {object|null} cfg - config flags (when option is 'sample')
|
||||
* @param {string} option - 'preset' or 'nickelmenu-only'
|
||||
* @param {object|null} cfg - config flags (when option is 'preset')
|
||||
* @param {function} progressFn
|
||||
* @returns {Uint8Array} zip contents
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user