feat(taildrop): fix DirectFileMode, void callbacks, and empty WaitingFiles
- Add SetStagedFileOps to Extension: sets fileOps without enabling DirectFileMode, so WASM clients use staged retrieval (WaitingFiles, OpenFile, DeleteFile) instead of direct-write mode. - Add directFileOps bool field: SetFileOps (Android SAF) sets it true; SetStagedFileOps (WASM JS) leaves it false. onChangeProfile now uses `fops != nil && e.directFileOps` to determine DirectFileMode. - Add jsCallVoid to jsFileOps: void ops (openWriter, write, closeWriter, remove) now use cb(err?: string) instead of cb(null, err: string). - Fix waitingFiles() returning JSON null when no files are waiting: normalise nil slice to empty slice before marshalling. - Update wireTaildropFileOps to call SetStagedFileOps. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -76,6 +76,12 @@ type Extension struct {
|
||||
// This is currently being used for Android to use the Storage Access Framework.
|
||||
fileOps FileOps
|
||||
|
||||
// directFileOps, when true, means that files received via fileOps should be
|
||||
// delivered directly to the caller (DirectFileMode=true). Set by SetFileOps.
|
||||
// SetStagedFileOps leaves this false so that received files are staged for
|
||||
// explicit retrieval via WaitingFiles/OpenFile (used by the WASM JS bridge).
|
||||
directFileOps bool
|
||||
|
||||
nodeBackendForTest ipnext.NodeBackend // if non-nil, pretend we're this node state for tests
|
||||
|
||||
mu sync.Mutex // Lock order: lb.mu > e.mu
|
||||
@@ -155,9 +161,10 @@ func (e *Extension) onChangeProfile(profile ipn.LoginProfileView, _ ipn.PrefsVie
|
||||
// Use the provided [FileOps] implementation (typically for SAF access on Android),
|
||||
// or create an [fsFileOps] instance rooted at fileRoot.
|
||||
//
|
||||
// A non-nil [FileOps] also implies that we are in DirectFileMode.
|
||||
// A non-nil [FileOps] with directFileOps=true implies DirectFileMode (Android SAF).
|
||||
// A non-nil [FileOps] with directFileOps=false uses staged mode (WASM JS bridge).
|
||||
fops := e.fileOps
|
||||
isDirectFileMode := fops != nil
|
||||
isDirectFileMode := fops != nil && e.directFileOps
|
||||
if fops == nil {
|
||||
var fileRoot string
|
||||
if fileRoot, isDirectFileMode = e.fileRoot(uid, activeLogin); fileRoot == "" {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build !android
|
||||
//go:build !android && !js
|
||||
|
||||
package taildrop
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build js
|
||||
|
||||
package taildrop
|
||||
|
||||
import "errors"
|
||||
|
||||
func init() {
|
||||
// On WASM there is no real filesystem. newFileOps is only reached when
|
||||
// SetFileOps was not called; return a clear error rather than panicking.
|
||||
newFileOps = func(dir string) (FileOps, error) {
|
||||
return nil, errors.New("taildrop: no filesystem on WASM; provide fileOps in the IPN config")
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,21 @@ func (e *Extension) SetDirectFileRoot(root string) {
|
||||
e.directFileRoot = root
|
||||
}
|
||||
|
||||
// SetFileOps sets the platform specific file operations. This is used
|
||||
// SetFileOps sets the platform-specific file operations. This is used
|
||||
// to call Android's Storage Access Framework APIs.
|
||||
// It implies DirectFileMode, so received files are delivered directly to the
|
||||
// caller rather than staged for retrieval via WaitingFiles/OpenFile.
|
||||
func (e *Extension) SetFileOps(fileOps FileOps) {
|
||||
e.fileOps = fileOps
|
||||
e.directFileOps = true
|
||||
}
|
||||
|
||||
// SetStagedFileOps sets the platform-specific file operations without enabling
|
||||
// DirectFileMode. Received files are staged for explicit retrieval via
|
||||
// WaitingFiles, OpenFile, and DeleteFile. Used by the WASM JS bridge.
|
||||
func (e *Extension) SetStagedFileOps(fileOps FileOps) {
|
||||
e.fileOps = fileOps
|
||||
e.directFileOps = false
|
||||
}
|
||||
|
||||
func (e *Extension) setPlatformDefaultDirectFileRoot() {
|
||||
|
||||
Reference in New Issue
Block a user