gRPC: панель генерирует несуществующее поле mode вместо multiMode

При генерации клиентского конфига настройка multiMode для gRPC-транспорта выписывается под именем mode. У gRPC в Xray поля mode не существует — Go молча отбрасывает неизвестные ключи JSON, поэтому multiMode у клиента всегда остаётся false, что бы администратор ни выставил в панели. Мультиплексирование gRPC не работает никогда.

backend-main/src/modules/subscription-template/generators/xray-json.generator.service.ts:115-119

grpc: (host) => ({
    serviceName: host.transportOptions.serviceName,
    authority: host.transportOptions.authority,
    mode: !!host.transportOptions.multiMode,   // ← должно быть multiMode
}),

В схеме панели поле названо верно (backend-main/libs/contract/models/resolved-proxy-config.schema.ts:73-77):

export const GrpcTransportOptionsSchema = z.object({
    authority: z.string().nullable(),
    serviceName: z.string().nullable(),
    multiMode: z.boolean(),
});

То есть ошибка только на этапе генерации конфига.

Для справки — структура gRPC-транспорта в Xray, infra/conf/transport_method.go:578-587:

type GRPCConfig struct {
    Authority           string `json:"authority"`
    ServiceName         string `json:"serviceName"`
    MultiMode           bool   `json:"multiMode"`
    IdleTimeout         int32  `json:"idle_timeout"`
    HealthCheckTimeout  int32  `json:"health_check_timeout"`
    PermitWithoutStream bool   `json:"permit_without_stream"`
    InitialWindowsSize  int32  `json:"initial_windows_size"`
    UserAgent           string `json:"user_agent"`
}

Поле mode в Xray есть только у XHTTP (там же, строка 260).

Баг незаметен, потому что подключение устанавливается: сервер регистрирует оба метода — и Tun, и TunMulti (transport/internet/grpc/hub.go:132). Клиент просто уходит в немультиплексированный режим, и со стороны всё выглядит работающим.

Исправление

grpc: (host) => ({
    serviceName: host.transportOptions.serviceName,
    authority: host.transportOptions.authority,
    multiMode: !!host.transportOptions.multiMode,
}),

Сопутствующая проблема: недоступные параметры keepalive

Схема и генератор поддерживают для gRPC только три поля. Параметры idle_timeout, health_check_timeout и permit_without_stream выставить через панель невозможно — их нет ни в схеме, ни в генераторе.

Для мобильных клиентов это ощутимо. Без permit_without_stream: true gRPC не отправляет PING, когда нет активных потоков, а приложение в фоне находится именно в этом состоянии. Соединение умирает незаметно: клиент показывает «подключено», трафик не идёт до переподключения вручную.

Предложение — добавить их по аналогии с остальными полями:

// resolved-proxy-config.schema.ts
export const GrpcTransportOptionsSchema = z.object({
    authority: z.string().nullable(),
    serviceName: z.string().nullable(),
    multiMode: z.boolean(),
    idleTimeout: z.int().nullable(),
    healthCheckTimeout: z.int().nullable(),
    permitWithoutStream: z.boolean().nullable(),
});
// xray-json.generator.service.ts
grpc: (host) => ({
    serviceName: host.transportOptions.serviceName,
    authority: host.transportOptions.authority,
    multiMode: !!host.transportOptions.multiMode,
    ...(host.transportOptions.idleTimeout != null && {
        idle_timeout: host.transportOptions.idleTimeout,
    }),
    ...(host.transportOptions.healthCheckTimeout != null && {
        health_check_timeout: host.transportOptions.healthCheckTimeout,
    }),
    ...(host.transportOptions.permitWithoutStream != null && {
        permit_without_stream: host.transportOptions.permitWithoutStream,
    }),
}),

Имена внутри панели могут быть любыми, но в сгенерированный конфиг они обязаны попадать ровно в том виде, в котором их ждёт Xray — со подчёркиваниями: idle_timeout, health_check_timeout, permit_without_stream. Расхождение здесь даёт ту же тихую поломку, что и с mode.

Исправление названия – будет.

Касательно новых полей – никаких новых полей у “хоста” не планируется. Существует объект Mapper – используйте его.

Исправление доступно в версии панели 3.4.5.