branding: make product identity runtime-configurable
Adopt upstream owpengram/owpengram-server's branding.Config/Configure/ Current in place of the old package-level constants. This is the piece the earlier merge attempt was blocked on (internal/branding failed to import during that merge). The default identity is unchanged -- every existing ProductName/ProductUsername/... default still reads "OwpenGram" -- so this is a pure capability add: nothing currently calls Configure, and every call site now reads the current snapshot via a function instead of a compile-time constant. Five string templates that concatenated branding.ProductName into a `const` had to become `var`, since a func call is no longer a valid const operand. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
7fef8438c5
commit
6649b70d5e
15 changed files with 224 additions and 51 deletions
|
|
@ -6,44 +6,133 @@
|
|||
package branding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"unicode"
|
||||
|
||||
"telesrv/internal/links"
|
||||
)
|
||||
|
||||
const (
|
||||
ProductName = "OwpenGram"
|
||||
ProductUsername = "owpengram"
|
||||
DesktopAppName = "OwpenGram Desktop"
|
||||
AndroidAppName = "OwpenGram Android"
|
||||
IOSAppName = "OwpenGram iOS"
|
||||
MacOSAppName = "OwpenGram macOS"
|
||||
WebAAppName = "OwpenGram Web A"
|
||||
WebKAppName = "OwpenGram Web K"
|
||||
PremiumName = "OwpenGram Premium"
|
||||
StarsName = "OwpenGram Stars"
|
||||
DefaultPublicURL = "https://owpengram.org"
|
||||
// Config is the deployment-wide, user-visible product identity. It is loaded
|
||||
// once during process startup; protocol identifiers and client detection
|
||||
// tokens deliberately remain outside this structure.
|
||||
type Config struct {
|
||||
ProductName string
|
||||
ProductUsername string
|
||||
DesktopAppName string
|
||||
AndroidAppName string
|
||||
IOSAppName string
|
||||
MacOSAppName string
|
||||
WebAAppName string
|
||||
WebKAppName string
|
||||
PremiumName string
|
||||
StarsName string
|
||||
PublicBaseURL string
|
||||
}
|
||||
|
||||
var (
|
||||
defaultConfig = Config{
|
||||
ProductName: "OwpenGram",
|
||||
ProductUsername: "owpengram",
|
||||
DesktopAppName: "OwpenGram Desktop",
|
||||
AndroidAppName: "OwpenGram Android",
|
||||
IOSAppName: "OwpenGram iOS",
|
||||
MacOSAppName: "OwpenGram macOS",
|
||||
WebAAppName: "OwpenGram Web A",
|
||||
WebKAppName: "OwpenGram Web K",
|
||||
PremiumName: "OwpenGram Premium",
|
||||
StarsName: "OwpenGram Stars",
|
||||
PublicBaseURL: links.DefaultDownloadURL,
|
||||
}
|
||||
configured atomic.Pointer[Config]
|
||||
)
|
||||
|
||||
// DefaultConfig returns a copy of the default product identity.
|
||||
func DefaultConfig() Config { return defaultConfig }
|
||||
|
||||
// Validate normalizes and validates a product identity without installing it.
|
||||
func Validate(cfg Config) (Config, error) {
|
||||
for _, field := range []struct {
|
||||
name string
|
||||
value *string
|
||||
}{
|
||||
{name: "product name", value: &cfg.ProductName},
|
||||
{name: "desktop app name", value: &cfg.DesktopAppName},
|
||||
{name: "Android app name", value: &cfg.AndroidAppName},
|
||||
{name: "iOS app name", value: &cfg.IOSAppName},
|
||||
{name: "macOS app name", value: &cfg.MacOSAppName},
|
||||
{name: "Web A app name", value: &cfg.WebAAppName},
|
||||
{name: "Web K app name", value: &cfg.WebKAppName},
|
||||
{name: "Premium name", value: &cfg.PremiumName},
|
||||
{name: "Stars name", value: &cfg.StarsName},
|
||||
} {
|
||||
normalized, err := validateDisplayName(*field.value)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("%s: %w", field.name, err)
|
||||
}
|
||||
*field.value = normalized
|
||||
}
|
||||
cfg.ProductUsername = strings.TrimPrefix(strings.TrimSpace(cfg.ProductUsername), "@")
|
||||
if !validProductUsername(cfg.ProductUsername) {
|
||||
return Config{}, fmt.Errorf("product username must be 5-32 ASCII username characters and start with a letter")
|
||||
}
|
||||
cfg.ProductUsername = strings.ToLower(cfg.ProductUsername)
|
||||
var err error
|
||||
cfg.PublicBaseURL, err = links.ValidateBaseURL(cfg.PublicBaseURL)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("public base URL: %w", err)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Configure installs the validated process-wide identity before services are
|
||||
// constructed. Readers only ever observe complete immutable snapshots.
|
||||
func Configure(cfg Config) error {
|
||||
normalized, err := Validate(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configured.Store(&normalized)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Current returns a copy of the installed product identity.
|
||||
func Current() Config {
|
||||
if cfg := configured.Load(); cfg != nil {
|
||||
return *cfg
|
||||
}
|
||||
return defaultConfig
|
||||
}
|
||||
|
||||
func ProductName() string { return Current().ProductName }
|
||||
func ProductUsername() string { return Current().ProductUsername }
|
||||
func PremiumName() string { return Current().PremiumName }
|
||||
func StarsName() string { return Current().StarsName }
|
||||
func PublicBaseURL() string { return Current().PublicBaseURL }
|
||||
|
||||
// ClientAppName returns the branded display name for a stored client platform.
|
||||
// Stored detection tokens remain unchanged; this is only used at presentation
|
||||
// boundaries such as account.getAuthorizations.
|
||||
func ClientAppName(platform string) string {
|
||||
cfg := Current()
|
||||
switch strings.ToLower(strings.TrimSpace(platform)) {
|
||||
case "android":
|
||||
return AndroidAppName
|
||||
return cfg.AndroidAppName
|
||||
case "ios":
|
||||
return IOSAppName
|
||||
return cfg.IOSAppName
|
||||
case "macos":
|
||||
return MacOSAppName
|
||||
return cfg.MacOSAppName
|
||||
case "telegram-tt", "weba":
|
||||
return WebAAppName
|
||||
return cfg.WebAAppName
|
||||
case "tweb", "webk":
|
||||
return WebKAppName
|
||||
return cfg.WebKAppName
|
||||
case "tdesktop", "desktop", "windows":
|
||||
return DesktopAppName
|
||||
return cfg.DesktopAppName
|
||||
default:
|
||||
return ProductName
|
||||
return cfg.ProductName
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,18 +167,49 @@ func UserVisibleText(value, publicBaseURL string) string {
|
|||
if technicalIDRE.MatchString(value) {
|
||||
return value
|
||||
}
|
||||
return officialBrandRE.ReplaceAllString(value, ProductName)
|
||||
return officialBrandRE.ReplaceAllString(value, ProductName())
|
||||
}
|
||||
|
||||
func publicDestination(raw string) (string, string) {
|
||||
raw = strings.TrimRight(strings.TrimSpace(raw), "/")
|
||||
if raw == "" {
|
||||
raw = DefaultPublicURL
|
||||
raw = PublicBaseURL()
|
||||
}
|
||||
parsed, err := url.Parse(raw)
|
||||
if err != nil || parsed.Scheme == "" || parsed.Hostname() == "" {
|
||||
raw = DefaultPublicURL
|
||||
raw = PublicBaseURL()
|
||||
parsed, _ = url.Parse(raw)
|
||||
}
|
||||
return raw, parsed.Host
|
||||
}
|
||||
|
||||
func validateDisplayName(raw string) (string, error) {
|
||||
name := strings.TrimSpace(raw)
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("must not be empty")
|
||||
}
|
||||
if len([]rune(name)) > 64 {
|
||||
return "", fmt.Errorf("must not exceed 64 characters")
|
||||
}
|
||||
for _, r := range name {
|
||||
if unicode.IsControl(r) {
|
||||
return "", fmt.Errorf("must not contain control characters")
|
||||
}
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func validProductUsername(username string) bool {
|
||||
if len(username) < 5 || len(username) > 32 {
|
||||
return false
|
||||
}
|
||||
for i, r := range username {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z':
|
||||
case i > 0 && (r >= '0' && r <= '9' || r == '_'):
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,13 +46,14 @@ func TestUserVisibleTextRebrandsLocalizedProductNames(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClientPresentationNames(t *testing.T) {
|
||||
cfg := Current()
|
||||
for platform, want := range map[string]string{
|
||||
"tdesktop": DesktopAppName,
|
||||
"android": AndroidAppName,
|
||||
"ios": IOSAppName,
|
||||
"macos": MacOSAppName,
|
||||
"telegram-tt": WebAAppName,
|
||||
"tweb": WebKAppName,
|
||||
"tdesktop": cfg.DesktopAppName,
|
||||
"android": cfg.AndroidAppName,
|
||||
"ios": cfg.IOSAppName,
|
||||
"macos": cfg.MacOSAppName,
|
||||
"telegram-tt": cfg.WebAAppName,
|
||||
"tweb": cfg.WebKAppName,
|
||||
} {
|
||||
if got := ClientAppName(platform); got != want {
|
||||
t.Fatalf("ClientAppName(%q) = %q, want %q", platform, got, want)
|
||||
|
|
@ -62,3 +63,55 @@ func TestClientPresentationNames(t *testing.T) {
|
|||
t.Fatalf("UserVisibleClientPlatform() = %q, want weba", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigureInstallsCompleteBrandSnapshot(t *testing.T) {
|
||||
previous := Current()
|
||||
t.Cleanup(func() {
|
||||
if err := Configure(previous); err != nil {
|
||||
t.Fatalf("restore branding: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
cfg := Config{
|
||||
ProductName: "Example Chat",
|
||||
ProductUsername: "@Example_Chat",
|
||||
DesktopAppName: "Example Workstation",
|
||||
AndroidAppName: "Example Droid",
|
||||
IOSAppName: "Example Phone",
|
||||
MacOSAppName: "Example Mac",
|
||||
WebAAppName: "Example Web Alpha",
|
||||
WebKAppName: "Example Web Kappa",
|
||||
PremiumName: "Example Plus",
|
||||
StarsName: "Example Credits",
|
||||
PublicBaseURL: "https://links.example.test/root/",
|
||||
}
|
||||
if err := Configure(cfg); err != nil {
|
||||
t.Fatalf("Configure: %v", err)
|
||||
}
|
||||
if got := Current(); got.ProductUsername != "example_chat" || got.PublicBaseURL != "https://links.example.test/root" {
|
||||
t.Fatalf("Current() = %+v", got)
|
||||
}
|
||||
if got := ClientAppName("android"); got != "Example Droid" {
|
||||
t.Fatalf("ClientAppName(android) = %q", got)
|
||||
}
|
||||
if got := UserVisibleText("Telegram at t.me/example", ""); got != "Example Chat at links.example.test/example" {
|
||||
t.Fatalf("UserVisibleText() = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRejectsIncompleteOrUnsafeBranding(t *testing.T) {
|
||||
for name, mutate := range map[string]func(*Config){
|
||||
"blank product": func(cfg *Config) { cfg.ProductName = " " },
|
||||
"control": func(cfg *Config) { cfg.StarsName = "bad\nname" },
|
||||
"username": func(cfg *Config) { cfg.ProductUsername = "3bad" },
|
||||
"public URL": func(cfg *Config) { cfg.PublicBaseURL = "file:///tmp/brand" },
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
mutate(&cfg)
|
||||
if _, err := Validate(cfg); err == nil {
|
||||
t.Fatal("Validate accepted invalid branding")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue