Steven
Steven6 λεπτά ανάγνωσης

Ένα Codebase, Δύο Εφαρμογές: Πώς να Κάνεις White-Label Χωρίς Fork

Το GeekBye και το Pavleur είναι δύο desktop εφαρμογές με διαφορετικό branding, χτισμένες από ένα και μόνο repository — χωρίς fork, χωρίς διπλασιασμένο codebase. Ιδού ο μηχανισμός χρόνου build που κάνει ένα codebase να μεταγλωττίζεται σε δύο προϊόντα, και το σφάλμα μίας γραμμής που έκανε τη δεύτερη εφαρμογή μας να συστήνεται με λάθος όνομα.

Μηχανική
Αρχιτεκτονική
Build
Εκδόσεις GeekBye
Ένα Codebase, Δύο Εφαρμογές: Πώς να Κάνεις White-Label Χωρίς Fork

Το GeekBye είναι μια εφαρμογή σημειώσεων συναντήσεων. Το Pavleur είναι μια εφαρμογή εξάσκησης συνεντεύξεων με AI. Μοιάζουν με δύο διαφορετικά προϊόντα επειδή είναι δύο διαφορετικά προϊόντα — αλλά χτίζονται από ένα codebase, χωρίς fork και χωρίς branch που ξεστρατίζει. Αυτή είναι η μηχανική πλευρά του white-labeling: όχι το «γιατί να πουλήσεις μια branded εφαρμογή» (αυτό είναι μια άλλη ανάρτηση), αλλά το πώς πραγματικά μεταγλωττίζεις ένα repository σε δύο εφαρμογές — και το άχαρο σφάλμα που κρύβεται σε κάθε white-label σύστημα μέχρι να πας να το ψάξεις.

Παράγε, μην κάνεις branch

Ο λάθος τρόπος να τρέχεις δύο προϊόντα από ένα codebase είναι να πασπαλίσεις if (product === 'pavleur') σε όλη την εφαρμογή, ή να κάνεις fork και να προσεύχεσαι τα δύο αντίγραφα να μην αποκλίνουν ποτέ. Και τα δύο σαπίζουν γρήγορα.

Η προσέγγιση του GeekBye είναι η παραγωγή κώδικα σε χρόνο build. Κάθε προϊόν έχει ένα αρχείο ρυθμίσεων — configs/geekbye/config.ts, configs/pavleur/config.ts — που κρατά την ταυτότητά του: όνομα, bundle id, URL protocol, το repository εκδόσεων για τις αυτόματες ενημερώσεις, και ούτω καθεξής. Πριν από ένα build, τρέχει ένα script:

node scripts/build-product.js pavleur

Αυτό το script διαβάζει τη ρύθμιση του επιλεγμένου προϊόντος και κάνει τρία συγκεκριμένα πράγματα:

  1. Παράγει δύο config modules — ένα για τον renderer (src/config/product.ts), ένα για το main process (electron/config/product.ts) — το καθένα σφραγισμένο με μια κεφαλίδα «ΜΗΝ ΕΠΕΞΕΡΓΑΖΕΣΤΕ ΧΕΙΡΟΚΙΝΗΤΑ — οι αλλαγές θα αντικατασταθούν». Η υπόλοιπη εφαρμογή απλώς κάνει import τα productName, applicationId και protocol από τη ρύθμιση και δεν σκέφτεται ποτέ ποιο brand είναι.
  2. Αντιγράφει μόνο τα assets του συγκεκριμένου brand — τα εικονίδια, το μικρό λογότυπο, το εικονίδιο των toast ειδοποιήσεων. Ένα build του GeekBye δεν περιέχει φυσικά ούτε ένα εικαστικό του Pavleur, και το αντίστροφο. Δεν υπάρχει τίποτα να διαρρεύσει.
  3. Ξαναγράφει το package.json (στα release builds) — το app id, το όνομα προϊόντος, το URL protocol, το GitHub repository εκδόσεων στο οποίο δημοσιεύει, και, το ωραίο, το branded κείμενο άδειας μικροφώνου, ώστε το macOS να ρωτά «Το Pavleur θα ήθελε πρόσβαση στο μικρόφωνο», όχι το λάθος brand.

Ολόκληρη η εφαρμογή είναι αδιάφορη ως προς το brand· το brand εγχέεται σε χρόνο build. Δεν υπάρχει runtime διακόπτης προϊόντος για να πάει στραβά, γιατί σε χρόνο εκτέλεσης υπάρχει πάντα μόνο ένα προϊόν ενσωματωμένο.

Ένα backend, δύο brands

Και οι δύο εφαρμογές μιλούν στο ίδιο backend. Πώς ξέρει λοιπόν ο server σε ποιο προϊόν — και σε ποια τιμολόγηση, prompts και όρια — ανήκει ένα αίτημα;

Δύο σήματα, και τα δύο ορισμένα σε χρόνο build. Πρώτον, ένα application id (geekbye ή pavleur) ενσωματωμένο στη ρύθμιση και σταλμένο με τα αιτήματα, ώστε το backend να δρομολογεί στη συμπεριφορά του σωστού προϊόντος. Δεύτερον, ένας User-Agent που ο client επισυνάπτει σε κάθε κλήση προς το backend, της μορφής Product/version (platform) — π.χ. Pavleur/1.2.5 (win32) ή GeekBye/1.7.3 (darwin). Αυτή η μία κεφαλίδα δίνει στο backend απόδοση ανά προϊόν και ανά λειτουργικό σύστημα δωρεάν, χωρίς ξεχωριστό πεδίο analytics. Οι αυτόματες ενημερώσεις, στο μεταξύ, μένουν απομονωμένες: κάθε προϊόν δημοσιεύει στο δικό του repository εκδόσεων, οπότε μια ενημέρωση του GeekBye δεν μπορεί ποτέ να σερβιριστεί σε μια εγκατάσταση του Pavleur.

Το onboarding διαφέρει επίσης ανά προϊόν — ο οδηγός του GeekBye αφορά την απομαγνητοφώνηση συναντήσεων, του Pavleur την εξάσκηση συνεντεύξεων — και ο σωστός επιλέγεται σε χρόνο εκτέλεσης από το ένα και μοναδικό ενσωματωμένο application id. Ίδιος μηχανισμός, δύο εξώπορτες.

Το σφάλμα: όταν το Pavleur αυτοαποκαλέστηκε GeekBye

Ιδού η ιστορία από το πεδίο, και είναι αυτή που κάθε white-label σύστημα έχει σε κάποια εκδοχή.

Η παραγωγή ρυθμίσεων δούλευε. Τα εικονίδια εναλλάσσονταν. Το μήνυμα για το μικρόφωνο ήταν branded. Τα protocols και τα bundle ids ήταν σωστά. Με κάθε ορατό μέτρο, ένα build του Pavleur ήταν Pavleur. Κι όμως, για ένα διάστημα, κάθε build του Pavleur τηλεφωνούσε στο backend αυτοαποκαλούμενο GeekBye.

Ο ένοχος ήταν ένα και μόνο hardcoded string, και κρυβόταν στο λιγότερο λαμπερό μέρος που μπορείς να φανταστείς: στον κατασκευαστή του User-Agent, χωμένο μέσα σε έναν retry βοηθό δικτύου. Οτιδήποτε έβλεπε ο χρήστης είχε περάσει από τη ρύθμιση, αλλά αυτό το ένα βαθιά θαμμένο βοηθητικό εργαλείο είχε ακόμη ένα literal `GeekBye/${version}` μέσα του. Κανείς δεν σκέφτεται ένα retry utility ως «branding», οπότε κανείς δεν το είχε ελέγξει. Η διόρθωση ήταν τέσσερις γραμμές — import το δυναμικό productName από τη ρύθμιση και παρεμβολή του αντί για το hardcoded brand — αλλά το μάθημα είναι όλη η ουσία:

Το white-labeling δεν είναι «άλλαξε το λογότυπο;». Είναι «περνά κάθε string ταυτότητας μέσα από τη ρύθμιση;» Και τα τελευταία υπολείμματα δεν ζουν ποτέ στο UI, εκεί που θα κοίταζες. Ζουν στον User-Agent, στον χειριστή του deep-link protocol, στο κείμενο των αιτημάτων άδειας, στα μηνύματα λάθους — στα υδραυλικά που κανείς δεν αρχειοθετεί ως «brand». Η μόνη αξιόπιστη άμυνα είναι να αντιμετωπίζεις το brand literal ως κάτι που κυνηγάς ενεργά: κάνε grep το codebase για το hardcoded όνομα και ρίξε το build αν εμφανιστεί οπουδήποτε εκτός της ρύθμισης που υποτίθεται ότι το ορίζει.

Τρία πράγματα που μας δίδαξε το white-labeling

  1. Παράγε, μην κάνεις branch. Το codegen χρόνου build δύο μικροσκοπικών config modules συν η επιλεκτική αντιγραφή assets κερδίζει και το fork και το runtime δάσος από ελέγχους προϊόντος. Η εφαρμογή μένει αδιάφορη ως προς το brand· το brand είναι μια είσοδος του build.
  2. Τα strings ταυτότητας διαρρέουν στα υδραυλικά. Το UI είναι το εύκολο 90%. Το δύσκολο 10% είναι ο User-Agent, το URL protocol, το string της άδειας μικροφώνου, τα κείμενα λαθών. Πέρασέ τα όλα από ένα export productName / applicationId — και κάνε grep για το γυμνό όνομα του brand ως πύλη CI, ώστε ένα αδέσποτο literal να μην μπορεί ποτέ να φτάσει σε παραγωγή.
  3. Ένα backend, πολλά brands, διάκριση με id και κεφαλίδα. Ένα application id χρόνου build συν ένας User-Agent της μορφής Product/version (platform) δίνει απόδοση ανά προϊόν και ανά πλατφόρμα με μηδέν διπλασιασμένη υποδομή — διαχωρίζοντας καθαρά την αμετάβλητη ταυτότητα χρόνου build από τη συμπεριφορά χρόνου εκτέλεσης.

Για την πλευρά προϊόντος-και-απορρήτου του να τρέχεις ένα white-label εργαλείο σημειώσεων κάτω από το δικό σου brand, δες το white-label AI εργαλείο σημειώσεων. Αυτό είναι το έκτο κεφάλαιο της ιστορίας του v1 που γίνεται GeekBye v2 — για το προηγούμενο κεφάλαιο, τι είναι πραγματικά μια έκδοση 127 commit (v1.7.0)· και για ολόκληρη την πορεία, η ανατομία της παράδοσης λογισμικού στην τελειότητα.

Σχετικά Άρθρα

Τι Είναι Πραγματικά μια Έκδοση 127 Commit
Steven
Steven6 λεπτά ανάγνωσης

Τι Είναι Πραγματικά μια Έκδοση 127 Commit

Το GeekBye v1.7.0 ήταν 127 commit σε έντεκα μέρες. Απ' έξω μοιάζει με εκατό μικρά πράγματα. Από μέσα ήταν δύο μεγάλες λειτουργίες πλεγμένες μαζί — και η μία χτίστηκε σε λάθος σημείο, μετά ξηλώθηκε και ξαναχτίστηκε στη μέση της έκδοσης. Ιδού η ανατομία μιας μεγάλης έκδοσης.

Μηχανική
Έκδοση
Αρχιτεκτονική
Μια χαμένη σύνδεση δεν θα έπρεπε να ρίχνει όλη την εφαρμογή — αλλά η δική μας το έκανε
Steven
Steven6 λεπτά ανάγνωσης

Μια χαμένη σύνδεση δεν θα έπρεπε να ρίχνει όλη την εφαρμογή — αλλά η δική μας το έκανε

Όταν το backend μας έπεσε στη μέση μιας σύσκεψης, δεν σταμάτησε απλώς τη μεταγραφή — έριξε ολόκληρη την εφαρμογή. Η αιτία ήταν ένα μόνο ανεπεξέργαστο συμβάν, και η διόρθωση ήταν δέκα γραμμές. Αυτή είναι η ομάδα εκδόσεων που έκανε το GeekBye να μένει συνδεδεμένο στον λογαριασμό και συνδεδεμένο στο δίκτυο μέσα από τα πράγματα που παλιά το σκότωναν.

Μηχανική
Αξιοπιστία
Electron
Η εφαρμογή Mac σας δίνει πρόσβαση στο μικρόφωνο — και μετά την ξεχνά σε κάθε εκκίνηση
Steven
Steven6 λεπτά ανάγνωσης

Η εφαρμογή Mac σας δίνει πρόσβαση στο μικρόφωνο — και μετά την ξεχνά σε κάθε εκκίνηση

Το GeekBye ζήτησε άδεια μικροφώνου, την παραχωρήσατε και δούλεψε. Στην επόμενη εκκίνηση: χάθηκε. Και η εφαρμογή δεν εμφανίστηκε καθόλου στις Ρυθμίσεις συστήματος → Μικρόφωνο. Ο ένοχος ήταν μια λειτουργία ασφαλείας του macOS που έτρεχε σιωπηλά την εφαρμογή από μια διαδρομή που εξαφανίζεται — ιδού η διάγνωση και η διόρθωση με μία μόνο προτροπή.

macOS
Άδειες
Μηχανική