162 lines
4.9 KiB
PHP
162 lines
4.9 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace app\common\library\admin;
|
||
|
||
use app\common\service\GameWebSocketAuthHelper;
|
||
use Webman\Http\Request;
|
||
|
||
final class WebSocketConfigHelper
|
||
{
|
||
public static function wsUrl(?Request $request = null): string
|
||
{
|
||
$url = trim((string) env('H5_WEBSOCKET_URL', ''));
|
||
if ($url !== '' && $request !== null && self::isLoopbackWsUrl($url) && !self::isLoopbackRequestHost($request)) {
|
||
$url = '';
|
||
}
|
||
if ($url !== '') {
|
||
return $url;
|
||
}
|
||
|
||
if ($request !== null) {
|
||
$proto = strtolower((string) $request->header('x-forwarded-proto', ''));
|
||
if ($proto === '') {
|
||
$proto = strtolower((string) $request->header('x-forwarded-protocol', ''));
|
||
}
|
||
$isHttps = $proto === 'https'
|
||
|| strtolower((string) $request->header('x-forwarded-ssl', '')) === 'on'
|
||
|| strtolower((string) $request->header('x-scheme', '')) === 'https';
|
||
$scheme = $isHttps ? 'wss' : 'ws';
|
||
|
||
$host = trim((string) $request->header('host', ''));
|
||
if ($host === '') {
|
||
$host = trim((string) $request->header('x-forwarded-host', ''));
|
||
}
|
||
if ($host !== '') {
|
||
return $scheme . '://' . $host . '/ws/';
|
||
}
|
||
}
|
||
|
||
return 'ws://127.0.0.1:3131/ws/';
|
||
}
|
||
|
||
/**
|
||
* 在基础 ws_url 上拼接握手鉴权 Query(**输出统一为连字符**:auth-token、user-token、admin-ws-token)。
|
||
*
|
||
* $tokens 的键可使用标准名或旧名下划线别名(auth_token 等),拼到 URL 时一律转为连字符。
|
||
*
|
||
* @param array<string, string> $tokens 如 auth-token、user-token、admin-ws-token
|
||
* @param array<string, string> $extraQuery 如 device_id、lang
|
||
*/
|
||
public static function appendTokensToWsUrl(string $wsUrl, array $tokens, array $extraQuery = []): string
|
||
{
|
||
$wsUrl = trim($wsUrl);
|
||
if ($wsUrl === '') {
|
||
return $wsUrl;
|
||
}
|
||
|
||
$authVal = self::pickTokenFromMap($tokens, [
|
||
GameWebSocketAuthHelper::QUERY_AUTH_TOKEN,
|
||
'auth_token',
|
||
'authToken',
|
||
]);
|
||
$userVal = self::pickTokenFromMap($tokens, [
|
||
GameWebSocketAuthHelper::QUERY_USER_TOKEN,
|
||
'user_token',
|
||
'userToken',
|
||
'token',
|
||
]);
|
||
$adminVal = self::pickTokenFromMap($tokens, [
|
||
GameWebSocketAuthHelper::QUERY_ADMIN_WS_TOKEN,
|
||
'admin_ws_token',
|
||
'adminWsToken',
|
||
]);
|
||
|
||
$canonical = [];
|
||
if ($authVal !== '') {
|
||
$canonical[GameWebSocketAuthHelper::QUERY_AUTH_TOKEN] = $authVal;
|
||
}
|
||
if ($userVal !== '') {
|
||
$canonical[GameWebSocketAuthHelper::QUERY_USER_TOKEN] = $userVal;
|
||
}
|
||
if ($adminVal !== '') {
|
||
$canonical[GameWebSocketAuthHelper::QUERY_ADMIN_WS_TOKEN] = $adminVal;
|
||
}
|
||
|
||
foreach ($extraQuery as $k => $v) {
|
||
if (!is_string($k) || $k === '') {
|
||
continue;
|
||
}
|
||
if (!is_scalar($v)) {
|
||
continue;
|
||
}
|
||
$s = trim((string) $v);
|
||
if ($s !== '') {
|
||
$canonical[$k] = $s;
|
||
}
|
||
}
|
||
|
||
if ($canonical === []) {
|
||
return $wsUrl;
|
||
}
|
||
|
||
$pairs = [];
|
||
foreach ($canonical as $key => $val) {
|
||
$pairs[] = $key . '=' . rawurlencode($val);
|
||
}
|
||
$sep = str_contains($wsUrl, '?') ? '&' : '?';
|
||
|
||
return $wsUrl . $sep . implode('&', $pairs);
|
||
}
|
||
|
||
/**
|
||
* @param array<string, string> $tokens
|
||
* @param list<string> $aliases
|
||
*/
|
||
private static function pickTokenFromMap(array $tokens, array $aliases): string
|
||
{
|
||
foreach ($aliases as $key) {
|
||
if (!isset($tokens[$key])) {
|
||
continue;
|
||
}
|
||
$val = $tokens[$key];
|
||
if (!is_string($val)) {
|
||
continue;
|
||
}
|
||
$s = trim($val);
|
||
if ($s !== '') {
|
||
return $s;
|
||
}
|
||
}
|
||
|
||
return '';
|
||
}
|
||
|
||
private static function isLoopbackWsUrl(string $url): bool
|
||
{
|
||
$host = parse_url($url, PHP_URL_HOST);
|
||
if (!is_string($host) || $host === '') {
|
||
return false;
|
||
}
|
||
$host = strtolower($host);
|
||
|
||
return in_array($host, ['127.0.0.1', 'localhost', '::1'], true);
|
||
}
|
||
|
||
private static function isLoopbackRequestHost(Request $request): bool
|
||
{
|
||
$host = strtolower(trim((string) $request->host(true)));
|
||
if ($host === '') {
|
||
$host = strtolower(trim((string) $request->header('host', '')));
|
||
}
|
||
if ($host === '') {
|
||
return false;
|
||
}
|
||
$hostOnly = preg_split('/:/', $host)[0] ?? $host;
|
||
|
||
return in_array($hostOnly, ['127.0.0.1', 'localhost', '::1'], true);
|
||
}
|
||
}
|
||
|