Proxy Client
Adding Client Link
In your backend folder /home/panel/backend/app/ServicesSubscribeService.php getConfigList() append config link. This will display in admin proxy client list.
/**
* Get array of subscription config type.
*
* @return array
*/
public static function getConfigList(): array
{
$configList = [
'Clash' => "?client=clash",
'ClashMeta' => "?client=meta",
'Json' => "?client=json",
'Loon' => "?client=loon",
'Stash' => "?client=stash",
'Singbox' => "?client=singbox",
'Surge' => "?client=surge",
'Surfboard' => "?client=surfboard",
'QuantumultX' => "?client=quantumultx",
'Shadowrocket' => "?client=shadowrocket",
'Shadowsocks' => "?client=shadowsocks",
'V2rayN(G)' => "?client=latest",
];
return (array)$configList;
}
Get Client
In your backend folder /home/panel/backend/app/ServicesSubscribeService.php getClient()
/**
* Get the appropriate client instance based on the client type.
*
* @param string $client The client type (e.g., 'clash', 'singbox').
* @return Clash|Json|Loon|Stash|SingBox|Surge|Surfboard|QuantumultX|Shadowrocket|Shadowsocks|Xray The client instance.
*/
private function getClient(string $client): Clash|Json|Loon|Stash|SingBox|Surge|Surfboard|QuantumultX|Shadowrocket|Shadowsocks|Xray
{
$clientMap = [
'clash' => Clash::class,
'meta' => Clash::class,
'json' => Json::class,
'loon' => Loon::class,
'stash' => Stash::class,
'singbox' => SingBox::class,
'surge' => Surge::class,
'surfboard' => Surfboard::class,
'quantumultx' => QuantumultX::class,
'shadowrocket' => Shadowrocket::class,
'shadowsocks' => Shadowsocks::class,
'latest' => Xray::class,
];
$clientClass = $clientMap[$client] ?? Xray::class;
return new $clientClass();
}
Client Items
$items of array are from /home/panel/backend/app/ServerService.php
/**
* Build server configuration item for a subscription.
*
* @param mixed $subscription The subscription object.
* @param Server $server The server model instance.
* @param bool $flag Whether to include emoji in remarks.
* @return array<string, mixed> The server configuration item.
*/
public function serveItem($subscription, Server $server, bool $flag = true): array
{
if ($subscription->status !== 1 ||
($subscription->traffic_expire && Carbon::parse($subscription->traffic_expire)->isPast()) ||
$subscription->total_traffic <= 0 ||
$subscription->total_traffic <= ($subscription->u + $subscription->d)) {
return [];
}
$security = json_decode($server->securitySettings ?? '{}', true, 512, JSON_UNESCAPED_SLASHES);
$transport = json_decode($server->transportSettings ?? '{}', true, 512, JSON_UNESCAPED_SLASHES);
$item = [
'type' => strtolower($server->type),
'name' => $server->remarks,
'remark' => $this->remarks($server, $flag),
'add' => $server->address ?? $server->ip,
'ip' => $server->ip,
'encryption' => $transport['encryption'] ?? 'none',
'method' => $transport['cipher'] ?? 'aes-128-gcm',
'port' => PortHelper::selectSinglePort((string)$server->server_port),
'net' => $this->transport($server->transportSettings),
'tls' => $this->security($server->securitySettings),
'passwd' => $this->generatePassword($server, $subscription),
'headerType' => 'none',
'path' => '/',
'host' => '',
'fm' => $transport['maskSettings'] ?? '',
];
$this->applySecuritySettings($item, $security);
$this->applyTransportSettings($item, $transport);
return $item;
}
/**
* Apply security settings to the server configuration item.
*
* @param array<string, mixed> $item The server configuration item.
* @param array<string, mixed> $security The decoded security settings.
* @return void
*/
private function applySecuritySettings(array &$item, array $security): void
{
match (strtolower((string) $item['tls'])) {
'tls' => [
$item['allowInsecure'] = $security['tlsSettings']['allowInsecure'] ?? false,
$item['alpn'] = $security['tlsSettings']['alpn'] ?? [],
$item['fragment'] = $security['tlsSettings']['fragment'] ?? null,
$item['sni'] = $security['tlsSettings']['serverName'] ?? $item['add'],
$item['fp'] = $security['tlsSettings']['fingerprint'] ?? '',
$item['ech'] = $security['tlsSettings']['echConfigList'] ?? "",
$item['vcn'] = $security['tlsSettings']['verifyPeerCertByName'] ?? "",
$item['pcs'] = $security['tlsSettings']['pinnedPeerCertSha256'] ?? "",
],
'reality' => [
$serverNames = (array) ($security['realitySettings']['serverNames'] ?? []),
$item['sni'] = $serverNames ? $serverNames[array_rand($serverNames)] : '',
$item['pbk'] = $security['realitySettings']['password'] ?? '',
$shortids = (array) ($security['realitySettings']['shortids'] ?? []),
$item['sid'] = $shortids ? $shortids[array_rand($shortids)] : '',
$item['fp'] = $security['realitySettings']['fingerprint'] ?? '',
$item['pqv'] = $security['realitySettings']['mldsa65Verify'] ?? '',
$item['spx'] = $security['realitySettings']['spiderX'] ?? '',
],
default => null,
};
}
/**
* Apply transport settings to the server configuration item.
*
* @param array<string, mixed> $item The server configuration item.
* @param array<string, mixed> $transport The decoded transport settings.
* @return void
*/
private function applyTransportSettings(array &$item, array $transport): void
{
$settings = $transport['transportProtocol']['settings'];
match (strtolower((string) $item['net'])) {
'tcp' => [
$item['headerType'] = $settings['header']['type'] ?? 'none',
$path = (array) ($settings['header']['request']['path'] ?? []),
$item['path'] = $path ? $path[array_rand($path)] : '/',
$host = (array) ($settings['header']['request']['headers']['Host'] ?? []),
$item['host'] = $host ? $host[array_rand($host)] : '',
$item['flow'] = $transport['flow'] ?? '',
],
'grpc' => [
$item['headerType'] = 'none',
$item['serviceName'] = $settings['servicename'] ?? '',
$item['authority'] = $settings['authority'] ?? '',
],
'kcp' => [
$item['headerType'] = 'none',
$item['seed'] = '',
$item['congestion'] = $settings['congestion'] ?? false,
],
'ws' => [
$item['headerType'] = 'none',
$item['path'] = $settings['path'] ?? '/',
$item['host'] = $settings['custom_host'] ?? $settings['host'] ?? '',
],
'httpupgrade' => [
$item['headerType'] = 'none',
$item['path'] = $settings['path'] ?? '/',
$item['host'] = $settings['custom_host'] ?? $settings['host'] ?? '',
],
'xhttp' => [
$item['headerType'] = 'none',
$item['path'] = $settings['path'] ?? '/',
$item['host'] = $settings['custom_host'] ?? $settings['host'] ?? '',
$item['mode'] = $settings['mode'] ?? 'auto',
$extra = $settings['extra'] ?? '',
$extra = is_array($extra) ? array_diff_key($extra, array_flip([
'noSSEHeader',
'scMaxBufferedPosts',
'scMaxEachPostBytes',
'scStreamUpServerSecs',
'xPaddingBytes'
])) : $extra,
$item['extra'] = $extra,
],
'hysteria' => [
$item['obfs'] = isset($transport['maskSettings']) ? $transport['maskSettings']['udp'][0]['type'] : "",
$item['obfs-password'] = isset($transport['maskSettings']) ? $transport['maskSettings']['udp'][0]['settings']['password'] : "",
],
default => null,
};
}
/**
* Validate and return the real path for a template file.
*
* @param string $path The template file path.
* @return string The validated real path.
* @throws \Exception If the path is invalid.
*/
public function validateTemplatePath(string $path): string
{
$realPath = realpath(base_path($path));
$basePath = realpath(base_path('/resources/profiles'));
if (!$realPath || !str_starts_with($realPath, $basePath)) {
throw new \Exception('Invalid template path');
}
return $realPath;
}
Client Class
Classes php files are storedin /home/panel/backend/app/Http/Controllers/Client/. Client must have a getContent() public function. Refere to other client classess.
/**
* Generate Xray configuration content for a given subscription.
*
* @param mixed $subscription The subscription object containing server group details.
* @param bool $flag The flag indicating specific server conditions.
* @param string $client The client type.
* @param ServerService $serverService The service for handling server-related operations.
* @return string The base64-encoded Xray configuration.
*/
public function getContent($subscription, bool $flag, string $client, ServerService $serverService): string
Client Profiles
Client profiles are located in /home/panel/backend/resources/profiles
Client Controller
Client profiles are located in /home/panel/backend/app/Http/Controllers/Guest/SubController.php
Append clent user agent name and link name
For example clash will be https://www.tld.com/subscribe/xxxxx?client=clash or https://www.tld.com/link/xxxxx?client=clash depending on your sublink format.
/**
* Determine the client type based on the user agent.
*
* @param string $userAgent The user agent string from the request.
* @return string The detected client type.
*/
private function getClient(string $userAgent): string
{
$agent = explode('/', $userAgent)[0] ?? '';
$clientMap = [
'Meta' => 'meta',
'verge' => 'meta',
'FlClash' => 'meta',
'Clash' => 'clash',
'Stash' => 'stash',
'Loon' => 'loon',
'Surge' => 'surge',
'Shadowrocket' => 'shadowrocket',
'Shadowsocks' => 'shadowsocks',
'QuantumultX' => 'quantumultx',
'Surfboard' => 'surfboard',
'SFA' => 'singbox',
];
foreach ($clientMap as $key => $client) {
if (str_contains($agent, $key)) {
return $client;
}
}
return 'latest';
}
