@sosweetham/tauri-plugin-sharehub-api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +197 -0
- package/dist-js/index.cjs +105 -0
- package/dist-js/index.d.ts +82 -0
- package/dist-js/index.js +98 -0
- package/dist-js/schemas.d.ts +49 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SoSweetHam
|
|
4
|
+
Portions Copyright (c) 2025 Vladimir Pankratov (the share-out implementation,
|
|
5
|
+
derived from tauri-plugin-sharekit, MIT)
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
[](https://crates.io/crates/tauri-plugin-sharehub)
|
|
2
|
+
[](https://www.npmjs.com/package/@sosweetham/tauri-plugin-sharehub-api)
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
|
|
5
|
+
# tauri-plugin-sharehub
|
|
6
|
+
|
|
7
|
+
Bidirectional native sharing for [Tauri 2](https://tauri.app) apps: share text and
|
|
8
|
+
files **out** to other apps, and receive text, links, images, and files shared **in**
|
|
9
|
+
from other apps as a share target.
|
|
10
|
+
|
|
11
|
+
## Bidirectional
|
|
12
|
+
|
|
13
|
+
Most share plugins only push content out. `sharehub` does both:
|
|
14
|
+
|
|
15
|
+
- **Share OUT.** Open the system share sheet to hand text or a file to another app.
|
|
16
|
+
Backed by `UIActivityViewController` (iOS), `ACTION_SEND` (Android),
|
|
17
|
+
`NSSharingServicePicker` (macOS), and `DataTransferManager` (Windows).
|
|
18
|
+
- **Share IN.** Register your app as a share target so other apps can send it text,
|
|
19
|
+
links, images, and files. On iOS a copyable Share Extension writes the shared blobs
|
|
20
|
+
plus a `manifest.json` into a shared App Group container and opens your app via a
|
|
21
|
+
deep link. On Android the plugin copies the `ACTION_SEND` / `ACTION_SEND_MULTIPLE`
|
|
22
|
+
streams into the app's files directory. Either way your app reads the queue with
|
|
23
|
+
`getPendingShares()` and pulls bytes lazily with `readSharedItem(id)`.
|
|
24
|
+
|
|
25
|
+
## Platform support
|
|
26
|
+
|
|
27
|
+
| Capability | iOS | Android | macOS | Windows | Linux |
|
|
28
|
+
| ----------------- | --- | ------- | ----- | ------- | ----- |
|
|
29
|
+
| Share text (OUT) | yes | yes | yes | yes | no |
|
|
30
|
+
| Share file (OUT) | yes | yes | yes | yes | no |
|
|
31
|
+
| Receive shares (IN) | yes | yes | no | no | no |
|
|
32
|
+
|
|
33
|
+
Desktop receive is a graceful no-op: `getPendingShares()` resolves to an empty list
|
|
34
|
+
rather than throwing, so the same code runs everywhere. On Linux the share-out calls
|
|
35
|
+
are no-ops.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
Add the Rust crate to your Tauri app:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
cargo add tauri-plugin-sharehub
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Add the JavaScript guest bindings with your package manager of choice:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
pnpm add @sosweetham/tauri-plugin-sharehub-api
|
|
49
|
+
# or: npm add @sosweetham/tauri-plugin-sharehub-api
|
|
50
|
+
# or: yarn add @sosweetham/tauri-plugin-sharehub-api
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Register the plugin in `src-tauri/src/lib.rs`:
|
|
54
|
+
|
|
55
|
+
```rust
|
|
56
|
+
// src-tauri/src/lib.rs
|
|
57
|
+
tauri::Builder::default()
|
|
58
|
+
.plugin(tauri_plugin_sharehub::init())
|
|
59
|
+
// ...
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Grant the default permission set in your capability file, e.g.
|
|
63
|
+
`src-tauri/capabilities/default.json`:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"permissions": ["sharehub:default"]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`sharehub:default` allows every command: `share_text`, `share_file`,
|
|
72
|
+
`get_pending_shares`, `read_shared_item`, and `clear_pending_shares`.
|
|
73
|
+
|
|
74
|
+
## Usage: share OUT
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { shareText, shareFile } from "@sosweetham/tauri-plugin-sharehub-api";
|
|
78
|
+
|
|
79
|
+
// Share plain text.
|
|
80
|
+
await shareText("Pendi is great!");
|
|
81
|
+
|
|
82
|
+
// Share a file by file:// URL.
|
|
83
|
+
await shareFile("file:///path/to/document.pdf", {
|
|
84
|
+
mimeType: "application/pdf",
|
|
85
|
+
title: "My Document",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Anchor the share sheet (iPad / macOS, in webview coordinates).
|
|
89
|
+
await shareText("Hello!", {
|
|
90
|
+
position: { x: 100, y: 200, preferredEdge: "bottom" },
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Usage: share IN
|
|
95
|
+
|
|
96
|
+
Your app receives shares by reading a queue. The reliable mechanism is to call
|
|
97
|
+
`getPendingShares()` on launch and from your deep-link handler, then read bytes for the
|
|
98
|
+
items you care about and clear the queue once handled.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import {
|
|
102
|
+
getPendingShares,
|
|
103
|
+
readSharedItem,
|
|
104
|
+
clearPendingShares,
|
|
105
|
+
onShare,
|
|
106
|
+
} from "@sosweetham/tauri-plugin-sharehub-api";
|
|
107
|
+
|
|
108
|
+
async function consumeShares() {
|
|
109
|
+
const { items } = await getPendingShares();
|
|
110
|
+
if (items.length === 0) return;
|
|
111
|
+
|
|
112
|
+
for (const item of items) {
|
|
113
|
+
if (item.kind === "text" || item.kind === "url") {
|
|
114
|
+
console.log("shared text/link:", item.text ?? item.url);
|
|
115
|
+
} else {
|
|
116
|
+
// image / file: pull the bytes lazily and wrap them in a File.
|
|
117
|
+
const buf = await readSharedItem(item.id);
|
|
118
|
+
const file = new File([buf], item.name ?? "shared", {
|
|
119
|
+
type: item.mimeType,
|
|
120
|
+
});
|
|
121
|
+
// ...upload or process `file`
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Only clear once everything has been handled successfully. A mid-flow
|
|
126
|
+
// crash leaves the share for the next launch.
|
|
127
|
+
await clearPendingShares();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Best-effort: fires when a share arrives while the app is already running.
|
|
131
|
+
// Silently no-ops where the platform has no live channel, so treat it as a
|
|
132
|
+
// nudge on top of the launch-time `getPendingShares()` path, not a replacement.
|
|
133
|
+
const unsubscribe = await onShare(() => {
|
|
134
|
+
void consumeShares();
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Deep-link + App Group model
|
|
139
|
+
|
|
140
|
+
iOS only lets a separate **Share Extension** target receive system shares, so it cannot
|
|
141
|
+
ship inside the plugin: it must be a target in *your* app. The extension copies the
|
|
142
|
+
shared blobs plus a `manifest.json` into a shared **App Group** container and opens your
|
|
143
|
+
app at `<yourScheme>://share`. Your app handles that deep link, navigates to a share
|
|
144
|
+
screen, and calls `getPendingShares()`.
|
|
145
|
+
|
|
146
|
+
A copyable extension template (`ShareViewController.swift`, `Info.plist`,
|
|
147
|
+
`ShareExt.entitlements`) and full setup steps live in
|
|
148
|
+
[`extensions/ios/README.md`](extensions/ios/README.md). The key gotchas: the same App
|
|
149
|
+
Group must be on both the host app and the extension entitlements and registered on your
|
|
150
|
+
Apple Developer account, and the extension's `appScheme` must match your
|
|
151
|
+
`tauri-plugin-deep-link` scheme.
|
|
152
|
+
|
|
153
|
+
On **Android**, add an `ACTION_SEND` / `ACTION_SEND_MULTIPLE` intent filter to your main
|
|
154
|
+
activity in `src-tauri/gen/android/app/src/main/AndroidManifest.xml` so the system lists
|
|
155
|
+
your app as a share target, for example:
|
|
156
|
+
|
|
157
|
+
```xml
|
|
158
|
+
<intent-filter>
|
|
159
|
+
<action android:name="android.intent.action.SEND" />
|
|
160
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
161
|
+
<data android:mimeType="*/*" />
|
|
162
|
+
</intent-filter>
|
|
163
|
+
<intent-filter>
|
|
164
|
+
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
|
165
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
166
|
+
<data android:mimeType="*/*" />
|
|
167
|
+
</intent-filter>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The plugin copies the incoming streams into the app's files directory and exposes them
|
|
171
|
+
through the same `getPendingShares()` / `readSharedItem()` API.
|
|
172
|
+
|
|
173
|
+
## Type generation
|
|
174
|
+
|
|
175
|
+
Every shape that crosses the JS bridge is defined once in Rust, in `src/models.rs`, and
|
|
176
|
+
generated outward:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
src/models.rs -> schemars JSON Schema -> Zod (guest-js/schemas.ts)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Run `pnpm generate-types` after editing `src/models.rs` to regenerate
|
|
183
|
+
`guest-js/schemas.ts` (driven by the dev-only `gen` Cargo feature). Never edit
|
|
184
|
+
`guest-js/schemas.ts` by hand. Inbound payloads cross from arbitrary external apps
|
|
185
|
+
through messy native parsing, so `getPendingShares()` validates its result at runtime
|
|
186
|
+
against the generated Zod schema before returning it. Outbound option types are
|
|
187
|
+
hand-written in `guest-js/index.ts` because they are JS-constructed arguments and need
|
|
188
|
+
no runtime parse.
|
|
189
|
+
|
|
190
|
+
## Credits
|
|
191
|
+
|
|
192
|
+
The share-OUT path is derived from Choochmeque's
|
|
193
|
+
[`tauri-plugin-sharekit`](https://github.com/Choochmeque/tauri-plugin-sharekit) (MIT).
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@tauri-apps/api/core');
|
|
4
|
+
var zod = require('zod');
|
|
5
|
+
|
|
6
|
+
// GENERATED by `pnpm generate-types` (schemars JSON Schema -> Zod). Do not edit.
|
|
7
|
+
// Source of truth: src/models.rs (SharePayload). Validates inbound share payloads.
|
|
8
|
+
const sharePayloadSchema = zod.z.object({ "items": zod.z.array(zod.z.object({ "id": zod.z.string().describe("Stable id for this item within the current pending batch."), "kind": zod.z.enum(["text", "url", "image", "file"]).describe("Discriminates how to interpret the remaining fields."), "mimeType": zod.z.union([zod.z.string().describe("Best-effort MIME type for `image` / `file` items."), zod.z.null().describe("Best-effort MIME type for `image` / `file` items.")]).describe("Best-effort MIME type for `image` / `file` items.").optional(), "name": zod.z.union([zod.z.string().describe("Original display name for `image` / `file` items."), zod.z.null().describe("Original display name for `image` / `file` items.")]).describe("Original display name for `image` / `file` items.").optional(), "size": zod.z.union([zod.z.number().int().gte(0).describe("Size in bytes for `image` / `file` items, when known."), zod.z.null().describe("Size in bytes for `image` / `file` items, when known.")]).describe("Size in bytes for `image` / `file` items, when known.").optional(), "text": zod.z.union([zod.z.string().describe("Present for `text` / `url` items."), zod.z.null().describe("Present for `text` / `url` items.")]).describe("Present for `text` / `url` items.").optional(), "url": zod.z.union([zod.z.string().describe("Present for `url` items (the shared link)."), zod.z.null().describe("Present for `url` items (the shared link).")]).describe("Present for `url` items (the shared link).").optional() }).describe("One item handed to the app by an external app's share action. **Metadata only** — bytes are fetched lazily via `read_shared_item(id)`. This is the public wire shape crossing the JS bridge (the on-disk path stays behind the Rust boundary).")) }).describe("The set of items currently waiting to be consumed by the app.");
|
|
9
|
+
|
|
10
|
+
// ----- Share OUT -----
|
|
11
|
+
/**
|
|
12
|
+
* Opens the native sharing interface to share the specified text.
|
|
13
|
+
*
|
|
14
|
+
* ```javascript
|
|
15
|
+
* import { shareText } from "@sosweetham/tauri-plugin-sharehub-api";
|
|
16
|
+
* await shareText('I am a shared message');
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
async function shareText(text, options) {
|
|
20
|
+
await core.invoke("plugin:sharehub|share_text", {
|
|
21
|
+
text,
|
|
22
|
+
...options,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Opens the native sharing interface to share a file.
|
|
27
|
+
*
|
|
28
|
+
* ```javascript
|
|
29
|
+
* import { shareFile } from "@sosweetham/tauri-plugin-sharehub-api";
|
|
30
|
+
* await shareFile('file:///path/to/file.pdf', {
|
|
31
|
+
* mimeType: 'application/pdf',
|
|
32
|
+
* title: 'Document.pdf'
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
* @param url - The file URL to share (must be a file:// URL)
|
|
36
|
+
* @param options - Optional settings including MIME type and title
|
|
37
|
+
*/
|
|
38
|
+
async function shareFile(url, options) {
|
|
39
|
+
await core.invoke("plugin:sharehub|share_file", {
|
|
40
|
+
url,
|
|
41
|
+
...options,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// ----- Share IN (receive / share-target) -----
|
|
45
|
+
/**
|
|
46
|
+
* Returns the items currently waiting to be consumed by this app (metadata only).
|
|
47
|
+
* Does **not** clear the queue — call {@link clearPendingShares} once handled, so a
|
|
48
|
+
* mid-flow crash leaves the share for next launch.
|
|
49
|
+
*
|
|
50
|
+
* The result is validated against {@link sharePayloadSchema} (generated from the Rust
|
|
51
|
+
* models): this is the one boundary where data originates from arbitrary external apps
|
|
52
|
+
* via messy native parsing, so the runtime check earns its keep. On platforms without
|
|
53
|
+
* an in-app share target (desktop) this resolves to an empty list rather than throwing.
|
|
54
|
+
*/
|
|
55
|
+
async function getPendingShares() {
|
|
56
|
+
const raw = await core.invoke("plugin:sharehub|get_pending_shares");
|
|
57
|
+
return sharePayloadSchema.parse(raw);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reads the raw bytes of a pending `image`/`file` item by id, returned as an
|
|
61
|
+
* `ArrayBuffer`. Wrap it in a `File`/`Blob` to upload:
|
|
62
|
+
*
|
|
63
|
+
* ```javascript
|
|
64
|
+
* const buf = await readSharedItem(item.id);
|
|
65
|
+
* const file = new File([buf], item.name ?? "shared", { type: item.mimeType });
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
async function readSharedItem(id) {
|
|
69
|
+
return await core.invoke("plugin:sharehub|read_shared_item", { id });
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Clears the pending queue and removes the copied blobs. Call only after the items
|
|
73
|
+
* have been successfully handled.
|
|
74
|
+
*/
|
|
75
|
+
async function clearPendingShares() {
|
|
76
|
+
await core.invoke("plugin:sharehub|clear_pending_shares");
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Best-effort subscription for shares that arrive while the app is already running.
|
|
80
|
+
*
|
|
81
|
+
* The **reliable** mechanism is {@link getPendingShares} on launch plus your app's
|
|
82
|
+
* deep-link handler (the share extension / intent opens your app, you navigate to a
|
|
83
|
+
* share screen and call `getPendingShares`). This listener is only a foreground
|
|
84
|
+
* nudge and silently no-ops where the platform has no live channel.
|
|
85
|
+
*
|
|
86
|
+
* @returns an unsubscribe function.
|
|
87
|
+
*/
|
|
88
|
+
async function onShare(cb) {
|
|
89
|
+
try {
|
|
90
|
+
const listener = await core.addPluginListener("sharehub", "received", (payload) => cb(payload));
|
|
91
|
+
return () => {
|
|
92
|
+
void listener.unregister();
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return () => { };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
exports.clearPendingShares = clearPendingShares;
|
|
101
|
+
exports.getPendingShares = getPendingShares;
|
|
102
|
+
exports.onShare = onShare;
|
|
103
|
+
exports.readSharedItem = readSharedItem;
|
|
104
|
+
exports.shareFile = shareFile;
|
|
105
|
+
exports.shareText = shareText;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type SharePayload } from "./schemas";
|
|
2
|
+
export type { SharePayload };
|
|
3
|
+
export type SharedItem = SharePayload["items"][number];
|
|
4
|
+
export type SharedItemKind = SharedItem["kind"];
|
|
5
|
+
export interface SharePosition {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
/** macOS only: which edge the picker appears from */
|
|
9
|
+
preferredEdge?: "top" | "bottom" | "left" | "right";
|
|
10
|
+
}
|
|
11
|
+
export interface ShareTextOptions {
|
|
12
|
+
/** Android only */
|
|
13
|
+
mimeType?: string;
|
|
14
|
+
/** Position for the share sheet (iPad/macOS only) */
|
|
15
|
+
position?: SharePosition;
|
|
16
|
+
}
|
|
17
|
+
export interface ShareFileOptions {
|
|
18
|
+
mimeType?: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
/** Position for the share sheet (iPad/macOS only) */
|
|
21
|
+
position?: SharePosition;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Opens the native sharing interface to share the specified text.
|
|
25
|
+
*
|
|
26
|
+
* ```javascript
|
|
27
|
+
* import { shareText } from "@sosweetham/tauri-plugin-sharehub-api";
|
|
28
|
+
* await shareText('I am a shared message');
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function shareText(text: string, options?: ShareTextOptions): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Opens the native sharing interface to share a file.
|
|
34
|
+
*
|
|
35
|
+
* ```javascript
|
|
36
|
+
* import { shareFile } from "@sosweetham/tauri-plugin-sharehub-api";
|
|
37
|
+
* await shareFile('file:///path/to/file.pdf', {
|
|
38
|
+
* mimeType: 'application/pdf',
|
|
39
|
+
* title: 'Document.pdf'
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
* @param url - The file URL to share (must be a file:// URL)
|
|
43
|
+
* @param options - Optional settings including MIME type and title
|
|
44
|
+
*/
|
|
45
|
+
export declare function shareFile(url: string, options?: ShareFileOptions): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Returns the items currently waiting to be consumed by this app (metadata only).
|
|
48
|
+
* Does **not** clear the queue — call {@link clearPendingShares} once handled, so a
|
|
49
|
+
* mid-flow crash leaves the share for next launch.
|
|
50
|
+
*
|
|
51
|
+
* The result is validated against {@link sharePayloadSchema} (generated from the Rust
|
|
52
|
+
* models): this is the one boundary where data originates from arbitrary external apps
|
|
53
|
+
* via messy native parsing, so the runtime check earns its keep. On platforms without
|
|
54
|
+
* an in-app share target (desktop) this resolves to an empty list rather than throwing.
|
|
55
|
+
*/
|
|
56
|
+
export declare function getPendingShares(): Promise<SharePayload>;
|
|
57
|
+
/**
|
|
58
|
+
* Reads the raw bytes of a pending `image`/`file` item by id, returned as an
|
|
59
|
+
* `ArrayBuffer`. Wrap it in a `File`/`Blob` to upload:
|
|
60
|
+
*
|
|
61
|
+
* ```javascript
|
|
62
|
+
* const buf = await readSharedItem(item.id);
|
|
63
|
+
* const file = new File([buf], item.name ?? "shared", { type: item.mimeType });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function readSharedItem(id: string): Promise<ArrayBuffer>;
|
|
67
|
+
/**
|
|
68
|
+
* Clears the pending queue and removes the copied blobs. Call only after the items
|
|
69
|
+
* have been successfully handled.
|
|
70
|
+
*/
|
|
71
|
+
export declare function clearPendingShares(): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Best-effort subscription for shares that arrive while the app is already running.
|
|
74
|
+
*
|
|
75
|
+
* The **reliable** mechanism is {@link getPendingShares} on launch plus your app's
|
|
76
|
+
* deep-link handler (the share extension / intent opens your app, you navigate to a
|
|
77
|
+
* share screen and call `getPendingShares`). This listener is only a foreground
|
|
78
|
+
* nudge and silently no-ops where the platform has no live channel.
|
|
79
|
+
*
|
|
80
|
+
* @returns an unsubscribe function.
|
|
81
|
+
*/
|
|
82
|
+
export declare function onShare(cb: (payload: SharePayload) => void): Promise<() => void>;
|
package/dist-js/index.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { invoke, addPluginListener } from '@tauri-apps/api/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
// GENERATED by `pnpm generate-types` (schemars JSON Schema -> Zod). Do not edit.
|
|
5
|
+
// Source of truth: src/models.rs (SharePayload). Validates inbound share payloads.
|
|
6
|
+
const sharePayloadSchema = z.object({ "items": z.array(z.object({ "id": z.string().describe("Stable id for this item within the current pending batch."), "kind": z.enum(["text", "url", "image", "file"]).describe("Discriminates how to interpret the remaining fields."), "mimeType": z.union([z.string().describe("Best-effort MIME type for `image` / `file` items."), z.null().describe("Best-effort MIME type for `image` / `file` items.")]).describe("Best-effort MIME type for `image` / `file` items.").optional(), "name": z.union([z.string().describe("Original display name for `image` / `file` items."), z.null().describe("Original display name for `image` / `file` items.")]).describe("Original display name for `image` / `file` items.").optional(), "size": z.union([z.number().int().gte(0).describe("Size in bytes for `image` / `file` items, when known."), z.null().describe("Size in bytes for `image` / `file` items, when known.")]).describe("Size in bytes for `image` / `file` items, when known.").optional(), "text": z.union([z.string().describe("Present for `text` / `url` items."), z.null().describe("Present for `text` / `url` items.")]).describe("Present for `text` / `url` items.").optional(), "url": z.union([z.string().describe("Present for `url` items (the shared link)."), z.null().describe("Present for `url` items (the shared link).")]).describe("Present for `url` items (the shared link).").optional() }).describe("One item handed to the app by an external app's share action. **Metadata only** — bytes are fetched lazily via `read_shared_item(id)`. This is the public wire shape crossing the JS bridge (the on-disk path stays behind the Rust boundary).")) }).describe("The set of items currently waiting to be consumed by the app.");
|
|
7
|
+
|
|
8
|
+
// ----- Share OUT -----
|
|
9
|
+
/**
|
|
10
|
+
* Opens the native sharing interface to share the specified text.
|
|
11
|
+
*
|
|
12
|
+
* ```javascript
|
|
13
|
+
* import { shareText } from "@sosweetham/tauri-plugin-sharehub-api";
|
|
14
|
+
* await shareText('I am a shared message');
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
async function shareText(text, options) {
|
|
18
|
+
await invoke("plugin:sharehub|share_text", {
|
|
19
|
+
text,
|
|
20
|
+
...options,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Opens the native sharing interface to share a file.
|
|
25
|
+
*
|
|
26
|
+
* ```javascript
|
|
27
|
+
* import { shareFile } from "@sosweetham/tauri-plugin-sharehub-api";
|
|
28
|
+
* await shareFile('file:///path/to/file.pdf', {
|
|
29
|
+
* mimeType: 'application/pdf',
|
|
30
|
+
* title: 'Document.pdf'
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
* @param url - The file URL to share (must be a file:// URL)
|
|
34
|
+
* @param options - Optional settings including MIME type and title
|
|
35
|
+
*/
|
|
36
|
+
async function shareFile(url, options) {
|
|
37
|
+
await invoke("plugin:sharehub|share_file", {
|
|
38
|
+
url,
|
|
39
|
+
...options,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// ----- Share IN (receive / share-target) -----
|
|
43
|
+
/**
|
|
44
|
+
* Returns the items currently waiting to be consumed by this app (metadata only).
|
|
45
|
+
* Does **not** clear the queue — call {@link clearPendingShares} once handled, so a
|
|
46
|
+
* mid-flow crash leaves the share for next launch.
|
|
47
|
+
*
|
|
48
|
+
* The result is validated against {@link sharePayloadSchema} (generated from the Rust
|
|
49
|
+
* models): this is the one boundary where data originates from arbitrary external apps
|
|
50
|
+
* via messy native parsing, so the runtime check earns its keep. On platforms without
|
|
51
|
+
* an in-app share target (desktop) this resolves to an empty list rather than throwing.
|
|
52
|
+
*/
|
|
53
|
+
async function getPendingShares() {
|
|
54
|
+
const raw = await invoke("plugin:sharehub|get_pending_shares");
|
|
55
|
+
return sharePayloadSchema.parse(raw);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Reads the raw bytes of a pending `image`/`file` item by id, returned as an
|
|
59
|
+
* `ArrayBuffer`. Wrap it in a `File`/`Blob` to upload:
|
|
60
|
+
*
|
|
61
|
+
* ```javascript
|
|
62
|
+
* const buf = await readSharedItem(item.id);
|
|
63
|
+
* const file = new File([buf], item.name ?? "shared", { type: item.mimeType });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
async function readSharedItem(id) {
|
|
67
|
+
return await invoke("plugin:sharehub|read_shared_item", { id });
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Clears the pending queue and removes the copied blobs. Call only after the items
|
|
71
|
+
* have been successfully handled.
|
|
72
|
+
*/
|
|
73
|
+
async function clearPendingShares() {
|
|
74
|
+
await invoke("plugin:sharehub|clear_pending_shares");
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Best-effort subscription for shares that arrive while the app is already running.
|
|
78
|
+
*
|
|
79
|
+
* The **reliable** mechanism is {@link getPendingShares} on launch plus your app's
|
|
80
|
+
* deep-link handler (the share extension / intent opens your app, you navigate to a
|
|
81
|
+
* share screen and call `getPendingShares`). This listener is only a foreground
|
|
82
|
+
* nudge and silently no-ops where the platform has no live channel.
|
|
83
|
+
*
|
|
84
|
+
* @returns an unsubscribe function.
|
|
85
|
+
*/
|
|
86
|
+
async function onShare(cb) {
|
|
87
|
+
try {
|
|
88
|
+
const listener = await addPluginListener("sharehub", "received", (payload) => cb(payload));
|
|
89
|
+
return () => {
|
|
90
|
+
void listener.unregister();
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return () => { };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { clearPendingShares, getPendingShares, onShare, readSharedItem, shareFile, shareText };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const sharePayloadSchema: z.ZodObject<{
|
|
3
|
+
items: z.ZodArray<z.ZodObject<{
|
|
4
|
+
id: z.ZodString;
|
|
5
|
+
kind: z.ZodEnum<["text", "url", "image", "file"]>;
|
|
6
|
+
mimeType: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNull]>>;
|
|
7
|
+
name: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNull]>>;
|
|
8
|
+
size: z.ZodOptional<z.ZodUnion<[z.ZodNumber, z.ZodNull]>>;
|
|
9
|
+
text: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNull]>>;
|
|
10
|
+
url: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNull]>>;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
id: string;
|
|
13
|
+
kind: "text" | "url" | "image" | "file";
|
|
14
|
+
text?: string | null | undefined;
|
|
15
|
+
url?: string | null | undefined;
|
|
16
|
+
mimeType?: string | null | undefined;
|
|
17
|
+
name?: string | null | undefined;
|
|
18
|
+
size?: number | null | undefined;
|
|
19
|
+
}, {
|
|
20
|
+
id: string;
|
|
21
|
+
kind: "text" | "url" | "image" | "file";
|
|
22
|
+
text?: string | null | undefined;
|
|
23
|
+
url?: string | null | undefined;
|
|
24
|
+
mimeType?: string | null | undefined;
|
|
25
|
+
name?: string | null | undefined;
|
|
26
|
+
size?: number | null | undefined;
|
|
27
|
+
}>, "many">;
|
|
28
|
+
}, "strip", z.ZodTypeAny, {
|
|
29
|
+
items: {
|
|
30
|
+
id: string;
|
|
31
|
+
kind: "text" | "url" | "image" | "file";
|
|
32
|
+
text?: string | null | undefined;
|
|
33
|
+
url?: string | null | undefined;
|
|
34
|
+
mimeType?: string | null | undefined;
|
|
35
|
+
name?: string | null | undefined;
|
|
36
|
+
size?: number | null | undefined;
|
|
37
|
+
}[];
|
|
38
|
+
}, {
|
|
39
|
+
items: {
|
|
40
|
+
id: string;
|
|
41
|
+
kind: "text" | "url" | "image" | "file";
|
|
42
|
+
text?: string | null | undefined;
|
|
43
|
+
url?: string | null | undefined;
|
|
44
|
+
mimeType?: string | null | undefined;
|
|
45
|
+
name?: string | null | undefined;
|
|
46
|
+
size?: number | null | undefined;
|
|
47
|
+
}[];
|
|
48
|
+
}>;
|
|
49
|
+
export type SharePayload = z.infer<typeof sharePayloadSchema>;
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sosweetham/tauri-plugin-sharehub-api",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "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.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "SoSweetHam <sosweetham@gmail.com>",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"types": "./dist-js/index.d.ts",
|
|
9
|
+
"main": "./dist-js/index.cjs",
|
|
10
|
+
"module": "./dist-js/index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
"types": "./dist-js/index.d.ts",
|
|
13
|
+
"import": "./dist-js/index.js",
|
|
14
|
+
"require": "./dist-js/index.cjs"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist-js",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/sosweetham/tauri-plugin-sharehub.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/sosweetham/tauri-plugin-sharehub#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/sosweetham/tauri-plugin-sharehub/issues"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"tauri",
|
|
31
|
+
"tauri-plugin",
|
|
32
|
+
"share",
|
|
33
|
+
"share-target",
|
|
34
|
+
"share-sheet",
|
|
35
|
+
"ios",
|
|
36
|
+
"android",
|
|
37
|
+
"macos",
|
|
38
|
+
"windows"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@tauri-apps/api": ">=2.0.0",
|
|
48
|
+
"zod": "^3.23.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
52
|
+
"json-schema-to-zod": "^2.6.0",
|
|
53
|
+
"rollup": "^4.9.6",
|
|
54
|
+
"tslib": "^2.6.2",
|
|
55
|
+
"typescript": "^5.3.3"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "rollup -c",
|
|
59
|
+
"check-types": "tsc --noEmit",
|
|
60
|
+
"generate-types": "node scripts/gen-zod.mjs"
|
|
61
|
+
}
|
|
62
|
+
}
|