settings = $settings; } public function show( Request $request, SoftphoneConfigResolver $resolver ) { $user = Auth::user(); if (!$user) { return $this->fail( 'unauthenticated', 'No authenticated Softphone session was found. Reopen the Softphone from the external system.', 401 ); } $companyId = (int) $user->company_id; if (!$this->settings->isEnabled($companyId)) { return $this->fail( 'integration_disabled', 'Softphone integration is disabled for this company.', 503 ); } if (!$user->is_active) { return $this->fail('user_inactive', 'This user is not active.', 403); } // Mark the session so the single-session policy keeps it alive. $request->session()->put(SoftphoneSessionManager::MARKER, true); // Shared Softphone configuration for the session's user. $data = $resolver->resolveForUser($user); return response() ->view('dashboard::phone-integration', ['data' => $data]) ->withHeaders($this->securityHeaders($companyId)); } /** * Headers needed for cross-origin embedding (no X-Frame-Options; embedding * is limited by the CSP frame-ancestors directive). */ protected function securityHeaders(int $companyId): array { return [ 'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0', 'Pragma' => 'no-cache', 'Referrer-Policy' => 'no-referrer', 'Vary' => 'Origin', 'Content-Security-Policy' => 'frame-ancestors ' . $this->settings->frameAncestors($companyId), 'Permissions-Policy' => 'microphone=(self), autoplay=(self), camera=()', ]; } /** * Small page shown when the Softphone cannot be opened. */ protected function fail(string $code, string $message, int $status) { Log::warning('softphone_integration: integration page rejected', [ 'reason' => $code, 'ip' => request()->ip(), ]); $html = '' . '' . 'Softphone unavailable' . '' . '

Softphone unavailable

' . '

' . e($message) . '

' . '' . ''; return response($html, $status)->withHeaders([ 'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0', 'Referrer-Policy' => 'no-referrer', 'Content-Type' => 'text/html; charset=UTF-8', ]); } }