Skip to content
Shiny.NET
Commercial SupportGuaranteed response times on Shiny bug fixes, straight from the maintainers. Plans from US$1,500 a year.See the plans

Share Target

NuGet package Shiny.AppDeviceBridge.ShareTarget

The share target bridge makes the app a place other apps can share to. What they send — text, a link, files — waits in a queue for the page, with the files copied into a file root.

This is the receiving side. To share from the page, use device/share on the App & Device bridge.

Terminal window
dotnet add package Shiny.AppDeviceBridge.ShareTarget
builder.UseAppDeviceBridge(
bridge => bridge.AddShareTargetBridge(),
webApp => { /* … */ }
);
Client C# TypeScript
IShareTargetBridge AddShareTargetBridgeClient() ShareTargetBridge

Each share is a SharedContent: an id, when it arrived, its text, subject and url, and its files. The page reads the queue when it boots, listens for sharetarget.received while it’s open, and removes each share once it has acted on it. That way a share that launched the app isn’t missed, and one seen live isn’t handled twice after a reload.

const shares = new ShareTargetBridge();
async function handle(share: SharedContent) {
if (share.url) await saveBookmark(share.url, share.subject);
for (const f of share.files) await importFile(f.file); // read through /_bridge/files
await shares.remove(share.id); // deletes its files too
}
for (const share of await shares.getPending()) await handle(share);
shares.onReceived(handle);
Topic Details
Queue every share is kept, oldest first, across restarts, until the page removes it. Past MaxPending (20) the oldest is dropped with its files.
Links url is only ever an absolute http or https URL, so the page can open it. Text that is nothing but such a link arrives as the url, which is how Android and most share sheets send links. Any other scheme (javascript:, file:, an app’s own) is kept as text.
Files copied into Root (data by default) under shares/{id}/, at most MaxFiles (20) per share, each within the server’s MaxFileWriteBytes. Names are cleaned to one safe path segment. A file that can’t be read or is too large is left out, and the rest of the share is kept.
Removing remove deletes the share’s files. Move a file somewhere else through the files bridge first to keep it.
No file root if Root doesn’t exist, the bridge answers 501, and shares keep their text and link but drop their files.
Background shares never go to background.js. The OS brings the app to the foreground to receive one.

Options:

Option Default Why
Root data Where shared files go. cache suits an app that acts on a share straight away.
MaxPending 20 The most shares kept waiting.
MaxFiles 20 The most files taken from one share.
MaxTextLength 65,536 Longer text is cut.
AcceptCommandLineFiles false Linux only: treat files on the command line as a share.

Platform setup. The OS only offers the app for what it declares:

Platform Details
Android ACTION_SEND and ACTION_SEND_MULTIPLE intent filters, with the MIME types to accept, on a SingleTop main activity. Text, links and files. Only content: files are taken: a file: path could name the app’s own private files.
iOS / Mac Catalyst CFBundleDocumentTypes for the file types to accept: the app appears under “Open in” and “Copy to”, and the files arrive through OpenUrl. Text and links from the share sheet need a share extension. The bridge doesn’t ship one, but an app with its own extension can hand content over with ShareTarget.ReceiveAsync.
Windows a packaged app with the Share Target declaration in its manifest: text, links and files. File type associations deliver files too.
maui-labs AppKit the head overrides OpenUrls and passes file URLs to ShareTarget.ReceiveAsync, because files that launch the app arrive before MauiProgram.
Linux %F in the .desktop file’s Exec line, plus o.AcceptCommandLineFiles = true. Cold starts only.
// maui-labs AppKit head
public override void OpenUrls(NSApplication application, NSUrl[] urls)
=> _ = ShareTarget.ReceiveAsync(new IncomingShare(
Files: [.. urls.Where(x => x.IsFileUrl).Select(x => IncomingShareFile.FromPath(x.Path!))]
));
GET/_bridge/sharetarget/pending#

The shares waiting for the page, oldest first.

C#IShareTargetBridge.GetPendingAsyncTSShareTargetBridge.getPending
Response
200 SharedContent[]
SharedContent · 6 fields
idstringNames the share for get and remove.
receivedAtstringWhen the app received it.
textstring | nullShared text. Text that is nothing but an http or https link is in url instead.
subjectstring | nullThe subject or title the sending app gave, where the platform carries one.
urlstring | nullA shared link: always an absolute http or https URL.
filesSharedFile[]Shared files, copied into a file root; empty when only text or a link was shared.
GET/_bridge/sharetarget/pending/{id}#

One waiting share. Fails with 404 when there is no share by the id.

C#IShareTargetBridge.GetAsyncTSShareTargetBridge.get
ParameterInType
idpathstringrequired
Response
200 SharedContent
SharedContent · 6 fields
idstringNames the share for get and remove.
receivedAtstringWhen the app received it.
textstring | nullShared text. Text that is nothing but an http or https link is in url instead.
subjectstring | nullThe subject or title the sending app gave, where the platform carries one.
urlstring | nullA shared link: always an absolute http or https URL.
filesSharedFile[]Shared files, copied into a file root; empty when only text or a link was shared.
Errors
404Not found
DELETE/_bridge/sharetarget/pending/{id}#

Takes a share off the queue and deletes its files — move a file elsewhere through the files bridge first to keep it. Fails with 404 when there is no share by the id.

C#IShareTargetBridge.RemoveAsyncTSShareTargetBridge.remove
ParameterInType
idpathstringrequired
Response
204 No content
Errors
404Not found
EVENTsharetarget.received#

A share arrived while the page is open. It also waits in the queue until removed.

C#IShareTargetBridge.OnReceivedAsyncTSShareTargetBridge.onReceived
Payload
SharedContent
idstringNames the share for get and remove.
receivedAtstringWhen the app received it.
textstring | nullShared text. Text that is nothing but an http or https link is in url instead.
subjectstring | nullThe subject or title the sending app gave, where the platform carries one.
urlstring | nullA shared link: always an absolute http or https URL.
filesSharedFile[]Shared files, copied into a file root; empty when only text or a link was shared.

JSON property names are camelCase and enums travel as their names. A ? marks a property the payload may omit;| null one that may be null.

SharedContentobject#

Something another app shared to this one.

idstringNames the share for get and remove.
receivedAtstringWhen the app received it.
textstring | nullShared text. Text that is nothing but an http or https link is in url instead.
subjectstring | nullThe subject or title the sending app gave, where the platform carries one.
urlstring | nullA shared link: always an absolute http or https URL.
filesSharedFile[]Shared files, copied into a file root; empty when only text or a link was shared.
SharedFileobject#

A shared file, copied into a file root under shares/{id}/ and read through /_bridge/files.

fileBridgeFileWhere it is.
fileNamestringThe name the sending app gave it.
contentTypestring | nullIts media type, when the platform said.
sizenumberIts size in bytes.
BridgeFileobject#

A file the page can reach through the files bridge: a named root and a path inside it. Bridges that take or return a file use this, so nothing reaches the page that /_bridge/files would not also serve.

rootstring
pathstring

These routes sit under AppDeviceBridgePolicies.Bridges like every other bridge: callers on this device by default. See Security.

What arrives is content from another app, so treat it as untrusted input. The bridge already does the following:

  • It never offers a url that isn’t http or https.
  • It cleans file names so they can’t escape shares/{id}/.
  • It looks up a share’s id in the queue rather than using it as a path.
  • On Android, it refuses file: URIs.

On Linux, only turn on AcceptCommandLineFiles for an app launched from its .desktop file, because any readable file named on the command line reaches the page.