@@ -2,7 +2,6 @@ package main
22
33import (
44 "bytes"
5- "encoding/base64"
65 "encoding/json"
76 "fmt"
87 "io"
@@ -27,7 +26,6 @@ const (
2726 reconnectBaseDelay = 1 * time .Second
2827 reconnectMaxDelay = 30 * time .Second
2928 pongWait = 60 * time .Second
30- maxFileSize = 50 * 1024 * 1024
3129)
3230
3331var sessionCodePattern = regexp .MustCompile (`^[0-9a-f]{12}$` )
@@ -84,6 +82,12 @@ func main() {
8482 fatal ("Usage: cya-bridge <session code>" )
8583 }
8684
85+ // Derive HTTP base URL from WebSocket URL for the connect link
86+ baseURL := strings .Replace (wsURL , "ws://" , "http://" , 1 )
87+ baseURL = strings .Replace (baseURL , "wss://" , "https://" , 1 )
88+ baseURL = strings .TrimSuffix (baseURL , "/ws" )
89+ connectURL := baseURL + "/c/" + code
90+
8791 quit := make (chan struct {})
8892 sig := make (chan os.Signal , 1 )
8993 signal .Notify (sig , syscall .SIGINT , syscall .SIGTERM )
@@ -105,7 +109,7 @@ func main() {
105109 default :
106110 }
107111
108- _ , reconnect := dialAndRun (wsURL , code , quit )
112+ _ , reconnect := dialAndRun (wsURL , code , connectURL , quit )
109113 if ! reconnect {
110114 return
111115 }
@@ -311,134 +315,14 @@ func readCommands(wsc *wsConn, dot string) bool {
311315 "exit_code" : code ,
312316 "truncated" : truncated ,
313317 })
314- case "file_read" :
315- var msg struct {
316- ID string `json:"id"`
317- Path string `json:"path"`
318- Encoding string `json:"encoding"`
319- }
320- _ = json .Unmarshal (data , & msg )
321- if msg .ID == "" {
322- continue
323- }
324- handleFileRead (wsc , msg .ID , msg .Path )
325- case "file_write" :
326- var msg struct {
327- ID string `json:"id"`
328- Path string `json:"path"`
329- Data string `json:"data"`
330- Encoding string `json:"encoding"`
331- }
332- _ = json .Unmarshal (data , & msg )
333- if msg .ID == "" {
334- continue
335- }
336- handleFileWrite (wsc , msg .ID , msg .Path , msg .Data , msg .Encoding )
337318 case "bye" :
338319 wsc .close ()
339320 return false
340321 }
341322 }
342323}
343324
344- func handleFileRead (wsc * wsConn , id , path string ) {
345- printLine (ansiCyan + "📖" + ansiReset , " " , ansiBold , "File read:" , ansiReset , " " , path )
346- data , size , err := readFile (path )
347- if err != nil {
348- wsc .sendJSON (map [string ]any {
349- "type" : "file_read_result" ,
350- "id" : id ,
351- "path" : path ,
352- "error" : err .Error (),
353- "size" : 0 ,
354- "data" : "" ,
355- "encoding" : "base64" ,
356- })
357- return
358- }
359- wsc .sendJSON (map [string ]any {
360- "type" : "file_read_result" ,
361- "id" : id ,
362- "path" : path ,
363- "data" : data ,
364- "size" : size ,
365- "encoding" : "base64" ,
366- })
367- }
368-
369- func handleFileWrite (wsc * wsConn , id , path , data , encoding string ) {
370- printLine (ansiCyan + "📝" + ansiReset , " " , ansiBold , "File write:" , ansiReset , " " , path )
371- bytesWritten , err := writeFile (path , data , encoding )
372- if err != nil {
373- wsc .sendJSON (map [string ]any {
374- "type" : "file_write_result" ,
375- "id" : id ,
376- "path" : path ,
377- "error" : err .Error (),
378- "bytes_written" : 0 ,
379- })
380- return
381- }
382- wsc .sendJSON (map [string ]any {
383- "type" : "file_write_result" ,
384- "id" : id ,
385- "path" : path ,
386- "bytes_written" : bytesWritten ,
387- })
388- }
389-
390- func readFile (path string ) (string , int64 , error ) {
391- fi , err := os .Stat (path )
392- if err != nil {
393- return "" , 0 , fmt .Errorf ("cannot access %s: %w" , path , err )
394- }
395- if fi .Size () > maxFileSize {
396- return "" , 0 , fmt .Errorf ("file too large: %d bytes (max %d)" , fi .Size (), maxFileSize )
397- }
398-
399- f , err := os .Open (path )
400- if err != nil {
401- return "" , 0 , fmt .Errorf ("cannot open %s: %w" , path , err )
402- }
403- defer f .Close ()
404-
405- data , err := io .ReadAll (f )
406- if err != nil {
407- return "" , 0 , fmt .Errorf ("cannot read %s: %w" , path , err )
408- }
409-
410- encoded := base64 .StdEncoding .EncodeToString (data )
411- return encoded , fi .Size (), nil
412- }
413-
414- func writeFile (path , data , encoding string ) (int , error ) {
415- var raw []byte
416- switch encoding {
417- case "base64" , "" :
418- var err error
419- raw , err = base64 .StdEncoding .DecodeString (data )
420- if err != nil {
421- // Try base64url
422- raw , err = base64 .URLEncoding .DecodeString (data )
423- if err != nil {
424- return 0 , fmt .Errorf ("invalid base64 data: %w" , err )
425- }
426- }
427- default :
428- return 0 , fmt .Errorf ("unsupported encoding: %s" , encoding )
429- }
430-
431- if len (raw ) > maxFileSize {
432- return 0 , fmt .Errorf ("data too large: %d bytes (max %d)" , len (raw ), maxFileSize )
433- }
434-
435- if err := os .WriteFile (path , raw , 0644 ); err != nil {
436- return 0 , fmt .Errorf ("cannot write %s: %w" , path , err )
437- }
438- return len (raw ), nil
439- }
440-
441- func dialAndRun (wsURL , code string , quit <- chan struct {}) (string , bool ) {
325+ func dialAndRun (wsURL , code , connectURL string , quit <- chan struct {}) (string , bool ) {
442326 dialer := websocket.Dialer {HandshakeTimeout : 15 * time .Second }
443327 conn , resp , err := dialer .Dial (wsURL , http.Header {})
444328 if err != nil {
@@ -459,6 +343,7 @@ func dialAndRun(wsURL, code string, quit <-chan struct{}) (string, bool) {
459343 setCurrentBridge (bridge )
460344
461345 dot := ansiCyan + "●" + ansiReset
346+ printLine (dot , " " , connectURL , ansiReset )
462347 printLine (dot , " " , ansiBold , code , ansiReset , " — Ctrl+C to disconnect" )
463348
464349 // Set up heartbeat: expect pong within pongWait
0 commit comments