Steven
Steven5 min läsning

En kodbas, två appar: Så white-labelar du utan att forka

GeekBye och Pavleur är två olika varumärkta skrivbordsappar byggda från ett enda repository — ingen fork, ingen dubblerad kodbas. Här är maskineriet vid byggtid som får en kodbas att kompilera till två produkter, och den enradsbugg som fick vår andra app att presentera sig med fel namn.

Utveckling
Arkitektur
Bygge
GeekBye-releaser
En kodbas, två appar: Så white-labelar du utan att forka

GeekBye är en mötesantecknare. Pavleur är en AI-app för intervjuträning. De ser ut som två olika produkter för att de är två olika produkter — men de är byggda från en kodbas, utan fork och utan branch som driver iväg. Det här är ingenjörssidan av white-labeling: inte "varför sälja en varumärkt app" (det är ett annat inlägg), utan hur du faktiskt kompilerar ett repository till två appar — och den oglamorösa bugg som gömmer sig i varje white-label-system tills du börjar leta.

Generera, förgrena inte

Fel sätt att köra två produkter från en kodbas är att strö if (product === 'pavleur') genom hela appen, eller att forka och be att de två kopiorna aldrig divergerar. Båda ruttnar snabbt.

GeekByes ansats är kodgenerering vid byggtid. Varje produkt har en config-fil — configs/geekbye/config.ts, configs/pavleur/config.ts — som håller dess identitet: namn, bundle-id, URL-protokoll, releases-repositoryt för autouppdateringar, och så vidare. Före ett bygge körs ett skript:

node scripts/build-product.js pavleur

Det skriptet läser den valda produktens config och gör tre konkreta saker:

  1. Genererar två config-moduler — en för renderaren (src/config/product.ts), en för huvudprocessen (electron/config/product.ts) — var och en stämplad med ett "DO NOT EDIT MANUALLY — changes will be overwritten"-sidhuvud. Resten av appen importerar bara productName, applicationId och protocol från config och tänker aldrig på vilket varumärke den är.
  2. Kopierar bara det varumärkets tillgångar — ikoner, den lilla logotypen, ikonen för toastnotiser. Ett GeekBye-bygge innehåller fysiskt noll Pavleur-grafik och tvärtom. Inget att läcka.
  3. Skriver om package.json (vid release-byggen) — app-id, produktnamn, URL-protokoll, GitHub-releases-repot att publicera till, och, snyggt nog, den varumärkta texten för mikrofonbehörighet så att macOS frågar "Pavleur vill få tillgång till mikrofonen", inte fel varumärke.

Hela appen är varumärkesagnostisk; varumärket injiceras vid byggtid. Det finns ingen produktväxel vid körning att göra fel, för vid körning finns bara en produkt inbakad.

En backend, två varumärken

Båda apparna pratar med samma backend. Så hur vet servern vilken produkt — och vilken prissättning, vilka prompter och gränser — en förfrågan tillhör?

Två signaler, båda satta vid byggtid. Först ett applikations-id (geekbye eller pavleur) inbakat i config och skickat med förfrågningar, så att backend kan dirigera till rätt produkts beteende. Sedan en User-Agent som klienten fäster vid varje backend-anrop, med formen Product/version (platform) — t.ex. Pavleur/1.2.5 (win32) eller GeekBye/1.7.3 (darwin). Det enda sidhuvudet ger backend attribution per produkt och per OS gratis, utan ett separat analysfält. Autouppdateringar hålls samtidigt isolerade: varje produkt publicerar till sitt eget releases-repository, så en GeekBye-uppdatering kan aldrig serveras till en Pavleur-installation.

Introduktionen skiljer sig också per produkt — GeekByes guide handlar om mötestranskribering, Pavleurs om intervjuträning — och den rätta väljs vid körning utifrån det enda inbakade applikations-id:t. Samma maskineri, två ytterdörrar.

Buggen: när Pavleur kallade sig GeekBye

Här är krigshistorien, och det är den som varje white-label-system har en version av.

Config-genereringen fungerade. Ikonerna byttes. Mikrofonfrågan var varumärkt. Protokollen och bundle-id:na var rätt. Med varje synligt mått var ett Pavleur-bygge Pavleur. Och ändå, ett tag, ringde varje Pavleur-bygge hem till backend och kallade sig GeekBye.

Boven var en enda hårdkodad sträng, och den gömde sig på den minst glamorösa plats man kan tänka sig: User-Agent-byggaren, instoppad i en retry-hjälpare för nätverk. Allt användarvänt hade dirigerats via config, men just den här djupa rörmokeri-utiliteten hade fortfarande en literal `GeekBye/${version}` i sig. Ingen tänker på en retry-utilitet som "varumärke", så ingen hade kontrollerat den. Fixen var fyra rader — importera det dynamiska productName från config och interpolera det i stället för det hårdkodade varumärket — men lärdomen är hela poängen:

White-labeling är inte "bytte logotypen." Det är "går varje identitetssträng via config." Och de sista kvarhållarna bor aldrig i gränssnittet, där du skulle titta. De bor i User-Agent, i hanteraren för deep-link-protokollet, i texten för behörighetsfrågan, i felmeddelandena — rörmokeriet som ingen sorterar under "varumärke". Det enda pålitliga försvaret är att behandla varumärkessträngen som något du aktivt jagar: greppa kodbasen efter det hårdkodade namnet och låt bygget misslyckas om det dyker upp någonstans utanför den config som ska definiera det.

Tre saker white-labeling lärde oss

  1. Generera, förgrena inte. Kodgenerering vid byggtid av två pyttesmå config-moduler plus selektiv tillgångskopiering slår både forkning och en snårskog av produktkontroller vid körning. Appen förblir varumärkesagnostisk; varumärket är en bygginmatning.
  2. Identitetssträngar läcker in i rörmokeriet. Gränssnittet är de enkla 90 procenten. De svåra 10 procenten är User-Agent, URL-protokollet, mikrofonbehörighetssträngen, feltexten. Dirigera dem alla via en enda productName / applicationId-export — och greppa efter det råa varumärkesnamnet som en CI-grind så att en vilsen literal aldrig kan skeppas.
  3. En backend, många varumärken, särskilj med id och sidhuvud. Ett applikations-id vid byggtid plus en Product/version (platform) User-Agent ger attribution per produkt och per plattform med noll dubblerad infrastruktur — och separerar rent den oföränderliga byggtidsidentiteten från körningsbeteendet.

För produkt- och integritetssidan av att köra en white-label-antecknare under ditt eget varumärke, se white-label-AI-antecknaren. Det här är det sjätte kapitlet i v1-historien som blir GeekBye v2 — för det föregående kapitlet, vad en release på 127 commits faktiskt är (v1.7.0); och för hela bågen, anatomin av att skeppa mjukvara till perfektion.