Steven
Steven5 мин четене

Една кодова база, две приложения: как да направиш white-label без fork

GeekBye и Pavleur са две различно брандирани десктоп приложения, изградени от едно-единствено хранилище — без fork, без дублирана кодова база. Ето машинарията по време на build, която компилира една кодова база в два продукта, и бъгът от един ред, заради който второто ни приложение се представяше с грешното име.

Инженерство
Архитектура
Build
Издания на GeekBye
Една кодова база, две приложения: как да направиш white-label без fork

GeekBye е бележник за срещи. Pavleur е AI приложение за упражняване на интервюта. Изглеждат като два различни продукта, защото са два различни продукта — но са изградени от една кодова база, без fork и без клон, който бавно се разминава. Това е инженерната страна на white-label подхода: не „защо да продаваш брандирано приложение" (това е друг пост), а как всъщност компилираш едно хранилище в две приложения — и непривлекателният бъг, който се крие във всяка white-label система, докато не тръгнеш да го търсиш.

Генерирай, не разклонявай

Грешният начин да караш два продукта от една кодова база е да поръсиш if (product === 'pavleur') из цялото приложение или да направиш fork и да се молиш двете копия никога да не се разминат. И двете загниват бързо.

Подходът на GeekBye е генериране на код по време на build. Всеки продукт има конфигурационен файл — configs/geekbye/config.ts, configs/pavleur/config.ts — съдържащ идентичността му: име, bundle id, URL протокол, хранилището с издания за автоматични обновявания и така нататък. Преди build се изпълнява един скрипт:

node scripts/build-product.js pavleur

Този скрипт чете конфигурацията на избрания продукт и прави три конкретни неща:

  1. Генерира два конфигурационни модула — един за renderer-а (src/config/product.ts) и един за main процеса (electron/config/product.ts) — всеки подпечатан с хедър „НЕ РЕДАКТИРАЙ РЪЧНО — промените ще бъдат презаписани". Останалата част от приложението просто импортира productName, applicationId и protocol от конфигурацията и никога не мисли кой бранд е.
  2. Копира само ресурсите на този бранд — иконите, малкото лого, иконата за toast известия. Един GeekBye build физически не съдържа нито един пиксел Pavleur графика и обратно. Няма какво да изтече.
  3. Пренаписва package.json (при release build-ове) — app id-то, името на продукта, URL протокола, GitHub хранилището с издания, в което се публикува, и, което е приятно, брандирания текст за разрешение за микрофона, така че macOS да пита „Pavleur иска достъп до микрофона", а не грешния бранд.

Цялото приложение е агностично към бранда; брандът се инжектира по време на build. Няма runtime превключвател на продукти, който да сгрешиш, защото по време на изпълнение винаги има само един вграден продукт.

Един backend, два бранда

Двете приложения говорят с един и същ backend. Как тогава сървърът знае към кой продукт — и към кои цени, промптове и лимити — принадлежи една заявка?

Два сигнала, и двата зададени по време на build. Първо, application id (geekbye или pavleur), вграден в конфигурацията и изпращан със заявките, така че backend-ът да може да насочи към поведението на правилния продукт. Второ, User-Agent, който клиентът прикача към всяко обръщение към backend-а, от вида Product/version (platform) — напр. Pavleur/1.2.5 (win32) или GeekBye/1.7.3 (darwin). Този един хедър дава на backend-а атрибуция по продукт и по операционна система безплатно, без отделно поле за аналитика. Автоматичните обновявания междувременно остават изолирани: всеки продукт публикува в собственото си хранилище с издания, така че обновяване на GeekBye никога не може да бъде сервирано на инсталация на Pavleur.

Онбордингът също се различава по продукт — уизардът на GeekBye е за транскрипция на срещи, този на Pavleur — за упражняване на интервюта — и правилният се избира по време на изпълнение от единствения вграден application id. Една и съща машинария, две входни врати.

Бъгът: когато Pavleur се нарече GeekBye

Ето историята от фронта — и това е историята, чиято версия има всяка white-label система.

Генерирането на конфигурация работеше. Иконите се сменяха. Подканата за микрофона беше брандирана. Протоколите и bundle id-тата бяха правилни. По всяка видима мярка един Pavleur build беше Pavleur. И въпреки това, за известно време, всеки Pavleur build се обаждаше на backend-а, наричайки се GeekBye.

Виновникът беше един-единствен хардкоднат низ, скрит на най-непривлекателното място, което можеш да си представиш: строителя на User-Agent, сгушен в мрежов retry помощник. Всичко видимо за потребителя беше прекарано през конфигурацията, но тази една дълбоко заровена инфраструктурна помощна функция все още имаше литерал `GeekBye/${version}` в себе си. Никой не мисли за retry функция като за „брандиране", затова никой не я беше проверил. Поправката беше четири реда — импортирай динамичното productName от конфигурацията и интерполирай него вместо хардкоднатия бранд — но поуката е цялата идея:

White-label не е „смени ли се логото". А „минава ли всеки низ за идентичност през конфигурацията". И последните упорити остатъци никога не живеят в UI-я, където би погледнал. Живеят в User-Agent, в обработчика на deep-link протокола, в текста на подканата за разрешения, в съобщенията за грешки — в тръбопровода, който никой не картотекира като „бранд". Единствената надеждна защита е да третираш бранд литерала като нещо, което активно преследваш: grep-ни кодовата база за хардкоднатото име и проваляй build-а, ако се появи където и да е извън конфигурацията, която трябва да го дефинира.

Три неща, на които ни научи white-label подходът

  1. Генерирай, не разклонявай. Codegen по време на build на два мънички конфигурационни модула плюс селективно копиране на ресурси бие и fork-а, и runtime гъсталака от продуктови проверки. Приложението остава агностично към бранда; брандът е входен параметър на build-а.
  2. Низовете за идентичност изтичат в тръбопровода. UI-ят е лесните 90%. Трудните 10% са User-Agent, URL протоколът, низът за разрешение за микрофона, текстовете на грешките. Прекарай ги всичките през един export на productName / applicationId — и grep-вай за суровото име на бранда като CI бариера, така че заблуден литерал никога да не може да стигне до потребителите.
  3. Един backend, много брандове, различавай по id и хедър. Application id от build време плюс User-Agent от вида Product/version (platform) дава атрибуция по продукт и по платформа с нула дублирана инфраструктура — чисто разделяйки неизменната идентичност от build време от поведението по време на изпълнение.

За продуктово-приватната страна на това да караш white-label бележник под собствения си бранд виж white-label AI бележника. Това е шестата глава от историята на v1, която се превръща в GeekBye v2 — за предишната глава, какво всъщност представлява едно издание със 127 комита (v1.7.0); а за цялата дъга, анатомията на доставянето на софтуер до съвършенство.

Свързани статии

Какво всъщност представлява едно издание със 127 комита
Steven
Steven6 мин четене

Какво всъщност представлява едно издание със 127 комита

GeekBye v1.7.0 бяха 127 комита за единайсет дни. Отвън това изглежда като сто дребни неща. Отвътре бяха две големи функции, преплетени една в друга — и едната от тях беше изградена на грешното място, после изтръгната и построена наново по средата на изданието. Ето анатомията на едно голямо издание.

Инженерство
Издание
Архитектура
Изтрихме 5000 реда аудио код — и транскриптите започнаха да се появяват по два пъти
Steven
Steven6 мин четене

Изтрихме 5000 реда аудио код — и транскриптите започнаха да се появяват по два пъти

GeekBye v1.6 изкорени два транскрибера на Swift, работещи на самото устройство, и ги замени с един унифициран пайплайн — нетно изтриване на над 5000 реда, направено докато хора бяха насред срещи в приложението. После транскриптите започнаха да се появяват по два пъти, защото грешката се премести в единствения слой, който още смяташе, че двигателите са два.

Инженеринг
Транскрипция
Архитектура
Проверката за безопасност, която направи приложението ни невъзможно за спиране
Steven
Steven6 мин четене

Проверката за безопасност, която направи приложението ни невъзможно за спиране

Автоматичното обновяване беше най-трудната функция, която някога сме пускали — шест издания за четири дни, за да спрем то да убива приложението. Най-лошата грешка беше тази, която сами въведохме, опитвайки се да сме внимателни: 500-милисекундна "предпазна" проверка, която превърна неуспешно обновяване в процес, който буквално не можеше да бъде спрян.

Инженерство
Electron
Надеждност