ensureSecret(); if ($host === '' || $secret === '') { return false; } $webhookUrl = $this->webhookUrl(); if ($this->isUnreachableWebhookUrl($webhookUrl)) { Log::warning('chat webhook register refused: unreachable/local URL', [ 'webhook_url' => $webhookUrl, 'hint' => 'Set chat_webhook_base_url to public Laravel URL (e.g. https://niazitala.ir/server) or fix APP_URL', ]); return false; } try { $response = Http::timeout(8)->post('https://'.$host.'/api/projects/set-webhook', [ 'api_key' => $chatApiKey, 'webhook_url' => $webhookUrl, 'webhook_secret' => $secret, ]); if (! $response->successful()) { Log::warning('chat webhook register non-2xx', [ 'host' => $host, 'webhook_url' => $webhookUrl, 'status' => $response->status(), 'body' => mb_substr($response->body(), 0, 300), ]); return false; } Log::info('chat webhook registered', [ 'host' => $host, 'webhook_url' => $webhookUrl, ]); return true; } catch (\Throwable $e) { Log::warning('chat webhook register failed', [ 'host' => $host, 'webhook_url' => $webhookUrl, 'error' => $e->getMessage(), ]); return false; } } /** * آدرس عمومی وب‌هوک — باید به لاراول بخورد نه فرانت SPA. * اولویت: chat_webhook_base_url (مثلاً https://niazitala.ir/server) سپس APP_URL. */ public function webhookUrl(): string { $base = trim((string) Controller::Config('chat_webhook_base_url')); if ($base === '' || $base === '0' || strtolower($base) === 'false') { $base = rtrim((string) config('app.url'), '/'); } else { $base = rtrim($base, '/'); } return $base.'/api/chat/support-message-notify'; } public function isUnreachableWebhookUrl(string $url): bool { $host = parse_url($url, PHP_URL_HOST); if (! is_string($host) || $host === '') { return true; } $host = strtolower($host); return in_array($host, ['localhost', '127.0.0.1', '::1', '0.0.0.0'], true) || str_ends_with($host, '.local') || str_ends_with($host, '.internal'); } public function ensureSecret(): string { $existing = $this->sanitizeSecret((string) Controller::Config('chat_webhook_secret')); if ($existing !== '') { return $existing; } $secret = Str::random(48); $row = Setting::where('key', 'chat_webhook_secret')->first(); if ($row) { $current = $this->sanitizeSecret((string) $row->value); if ($current === '') { $row->value = $secret; $row->save(); } else { // مقدار ذخیره‌شده کاراکتر نامعتبر هدر داشت — با نسخهٔ تمیز جایگزین کن if ($current !== trim((string) $row->value)) { $row->value = $current; $row->save(); } return $current; } } else { Setting::create([ 'key' => 'chat_webhook_secret', 'value' => $secret, 'title_fa' => 'رمز وب‌هوک چت', 'is_active' => true, 'can_edit' => 0, 'description' => 'رمز مشترک بین لاراول و سرور چت برای اعلان پیام پشتیبانی. فقط سرور؛ در صورت خالی بودن به‌صورت خودکار ساخته می‌شود.', 'type' => 'text', ]); } return $secret; } /** فقط ASCII قابل‌استفاده در هدر HTTP (بدون فاصله/خط جدید/یونیکد). */ public function sanitizeSecret(string $secret): string { $secret = trim($secret); if ($secret === '') { return ''; } // حذف کاراکترهای غیرقابل‌قبول برای header value $clean = preg_replace('/[^\x21-\x7E]/', '', $secret) ?? ''; return trim($clean); } }