@sosweetham/tauri-plugin-sharehub-api
v0.1.3
Published
Share content to and from Tauri 2 apps through the native OS share UI: share out (text/files) and receive shares in (text, links, images, files) as a share target. iOS, Android, macOS, Windows.
Maintainers
Readme
tauri-plugin-sharehub
Bidirectional native sharing for Tauri 2 apps: share text and files out to other apps, and receive text, links, images, and files shared in from other apps as a share target.
Bidirectional
Most share plugins only push content out. sharehub does both:
- Share OUT. Open the system share sheet to hand text or a file to another app.
Backed by
UIActivityViewController(iOS),ACTION_SEND(Android),NSSharingServicePicker(macOS), andDataTransferManager(Windows). - Share IN. Register your app as a share target so other apps can send it text,
links, images, and files. On iOS a copyable Share Extension writes the shared blobs
plus a
manifest.jsoninto a shared App Group container and opens your app via a deep link. On Android the plugin copies theACTION_SEND/ACTION_SEND_MULTIPLEstreams into the app's files directory. Either way your app reads the queue withgetPendingShares()and pulls bytes lazily withreadSharedItem(id).
Platform support
| Capability | iOS | Android | macOS | Windows | Linux | | ----------------- | --- | ------- | ----- | ------- | ----- | | Share text (OUT) | yes | yes | yes | yes | no | | Share file (OUT) | yes | yes | yes | yes | no | | Receive shares (IN) | yes | yes | no | no | no |
Desktop receive is a graceful no-op: getPendingShares() resolves to an empty list
rather than throwing, so the same code runs everywhere. On Linux the share-out calls
are no-ops.
Install
Add the Rust crate to your Tauri app:
cargo add tauri-plugin-sharehubAdd the JavaScript guest bindings with your package manager of choice:
pnpm add @sosweetham/tauri-plugin-sharehub-api
# or: npm add @sosweetham/tauri-plugin-sharehub-api
# or: yarn add @sosweetham/tauri-plugin-sharehub-apiRegister the plugin in src-tauri/src/lib.rs:
// src-tauri/src/lib.rs
tauri::Builder::default()
.plugin(tauri_plugin_sharehub::init())
// ...Grant the default permission set in your capability file, e.g.
src-tauri/capabilities/default.json:
{
"permissions": ["sharehub:default"]
}sharehub:default allows every command: share_text, share_file,
get_pending_shares, read_shared_item, and clear_pending_shares.
Usage: share OUT
import { shareText, shareFile } from "@sosweetham/tauri-plugin-sharehub-api";
// Share plain text.
await shareText("Pendi is great!");
// Share a file by file:// URL.
await shareFile("file:///path/to/document.pdf", {
mimeType: "application/pdf",
title: "My Document",
});
// Anchor the share sheet (iPad / macOS, in webview coordinates).
await shareText("Hello!", {
position: { x: 100, y: 200, preferredEdge: "bottom" },
});Usage: share IN
Your app receives shares by reading a queue. The reliable mechanism is to call
getPendingShares() on launch and from your deep-link handler, then read bytes for the
items you care about and clear the queue once handled.
import {
getPendingShares,
readSharedItem,
clearPendingShares,
onShare,
} from "@sosweetham/tauri-plugin-sharehub-api";
async function consumeShares() {
const { items } = await getPendingShares();
if (items.length === 0) return;
for (const item of items) {
if (item.kind === "text" || item.kind === "url") {
console.log("shared text/link:", item.text ?? item.url);
} else {
// image / file: pull the bytes lazily and wrap them in a File.
const buf = await readSharedItem(item.id);
const file = new File([buf], item.name ?? "shared", {
type: item.mimeType,
});
// ...upload or process `file`
}
}
// Only clear once everything has been handled successfully. A mid-flow
// crash leaves the share for the next launch.
await clearPendingShares();
}
// Best-effort: fires when a share arrives while the app is already running.
// Silently no-ops where the platform has no live channel, so treat it as a
// nudge on top of the launch-time `getPendingShares()` path, not a replacement.
const unsubscribe = await onShare(() => {
void consumeShares();
});Deep-link + App Group model
iOS only lets a separate Share Extension target receive system shares, so it cannot
ship inside the plugin: it must be a target in your app. The extension copies the
shared blobs plus a manifest.json into a shared App Group container and opens your
app at <yourScheme>://share. Your app handles that deep link, navigates to a share
screen, and calls getPendingShares().
A copyable extension template (ShareViewController.swift, Info.plist,
ShareExt.entitlements) and full setup steps live in
extensions/ios/README.md. The key gotchas: the same App
Group must be on both the host app and the extension entitlements and registered on your
Apple Developer account, and the extension's appScheme must match your
tauri-plugin-deep-link scheme.
On Android, add an ACTION_SEND / ACTION_SEND_MULTIPLE intent filter to your main
activity in src-tauri/gen/android/app/src/main/AndroidManifest.xml so the system lists
your app as a share target, for example:
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>The plugin copies the incoming streams into the app's files directory and exposes them
through the same getPendingShares() / readSharedItem() API.
Type generation
Every shape that crosses the JS bridge is defined once in Rust, in src/models.rs, and
generated outward:
src/models.rs -> schemars JSON Schema -> Zod (guest-js/schemas.ts)Run pnpm generate-types after editing src/models.rs to regenerate
guest-js/schemas.ts (driven by the dev-only gen Cargo feature). Never edit
guest-js/schemas.ts by hand. Inbound payloads cross from arbitrary external apps
through messy native parsing, so getPendingShares() validates its result at runtime
against the generated Zod schema before returning it. Outbound option types are
hand-written in guest-js/index.ts because they are JS-constructed arguments and need
no runtime parse.
Credits
The share-OUT path is derived from Choochmeque's
tauri-plugin-sharekit (MIT).
License
MIT
