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 }}
-
+