
De backend uit het uploadpad halen
GeekBye neemt je scherm op en bewaart de video in je Google Drive. De eerste versie stuurde elke opname onderweg via de eigen servers van GeekBye; een release later ging het bestand rechtstreeks van je machine naar Drive, en de backend werd gedegradeerd tot het vasthouden van één enkele pointer. Het interessante is hoe weinig code de 'directe, hervatbare' versie eigenlijk bevat — want de hervatbaarheid kwam voort uit het verwijderen van een proxy, niet uit het schrijven ervan.
GeekBye kan je scherm opnemen — video, systeemaudio en microfoon — en de voltooide opname automatisch in je Google Drive zetten. Tussen v1.8.11 en v1.8.13 veranderde de beschrijving van de functie voor de gebruiker nauwelijks, maar het pad dat de video aflegt om bij Drive te komen werd volledig omgeleid. In de eerste versie reisde je opname via de servers van GeekBye. In de tweede ging hij rechtstreeks van je machine naar je Drive, en de backend van GeekBye zag er nooit een byte van. Die omleiding is het hele verhaal, en het bevredigende is dat de "betere" versie opvallend minder code bevat dan degene die hij verving.
De pijplijn, van begin tot eind
Het opnemen gebeurt in de renderer. ScreenRecordingService.ts maakt een MediaRecorder over de capture-stream met { mimeType: 'video/webm;codecs=vp9,opus', videoBitsPerSecond: 1_000_000 } en start hem met een timeslice van één seconde (CHUNK_INTERVAL_MS = 1000). Elke ondataavailable-blob wordt over IPC naar het main-proces gestuurd, waar de recording:save-chunk-handler hem met fs.promises.appendFile aan een bestand op schijf toevoegt. De moeite waard om vroeg te markeren, want het is verleidelijk om verkeerd te lezen: die cadans van één seconde is het ritme van het wegschrijven-naar-schijf, geen upload-chunkgrootte. Tegen de tijd dat de upload begint, staat er één enkel voltooid WebM-bestand op schijf.
Wat tussen de twee releases verschilt, is alleen wat er met dat voltooide bestand gebeurt.
Het ervoor: alles via de backend (v1.8.11)
Het uploadpad van v1.8.11, in CloudUploader.processRecording(), is het voor de hand liggende ontwerp en een volkomen redelijke eerste versie:
POST /api/geekbye/recordingsom een backend-record te maken met de metadata van de opname.- Lees de hele video in het geheugen en POST hem als multipart naar de backend —
const fileBuffer = fs.readFileSync(filePath)— opPOST /api/geekbye/recordings/${id}/upload. De backend uploadt dat bestand vervolgens naar Drive en retourneert eendriveUrl. POST …/processom het transcript voor AI-analyse te versturen.
Het dragende detail is stap 2: de client laadt de hele opname in het geheugen en elke byte ervan passeert onderweg naar Drive de eigen servers van GeekBye. Voor een korte clip is dat prima. Voor een lange schermopname zijn het drie problemen tegelijk — geheugendruk op de client door readFileSync, bandbreedte- en relaykosten op de backend voor een bestand dat hij niet eens bewaart, en één monolithische POST die vanaf nul opnieuw moet beginnen als het netwerk halverwege hapert.
Het is ook eerlijk om te zeggen dat dit "ervoor" geen ongerept oorspronkelijk ontwerp was. De functie van v1.8.11 perste een pivot van binnen één week samen: een vroege versie uploadde naar Cloudflare R2 via vooraf ondertekende URL's en converteerde WebM naar MP4 met een meegeleverde ffmpeg-static; dat werd vervangen door de door de backend bemiddelde Drive-flow; en toen werd de ffmpeg-conversie er volledig uit gehaald ten gunste van het uploaden van de WebM as-is (de commit merkt op dat het de bitrate verlaagde en ongeveer vier keer kleinere bestanden opleverde — een schatting ter plekke, geen benchmark). Dus zelfs het "ervoor" had al één keer geleerd om een native afhankelijkheid te verwijderen. De volgende release zou ook de proxy verwijderen.
Het erna: rechtstreeks naar Drive (v1.8.13)
v1.8.13 herschrijft CloudUploader.processRecording() rond één zin uit de commit die hem uitbracht: upload rechtstreeks naar Drive, "keeping backend for metadata record + AI transcript analysis only." Het nieuwe pad:
POST /api/geekbye/recordings— alleen metadata (titel, duur, bestandsgrootte,format: 'webm'). Het commentaar in de code is bot:// Create backend record (metadata only — no file upload).- Upload de video rechtstreeks naar Google Drive, waarbij de backend-tussenpersoon volledig wordt overgeslagen.
PATCH /api/geekbye/recordings/${backendRecordingId}met de resulterende{ driveUrl, driveFolderId }— het backend-record krijgt te horen waar het bestand belandde.- Het transcript gaat nog steeds naar
…/processvoor analyse.
De backend ging van in het pad van het bestand zitten naar achteraf op de hoogte gebracht worden van het bestand. Hij houdt een rij en een pointer vast; de bytes leven alleen op de schijf van de gebruiker en in de Drive van de gebruiker.
Een echte Drive-client worden
Om de client rechtstreeks met Drive te laten praten, moet hij een echte Google Drive API-client zijn, en v1.8.13 voegt de machinerie daarvoor toe: een nieuwe DriveService (de Drive-client), een nieuwe DriveAuthRepository ondersteund door een nieuwe drive_auth SQLite-tabel, en de googleapis SDK als afhankelijkheid.
De autorisatie is een nette overdracht van bevoegdheid. Het app-config-endpoint van de backend geeft Google OAuth client-credentials — een clientId en clientSecret — door aan de app, die CloudUploader uitleest en doorgeeft aan driveService.initialize(...). Vanaf daar draait de desktop-app de hele OAuth-flow zelf: hij zet een tijdelijke http-server op een willekeurige 127.0.0.1-poort op, opent het toestemmingsscherm in de systeembrowser met shell.openExternal, vangt de redirect op een /callback op, en wisselt de code om voor tokens met google-auth-library. Die tokens worden lokaal opgeslagen — saveDriveAuth(access_token, refresh_token, expiry_date) in drive_auth — met de drive.file-scope, die de app alleen de bestanden laat beheren die hij zelf aanmaakt. Ook het verversen is lokaal: een oauth2Client.on('tokens', …)-listener schrijft de ververste tokens rechtstreeks terug naar de tabel. De backend draagt de bevoegdheid één keer over en blijft daarna buiten beeld.
Het "hervatbare" deel, eerlijk
Hier is het detail waarover ik het meest eerlijk wil zijn, want het is precies het soort ding dat een changelog afrondt. De release-notitie zegt "resumable uploads," en dat klopt vanuit het gezichtspunt van de gebruiker. Maar GeekBye implementeerde het hervatbare protocol niet. Er is geen chunkgrootte-constante, geen byte-offset-tracker, en nergens in de broncode van GeekBye enige afhandeling van Content-Range / 308 Resume Incomplete. Grep ernaar en je krijgt niets — ze leven binnen de googleapis SDK.
Wat de eigen code van GeekBye doet, is dit:
const fileStats = statSync(videoFilePath)
const videoStream = createReadStream(videoFilePath)
const videoRes = await this.drive.files.create(
{
requestBody: { name: `${title}.webm`, parents: [folderId] },
media: { mimeType: 'video/webm', body: videoStream },
fields: 'id',
},
{
onUploadProgress: (evt) => {
const percent = Math.round((evt.bytesRead / fileStats.size) * 100)
onProgress(percent)
},
},
)
Twee keuzes doen al het werk. Ten eerste is media.body een stream (createReadStream), geen buffer — en juist het doorgeven van een stream is wat googleapis een hervatbare sessie namens jou laat onderhandelen in plaats van het in één keer te doen. Ten tweede wordt de voortgang uitgelezen van de onUploadProgress-callback van de SDK, afgezet tegen fileStats.size. Dat is de volledige implementatie van "resumable upload" op applicatieniveau: stream erin, voortgang eruit. Het lastige protocol — een sessie openen, in chunks uploaden, hervatten vanaf een offset na een onderbreking — is de taak van de SDK, en de juiste engineeringbeslissing was om die het werk te laten doen in plaats van het opnieuw uit te vinden.
Wat de hele release in een ander licht plaatst. De betrouwbaarheidsverbetering kwam niet voort uit het schrijven van een upload-engine. Ze kwam voort uit het verwijderen van de proxy die ervoor stond — en, eerder diezelfde week, het verwijderen van de ffmpeg-stap die daar weer voor stond. Minder code, meer veerkracht, want het veerkrachtige ding was een beproefde library op het moment dat je stopte met hem met de hand een buffer te voeren.
De stukken die echt van GeekBye zijn
De overdracht aan de SDK betekent niet dat er niets meer te bouwen viel. Twee stukken echte applicatielogica omringen hem.
Een terugrol voor verweesde mappen. Elke opname krijgt zijn eigen Drive-submap (aangemaakt met mimeType: 'application/vnd.google-apps.folder'), en het transcript belandt daar als een tweede bestand naast de video. Als de upload een fout gooit, verwijdert de catch die map — this.drive.files.delete({ fileId: folderId }), gelogd als Rolled back orphaned Drive folder — zodat een mislukte upload geen spoor van lege mappen in je Drive achterlaat. Een container aanmaken voordat je weet dat de operatie zal slagen is een kleine risicopost; het faalpad die laten opruimen is de fix.
Levenscyclus rond een singleton. DriveService is een singleton die een in-memory OAuth-client vasthoudt, en die in-memory client overleeft geen herstart van de app ook al doen de tokens dat wel (die staan in de database). Dus herinitialiseert CloudUploader DriveService vanuit de opgeslagen tokens vóór elke upload, wat betekent dat een opname die vlak na een herstart wordt gemaakt nog steeds uploadt zonder dat je opnieuw hoeft te verbinden. En de reviewronde maakte van de drive-connect-handler fire-and-forget, omdat een blokkerende connect-aanroep de statuspolling van de renderer ophield — de UI kon geen "connecting…" tonen als de connect-aanroep de thread vasthield. Die twee — herinitialiseren-vanuit-opslag en niet-blokkerende connect — zijn de echte randgevallen van het directe pad, en opvallend genoeg gaat geen van beide over upload-chunking. Wanneer je het lastige deel delegeert, zijn de bugs die je overhoudt levenscyclusbugs.
Drie dingen die deze release ons leerde
- Vraag waar de bytes stromen, niet alleen of het werkt. De proxy van v1.8.11 werkte. Maar elke opname via je eigen servers routeren kost je bandbreedte, geheugen en herstartbaarheid voor een bestand dat je niet bewaart. Het datapad opnieuw tekenen zodat de client rechtstreeks met de bestemming praat, is vaak de hele optimalisatie.
- De beste hervatbare upload is degene die je niet schreef. Een stream doorgeven aan een volwassen SDK kocht een volledige hervatbare sessie voor twee regels code. Het instinct om chunking en offset-rekenwerk met de hand te bouwen zou meer code en minder betrouwbaarheid hebben opgeleverd.
- Wanneer je de kern delegeert, erf je levenscyclus, geen algoritmes. De echte bugs van de directe client waren "herinitialiseer de singleton na een herstart" en "blokkeer de connect-aanroep niet," geen byte-offsets. Dat is een goede ruil — levenscyclusbugs zijn zichtbaar en lokaal; protocolbugs zijn geen van beide.
Voor het vorige hoofdstuk in het v1-verhaal, geboren en gefixt in twaalf minuten (v1.8.10); en voor de hele boog, de anatomie van software tot in de perfectie uitbrengen.