
At tage backend ud af upload-stien
GeekBye optager din skærm og gemmer videoen til din Google Drive. Den første version sendte hver optagelse gennem GeekByes egne servere på vejen dertil; en release senere gik filen direkte fra din maskine til Drive, og backend blev degraderet til at holde en enkelt pointer. Den interessante del er, hvor lidt kode den 'direkte, genoptagelige' version faktisk indeholder — fordi genoptageligheden kom fra at slette en proxy, ikke fra at skrive en.
GeekBye kan optage din skærm — video, systemlyd og mikrofon — og droppe den færdige optagelse i din Google Drive automatisk. Mellem v1.8.11 og v1.8.13 ændrede funktionens brugervendte beskrivelse sig knap nok, men stien videoen tager for at nå Drive blev omdirigeret fuldstændigt. I den første version rejste din optagelse gennem GeekByes servere. I den anden gik den direkte fra din maskine til din Drive, og GeekByes backend så aldrig en byte af den. Den omdirigering er hele historien, og det tilfredsstillende er, at den "bedre" version indeholder påfaldende mindre kode end den, den erstattede.
Pipelinen, fra ende til ende
Optagelse sker i rendereren. ScreenRecordingService.ts opretter en MediaRecorder over optagelsesstrømmen med { mimeType: 'video/webm;codecs=vp9,opus', videoBitsPerSecond: 1_000_000 } og starter den med et ét-sekunds tidsudsnit (CHUNK_INTERVAL_MS = 1000). Hver ondataavailable-blob sendes over IPC til hovedprocessen, hvor recording:save-chunk-handleren føjer den til en fil på disk med fs.promises.appendFile. Værd at flage tidligt, fordi det er fristende at fejllæse: den ét-sekunds kadence er disk-skrivnings-rytmen, ikke en upload-stumpstørrelse. Når uploaden begynder, ligger der en enkelt færdig WebM-fil på disken.
Det, der adskiller sig mellem de to releases, er kun, hvad der sker med den færdige fil.
Før: alt gennem backend (v1.8.11)
v1.8.11-upload-stien, i CloudUploader.processRecording(), er det oplagte design og en fuldstændig rimelig første version:
POST /api/geekbye/recordingsfor at oprette en backend-post med optagelsens metadata.- Læs hele videoen ind i hukommelsen og POST den til backend som multipart —
const fileBuffer = fs.readFileSync(filePath)— og ramPOST /api/geekbye/recordings/${id}/upload. Backend uploader så den fil til Drive og returnerer endriveUrl. POST …/processfor at sende transskriptet til AI-analyse.
Den bærende detalje er trin 2: klienten loader hele optagelsen ind i hukommelsen, og hver byte af den transiterer GeekByes egne servere på vejen til Drive. For et kort klip er det fint. For en lang skærmoptagelse er det tre problemer på én gang — hukommelsespres på klienten fra readFileSync, båndbredde- og relæomkostning på backend for en fil, den ikke engang beholder, og en enkelt monolitisk POST, der skal starte forfra fra nul, hvis netværket hakker halvvejs.
Det er også værd at være ærlig om, at dette "før" ikke var noget uberørt oprindeligt design. v1.8.11-funktionen maste et pivot inden for samme uge sammen: en tidlig udgave uploadede til Cloudflare R2 via præsignerede URL'er og konverterede WebM til MP4 med en medfølgende ffmpeg-static; det blev erstattet af det backend-medierede Drive-flow; og så blev ffmpeg-konverteringen revet helt ud til fordel for at uploade WebM som den er (commiten bemærker, at den sænkede bitraten og producerede omkring fire gange mindre filer — et indbygget skøn, ikke et benchmark). Så selv "før" havde allerede lært at slette en nativ afhængighed én gang. Den næste release ville slette proxyen også.
Efter: direkte til Drive (v1.8.13)
v1.8.13 omskriver CloudUploader.processRecording() omkring én sætning fra commiten, der shippede det: upload direkte til Drive, "keeping backend for metadata record + AI transcript analysis only." Den nye sti:
POST /api/geekbye/recordings— kun metadata (titel, varighed, filstørrelse,format: 'webm'). Kommentaren i koden er kontant:// Create backend record (metadata only — no file upload).- Upload videoen direkte til Google Drive, og spring backend-mellemmanden helt over.
PATCH /api/geekbye/recordings/${backendRecordingId}med den resulterende{ driveUrl, driveFolderId }— backend-posten får fortalt, hvor filen endte.- Transskriptet går stadig til
…/processtil analyse.
Backend gik fra at være i filens sti til at blive fortalt om filen bagefter. Den holder en række og en pointer; bytes bor kun på brugerens disk og i brugerens Drive.
At blive en rigtig Drive-klient
For at klienten kan tale direkte med Drive, er den nødt til at være en faktisk Google Drive API-klient, og v1.8.13 tilføjer maskineriet til det: en ny DriveService (Drive-klienten), en ny DriveAuthRepository understøttet af en ny drive_auth SQLite-tabel, og googleapis-SDK'en som en afhængighed.
Autorisationen er en ren overdragelse af kapabilitet. Backendens app-config-endpoint relæer Google OAuth klientlegitimationer — et clientId og et clientSecret — ned til appen, som CloudUploader læser og sender til driveService.initialize(...). Derfra kører desktop-appen hele OAuth-flowet selv: den rejser en midlertidig http-server på en tilfældig 127.0.0.1-port, åbner samtykkeskærmen i systembrowseren med shell.openExternal, fanger omdirigeringen på et /callback, og bytter koden for tokens med google-auth-library. De tokens gemmes lokalt — saveDriveAuth(access_token, refresh_token, expiry_date) ind i drive_auth — med drive.file-scopet, som lader appen kun håndtere de filer, den opretter. Opdatering er også lokal: en oauth2Client.on('tokens', …)-lytter skriver opdaterede tokens direkte tilbage til tabellen. Backend overdrager kapabiliteten én gang og holder sig så ude af loopet.
Den "genoptagelige" del, ærligt
Her er detaljen, jeg mest vil være lige til om, fordi det er præcis den slags ting, en ændringslog runder af. Release-noten siger "resumable uploads", og det er sandt, der hvor brugeren står. Men GeekBye implementerede ikke den genoptagelige protokol. Der er ingen stumpstørrelse-konstant, ingen byte-offset-tracker og ingen Content-Range / 308 Resume Incomplete-håndtering nogen steder i GeekByes kildekode. Grep efter dem, og du får ingenting — de bor inde i googleapis-SDK'en.
Det, GeekByes egen kode gør, er dette:
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)
},
},
)
To valg gør hele arbejdet. Først er media.body en stream (createReadStream), ikke en buffer — og at sende en stream er præcis, hvad der får googleapis til at forhandle en genoptagelig session på dine vegne i stedet for at gøre det i ét skud. For det andet aflæses forløbet fra SDK'ens onUploadProgress-callback mod fileStats.size. Det er hele "genoptagelig upload"-implementeringen på applikationsniveau: stream ind, forløb ud. Den svære protokol — åbn en session, upload i stumper, genoptag fra en offset efter et frafald — er SDK'ens arbejde, og den rigtige ingeniørbeslutning var at lade den gøre det arbejde i stedet for at genopfinde det.
Hvilket omrammer hele releasen. Pålidelighedsforbedringen kom ikke fra at skrive en upload-motor. Den kom fra at slette proxyen, der sad foran en — og, tidligere samme uge, at slette ffmpeg-trinnet foran den. Mindre kode, mere modstandsdygtighed, fordi den modstandsdygtige ting var et velbrugt bibliotek i det øjeblik, du holdt op med at håndfodre det med en buffer.
De stumper, der ægte er GeekByes
At overdrage overførslen til SDK'en betyder ikke, at der ikke var noget tilbage at bygge. To stykker ægte applikationslogik omgiver den.
En forældreløs-mappe-tilbagerulning. Hver optagelse får sin egen Drive-undermappe (oprettet med mimeType: 'application/vnd.google-apps.folder'), og transskriptet lander der som en anden fil ved siden af videoen. Hvis uploaden kaster, sletter catch den mappe — this.drive.files.delete({ fileId: folderId }), logget som Rolled back orphaned Drive folder — så en fejlet upload ikke efterlader et spor af tomme mapper i din Drive. At oprette en beholder, før du ved, at operationen vil lykkes, er en lille forpligtelse; at få fejlstien til at rydde op efter den er fixet.
Livscyklus omkring en singleton. DriveService er en singleton, der holder en in-memory OAuth-klient, og den in-memory klient overlever ikke en app-genstart, selvom tokens gør (de er i databasen). Så CloudUploader geninitialiserer DriveService fra de gemte tokens før hver upload, hvilket betyder, at en optagelse lavet lige efter genstart stadig uploader uden at bede dig om at genforbinde. Og gennemgangsrunden gjorde drive-connect-handleren til fire-and-forget, fordi et blokerende connect-kald bremsede rendererens statuspolling — brugerfladen kunne ikke vise "forbinder…", hvis connect-kaldet holdt tråden. De to — geninitialisér-fra-lager og ikke-blokerende connect — er den direkte stis rigtige kanttilfælde, og bemærkelsesværdigt handler ingen af dem om upload-stumpning. Når du delegerer den svære del, er de bugs, du sidder tilbage med, livscyklusbugs.
Tre ting, denne release lærte os
- Spørg, hvor bytes flyder, ikke bare om det virker. v1.8.11-proxyen virkede. Men at route hver optagelse gennem dine egne servere koster dig båndbredde, hukommelse og genoptagelighed for en fil, du ikke beholder. At gentegne datastien, så klienten taler direkte med destinationen, er ofte hele optimeringen.
- Den bedste genoptagelige upload er den, du ikke skrev. At sende en stream til et modent SDK købte en fuld genoptagelig session for to linjer kode. Instinktet til at håndrulle stumpning og offset-matematik ville have produceret mere kode og mindre pålidelighed.
- Når du delegerer kernen, arver du livscyklus, ikke algoritmer. Den direkte klients rigtige bugs var "geninitialisér singletonen efter genstart" og "blokér ikke connect-kaldet", ikke byte-offsets. Det er en god handel — livscyklusbugs er synlige og lokale; protokolbugs er ingen af delene.
For det forrige kapitel i v1-historien, født og fixet på tolv minutter (v1.8.10); og for hele buen, anatomien i at shippe software til perfektion.