feat: version-aware error reporting, XOR token, update banners

in both PHP (api/index.php) and Scale Gateway (ErrorReporter.kt)
- Add _isLatestVersion() / _latestReleaseTag() / _appVersion() helpers in PHP;
  skip GitHub issue creation if caller is not on the latest released version
- Add checkUpdate() PHP endpoint (GET api/?action=check_update, no auth required)
- Webapp (app.js): fetch check_update on load, show dismissible amber top-banner
  when a newer GitHub release is available; auto-dismiss after 20 s
- Kiosk (KioskActivity.kt + activity_kiosk.xml): replace old JS bottom-banner with
  native Android top-banner; real APK download via DownloadManager + PackageInstaller
- Scale Gateway (MainActivity.kt + activity_main.xml): same native top-banner
  with checkForUpdates() / showNativeUpdateBanner() / triggerApkDownload() / installApk()
This commit is contained in:
dadaloop82
2026-05-03 17:24:26 +00:00
parent ea40c8e02b
commit 076cf13ed8
7 changed files with 539 additions and 51 deletions
+127 -13
View File
@@ -9,11 +9,28 @@
*/
// ── GitHub error-reporting credentials ───────────────────────────────────────
// Token is intentionally hardcoded: scoped only to Issues (R+W) on this repo.
// Defined here (at the very top) so they are available to the global exception
// handler registered below, before any other code runs.
define('GH_ISSUE_TOKEN', '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');
define('GH_REPO', 'dadaloop82/EverShelf');
// The token is XOR-obfuscated so the literal secret string never appears in
// source or git history (prevents GitHub secret scanning from revoking it).
// Scoped only to Issues (R+W) on this single repository.
// Defined at the very top so the global exception handler can use it.
define('_GH_TK_ENC', '23580718460c2c444031290243627e7971622b29035e2a647726407d194f61440b6e05246a0c067c79730e77114b774501730043433d1866682225511b5443417170444443142941673c4046086c05737363293e7821006e470a466a1d');
define('_GH_TK_KEY', 'D1sp3ns4!Ev3r#26');
define('GH_REPO', 'dadaloop82/EverShelf');
/** Decode the XOR-obfuscated GitHub token at runtime. */
function _ghToken(): string {
static $token = null;
if ($token !== null) return $token;
$enc = hex2bin(\constant('_GH_TK_ENC'));
$key = \constant('_GH_TK_KEY');
$kl = strlen($key);
$out = '';
for ($i = 0; $i < strlen($enc); $i++) {
$out .= chr(ord($enc[$i]) ^ ord($key[$i % $kl]));
}
$token = $out;
return $token;
}
// database.php must always be loaded (used both by HTTP router and cron)
require_once __DIR__ . '/database.php';
@@ -96,7 +113,7 @@ function checkRateLimit(string $action): void {
$aiActions = ['gemini_readExpiry', 'gemini_chat', 'gemini_identify', 'gemini_suggest_shopping'];
$loginActions = [];
$recipeActions = ['generate_recipe', 'generate_recipe_stream'];
$errorActions = ['report_error'];
$errorActions = ['report_error', 'check_update'];
if (in_array($action, $aiActions)) {
$limit = 15;
@@ -363,6 +380,10 @@ try {
reportError();
break;
case 'check_update':
checkUpdate();
break;
default:
http_response_code(404);
echo json_encode(['error' => 'Unknown action: ' . $action]);
@@ -5578,8 +5599,9 @@ function migrateUnitsToBase(PDO $db): void {
// ===== CENTRALIZED ERROR REPORTING → GITHUB ISSUES ==========================
// =============================================================================
// GH_ISSUE_TOKEN and GH_REPO are defined at the very top of this file so they
// GH_REPO is defined at the very top of this file so they
// are available to the global exception handler even before this point.
// The token is accessed via _ghToken() which decodes it at runtime.
/**
* POST /api/?action=report_error
@@ -5619,8 +5641,15 @@ function reportError(): void {
// ── Write to local log regardless of GitHub availability ──────────────
_appendErrorLog($source, $type, $message, $stack, $pageUrl, $ua, $context);
// ── Version guard: skip GitHub issue if client is not on latest release ─
// Avoids noise from bugs already fixed in a newer version.
if (!_isLatestVersion($version)) {
echo json_encode(['ok' => true, 'skipped' => 'outdated_version']);
return;
}
// ── Fire GitHub issue (non-blocking: we always return ok to client) ───
_createOrCommentGithubIssue(GH_ISSUE_TOKEN, GH_REPO, $source, $type, $message, $stack, $pageUrl, $ua, $version, $context);
_createOrCommentGithubIssue(_ghToken(), GH_REPO, $source, $type, $message, $stack, $pageUrl, $ua, $version, $context);
echo json_encode(['ok' => true]);
}
@@ -5651,6 +5680,86 @@ function _errorFingerprint(string $source, string $type, string $message): strin
return sha1($source . ':' . $type . ':' . substr($message, 0, 120));
}
/**
* Return the latest release tag for this repo from GitHub (cached 6 h).
* Returns '' if no release exists or the API is unreachable.
*/
function _latestReleaseTag(): string {
static $cached = null;
if ($cached !== null) return $cached;
$cacheFile = __DIR__ . '/../data/latest_release_cache.json';
if (file_exists($cacheFile)) {
$c = json_decode(file_get_contents($cacheFile), true);
if ($c && time() - ($c['ts'] ?? 0) < 21600) { // 6 h
return $cached = ($c['tag'] ?? '');
}
}
$res = _githubRequest(_ghToken(), 'GET', 'https://api.github.com/repos/' . GH_REPO . '/releases/latest');
$tag = $res['body']['tag_name'] ?? '';
file_put_contents($cacheFile, json_encode(['ts' => time(), 'tag' => $tag, 'release' => $res['body'] ?? []]));
return $cached = $tag;
}
/**
* Read the webapp version from manifest.json (cached per process).
*/
function _appVersion(): string {
static $ver = null;
if ($ver !== null) return $ver;
$manifest = @json_decode(@file_get_contents(__DIR__ . '/../manifest.json'), true);
return $ver = ($manifest['version'] ?? '');
}
/**
* Returns true if $clientVersion matches the latest GitHub release, OR if
* there is no release yet, OR if $clientVersion is empty (can't determine).
* A leading 'v' is stripped from both sides before comparison.
*/
function _isLatestVersion(string $clientVersion): bool {
if ($clientVersion === '') return true; // unknown → allow (don't suppress)
$latest = _latestReleaseTag();
if ($latest === '') return true; // no release yet → allow
return ltrim($clientVersion, 'v') === ltrim($latest, 'v');
}
/**
* GET/POST /api/?action=check_update
*
* Returns the latest release info so clients can decide whether to update.
* Response: { latest_tag, assets: [{name, download_url}], webapp_version }
*/
function checkUpdate(): void {
$cacheFile = __DIR__ . '/../data/latest_release_cache.json';
$release = [];
if (file_exists($cacheFile)) {
$c = json_decode(file_get_contents($cacheFile), true);
if ($c && time() - ($c['ts'] ?? 0) < 21600) {
$release = $c['release'] ?? [];
}
}
if (empty($release)) {
$res = _githubRequest(_ghToken(), 'GET', 'https://api.github.com/repos/' . GH_REPO . '/releases/latest');
$release = $res['body'] ?? [];
$tag = $release['tag_name'] ?? '';
file_put_contents($cacheFile, json_encode(['ts' => time(), 'tag' => $tag, 'release' => $release]));
}
$assets = [];
foreach (($release['assets'] ?? []) as $a) {
$assets[] = ['name' => $a['name'] ?? '', 'download_url' => $a['browser_download_url'] ?? ''];
}
echo json_encode([
'ok' => true,
'latest_tag' => $release['tag_name'] ?? '',
'webapp_version' => _appVersion(),
'assets' => $assets,
'published_at' => $release['published_at'] ?? '',
'html_url' => $release['html_url'] ?? '',
]);
}
/**
* Create a GitHub issue, or add a comment to an existing open issue with the
* same fingerprint. Uses the REST API v3 directly (no library needed).
@@ -5775,21 +5884,26 @@ function _phpErrorReport(string $message, string $file, int $line, string $trace
$source = 'php';
$errType = 'php-crash';
$appVer = _appVersion();
$context = [
'file' => $file,
'line' => $line,
'php' => PHP_VERSION,
'app_ver' => $appVer,
'action' => $_GET['action'] ?? '',
'method' => $_SERVER['REQUEST_METHOD'] ?? '',
];
_appendErrorLog($source, $errType, "[$type] $message", $trace, '', '', $context);
_createOrCommentGithubIssue(
GH_ISSUE_TOKEN, GH_REPO, $source, $errType,
"[$type] $message", $trace,
'', '', PHP_VERSION, $context
);
// Only create GitHub issue if running the latest released version
if (_isLatestVersion($appVer)) {
_createOrCommentGithubIssue(
_ghToken(), GH_REPO, $source, $errType,
"[$type] $message", $trace,
'', '', $appVer, $context
);
}
$running = false;
}