where('device_token_hash', $this->hashToken($token)) ->where('status', '!=', Device::STATUS_DISABLED) ->first(); } public function issueToken(Device $device): string { $plain = $this->generateToken(); $meta = $device->metadata ?? []; if ($this->isQueueDeviceType($device->type)) { $meta['access_token'] = $plain; } $device->update([ 'device_token_hash' => $this->hashToken($plain), 'metadata' => $meta, ]); return $plain; } public function revokeToken(Device $device): Device { $meta = $device->metadata ?? []; unset($meta['access_token']); $device->update([ 'device_token_hash' => null, 'metadata' => $meta, ]); 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) { 'kiosk', 'display' => Device::MODE_MANUAL, '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 isQueueDeviceType(string $type): bool { return in_array($type, config('care.queue_device_types', []), true); } 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->isQueueDeviceType($type)) { return $plans->hasPaidPlan($organization); } if (! $this->typeRequiresPro($type)) { return true; } return $plans->hasPaidPlan($organization); } public function publicAccessToken(Device $device): ?string { $token = $device->metadata['access_token'] ?? null; return is_string($token) && $token !== '' ? $token : null; } public function connectionHint(string $type, string $mode): string { if ($type === 'kiosk') { return 'Walk-up ticket kiosk. Open the public kiosk URL on a tablet in kiosk mode.'; } if ($type === 'display') { return 'Waiting-room / counter display. Opens the linked TV board URL on a screen or tablet.'; } 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.', }; } }