feat(tsconnect): add outgoing file transfer progress notifications
- Export UpdateOutgoingFiles on taildrop.Extension so it can be called from outside the package (wasm bridge, package main). - Wrap sendFile's PUT body with progresstracking.NewReader so bytes-sent is sampled roughly once per second during transfer. - Create an OutgoingFile entry (with UUID, peer ID, name, declared size) before the PUT and call UpdateOutgoingFiles on each progress tick and on completion (setting Finished/Succeeded). This flows into the IPN notify stream as OutgoingFiles notifications. - Add jsOutgoingFile struct and wire n.OutgoingFiles into a new notifyOutgoingFiles callback in run(), mirroring notifyIncomingFiles. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,8 +23,11 @@ import (
|
||||
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
"tailscale.com/feature/taildrop"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/util/progresstracking"
|
||||
"tailscale.com/util/rands"
|
||||
)
|
||||
|
||||
// Compile-time check that jsFileOps implements taildrop.FileOps.
|
||||
@@ -78,7 +81,8 @@ func (i *jsIPN) listFileTargets() js.Value {
|
||||
})
|
||||
}
|
||||
|
||||
// sendFile sends data as filename to the peer identified by stableNodeID.
|
||||
// sendFile sends data as filename to the peer identified by stableNodeID,
|
||||
// reporting progress via notifyOutgoingFiles callbacks roughly once per second.
|
||||
func (i *jsIPN) sendFile(stableNodeID, filename string, data js.Value) js.Value {
|
||||
return makePromise(func() (any, error) {
|
||||
ext, err := i.taildropExt()
|
||||
@@ -105,20 +109,46 @@ func (i *jsIPN) sendFile(stableNodeID, filename string, data js.Value) js.Value
|
||||
}
|
||||
b := make([]byte, data.Get("byteLength").Int())
|
||||
js.CopyBytesToGo(b, data)
|
||||
req, err := http.NewRequest("PUT", dstURL.String()+"/v0/put/"+url.PathEscape(filename), bytes.NewReader(b))
|
||||
|
||||
outgoing := &ipn.OutgoingFile{
|
||||
ID: rands.HexString(30),
|
||||
PeerID: tailcfg.StableNodeID(stableNodeID),
|
||||
Name: filename,
|
||||
DeclaredSize: int64(len(b)),
|
||||
Started: time.Now(),
|
||||
}
|
||||
updates := map[string]*ipn.OutgoingFile{outgoing.ID: outgoing}
|
||||
|
||||
// Report final state (success or failure) when the function returns.
|
||||
var sendErr error
|
||||
defer func() {
|
||||
outgoing.Finished = true
|
||||
outgoing.Succeeded = sendErr == nil
|
||||
ext.UpdateOutgoingFiles(updates)
|
||||
}()
|
||||
|
||||
body := progresstracking.NewReader(bytes.NewReader(b), time.Second, func(n int, _ error) {
|
||||
outgoing.Sent = int64(n)
|
||||
ext.UpdateOutgoingFiles(updates)
|
||||
})
|
||||
|
||||
req, err := http.NewRequest("PUT", dstURL.String()+"/v0/put/"+url.PathEscape(filename), body)
|
||||
if err != nil {
|
||||
sendErr = err
|
||||
return nil, err
|
||||
}
|
||||
req.ContentLength = int64(len(b))
|
||||
client := &http.Client{Transport: i.lb.Dialer().PeerAPITransport()}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
sendErr = err
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("send file: %s: %s", resp.Status, bytes.TrimSpace(body))
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
sendErr = fmt.Errorf("send file: %s: %s", resp.Status, bytes.TrimSpace(respBody))
|
||||
return nil, sendErr
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user