Miscellaneous

Proxy Client

Adding a new proxy client type

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';
    }

Copyright © 2026 XMPlus