Task model

Every SMB operation returns SmbTask<T>. Creating the task starts native work immediately — you choose when to subscribe, await, or cancel.

Overview

Three optional actions on any task:

ActionWhenAPI
Get resultYou need the return valueawait task.result()
Watch progressUI needs live updatestask.subscribe() or useSubscribe
CancelUser abortstask.cancel()

Get the result

basic.ts
await smb.connect('smb://192.168.1.100/Share', creds).result();

const files = await smb.listDirectory('/', false, -1).result();
await smb.downloadFile('/doc.pdf', '/local/doc.pdf').result();

Hold the handle and await later:

deferred.ts
const task = smb.copyItem('/src', '/dst', true);
await task.result();

Batch multiple tasks — like Promise.allSettled:

batch.ts
import { SmbTask } from '@cetapod/react-native-smb';

const tasks = files.map((f) => smb.deleteItem(f.path));
const results = await SmbTask.settleAll(tasks);

Watch progress

subscribe.ts
const task = smb.downloadFile(remote, local);

// Live getters on the task object
const unsub = task.subscribe();

// Or with a callback
const unsub2 = task.subscribe((snap) => {
  console.log(snap.progress, snap.bytesPerSecond, snap.status);
});

// Read getters after subscribing
console.log(task.progress, task.status);

React — useSubscribe subscribes automatically on mount (and whenever task changes), and unsubscribes on unmount:

DownloadBar.tsx
import { useSubscribe, statusLabel } from '@cetapod/react-native-smb';

function DownloadBar({ task }) {
  const { progress, status, cancel } = useSubscribe(task);

  return (
    <>
      <Text>{Math.round(progress * 100)}%</Text>
      <Text>{statusLabel(status)}</Text>
      <Button onPress={cancel} title="Cancel" />
    </>
  );
}

The hook also returns subscribe, in case you want to attach an extra listener callback (e.g. for logging) on top of the automatic subscription:

logging.tsx
const { subscribe } = useSubscribe(task);
useEffect(() => subscribe((snap) => console.log(snap)), [subscribe]);
Don't destructure progress at creation time. task.progress and task.status are live getters on the imperative API — read them after calling task.subscribe(). useSubscribe subscribes for you automatically, so its progress/status are always live.

Snapshot fields

subscribe() and get() return SmbTaskState:

FieldDescription
progress0–1 (determinate for transfers)
statusTaskStatus enum
bytesDone / totalBytesRaw byte counts
bytesPerSecond / etaSecondsThroughput estimate
determinatetrue for download/upload/copy
sourcePath / destinationPathPaths for transfer ops
errorCode / errorMessageSet on failure

Cancel

cancel.ts
const task = smb.downloadFile(remote, local);
task.subscribe();

// User taps cancel
task.cancel();
unsub();

Cancelled tasks reject with SmbTaskError where code === SmbError.Cancelled.

Fire-and-forget

Start work without awaiting — useful for bulk ops with a transfer tray:

bulk.ts
files.forEach((f) => smb.downloadFile(f.remote, f.local));

// Reload when all settle
const tasks = files.map((f) => smb.deleteItem(f.path));
void SmbTask.settleAll(tasks).then(() => reload());

Transfer tray

TransferTray.tsx
import { useTransfers, useTransferActions } from '@cetapod/react-native-smb';

function TransferTray({ smb }) {
  const tasks = useTransfers(smb);
  const { clearCompleted, hide } = useTransferActions(smb);

  // tasks is SmbTaskState[] — all active transfers
}

See the example app's TransferTrayPanel for a complete implementation.

Error handling

errors.ts
import { SmbError, SmbTaskError } from '@cetapod/react-native-smb';

try {
  await smb.getPathInfo('/nonexistent').result();
} catch (error) {
  if (error instanceof SmbTaskError) {
    if (error.code === SmbError.NotFound) { /* ... */ }
    if (error.code === SmbError.AccessDenied) { /* ... */ }
  }
}

See SmbError for all error codes.

Lifecycle

When the user logs out or you're done with a client, call dispose():

lifecycle.ts
smb.dispose(); // stops transfer store listener

Sync state checks (isConnected, isInitialized) are available anytime without a task.