Initial open source release
This commit is contained in:
commit
74992e893f
377 changed files with 118084 additions and 0 deletions
276
cmd/telesrv/exe_integration_test.go
Normal file
276
cmd/telesrv/exe_integration_test.go
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
tdcrypto "github.com/gotd/td/crypto"
|
||||
"github.com/gotd/td/exchange"
|
||||
"github.com/gotd/td/mtproxy"
|
||||
"github.com/gotd/td/mtproxy/obfuscator"
|
||||
"github.com/gotd/td/proto/codec"
|
||||
"github.com/gotd/td/session"
|
||||
"github.com/gotd/td/telegram"
|
||||
"github.com/gotd/td/telegram/dcs"
|
||||
"github.com/gotd/td/tg"
|
||||
"github.com/gotd/td/transport"
|
||||
|
||||
"telesrv/internal/mtprotoedge"
|
||||
)
|
||||
|
||||
func TestExecutablePrivateMessageRoundTrip(t *testing.T) {
|
||||
exe := os.Getenv("TELESRV_TEST_EXE")
|
||||
if exe == "" {
|
||||
t.Skip("set TELESRV_TEST_EXE to run executable-level MTProto roundtrip")
|
||||
}
|
||||
repoRoot := filepath.Clean(filepath.Join("..", ".."))
|
||||
if !filepath.IsAbs(exe) {
|
||||
exe = filepath.Join(repoRoot, exe)
|
||||
}
|
||||
exeAbs, err := filepath.Abs(exe)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve executable path: %v", err)
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("reserve listen port: %v", err)
|
||||
}
|
||||
addr := ln.Addr().String()
|
||||
_ = ln.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd := exec.CommandContext(ctx, exeAbs)
|
||||
cmd.Dir = repoRoot
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Env = append(os.Environ(),
|
||||
"TELESRV_LISTEN="+addr,
|
||||
"TELESRV_ADVERTISE_IP=127.0.0.1",
|
||||
)
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("start executable: %v", err)
|
||||
}
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- cmd.Wait() }()
|
||||
processDone := false
|
||||
t.Cleanup(func() {
|
||||
if processDone {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
default:
|
||||
_ = cmd.Process.Kill()
|
||||
<-done
|
||||
}
|
||||
})
|
||||
select {
|
||||
case err := <-done:
|
||||
processDone = true
|
||||
t.Fatalf("executable exited before client test: %v\nstdout:\n%s\nstderr:\n%s", err, stdout.String(), stderr.String())
|
||||
case <-time.After(4 * time.Second):
|
||||
case <-ctx.Done():
|
||||
t.Fatalf("wait executable startup: %v\nstdout:\n%s\nstderr:\n%s", ctx.Err(), stdout.String(), stderr.String())
|
||||
}
|
||||
|
||||
rsaKey, err := mtprotoedge.LoadOrGenerateRSAKey(filepath.Join(repoRoot, "data", "server_rsa.pem"))
|
||||
if err != nil {
|
||||
t.Fatalf("load rsa key: %v", err)
|
||||
}
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("split addr: %v", err)
|
||||
}
|
||||
var port int
|
||||
if _, err := fmt.Sscanf(portStr, "%d", &port); err != nil {
|
||||
t.Fatalf("parse port %q: %v", portStr, err)
|
||||
}
|
||||
|
||||
newClient := func(storage *session.StorageMemory) *telegram.Client {
|
||||
opts := telegram.Options{
|
||||
PublicKeys: []exchange.PublicKey{{RSA: &rsaKey.PublicKey}},
|
||||
Resolver: noSecretObfuscatedResolver{addr: addr, dc: 2},
|
||||
DCList: dcs.List{Options: []tg.DCOption{{ID: 2, IPAddress: host, Port: port, Static: true}}},
|
||||
Logger: zaptest.NewLogger(t).Named("exe-client"),
|
||||
SessionStorage: storage,
|
||||
UpdateHandler: telegram.UpdateHandlerFunc(func(context.Context, tg.UpdatesClass) error { return nil }),
|
||||
}
|
||||
return telegram.NewClient(1, "hash", opts)
|
||||
}
|
||||
messagesOf := func(history tg.MessagesMessagesClass) []tg.MessageClass {
|
||||
t.Helper()
|
||||
switch v := history.(type) {
|
||||
case *tg.MessagesMessages:
|
||||
return v.Messages
|
||||
case *tg.MessagesMessagesSlice:
|
||||
return v.Messages
|
||||
default:
|
||||
t.Fatalf("history = %T %+v, want messages", history, history)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
signUp := func(storage *session.StorageMemory, phone, firstName string) tg.User {
|
||||
t.Helper()
|
||||
client := newClient(storage)
|
||||
var out tg.User
|
||||
if err := client.Run(ctx, func(ctx context.Context) error {
|
||||
raw := tg.NewClient(client)
|
||||
sent, err := raw.AuthSendCode(ctx, &tg.AuthSendCodeRequest{
|
||||
PhoneNumber: phone,
|
||||
APIID: 1,
|
||||
APIHash: "hash",
|
||||
Settings: tg.CodeSettings{},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash := sent.(*tg.AuthSentCode).PhoneCodeHash
|
||||
signInRes, err := raw.AuthSignIn(ctx, &tg.AuthSignInRequest{
|
||||
PhoneNumber: phone,
|
||||
PhoneCodeHash: hash,
|
||||
PhoneCode: "12345",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if authz, ok := signInRes.(*tg.AuthAuthorization); ok {
|
||||
out = *(authz.User.(*tg.User))
|
||||
return nil
|
||||
}
|
||||
res, err := raw.AuthSignUp(ctx, &tg.AuthSignUpRequest{
|
||||
PhoneNumber: phone,
|
||||
PhoneCodeHash: hash,
|
||||
FirstName: firstName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authz := res.(*tg.AuthAuthorization)
|
||||
out = *(authz.User.(*tg.User))
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("sign up %s: %v\nstdout:\n%s\nstderr:\n%s", firstName, err, stdout.String(), stderr.String())
|
||||
}
|
||||
return out
|
||||
}
|
||||
sendAndAssertHistory := func(storage *session.StorageMemory, to tg.User, body string, randomID int64, wantOut bool) {
|
||||
t.Helper()
|
||||
client := newClient(storage)
|
||||
if err := client.Run(ctx, func(ctx context.Context) error {
|
||||
raw := tg.NewClient(client)
|
||||
if _, err := raw.MessagesSendMessage(ctx, &tg.MessagesSendMessageRequest{
|
||||
Peer: &tg.InputPeerUser{UserID: to.ID, AccessHash: to.AccessHash},
|
||||
Message: body,
|
||||
RandomID: randomID,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
history, err := raw.MessagesGetHistory(ctx, &tg.MessagesGetHistoryRequest{
|
||||
Peer: &tg.InputPeerUser{UserID: to.ID, AccessHash: to.AccessHash},
|
||||
Limit: 10,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgs := messagesOf(history)
|
||||
if len(msgs) == 0 {
|
||||
t.Fatalf("history = %T %+v, want messages", history, history)
|
||||
}
|
||||
msg, ok := msgs[0].(*tg.Message)
|
||||
if !ok || msg.Message != body || msg.Out != wantOut {
|
||||
t.Fatalf("latest message = %#v, want out=%v text=%q", msgs[0], wantOut, body)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("send %q: %v\nstdout:\n%s\nstderr:\n%s", body, err, stdout.String(), stderr.String())
|
||||
}
|
||||
}
|
||||
readLatest := func(storage *session.StorageMemory, from tg.User, body string, wantOut bool) {
|
||||
t.Helper()
|
||||
client := newClient(storage)
|
||||
if err := client.Run(ctx, func(ctx context.Context) error {
|
||||
raw := tg.NewClient(client)
|
||||
history, err := raw.MessagesGetHistory(ctx, &tg.MessagesGetHistoryRequest{
|
||||
Peer: &tg.InputPeerUser{UserID: from.ID, AccessHash: from.AccessHash},
|
||||
Limit: 10,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgs := messagesOf(history)
|
||||
if len(msgs) == 0 {
|
||||
t.Fatalf("history = %T %+v, want messages", history, history)
|
||||
}
|
||||
msg, ok := msgs[0].(*tg.Message)
|
||||
if !ok || msg.Message != body || msg.Out != wantOut {
|
||||
t.Fatalf("latest message = %#v, want out=%v text=%q", msgs[0], wantOut, body)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("read latest %q: %v\nstdout:\n%s\nstderr:\n%s", body, err, stdout.String(), stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
suffix := time.Now().UnixNano() % 100000000
|
||||
storageA := &session.StorageMemory{}
|
||||
storageB := &session.StorageMemory{}
|
||||
userA := signUp(storageA, fmt.Sprintf("+1555%08d", suffix), "ExeAlice")
|
||||
userB := signUp(storageB, fmt.Sprintf("+1556%08d", suffix), "ExeBob")
|
||||
|
||||
sendAndAssertHistory(storageA, userB, "exe hello bob", time.Now().UnixNano(), true)
|
||||
readLatest(storageB, userA, "exe hello bob", false)
|
||||
sendAndAssertHistory(storageB, userA, "exe hi alice", time.Now().UnixNano()+1, true)
|
||||
readLatest(storageA, userB, "exe hi alice", false)
|
||||
}
|
||||
|
||||
type noSecretObfuscatedResolver struct {
|
||||
addr string
|
||||
dc int
|
||||
}
|
||||
|
||||
func (r noSecretObfuscatedResolver) Primary(ctx context.Context, _ int, _ dcs.List) (transport.Conn, error) {
|
||||
return r.connect(ctx)
|
||||
}
|
||||
|
||||
func (r noSecretObfuscatedResolver) MediaOnly(ctx context.Context, _ int, _ dcs.List) (transport.Conn, error) {
|
||||
return r.connect(ctx)
|
||||
}
|
||||
|
||||
func (r noSecretObfuscatedResolver) CDN(ctx context.Context, _ int, _ dcs.List) (transport.Conn, error) {
|
||||
return r.connect(ctx)
|
||||
}
|
||||
|
||||
func (r noSecretObfuscatedResolver) connect(ctx context.Context) (_ transport.Conn, rerr error) {
|
||||
var dialer net.Dialer
|
||||
conn, err := dialer.DialContext(ctx, "tcp", r.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
obfsConn := obfuscator.Obfuscated2(tdcrypto.DefaultRand(), conn)
|
||||
if err := obfsConn.Handshake(codec.IntermediateClientStart, r.dc, mtproxy.Secret{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proto := transport.NewProtocol(func() transport.Codec {
|
||||
return codec.NoHeader{Codec: codec.Intermediate{}}
|
||||
})
|
||||
return proto.Handshake(obfsConn)
|
||||
}
|
||||
212
cmd/telesrv/main.go
Normal file
212
cmd/telesrv/main.go
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
// Command telesrv 是基于 gotd/td 的 Telegram-like server(第一兼容目标:Telegram Desktop)。
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotd/td/clock"
|
||||
"github.com/gotd/td/exchange"
|
||||
"github.com/gotd/td/tg"
|
||||
|
||||
"telesrv/internal/app/account"
|
||||
"telesrv/internal/app/auth"
|
||||
channelapp "telesrv/internal/app/channels"
|
||||
"telesrv/internal/app/contacts"
|
||||
"telesrv/internal/app/dialogs"
|
||||
filesapp "telesrv/internal/app/files"
|
||||
"telesrv/internal/app/help"
|
||||
"telesrv/internal/app/langpack"
|
||||
"telesrv/internal/app/maintenance"
|
||||
messageapp "telesrv/internal/app/messages"
|
||||
"telesrv/internal/app/updates"
|
||||
"telesrv/internal/app/users"
|
||||
"telesrv/internal/config"
|
||||
"telesrv/internal/mtprotoedge"
|
||||
"telesrv/internal/rpc"
|
||||
"telesrv/internal/store/postgres"
|
||||
"telesrv/internal/store/redisstore"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger, err := zap.NewDevelopment()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "init logger:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer func() { _ = logger.Sync() }()
|
||||
|
||||
if err := run(logger); err != nil {
|
||||
logger.Error("telesrv 退出", zap.Error(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(logger *zap.Logger) error {
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("load config: %w", err)
|
||||
}
|
||||
|
||||
rsaKey, err := mtprotoedge.LoadOrGenerateRSAKey(cfg.RSAKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server rsa key: %w", err)
|
||||
}
|
||||
fingerprint := exchange.PrivateKey{RSA: rsaKey}.Fingerprint()
|
||||
|
||||
_, portStr, err := net.SplitHostPort(cfg.ListenAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse listen addr %q: %w", cfg.ListenAddr, err)
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse listen port %q: %w", portStr, err)
|
||||
}
|
||||
|
||||
// tg.Layer 来自 gotd/td v0.144.0(应为 225),需与目标 TDesktop 基线对齐。
|
||||
logger.Info("telesrv 启动",
|
||||
zap.String("listen", cfg.ListenAddr),
|
||||
zap.Int("dc", cfg.DC),
|
||||
zap.String("advertise", net.JoinHostPort(cfg.AdvertiseIP, portStr)),
|
||||
zap.Int("tl_layer", tg.Layer),
|
||||
zap.String("rsa_key", cfg.RSAKeyPath),
|
||||
zap.Int64("rsa_fingerprint", fingerprint),
|
||||
)
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
// 持久化依赖:先迁移 schema,再建立连接。auth_key 落 PostgreSQL、session 落 Redis。
|
||||
// 依赖由 deploy/docker-compose.yml 启动;连不上则启动失败(开发期须先 docker compose up)。
|
||||
if err := postgres.Migrate(cfg.PostgresDSN); err != nil {
|
||||
return fmt.Errorf("postgres migrate: %w", err)
|
||||
}
|
||||
pool, err := postgres.Open(ctx, cfg.PostgresDSN,
|
||||
postgres.WithMaxConns(cfg.PostgresMaxConns),
|
||||
postgres.WithMinConns(cfg.PostgresMinConns),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect postgres: %w", err)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
rdb, err := redisstore.Open(ctx, cfg.RedisAddr, cfg.RedisPassword, cfg.RedisDB)
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect redis: %w", err)
|
||||
}
|
||||
defer func() { _ = rdb.Close() }()
|
||||
logger.Info("持久化依赖就绪", zap.String("redis", cfg.RedisAddr))
|
||||
|
||||
ln, err := net.Listen("tcp", cfg.ListenAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen %q: %w", cfg.ListenAddr, err)
|
||||
}
|
||||
|
||||
authKeyStore := postgres.NewAuthKeyStore(pool)
|
||||
userStore := postgres.NewUserStore(pool)
|
||||
authzStore := postgres.NewAuthorizationStore(pool)
|
||||
updateStateStore := postgres.NewUpdateStateStore(pool)
|
||||
updateEventStore := postgres.NewUpdateEventStore(pool)
|
||||
dispatchOutboxStore := postgres.NewDispatchOutboxStore(pool, postgres.WithLeaseTimeout(cfg.OutboxLeaseTimeout))
|
||||
ptsAllocator := redisstore.NewPtsAllocator(rdb, updateEventStore)
|
||||
boxIDAllocator := redisstore.NewBoxIDAllocator(rdb, postgres.NewMessageBoxCounterSource(pool))
|
||||
channelIDAllocator := redisstore.NewChannelIDAllocator(rdb, postgres.NewChannelIDCounterSource(pool))
|
||||
channelPtsAllocator := redisstore.NewChannelPtsAllocator(rdb, postgres.NewChannelPtsCounterSource(pool))
|
||||
channelMessageIDAllocator := redisstore.NewChannelMessageIDAllocator(rdb, postgres.NewChannelMessageIDCounterSource(pool))
|
||||
contactStore := postgres.NewContactStore(pool)
|
||||
dialogStore := postgres.NewDialogStore(pool)
|
||||
messageStore := postgres.NewMessageStore(pool, postgres.WithMessageAllocators(boxIDAllocator, ptsAllocator))
|
||||
channelStore := postgres.NewChannelStore(pool, postgres.WithChannelAllocators(channelIDAllocator, channelPtsAllocator, channelMessageIDAllocator))
|
||||
mediaStore := postgres.NewMediaStore(pool)
|
||||
blobBackend, err := filesapp.NewLocalFS(cfg.BlobDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init blob backend: %w", err)
|
||||
}
|
||||
filesService := filesapp.NewService(mediaStore, blobBackend, cfg.DC)
|
||||
if stats, err := filesService.SeedMedia(ctx, cfg.StickerSeedDir, cfg.StickerSeedMaxSets); err != nil {
|
||||
return fmt.Errorf("seed media: %w", err)
|
||||
} else if !stats.Skipped {
|
||||
logger.Info("媒体种子导入完成",
|
||||
zap.String("dir", cfg.StickerSeedDir),
|
||||
zap.Int("reactions", stats.Reactions),
|
||||
zap.Int("sticker_sets", stats.StickerSets),
|
||||
zap.Int("documents", stats.Documents),
|
||||
zap.Int("blobs", stats.Blobs),
|
||||
)
|
||||
}
|
||||
if stats, err := filesService.WarmCaches(ctx); err != nil {
|
||||
logger.Warn("媒体资源缓存预热失败", zap.Error(err))
|
||||
} else if stats.StickerSets > 0 || stats.Documents > 0 || stats.Blobs > 0 {
|
||||
logger.Info("媒体资源缓存预热完成",
|
||||
zap.Int("sticker_sets", stats.StickerSets),
|
||||
zap.Int("documents", stats.Documents),
|
||||
zap.Int("blobs", stats.Blobs),
|
||||
)
|
||||
}
|
||||
langPackStore := postgres.NewLangPackStore(pool)
|
||||
passwordStore := postgres.NewPasswordStore(pool)
|
||||
helpStore := postgres.NewHelpStore(pool)
|
||||
tempAuthKeyStore := postgres.NewTempAuthKeyBindingStore(pool)
|
||||
sessionStore := redisstore.NewSessionStore(rdb, redisstore.DefaultSessionTTL)
|
||||
codeStore := redisstore.NewCodeStore(rdb)
|
||||
rateLimiter := redisstore.NewRateLimiter(rdb)
|
||||
activeSessions := mtprotoedge.NewSessionManager(logger.Named("mtprotoedge").Named("sessions"))
|
||||
go maintenance.NewRetentionWorker(dispatchOutboxStore, logger.Named("maintenance").Named("retention"),
|
||||
cfg.UpdateEventRetention,
|
||||
cfg.RetentionInterval,
|
||||
cfg.RetentionBatch,
|
||||
).Run(ctx)
|
||||
go rpc.NewOutboxDispatcher(updateEventStore, dispatchOutboxStore, activeSessions, logger.Named("rpc").Named("outbox"),
|
||||
rpc.WithOutboxWorkers(cfg.OutboxWorkers),
|
||||
rpc.WithOutboxBatch(cfg.OutboxBatch),
|
||||
rpc.WithOutboxInterval(cfg.OutboxInterval),
|
||||
rpc.WithOutboxPushTimeout(cfg.OutboundPushTimeout),
|
||||
).Run(ctx)
|
||||
langPackService := langpack.NewService(langPackStore)
|
||||
if seeded, err := langPackService.SeedDirectory(ctx, cfg.LangPackSeedDir); err != nil {
|
||||
return fmt.Errorf("seed langpack: %w", err)
|
||||
} else if seeded > 0 {
|
||||
logger.Info("语言包种子导入完成", zap.String("dir", cfg.LangPackSeedDir), zap.Int("strings", seeded))
|
||||
}
|
||||
|
||||
router := rpc.New(rpc.Config{
|
||||
DC: cfg.DC,
|
||||
IP: cfg.AdvertiseIP,
|
||||
Port: port,
|
||||
OutboundPushTimeout: cfg.OutboundPushTimeout,
|
||||
}, rpc.Deps{
|
||||
Auth: auth.NewService(userStore, authzStore, codeStore, authKeyStore, tempAuthKeyStore, cfg.DevAuthCode, auth.WithLoginMessages(messageStore, dialogStore)),
|
||||
Account: account.NewService(passwordStore, account.WithReactionSettings(passwordStore)),
|
||||
Help: help.NewService(helpStore, helpStore),
|
||||
Users: users.NewService(userStore, users.WithPhotoProvider(mediaStore)),
|
||||
Updates: updates.NewService(updateStateStore, updateEventStore, updates.WithPtsAllocator(ptsAllocator)),
|
||||
Contacts: contacts.NewService(contactStore, userStore),
|
||||
Dialogs: dialogs.NewService(dialogStore, channelStore),
|
||||
Messages: messageapp.NewService(messageStore, dialogStore),
|
||||
Channels: channelapp.NewService(channelStore),
|
||||
Files: filesService,
|
||||
LangPack: langPackService,
|
||||
Sessions: activeSessions,
|
||||
Limiter: rateLimiter,
|
||||
}, logger.Named("rpc"), clock.System)
|
||||
activeSessions.SetLifecycleObserver(router)
|
||||
|
||||
srv := mtprotoedge.New(mtprotoedge.Options{
|
||||
Logger: logger.Named("mtprotoedge"),
|
||||
DC: cfg.DC,
|
||||
RSAKey: rsaKey,
|
||||
RPC: router,
|
||||
AuthKeys: authKeyStore,
|
||||
Sessions: sessionStore,
|
||||
ActiveSessions: activeSessions,
|
||||
ObfuscatedTCP: true,
|
||||
})
|
||||
return srv.Serve(ctx, ln)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue