From 048b9ed2912d58b711988f71b1fd610aabcf77ae Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Wed, 16 Sep 2026 14:29:05 +0800 Subject: [PATCH] feat(plugins): add host.copy bridge for plugin clipboard writes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../plugins/PluginWorkbenchHost.vue | 4 +- .../src/lib/plugins/pluginHostBridge.spec.ts | 54 +++++++++++++++++++ .../src/lib/plugins/pluginHostBridge.ts | 14 +++++ plugins/sdk/dev-host/browser-bridge.mjs | 1 + plugins/sdk/dev-host/ui/App.vue | 41 +++++++++++++- 5 files changed, 112 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/components/plugins/PluginWorkbenchHost.vue b/apps/desktop/src/components/plugins/PluginWorkbenchHost.vue index 49914cb187..5b8dc240af 100644 --- a/apps/desktop/src/components/plugins/PluginWorkbenchHost.vue +++ b/apps/desktop/src/components/plugins/PluginWorkbenchHost.vue @@ -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(() => { {{ error }}