beforePluginAction()) { return $this->fail($error); } return $this->success($this->buildPayload($request->user())); } public function panelUrl(Request $request) { if ($error = $this->beforePluginAction()) { return $this->fail($error); } $user = $request->user(); $ttlMinutes = max(1, (int) $this->getConfig('signed_url_ttl_minutes', 60)); $expiresAt = now()->addMinutes($ttlMinutes); return $this->success([ 'url' => URL::temporarySignedRoute( 'user-online-devices.panel', $expiresAt, ['user' => $user->id] ), 'expires_at' => $expiresAt->timestamp, ]); } public function userStatus(): View { abort_unless($this->isPluginEnabled(), 404); return view('UserOnlineDevices::userstatus'); } public function panel(Request $request, int $user): View { abort_unless($this->isPluginEnabled(), 404); $targetUser = User::query()->findOrFail($user); $payload = $this->buildPayload($targetUser); $ttlMinutes = max(1, (int) $this->getConfig('signed_url_ttl_minutes', 60)); return view('UserOnlineDevices::panel', [ 'payload' => $payload, 'snapshotUrl' => URL::temporarySignedRoute( 'user-online-devices.snapshot', now()->addMinutes($ttlMinutes), ['user' => $targetUser->id] ), 'refreshInterval' => max(5, (int) $this->getConfig('refresh_interval_seconds', 15)), ]); } public function snapshot(Request $request, int $user) { abort_unless($this->isPluginEnabled(), 404); $targetUser = User::query()->findOrFail($user); return response()->json([ 'data' => $this->buildPayload($targetUser), ]); } private function buildPayload(User $user): array { $devices = $this->deviceStateService->getUsersDevices([$user->id]); $ips = collect($devices[$user->id] ?? []) ->filter(fn ($ip) => is_string($ip) && $ip !== '') ->unique() ->values(); return [ 'user' => [ 'id' => $user->id, 'email' => $user->email, 'online_count' => $ips->count(), 'last_online_at' => $user->last_online_at, ], 'online_devices' => $this->formatOnlineDevices($ips), 'active_sessions' => $this->shouldShowActiveSessions() ? $this->formatActiveSessions($user) : [], 'meta' => [ 'generated_at' => time(), 'refresh_interval_seconds' => max(5, (int) $this->getConfig('refresh_interval_seconds', 15)), 'mask_ip' => (bool) $this->getConfig('mask_ip', false), 'show_active_sessions' => $this->shouldShowActiveSessions(), ], ]; } private function formatOnlineDevices(Collection $ips): array { return $ips->values()->map(function (string $ip, int $index) { return [ 'name' => 'Device ' . ($index + 1), 'ip' => $this->maskIp($ip), 'raw_ip' => $ip, ]; })->all(); } private function formatActiveSessions(User $user): array { return $user->tokens() ->orderByDesc('last_used_at') ->get(['id', 'name', 'last_used_at', 'created_at', 'expires_at']) ->map(function ($token) { return [ 'id' => $token->id, 'device' => $token->name ?: 'Unknown Session', 'last_used_at' => $token->last_used_at?->timestamp, 'created_at' => $token->created_at?->timestamp, 'expires_at' => $token->expires_at?->timestamp, ]; }) ->all(); } private function shouldShowActiveSessions(): bool { return (bool) $this->getConfig('show_active_sessions', true); } private function maskIp(string $ip): string { if (!(bool) $this->getConfig('mask_ip', false)) { return $ip; } if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { $parts = explode('.', $ip); $parts[3] = '*'; return implode('.', $parts); } if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { $parts = explode(':', $ip); $count = count($parts); if ($count > 2) { $parts[$count - 1] = '****'; $parts[$count - 2] = '****'; } return implode(':', $parts); } return $ip; } }