Stage 9: Telegram integration (connector side-service, Mini App, out-of-app push)
Tests · Go / test (push) Successful in 7s
Tests · Integration / integration (push) Successful in 12s
Tests · Go / test (pull_request) Successful in 6s
Tests · Integration / integration (pull_request) Successful in 11s
Tests · UI / test (pull_request) Successful in 19s

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.
This commit is contained in:
Ilia Denisov
2026-06-04 01:42:54 +02:00
parent 1012fb47a0
commit cf66ed7e26
86 changed files with 3624 additions and 372 deletions
+7 -1
View File
@@ -104,7 +104,9 @@ table Ack {
// --- profile (authenticated) ---
// Profile is the authenticated account's own profile view. away_start/away_end are
// the "HH:MM" daily away-window bounds (added trailing — backward-compatible).
// the "HH:MM" daily away-window bounds. notifications_in_app_only (default true)
// suppresses out-of-app platform push, leaving only the in-app live stream (both
// added trailing — backward-compatible).
table Profile {
user_id:string;
display_name:string;
@@ -116,6 +118,7 @@ table Profile {
is_guest:bool;
away_start:string;
away_end:string;
notifications_in_app_only:bool = true;
}
// --- game (authenticated) ---
@@ -256,6 +259,8 @@ table AccountRef {
// UpdateProfileRequest overwrites the full editable profile (the client sends the
// complete desired profile). away_start/away_end are "HH:MM" bounds.
// notifications_in_app_only (trailing — backward-compatible) toggles out-of-app
// platform push off when set.
table UpdateProfileRequest {
display_name:string;
preferred_language:string;
@@ -264,6 +269,7 @@ table UpdateProfileRequest {
away_end:string;
block_chat:bool;
block_friend_requests:bool;
notifications_in_app_only:bool = true;
}
// EmailBindRequest asks the backend to send a confirm-code binding email to the
+16 -1
View File
@@ -137,8 +137,20 @@ func (rcv *Profile) AwayEnd() []byte {
return nil
}
func (rcv *Profile) NotificationsInAppOnly() bool {
o := flatbuffers.UOffsetT(rcv._tab.Offset(24))
if o != 0 {
return rcv._tab.GetBool(o + rcv._tab.Pos)
}
return true
}
func (rcv *Profile) MutateNotificationsInAppOnly(n bool) bool {
return rcv._tab.MutateBoolSlot(24, n)
}
func ProfileStart(builder *flatbuffers.Builder) {
builder.StartObject(10)
builder.StartObject(11)
}
func ProfileAddUserId(builder *flatbuffers.Builder, userId flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(userId), 0)
@@ -170,6 +182,9 @@ func ProfileAddAwayStart(builder *flatbuffers.Builder, awayStart flatbuffers.UOf
func ProfileAddAwayEnd(builder *flatbuffers.Builder, awayEnd flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(9, flatbuffers.UOffsetT(awayEnd), 0)
}
func ProfileAddNotificationsInAppOnly(builder *flatbuffers.Builder, notificationsInAppOnly bool) {
builder.PrependBoolSlot(10, notificationsInAppOnly, true)
}
func ProfileEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
return builder.EndObject()
}
+16 -1
View File
@@ -105,8 +105,20 @@ func (rcv *UpdateProfileRequest) MutateBlockFriendRequests(n bool) bool {
return rcv._tab.MutateBoolSlot(16, n)
}
func (rcv *UpdateProfileRequest) NotificationsInAppOnly() bool {
o := flatbuffers.UOffsetT(rcv._tab.Offset(18))
if o != 0 {
return rcv._tab.GetBool(o + rcv._tab.Pos)
}
return true
}
func (rcv *UpdateProfileRequest) MutateNotificationsInAppOnly(n bool) bool {
return rcv._tab.MutateBoolSlot(18, n)
}
func UpdateProfileRequestStart(builder *flatbuffers.Builder) {
builder.StartObject(7)
builder.StartObject(8)
}
func UpdateProfileRequestAddDisplayName(builder *flatbuffers.Builder, displayName flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(displayName), 0)
@@ -129,6 +141,9 @@ func UpdateProfileRequestAddBlockChat(builder *flatbuffers.Builder, blockChat bo
func UpdateProfileRequestAddBlockFriendRequests(builder *flatbuffers.Builder, blockFriendRequests bool) {
builder.PrependBoolSlot(6, blockFriendRequests, false)
}
func UpdateProfileRequestAddNotificationsInAppOnly(builder *flatbuffers.Builder, notificationsInAppOnly bool) {
builder.PrependBoolSlot(7, notificationsInAppOnly, true)
}
func UpdateProfileRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
return builder.EndObject()
}