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.

Relaterade artiklar

Att ta backend ur uppladdningsvägen
Steven
Steven8 min läsning

Att ta backend ur uppladdningsvägen

GeekBye spelar in din skärm och sparar videon till din Google Drive. Den första versionen skickade varje inspelning genom GeekByes egna servrar på vägen dit; en release senare gick filen rakt från din maskin till Drive, och backend degraderades till att hålla en enda pekare. Den intressanta delen är hur lite kod den 'direkta, återupptagbara' versionen faktiskt innehåller — eftersom återupptagbarheten kom från att radera en proxy, inte från att skriva en.

Utveckling
Arkitektur
Desktop
En CSS-variabel, fem granskningsrundor och en Swift-verktygskedja som ljög
Steven
Steven6 min läsning

En CSS-variabel, fem granskningsrundor och en Swift-verktygskedja som ljög

GeekBye v2.0.7 gjorde hela overlayn justerbart genomskinlig — vilket låter som en enda rad CSS men absolut inte var det. Den verkliga historien är vad kodgranskningen fångade: två ytor som vägrade tona bort, en inspelningsrad som såg fel ut vid samma opacitet, och en kompilerad binär som fladdrade 672 byte fram och tillbaka eftersom vår egen dokumentation gav en granskare fel Swift-version.

Utveckling
Design
Bygge
Vi raderade 5 000 rader ljudkod — och transkript började dyka upp två gånger
Steven
Steven6 min läsning

Vi raderade 5 000 rader ljudkod — och transkript började dyka upp två gånger

GeekBye v1.6 slet ut två Swift-transkriberare på enheten och ersatte dem med en enda enhetlig pipeline — en nettoradering på över 5 000 rader, gjord medan folk var mitt i ett möte i appen. Sedan började transkript dyka upp två gånger, eftersom buggen flyttade till det enda lager som fortfarande trodde att det fanns två motorer.

Utveckling
Transkribering
Arkitektur