| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- <?php
- // Status report to the Manage server.
- //
- // There is no cron on this host, so the heartbeat rides along with admin page
- // loads: throttled on admin/settings.php via manageHeartbeatSendIfDue(), and
- // unthrottled right after a backup or an update, when the reported values have
- // just changed.
- //
- // The response doubles as a cheap update check, which is why the server
- // dashboard stays current without polling the shop.
- declare(strict_types=1);
- function manageHeartbeatPayload(): array
- {
- $lastBackupAt = '';
- $pendingMigrations = 0;
- try {
- $backups = manageBackupList();
- if ($backups !== []) {
- $lastBackupAt = (string) ($backups[0]['created_at'] ?? '');
- }
- } catch (Throwable $exception) {
- // A broken backup index must not stop the heartbeat; the server simply
- // keeps its previous value.
- manageClientLog('WARNING', 'Heartbeat could not read backup index', [
- 'error' => $exception->getMessage(),
- ]);
- }
- try {
- $pendingMigrations = count(manageUpdatePendingMigrations());
- } catch (Throwable $exception) {
- manageClientLog('WARNING', 'Heartbeat could not read migrations', [
- 'error' => $exception->getMessage(),
- ]);
- }
- $diskFree = 0;
- $free = @disk_free_space(manageClientAppRoot());
- if (is_float($free) || is_int($free)) {
- $diskFree = (int) $free;
- }
- return [
- 'version' => manageClientVersion(),
- 'php_version' => PHP_VERSION,
- 'disk_free' => $diskFree,
- 'pending_migrations' => $pendingMigrations,
- 'last_backup_at' => $lastBackupAt,
- ];
- }
- /**
- * Sends the heartbeat.
- *
- * @return array{success: bool, latest: string, update_available: bool, server_time: string}
- */
- function manageHeartbeatSend(): array
- {
- $decoded = manageClientRequestJson(
- 'POST',
- 'heartbeat.php',
- manageHeartbeatPayload(),
- (int) MANAGE_HTTP_TIMEOUT
- );
- manageWriteJson((string) MANAGE_HEARTBEAT_STATE, [
- 'sent_at' => date(DATE_ATOM),
- 'latest' => (string) ($decoded['latest'] ?? ''),
- 'update_available' => !empty($decoded['update_available']),
- ]);
- return [
- 'success' => !empty($decoded['success']),
- 'latest' => (string) ($decoded['latest'] ?? ''),
- 'update_available' => !empty($decoded['update_available']),
- 'server_time' => (string) ($decoded['server_time'] ?? ''),
- ];
- }
- /**
- * Heartbeat that never throws. For call sites inside a page render, where an
- * unreachable server must not surface as an error.
- */
- function manageHeartbeatSendQuietly(): ?array
- {
- if (!manageClientConfigured()) {
- return null;
- }
- try {
- return manageHeartbeatSend();
- } catch (Throwable $exception) {
- manageClientLog('WARNING', 'Heartbeat failed', ['error' => $exception->getMessage()]);
- return null;
- }
- }
- function manageHeartbeatLastSentAt(): int
- {
- $timestamp = strtotime((string) (manageReadJson((string) MANAGE_HEARTBEAT_STATE)['sent_at'] ?? ''));
- return $timestamp === false ? 0 : $timestamp;
- }
- /**
- * Sends a heartbeat at most once per hour, so opening the settings page
- * repeatedly does not mean one HTTP round trip per render. Never throws.
- */
- function manageHeartbeatSendIfDue(int $intervalSeconds = 3600): ?array
- {
- if (time() - manageHeartbeatLastSentAt() < max(1, $intervalSeconds)) {
- return null;
- }
- return manageHeartbeatSendQuietly();
- }
|