where('device_token_hash', $this->hashToken($token)) ->where('status', '!=', Device::STATUS_DISABLED) ->first(); } public function issueToken(Device $device): string { $plain = $this->generateToken(); $device->update(['device_token_hash' => $this->hashToken($plain)]); return $plain; } public function revokeToken(Device $device): Device { $device->update(['device_token_hash' => null]); return $device->fresh(); } public function recordHeartbeat(Device $device, ?array $metadata = null): Device { $payload = [ 'status' => Device::STATUS_ACTIVE, 'last_seen_at' => now(), ]; if ($metadata !== null) { $payload['metadata'] = array_merge($device->metadata ?? [], [ 'agent' => $metadata, ]); } $device->update($payload); return $device->fresh(); } public function defaultConnectionMode(string $type): string { return match ($type) { 'barcode_scanner', 'qr_scanner' => Device::MODE_BROWSER_WEDGE, 'badge_printer' => Device::MODE_MANUAL, 'thermometer', 'bp_monitor', 'pulse_ox', 'weight_scale', 'lab_analyzer', 'agent' => Device::MODE_AGENT, default => Device::MODE_MANUAL, }; } public function typeRequiresPro(string $type): bool { return in_array($type, config('care.device_agent_types', []), true); } public function canRegisterType(Organization $organization, string $type, PlanService $plans): bool { if (! $this->typeRequiresPro($type)) { return true; } return $plans->hasPaidPlan($organization); } public function connectionHint(string $type, string $mode): string { return match ($mode) { Device::MODE_BROWSER_WEDGE => 'Works in the browser today as a USB keyboard wedge (scan into the focused field). No local agent required.', Device::MODE_AGENT => 'Needs the Care Device Agent on a local machine (serial / BLE / vendor SDK). Browser alone cannot talk to this hardware.', Device::MODE_WEB_BLUETOOTH => 'Experimental Web Bluetooth path — not broadly supported; prefer the local agent for clinical devices.', default => 'Manual / inventory only — no automated capture path configured.', }; } }