Promote development → master (initial production release: pre-release line + Stage 18) #104

Merged
developer merged 307 commits from development into master 2026-06-22 05:05:48 +00:00
12 changed files with 61 additions and 17 deletions
Showing only changes of commit 6679260d0a - Show all commits
+2
View File
@@ -20,6 +20,7 @@ type sessionResponse struct {
UserID string `json:"user_id"` UserID string `json:"user_id"`
IsGuest bool `json:"is_guest"` IsGuest bool `json:"is_guest"`
DisplayName string `json:"display_name"` DisplayName string `json:"display_name"`
ServiceLanguage string `json:"service_language"`
} }
// okResponse is a simple success acknowledgement. // okResponse is a simple success acknowledgement.
@@ -163,6 +164,7 @@ func sessionResponseFor(token string, acc account.Account) sessionResponse {
UserID: acc.ID.String(), UserID: acc.ID.String(),
IsGuest: acc.IsGuest, IsGuest: acc.IsGuest,
DisplayName: acc.DisplayName, DisplayName: acc.DisplayName,
ServiceLanguage: acc.ServiceLanguage,
} }
} }
+1
View File
@@ -48,6 +48,7 @@ func (s *Server) handleTelegramAuth(c *gin.Context) {
s.abortErr(c, err) s.abortErr(c, err)
return return
} }
acc.ServiceLanguage = req.ServiceLanguage // reflect this login's bot in the session response
s.mintSession(c, acc) s.mintSession(c, acc)
} }
+1
View File
@@ -18,6 +18,7 @@ type SessionResp struct {
UserID string `json:"user_id"` UserID string `json:"user_id"`
IsGuest bool `json:"is_guest"` IsGuest bool `json:"is_guest"`
DisplayName string `json:"display_name"` DisplayName string `json:"display_name"`
ServiceLanguage string `json:"service_language"`
} }
// ProfileResp is an account's own profile. // ProfileResp is an account's own profile.
+2
View File
@@ -36,6 +36,7 @@ func encodeSession(s backendclient.SessionResp, supportedLangs []string) []byte
token := b.CreateString(s.Token) token := b.CreateString(s.Token)
uid := b.CreateString(s.UserID) uid := b.CreateString(s.UserID)
name := b.CreateString(s.DisplayName) name := b.CreateString(s.DisplayName)
svcLang := b.CreateString(s.ServiceLanguage)
langs := buildSupportedLanguagesVector(b, supportedLangs) langs := buildSupportedLanguagesVector(b, supportedLangs)
fb.SessionStart(b) fb.SessionStart(b)
fb.SessionAddToken(b, token) fb.SessionAddToken(b, token)
@@ -43,6 +44,7 @@ func encodeSession(s backendclient.SessionResp, supportedLangs []string) []byte
fb.SessionAddIsGuest(b, s.IsGuest) fb.SessionAddIsGuest(b, s.IsGuest)
fb.SessionAddDisplayName(b, name) fb.SessionAddDisplayName(b, name)
fb.SessionAddSupportedLanguages(b, langs) fb.SessionAddSupportedLanguages(b, langs)
fb.SessionAddServiceLanguage(b, svcLang)
b.Finish(fb.SessionEnd(b)) b.Finish(fb.SessionEnd(b))
return b.FinishedBytes() return b.FinishedBytes()
} }
@@ -43,7 +43,7 @@ func TestTelegramAuthForwardsSeedFields(t *testing.T) {
t.Errorf("unexpected path %q", r.URL.Path) t.Errorf("unexpected path %q", r.URL.Path)
} }
_ = json.NewDecoder(r.Body).Decode(&gotBody) _ = json.NewDecoder(r.Body).Decode(&gotBody)
_, _ = w.Write([]byte(`{"token":"tok-tg","user_id":"u-tg","is_guest":false,"display_name":"Иван"}`)) _, _ = w.Write([]byte(`{"token":"tok-tg","user_id":"u-tg","is_guest":false,"display_name":"Иван","service_language":"ru"}`))
}) })
defer cleanup() defer cleanup()
@@ -67,6 +67,11 @@ func TestTelegramAuthForwardsSeedFields(t *testing.T) {
if got := sessionLanguages(sess); len(got) != 1 || got[0] != "ru" { if got := sessionLanguages(sess); len(got) != 1 || got[0] != "ru" {
t.Errorf("session supported_languages = %v, want [ru]", got) t.Errorf("session supported_languages = %v, want [ru]", got)
} }
// The bot's service language rides the Session so the UI can build a share link to
// the same bot.
if got := string(sess.ServiceLanguage()); got != "ru" {
t.Errorf("session service_language = %q, want ru", got)
}
// The validated launch fields are forwarded so the backend can seed a new account; // The validated launch fields are forwarded so the backend can seed a new account;
// service_language is recorded to route the account's later out-of-app push. // service_language is recorded to route the account's later out-of-app push.
if gotBody["external_id"] != "42" || gotBody["language_code"] != "ru" || gotBody["first_name"] != "Иван" || gotBody["service_language"] != "ru" { if gotBody["external_id"] != "42" || gotBody["language_code"] != "ru" || gotBody["first_name"] != "Иван" || gotBody["service_language"] != "ru" {
+4
View File
@@ -123,6 +123,10 @@ table Session {
is_guest:bool; is_guest:bool;
display_name:string; display_name:string;
supported_languages:[string]; supported_languages:[string];
// service_language is the language tag (en/ru) of the Telegram bot the session was
// minted through; empty for a non-Telegram login. The UI uses it to build a share
// link that points at the same bot.
service_language:string;
} }
// Ack is a simple success acknowledgement (e.g. an email-code request). // Ack is a simple success acknowledgement (e.g. an email-code request).
+12 -1
View File
@@ -94,8 +94,16 @@ func (rcv *Session) SupportedLanguagesLength() int {
return 0 return 0
} }
func (rcv *Session) ServiceLanguage() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(14))
if o != 0 {
return rcv._tab.ByteVector(o + rcv._tab.Pos)
}
return nil
}
func SessionStart(builder *flatbuffers.Builder) { func SessionStart(builder *flatbuffers.Builder) {
builder.StartObject(5) builder.StartObject(6)
} }
func SessionAddToken(builder *flatbuffers.Builder, token flatbuffers.UOffsetT) { func SessionAddToken(builder *flatbuffers.Builder, token flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(token), 0) builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(token), 0)
@@ -115,6 +123,9 @@ func SessionAddSupportedLanguages(builder *flatbuffers.Builder, supportedLanguag
func SessionStartSupportedLanguagesVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { func SessionStartSupportedLanguagesVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT {
return builder.StartVector(4, numElems, 4) return builder.StartVector(4, numElems, 4)
} }
func SessionAddServiceLanguage(builder *flatbuffers.Builder, serviceLanguage flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(5, flatbuffers.UOffsetT(serviceLanguage), 0)
}
func SessionEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { func SessionEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
return builder.EndObject() return builder.EndObject()
} }
+14 -2
View File
@@ -58,8 +58,15 @@ supportedLanguagesLength():number {
return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0; return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0;
} }
serviceLanguage():string|null
serviceLanguage(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
serviceLanguage(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 14);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startSession(builder:flatbuffers.Builder) { static startSession(builder:flatbuffers.Builder) {
builder.startObject(5); builder.startObject(6);
} }
static addToken(builder:flatbuffers.Builder, tokenOffset:flatbuffers.Offset) { static addToken(builder:flatbuffers.Builder, tokenOffset:flatbuffers.Offset) {
@@ -94,18 +101,23 @@ static startSupportedLanguagesVector(builder:flatbuffers.Builder, numElems:numbe
builder.startVector(4, numElems, 4); builder.startVector(4, numElems, 4);
} }
static addServiceLanguage(builder:flatbuffers.Builder, serviceLanguageOffset:flatbuffers.Offset) {
builder.addFieldOffset(5, serviceLanguageOffset, 0);
}
static endSession(builder:flatbuffers.Builder):flatbuffers.Offset { static endSession(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject(); const offset = builder.endObject();
return offset; return offset;
} }
static createSession(builder:flatbuffers.Builder, tokenOffset:flatbuffers.Offset, userIdOffset:flatbuffers.Offset, isGuest:boolean, displayNameOffset:flatbuffers.Offset, supportedLanguagesOffset:flatbuffers.Offset):flatbuffers.Offset { static createSession(builder:flatbuffers.Builder, tokenOffset:flatbuffers.Offset, userIdOffset:flatbuffers.Offset, isGuest:boolean, displayNameOffset:flatbuffers.Offset, supportedLanguagesOffset:flatbuffers.Offset, serviceLanguageOffset:flatbuffers.Offset):flatbuffers.Offset {
Session.startSession(builder); Session.startSession(builder);
Session.addToken(builder, tokenOffset); Session.addToken(builder, tokenOffset);
Session.addUserId(builder, userIdOffset); Session.addUserId(builder, userIdOffset);
Session.addIsGuest(builder, isGuest); Session.addIsGuest(builder, isGuest);
Session.addDisplayName(builder, displayNameOffset); Session.addDisplayName(builder, displayNameOffset);
Session.addSupportedLanguages(builder, supportedLanguagesOffset); Session.addSupportedLanguages(builder, supportedLanguagesOffset);
Session.addServiceLanguage(builder, serviceLanguageOffset);
return Session.endSession(builder); return Session.endSession(builder);
} }
} }
+2 -1
View File
@@ -171,6 +171,7 @@ describe('codec', () => {
isGuest: true, isGuest: true,
displayName: 'Me', displayName: 'Me',
supportedLanguages: ['en', 'ru'], supportedLanguages: ['en', 'ru'],
serviceLanguage: '',
}); });
}); });
@@ -311,7 +312,7 @@ describe('codec', () => {
b.finish(fb.LinkResult.endLinkResult(b)); b.finish(fb.LinkResult.endLinkResult(b));
const r = decodeLinkResult(b.asUint8Array()); const r = decodeLinkResult(b.asUint8Array());
expect(r.status).toBe('merged'); expect(r.status).toBe('merged');
expect(r.session).toEqual({ token: 'tok-9', userId: 'a-1', isGuest: false, displayName: 'Kaya', supportedLanguages: [] }); expect(r.session).toEqual({ token: 'tok-9', userId: 'a-1', isGuest: false, displayName: 'Kaya', supportedLanguages: [], serviceLanguage: '' });
}); });
it('decodes an Invitation with inviter and invitees', () => { it('decodes an Invitation with inviter and invitees', () => {
+1
View File
@@ -292,6 +292,7 @@ function sessionFromTable(t: fb.Session): Session {
isGuest: t.isGuest(), isGuest: t.isGuest(),
displayName: s(t.displayName()), displayName: s(t.displayName()),
supportedLanguages, supportedLanguages,
serviceLanguage: s(t.serviceLanguage()),
}; };
} }
+1
View File
@@ -25,6 +25,7 @@ export const SESSION: Session = {
displayName: 'You', displayName: 'You',
// Both languages by default, so the mock-driven UI offers every variant. // Both languages by default, so the mock-driven UI offers every variant.
supportedLanguages: ['en', 'ru'], supportedLanguages: ['en', 'ru'],
serviceLanguage: '',
}; };
export const PROFILE: Profile = { export const PROFILE: Profile = {
+3
View File
@@ -234,6 +234,9 @@ export interface Session {
// variants these languages support (en -> English; ru -> Russian + Эрудит). Empty // variants these languages support (en -> English; ru -> Russian + Эрудит). Empty
// means ungated (all variants). // means ungated (all variants).
supportedLanguages: string[]; supportedLanguages: string[];
// serviceLanguage is the en/ru tag of the Telegram bot this session was minted
// through (empty for a non-Telegram login); used to share a link to the same bot.
serviceLanguage: string;
} }
// LinkResult is the outcome of an account link/merge step. status is // LinkResult is the outcome of an account link/merge step. status is