File Manager Service

FileManagerService — Reveal files and move to trash

Runs in: both worker and view.

Permissions required: fs:read (reveal), fs:write (trash).

Cross-platform file manager operations. Reveal files in the OS file manager or move them to the OS trash (recoverable).

interface IFileManagerService {
  /**
   * Reveals a file or directory in the OS file manager, selecting it.
   * @param path Absolute path to the file or directory.
   */
  showInFileManager(path: string): Promise<void>;

  /**
   * Moves a file or directory to the OS trash / recycle bin.
   * Path must be within the user's home directory tree.
   * @param path Absolute path to the file or directory.
   */
  trash(path: string): Promise<void>;
}

Minimal usage:

import type { IFileManagerService } from 'asyar-sdk';

const fm = context.getService<IFileManagerService>('fs');

// Reveal a file in the OS file manager
await fm.showInFileManager('/Users/me/Documents/report.pdf');

// Move a file to trash (recoverable by the user)
await fm.trash('/Users/me/Downloads/old-file.zip');

Typical pattern — file manager extension with feedback:

import type { IFileManagerService, IFeedbackService } from 'asyar-sdk';

const fm       = context.getService<IFileManagerService>('fs');
const feedback = context.getService<IFeedbackService>('feedback');

async function revealDownload(path: string) {
  try {
    await fm.showInFileManager(path);
  } catch (err) {
    await feedback.showToast({
      title: 'File not found',
      message: String(err),
      style: 'failure',
    });
  }
}

async function cleanupFile(path: string) {
  const confirmed = await feedback.confirmAlert({
    title: 'Move to Trash?',
    message: `This will trash ${path.split('/').pop()}`,
    primaryAction: 'Trash',
  });

  if (confirmed) {
    await fm.trash(path);
    await feedback.showHUD('Moved to Trash');
  }
}

How it works under the hood

Extension iframe                         Host (Svelte + Rust)
─────────────────────────────────────────────────────────────────────────
1. SDK proxy calls:
   broker.invoke('fs:showInFileManager',
     { path })
                                          ─────────────────────────────►
                                          2. ExtensionIpcRouter:
                                             namespace lookup ('fs')
                                             permission check (fs:read)
                                          3. fileManagerService
                                               .showInFileManager(path)
                                             → invoke('show_in_file_manager')
                                          4. Rust: validate path
                                             → std::process::Command
                                               (platform-specific)
                                             return Ok(())
                                          ◄─────────────────────────────
5. Promise resolves in extension

Both operations are synchronous request/response — no streaming, no callbacks, no handles. The Rust command validates the path and delegates to the OS immediately.

Cross-platform behavior

Platform showInFileManager trash
macOS open -R <path> — opens Finder and selects the item trash crate → NSFileManager.trashItem
Windows explorer /select,<path> — opens Explorer and selects the item trash crate → Shell API with FOFX_RECYCLEONDELETE
Linux xdg-open <parent_dir> — opens the parent directory in the default file manager trash crate → freedesktop.org trash spec

Linux note: Most Linux file managers do not support selecting a specific file via CLI. showInFileManager opens the containing folder instead of highlighting the file.

Path restrictions

  • Both methods: Path must be absolute and must exist on disk. Relative paths and non-existent paths are rejected.
  • trash() only: Path must be within the user's home directory tree. Paths outside (e.g., /etc/hosts, /usr/bin/python) are rejected for safety. Path traversal attacks (e.g., ~/../../etc/hosts) are caught via normalization.

Error handling

Scenario Behavior
Relative path Promise rejects: "Path must be absolute: ..."
Path does not exist Promise rejects: "Path does not exist: ..."
trash() outside home directory Promise rejects: "Access denied: path '...' is outside home directory"
fs:read / fs:write not in manifest IPC call rejects (permission denied)
OS file manager unavailable Promise rejects with platform-specific spawn error

Security model

  • Path validation in Rust. All validation happens in the Rust layer — the TS host service is a thin passthrough.
  • Home directory restriction. trash() is restricted to the home directory tree to prevent extensions from trashing system files or other users' data.
  • Traversal prevention. Paths are normalized (resolving .. components) before the home directory check, preventing traversal attacks.
  • No per-extension state. Unlike ShellService, FileManagerService stores no trust records or per-extension data. Uninstalling an extension requires no cleanup for this service.

Privacy & security note for reviewers

fs:write grants the ability to move files to the OS trash. Reviewers will verify:

  • trash() is called only in response to an explicit user action, not silently on load.
  • The extension documents which files it may trash and why.
  • No extension uses trash() to destroy user data maliciously (the OS trash is recoverable, but the action still warrants scrutiny).