vojo/src/app/plugins/shareTarget.ts

69 lines
2.4 KiB
TypeScript

// Bridge to the native ShareTargetPlugin (see
// android/app/src/main/java/chat/vojo/app/ShareTargetPlugin.java).
//
// On Android the native plugin captures ACTION_SEND / ACTION_SEND_MULTIPLE
// intents into the app cache. JS picks them up on boot via pickPendingShare()
// and reacts to warm shares via the `shareReceived` event.
//
// Web has no system share-sheet handoff, so the fallback is a no-op that
// always reports an empty slot.
import { Capacitor, registerPlugin, type PluginListenerHandle } from '@capacitor/core';
import { isAndroidPlatform } from '../utils/capacitor';
// Mirror of the JSObject the native plugin assembles.
export interface SharedFile {
name: string;
mimeType: string;
size: number;
// Absolute filesystem path inside the app cache. Wrap with
// Capacitor.convertFileSrc() before fetch().
path: string;
}
export type PendingShare =
| { empty: true }
| {
empty: false;
text?: string;
subject?: string;
items: SharedFile[];
};
interface ShareTargetPluginIface {
pickPendingShare(options?: { consume?: boolean }): Promise<PendingShare>;
addListener(eventName: 'shareReceived', listener: () => void): Promise<PluginListenerHandle>;
}
const plugin = registerPlugin<ShareTargetPluginIface>('ShareTarget', {
web: {
pickPendingShare: async () => ({ empty: true } as PendingShare),
addListener: async () => ({
remove: async () => undefined,
}),
},
});
export const shareTarget = {
async pickPendingShare(consume = true): Promise<PendingShare> {
if (!isAndroidPlatform()) return { empty: true };
try {
return await plugin.pickPendingShare({ consume });
} catch {
// Old APK installed before the plugin shipped — be silent and treat
// as "no pending share" so the rest of the app boots normally.
return { empty: true };
}
},
addShareReceivedListener(cb: () => void): Promise<PluginListenerHandle> {
if (!isAndroidPlatform()) {
return Promise.resolve({ remove: async () => undefined });
}
return plugin.addListener('shareReceived', cb);
},
};
// Convert the cached absolute path into a URL fetchable inside the WebView
// (Capacitor wraps it as https://localhost/_capacitor_file_/…). Exposed here
// so the consumer doesn't have to import @capacitor/core directly.
export const sharedFilePathToUrl = (path: string): string => Capacitor.convertFileSrc(path);