@xcwds/plugin-share

Sharing for an @xcwds app on @xcwds/sveltekit: a Share button in the installed app that shares a page's link and nothing you typed, and a private share target, so other apps can share into yours without the shared content ever reaching a server.

// xcwds.config.ts
import { defineConfig } from '@xcwds/core';
import share from '@xcwds/plugin-share';
import shell from '@xcwds/plugin-shell';

export default defineConfig({
	brand,
	// After the shell, whose header gets the button.
	plugins: [shell({ sections }), share({ target: '/utils/url-sanitizer' })]
});

Options

Option Default What it does
target none The page that receives shares (an app path). Without it the app is no share target.
exclude ['/settings'] Paths (and everything under them) whose pages never show the Share button.

The Share button

In the installed app there's no browser toolbar to share from, so the shell's header gets a Share button (with @xcwds/plugin-shell). It shares the page's title, "Title on App" (Home: "App: tagline") and its link: origin, base path and path only, never the query or hash, which can hold what you typed. It opens the system share sheet, or copies the link (with a toast) where there is none. It doesn't show in a browser tab, on excluded paths, on routes marked private (such as private tools from @xcwds/plugin-tools) or on error pages. Exclude other private pages, such as the share target if what it shows is private.

app.share.share({ title, text, url }) does the same from your own button.

The share target

With target, the manifest gets a share_target (GET, url, text and title), so Android lists the app in its share sheet. A share opens target?url=โ€ฆ&text=โ€ฆ&title=โ€ฆ. The service worker answers that request itself, before it leaves the device, with a 303 to the same page with the fields in the fragment (#shared.url=โ€ฆ&shared.text=โ€ฆ&shared.title=โ€ฆ&url=<joined>, url last), which browsers never send to a server. The target page reads it and clears the address:

<script lang="ts">
	import type { Shared } from '@xcwds/plugin-share';
	import { useApp } from '@xcwds/sveltekit';
	import { onMount } from 'svelte';

	let shared = $state<Shared | null>(null);
	onMount(() => useApp().shared?.listen((value) => (shared = value)));
</script>

listen calls back now and on every later share into an open tab (Safari may only change the hash), and replaces the address with the bare page so nothing stays in history. joined is every field joined with spaces (share sheets often put the link in text); url, text and title are the fields as sent. An iPhone Shortcut can open target#url=<link> directly: everything after url= is the link, decoded whole, so a raw & or + in it survives.

The first share after install can open the target before the worker controls the page (Android may open it cold). Then the page reads the query instead and clears it the same way, but that one request did reach the server with the query (on GitHub Pages, its logs).

API

From the package's types and doc comments.

Options (ShareOptions)

target? string
The page that receives shares from other apps (Android's share sheet, via the manifest's share_target), e.g. /utils/url-sanitizer. Without it the app is no share target.
exclude? string[]
Paths (and everything under them) whose page never shows the Share button.

Decorators

app.share? AppShare
From @xcwds/plugin-share.
app.share.exclude readonly string[]
Paths (and everything under them) that never show the Share button.
app.share.share (data: ShareData) => Promise<ShareResult>
Shares data (or copies its link), and says so with a toast when it copied or failed.
app.shared? AppShared
From @xcwds/plugin-share, when it has a target.
app.shared.target string
The target page (an app path).
app.shared.listen (listener: (shared: Shared) => void) => () => void
Calls listener with what was shared, now and whenever another share arrives (Safari may reuse an open tab and only change the hash), and clears it from the address bar and history. Call it from the target page's onMount; returns a function that stops it.

Edit this page on GitHub