feat(plugins): add host.copy bridge for plugin clipboard writes
Plugin workbenches run in a sandbox="allow-scripts" iframe with an opaque origin, so every scripted clipboard path (async Clipboard API, offscreen textarea + execCommand) is denied there — plugins could not copy share links to the system clipboard. The bridge now exposes host.copy: the workbench host reuses copyToClipboard (Tauri clipboard-manager on desktop, Web Clipboard with a legacy fallback on web hosts), and the sandbox iframe additionally gets allow="clipboard-write" so engines that honor the delegation can write directly. The dev host matches production: its debug page answers host.copy in the top-level document and its plugin iframes carry the same allow attribute. No new plugin permission is required, so old hosts keep installing new plugins and old plugins simply never call the method.
This commit is contained in:
5 files changed
+112
-2
No files matched your search
@@ -3,6 +3,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue"
|
||||
import { AlertTriangle, Loader2 } from "@lucide/vue";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
||||
import { copyToClipboard } from "@/lib/common/clipboard";
|
||||
import { PluginHostBridge, pluginSandboxDocument, type PluginBridgeTheme, type PluginSaveFileRequest, type PluginSaveFileResult, type PluginWorkbenchContext } from "@/lib/plugins/pluginHostBridge";
|
||||
import type { InstalledPlugin, PluginWorkbenchContribution } from "@/types/database";
|
||||
import { useI18n } from "vue-i18n";
|
||||
@@ -73,6 +74,7 @@ function createBridge() {
|
||||
openFilesystem: async (pluginId, providerId, context) => emit("openFilesystem", pluginId, providerId, context),
|
||||
closeTab: () => emit("closeTab"),
|
||||
saveFile: (_pluginId, request, data) => savePluginFile(request, data),
|
||||
copyText: (_pluginId, text) => copyToClipboard(text),
|
||||
},
|
||||
appLocale.value,
|
||||
currentBridgeTheme(),
|
||||
@@ -253,7 +255,7 @@ onBeforeUnmount(() => {
|
||||
<span>{{ error }}</span>
|
||||
</div>
|
||||
<template v-else>
|
||||
<iframe ref="iframe" :title="title" :srcdoc="source" sandbox="allow-scripts" referrerpolicy="no-referrer" class="size-full border-0 bg-transparent" @load="onFrameLoad" />
|
||||
<iframe ref="iframe" :title="title" :srcdoc="source" sandbox="allow-scripts" allow="clipboard-write" referrerpolicy="no-referrer" class="size-full border-0 bg-transparent" @load="onFrameLoad" />
|
||||
<!-- Cover until the frame has actually painted: the iframe stays mounted
|
||||
underneath so its load event can fire (v-else on the overlay would
|
||||
deadlock), it just isn't visible yet. Fully opaque so the covered
|
||||
|
||||
Reference in new issue
Block a user