Files
scrabble-game/gateway/internal/connector/client.go
T
Ilia Denisov 3590df28db
Tests · Go / test (push) Successful in 7s
Tests · Go / test (pull_request) Successful in 6s
Tests · Integration / integration (pull_request) Successful in 12s
Tests · UI / test (pull_request) Failing after 5m9s
Stage 9: Telegram integration (connector side-service, Mini App, out-of-app push)
New platform/telegram connector (own container, bot token only there):
- go-telegram/bot long-poll loop: /start deep-links + Mini App launch button.
- gRPC API pkg/proto/telegram/v1 (Telegram service): ValidateInitData, Notify
  (renders a localized message + deep-link button), SendToUser/SendToGameChannel
  (admin, wired in Stage 10). Generic methods are platform-agnostic (external_id).
- Bot API base override for Telegram's test environment; Dockerfile + compose
  (VPN sidecar, no public ingress); README.

Gateway:
- initData validation relocated from the gateway into the connector; the gateway
  calls ValidateInitData over gRPC (GATEWAY_CONNECTOR_ADDR), drops the bot token,
  and deletes internal/auth.
- Out-of-app push: runPushPump routes events whose recipient has no live in-app
  stream to connector.Notify, gated by /internal/push-target + the in-app-only
  flag (race-free de-dup); HasSubscribers added to the push hub.

Backend:
- Migration 00007 accounts.notifications_in_app_only (default true) + jetgen.
- ProvisionTelegram seeds a new account's language/display name from the launch
  fields; IdentityExternalID reverse lookup; /internal/push-target handler.

UI:
- Telegram Mini App launch: detect initData, apply themeParams, authTelegram,
  route the deep-link start_param (g/i/f); /telegram/ guard redirects outside
  Telegram. Vite relative base + telegram-web-app.js. In-app-only profile toggle;
  share-to-Telegram link for a friend code. Vitest + Playwright coverage.

Wire/docs/CI: fbs Profile/UpdateProfileRequest gain notifications_in_app_only
(Go + TS); go.work uses ./platform/telegram; go-unit.yaml covers it; PLAN,
ARCHITECTURE, FUNCTIONAL (+ru), UI_DESIGN, READMEs updated.
2026-06-04 01:48:03 +02:00

83 lines
2.6 KiB
Go

// Package connector is the gateway's gRPC client for the Telegram connector
// side-service: it validates Mini App initData and delivers out-of-app push. The
// connector lives on the trusted internal network, so the connection uses insecure
// (plaintext) transport credentials (ARCHITECTURE.md §12).
package connector
import (
"context"
"errors"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
telegramv1 "scrabble/pkg/proto/telegram/v1"
)
// ErrInvalidInitData is returned by ValidateInitData when the connector rejects the
// launch data (a gRPC InvalidArgument), letting the transcode layer surface a stable
// result code.
var ErrInvalidInitData = errors.New("connector: invalid telegram init data")
// User is a validated Mini App identity.
type User struct {
ExternalID string
Username string
FirstName string
LanguageCode string
}
// Client wraps the connector's Telegram gRPC service.
type Client struct {
conn *grpc.ClientConn
c telegramv1.TelegramClient
}
// New dials the connector gRPC endpoint.
func New(addr string) (*Client, error) {
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, fmt.Errorf("connector: dial %s: %w", addr, err)
}
return &Client{conn: conn, c: telegramv1.NewTelegramClient(conn)}, nil
}
// Close releases the gRPC connection.
func (c *Client) Close() error { return c.conn.Close() }
// ValidateInitData verifies Mini App launch data and returns the user identity,
// mapping a connector InvalidArgument to ErrInvalidInitData.
func (c *Client) ValidateInitData(ctx context.Context, initData string) (User, error) {
resp, err := c.c.ValidateInitData(ctx, &telegramv1.ValidateInitDataRequest{InitData: initData})
if err != nil {
if status.Code(err) == codes.InvalidArgument {
return User{}, ErrInvalidInitData
}
return User{}, err
}
return User{
ExternalID: resp.GetExternalId(),
Username: resp.GetUsername(),
FirstName: resp.GetFirstName(),
LanguageCode: resp.GetLanguageCode(),
}, nil
}
// Notify delivers an out-of-app notification for a push event; delivered reports
// whether a message was actually sent.
func (c *Client) Notify(ctx context.Context, externalID, kind string, payload []byte, language string) (bool, error) {
resp, err := c.c.Notify(ctx, &telegramv1.NotifyRequest{
ExternalId: externalID,
Kind: kind,
Payload: payload,
Language: language,
})
if err != nil {
return false, err
}
return resp.GetDelivered(), nil
}