@qzsy/dsh-bridge 0.0.0-stage → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,498 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@qzsy/dsh-bridge",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ var React = require("react");
7
+ "use strict";
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
12
+ var __export = (target, all) => {
13
+ for (var name2 in all)
14
+ __defProp(target, name2, { get: all[name2], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
+
26
+ // client/index.js
27
+ var index_exports = {};
28
+ __export(index_exports, {
29
+ BridgePanel: () => BridgePanel,
30
+ apply: () => apply,
31
+ inject: () => inject,
32
+ name: () => name
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // lib/identity.js
37
+ var PLUGIN_ID = "qzsy-dsh-bridge";
38
+ var PACKAGE_NAME = "@qzsy/dsh-bridge";
39
+ var RPC_CHANNEL = `/${PLUGIN_ID}`;
40
+ var RPC_ENDPOINTS = Object.freeze({
41
+ getStatus: "getStatus",
42
+ saveGateway: "saveGateway",
43
+ requestDeviceCode: "requestDeviceCode",
44
+ bindDevice: "bindDevice",
45
+ unbindDevice: "unbindDevice",
46
+ startTunnel: "startTunnel",
47
+ stopTunnel: "stopTunnel",
48
+ getAccessUrl: "getAccessUrl",
49
+ setAccessPassword: "setAccessPassword",
50
+ clearAccessPassword: "clearAccessPassword"
51
+ });
52
+
53
+ // client/locales.mjs
54
+ var zh = {
55
+ title: "\u8FDC\u7A0B\u8BBF\u95EE",
56
+ subtitle: "DSH \u8FDC\u7A0B\u96A7\u9053\u4E91 \xB7 \u5B98\u65B9\u5BA2\u6237\u7AEF",
57
+ gateway: "SaaS \u7F51\u5173\u5730\u5740",
58
+ save: "\u4FDD\u5B58\u7F51\u5173",
59
+ saved: "\u5DF2\u4FDD\u5B58",
60
+ device: "\u8BBE\u5907",
61
+ notBound: "\u672A\u7ED1\u5B9A",
62
+ bound: "\u5DF2\u7ED1\u5B9A",
63
+ getCode: "\u83B7\u53D6\u8BBE\u5907\u7801",
64
+ codeTitle: "\u5728\u63A7\u5236\u53F0\u6DFB\u52A0\u8BBE\u5907\u5E76\u8F93\u5165\u6B64\u8BBE\u5907\u7801",
65
+ codeExpires: "\u8BBE\u5907\u7801 10 \u5206\u949F\u5185\u6709\u6548\uFF0C\u7ED1\u5B9A\u540E\u81EA\u52A8\u6D88\u5931\u3002",
66
+ openConsole: "\u6253\u5F00\u63A7\u5236\u53F0",
67
+ bindManual: "\u5DF2\u6709\u8BBE\u5907\u7801\uFF1F\u76F4\u63A5\u7ED1\u5B9A",
68
+ bind: "\u7ED1\u5B9A",
69
+ unbind: "\u89E3\u7ED1\u8BBE\u5907",
70
+ unbindConfirm: "\u786E\u8BA4\u89E3\u7ED1\uFF1F",
71
+ cancel: "\u53D6\u6D88",
72
+ tunnel: "\u96A7\u9053",
73
+ start: "\u5F00\u542F\u96A7\u9053",
74
+ stop: "\u5173\u95ED\u96A7\u9053",
75
+ connecting: "\u8FDE\u63A5\u4E2D\u2026",
76
+ ready: "\u5728\u7EBF",
77
+ offline: "\u79BB\u7EBF",
78
+ publicUrl: "\u516C\u7F51\u5730\u5740",
79
+ copy: "\u590D\u5236",
80
+ copied: "\u5DF2\u590D\u5236",
81
+ accessUrl: "\u5E26\u8BBF\u95EE\u4EE4\u724C\u7684\u94FE\u63A5\uFF08\u9996\u6B21\u6253\u5F00\u540E\u5199\u5165\u4F1A\u8BDD\uFF09",
82
+ qr: "\u626B\u7801\u6253\u5F00",
83
+ lastError: "\u6700\u8FD1\u9519\u8BEF",
84
+ password: "\u8BBF\u95EE\u53E3\u4EE4",
85
+ passwordHint: "\u9996\u6B21\u4F7F\u7528\u5FC5\u987B\u5148\u8BBE\u7F6E\u8BBF\u95EE\u53E3\u4EE4\uFF1B\u8FDC\u7A0B/\u5C40\u57DF\u7F51\u8BBF\u5BA2\u9700\u51ED\u6B64\u53E3\u4EE4\u901A\u8FC7\u672C\u5730\u95E8\u7981\u3002",
86
+ passwordSet: "\u5DF2\u8BBE\u7F6E\u8BBF\u95EE\u53E3\u4EE4\uFF08\u53EF\u5728\u6B64\u4FEE\u6539\uFF09\u3002",
87
+ passwordPlaceholder: "\u81F3\u5C11 6 \u4F4D",
88
+ savePassword: "\u4FDD\u5B58\u53E3\u4EE4",
89
+ adminLocalOnly: "\u5F53\u524D\u4E0D\u662F\u7269\u7406\u672C\u673A\u6253\u5F00\uFF1A\u9762\u677F\u53EA\u8BFB\uFF0C\u8BF7\u5728\u8FD0\u884C DSH \u7684\u7535\u8111\u4E0A\u64CD\u4F5C\u3002",
90
+ noPublicUrl: "\u5C1A\u672A\u83B7\u53D6\u516C\u7F51\u5730\u5740\uFF08\u5148\u5F00\u542F\u96A7\u9053\uFF09",
91
+ autoStart: "\u968F DSH \u542F\u52A8\u81EA\u52A8\u5F00\u542F",
92
+ status: "\u72B6\u6001",
93
+ version: "\u7248\u672C",
94
+ loadFailed: "\u65E0\u6CD5\u8FDE\u63A5\u63D2\u4EF6 RPC"
95
+ };
96
+ var en = {
97
+ title: "Remote Access",
98
+ subtitle: "DSH Remote Tunnel Cloud \xB7 Official Client",
99
+ gateway: "SaaS gateway URL",
100
+ save: "Save gateway",
101
+ saved: "Saved",
102
+ device: "Device",
103
+ notBound: "Not bound",
104
+ bound: "Bound",
105
+ getCode: "Get device code",
106
+ codeTitle: "Add the device in the console and enter this code",
107
+ codeExpires: "The code is valid for 10 minutes and disappears after binding.",
108
+ openConsole: "Open console",
109
+ bindManual: "Already have a code? Bind it",
110
+ bind: "Bind",
111
+ unbind: "Unbind device",
112
+ unbindConfirm: "Confirm unbind?",
113
+ cancel: "Cancel",
114
+ tunnel: "Tunnel",
115
+ start: "Start tunnel",
116
+ stop: "Stop tunnel",
117
+ connecting: "Connecting\u2026",
118
+ ready: "Online",
119
+ offline: "Offline",
120
+ publicUrl: "Public URL",
121
+ copy: "Copy",
122
+ copied: "Copied",
123
+ accessUrl: "Access-token URL (writes a session cookie on first open)",
124
+ qr: "Scan to open",
125
+ lastError: "Last error",
126
+ password: "Access password",
127
+ passwordHint: "Set an access password before first use; remote/LAN visitors must pass the local gate with it.",
128
+ passwordSet: "Access password is set (you can change it here).",
129
+ passwordPlaceholder: "At least 6 characters",
130
+ savePassword: "Save password",
131
+ adminLocalOnly: "Opened outside the physical host: panel is read-only. Use the DSH machine to manage.",
132
+ noPublicUrl: "No public URL yet (start the tunnel first)",
133
+ autoStart: "Auto-start with DSH",
134
+ status: "Status",
135
+ version: "Version",
136
+ loadFailed: "Cannot reach plugin RPC"
137
+ };
138
+ function detectLang(host = {}) {
139
+ try {
140
+ const fromHost = host?.locale?.current?.();
141
+ if (fromHost === "en" || fromHost === "zh") return fromHost;
142
+ } catch {
143
+ }
144
+ try {
145
+ const documentLang = globalThis.document?.documentElement?.getAttribute?.("lang");
146
+ if (documentLang) return String(documentLang).toLowerCase().startsWith("zh") ? "zh" : "en";
147
+ } catch {
148
+ }
149
+ return "zh";
150
+ }
151
+ function createTranslator(host = {}) {
152
+ const lang = detectLang(host);
153
+ const dict = lang === "en" ? en : zh;
154
+ return (key, vars = {}) => {
155
+ let text = dict[key] ?? zh[key] ?? key;
156
+ for (const [name2, value] of Object.entries(vars ?? {})) text = text.replaceAll(`\${${name2}}`, String(value));
157
+ return text;
158
+ };
159
+ }
160
+
161
+ // client/index.js
162
+ var name = PLUGIN_ID;
163
+ var inject = ["slots", "connection"];
164
+ var h = (type, props, ...children) => React.createElement(type, props, ...children);
165
+ var styles = {
166
+ wrap: { padding: "4px 0", maxWidth: 720, fontFamily: "inherit" },
167
+ card: { border: "1px solid var(--dsw-alias-border-secondary, #e5e7eb)", borderRadius: 12, padding: 16, marginBottom: 12 },
168
+ row: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" },
169
+ label: { fontSize: 12, color: "var(--dsw-alias-label-secondary, #6b7280)", marginBottom: 6, display: "block" },
170
+ value: { fontSize: 13, lineHeight: 1.6, wordBreak: "break-all" },
171
+ input: { flex: "1 1 320px", minWidth: 0, padding: "8px 10px", borderRadius: 8, border: "1px solid var(--dsw-alias-border-secondary, #d1d5db)", font: "inherit", fontSize: 13 },
172
+ btn: { padding: "7px 14px", borderRadius: 8, border: "1px solid var(--dsw-alias-border-secondary, #d1d5db)", background: "transparent", color: "inherit", cursor: "pointer", fontSize: 13 },
173
+ btnPrimary: { padding: "7px 14px", borderRadius: 8, border: "none", background: "var(--dsw-alias-brand-primary, #4f6ef7)", color: "#fff", cursor: "pointer", fontSize: 13, fontWeight: 600 },
174
+ badge: { display: "inline-block", fontSize: 12, padding: "2px 8px", borderRadius: 999, border: "1px solid var(--dsw-alias-border-secondary, #d1d5db)" },
175
+ code: { fontSize: 28, fontWeight: 700, letterSpacing: 4, fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", margin: "8px 0" },
176
+ error: { color: "var(--dsw-alias-state-error-primary, #dc2626)", fontSize: 12, lineHeight: 1.6, wordBreak: "break-all" },
177
+ muted: { color: "var(--dsw-alias-label-secondary, #6b7280)", fontSize: 12, lineHeight: 1.6 },
178
+ qr: { width: 168, height: 168, borderRadius: 8, background: "#fff", padding: 6 }
179
+ };
180
+ function CopyButton({ value, t }) {
181
+ const [copied, setCopied] = React.useState(false);
182
+ const copy = React.useCallback(async () => {
183
+ if (!value) return;
184
+ try {
185
+ await navigator.clipboard.writeText(value);
186
+ setCopied(true);
187
+ setTimeout(() => setCopied(false), 1500);
188
+ } catch {
189
+ }
190
+ }, [value]);
191
+ return h("button", { style: styles.btn, onClick: copy, disabled: !value }, copied ? t("copied") : t("copy"));
192
+ }
193
+ function UrlRow({ label, url, t }) {
194
+ if (!url) return null;
195
+ return h(
196
+ "div",
197
+ { style: { marginTop: 10 } },
198
+ h("span", { style: styles.label }, label),
199
+ h(
200
+ "div",
201
+ { style: styles.row },
202
+ h("input", { style: styles.input, readOnly: true, value: url, onFocus: (e) => e.currentTarget.select() }),
203
+ h(CopyButton, { value: url, t })
204
+ )
205
+ );
206
+ }
207
+ function BridgePanel() {
208
+ const t = createTranslator({});
209
+ const [adminToken, setAdminToken] = React.useState("");
210
+ const rpc = React.useCallback((endpoint, payload, signal) => ctxRpc(endpoint, { ...payload ?? {}, ...adminToken ? { adminToken } : {} }, signal), [adminToken]);
211
+ const [status, setStatus] = React.useState(null);
212
+ const [gatewayDraft, setGatewayDraft] = React.useState("");
213
+ const [codeDraft, setCodeDraft] = React.useState("");
214
+ const [busy, setBusy] = React.useState("");
215
+ const [error, setError] = React.useState("");
216
+ const [notice, setNotice] = React.useState("");
217
+ const [confirmUnbind, setConfirmUnbind] = React.useState(false);
218
+ const [accessUrl, setAccessUrl] = React.useState("");
219
+ const [qrDataUrl, setQrDataUrl] = React.useState("");
220
+ const [passwordDraft, setPasswordDraft] = React.useState("");
221
+ const loadAdminToken = React.useCallback(async (port) => {
222
+ if (!port) return;
223
+ try {
224
+ const res = await fetch(`http://127.0.0.1:${port}/__qzsy_bridge__/loopback-token`, { cache: "no-store" });
225
+ const data = await res.json();
226
+ if (data?.adminToken) setAdminToken(data.adminToken);
227
+ } catch {
228
+ }
229
+ }, []);
230
+ const refresh = React.useCallback(async () => {
231
+ const res = await rpc(RPC_ENDPOINTS.getStatus);
232
+ if (res?.ok && res.value) {
233
+ setStatus(res.value);
234
+ setGatewayDraft((current) => current || res.value.gatewayUrl || "");
235
+ setError(res.value.lastError || "");
236
+ if (!adminToken) await loadAdminToken(res.value.proxy?.port);
237
+ } else if (res && res.ok === false) {
238
+ setError(res.error?.message ?? t("loadFailed"));
239
+ }
240
+ }, [t, adminToken, loadAdminToken]);
241
+ React.useEffect(() => {
242
+ refresh().catch(() => {
243
+ });
244
+ const timer = setInterval(() => {
245
+ refresh().catch(() => {
246
+ });
247
+ }, 3e3);
248
+ return () => clearInterval(timer);
249
+ }, [refresh]);
250
+ React.useEffect(() => {
251
+ if (!adminToken || !status?.publicUrl) {
252
+ setAccessUrl("");
253
+ setQrDataUrl("");
254
+ return;
255
+ }
256
+ let cancelled = false;
257
+ rpc(RPC_ENDPOINTS.getAccessUrl).then((res) => {
258
+ if (cancelled || !res?.ok) return;
259
+ setAccessUrl(res.value?.url ?? "");
260
+ setQrDataUrl(res.value?.qrDataUrl ?? "");
261
+ }).catch(() => {
262
+ });
263
+ return () => {
264
+ cancelled = true;
265
+ };
266
+ }, [adminToken, status?.publicUrl, rpc]);
267
+ const run = React.useCallback(async (key, fn) => {
268
+ setBusy(key);
269
+ setError("");
270
+ setNotice("");
271
+ try {
272
+ const res = await fn();
273
+ if (res && res.ok === false) throw new Error(res.error?.message ?? "RPC failed");
274
+ await refresh();
275
+ return res?.value;
276
+ } catch (err) {
277
+ setError(err?.message ?? String(err));
278
+ return null;
279
+ } finally {
280
+ setBusy("");
281
+ }
282
+ }, [refresh]);
283
+ const saveGateway = () => run("gateway", () => rpc(RPC_ENDPOINTS.saveGateway, { gatewayUrl: gatewayDraft })).then(() => setNotice(t("saved")));
284
+ const requestCode = () => run("code", () => rpc(RPC_ENDPOINTS.requestDeviceCode));
285
+ const bindCode = () => run("bind", () => rpc(RPC_ENDPOINTS.bindDevice, { code: codeDraft })).then((value) => {
286
+ if (value) setCodeDraft("");
287
+ });
288
+ const startTunnel = () => run("start", () => rpc(RPC_ENDPOINTS.startTunnel, { autoStart: true }));
289
+ const stopTunnel = () => run("stop", () => rpc(RPC_ENDPOINTS.stopTunnel));
290
+ const unbind = () => run("unbind", () => rpc(RPC_ENDPOINTS.unbindDevice)).then(() => setConfirmUnbind(false));
291
+ const savePassword = () => run("password", () => rpc(RPC_ENDPOINTS.setAccessPassword, { password: passwordDraft })).then((value) => {
292
+ if (value) {
293
+ setPasswordDraft("");
294
+ setNotice(t("passwordSet"));
295
+ }
296
+ });
297
+ const bound = Boolean(status?.device?.bound);
298
+ const connected = Boolean(status?.connected);
299
+ const phase = status?.phase ?? "idle";
300
+ const publicUrl = status?.publicUrl ?? "";
301
+ const pairing = status?.pairing;
302
+ const passwordSet = Boolean(status?.access?.passwordSet);
303
+ const stateText = connected ? t("ready") : phase === "connecting" || phase === "reconnecting" ? t("connecting") : t("offline");
304
+ return h(
305
+ "div",
306
+ { style: styles.wrap },
307
+ h(
308
+ "div",
309
+ { style: { marginBottom: 12 } },
310
+ h("div", { style: { fontSize: 15, fontWeight: 700 } }, t("subtitle")),
311
+ h("div", { style: styles.muted, marginTop: 4 }, `${PACKAGE_NAME} \xB7 v${status?.version ?? "\u2014"}`)
312
+ ),
313
+ h(
314
+ "div",
315
+ { style: styles.card },
316
+ h(
317
+ "div",
318
+ { style: styles.row },
319
+ h("span", { style: styles.badge }, `${t("status")}: ${stateText}`),
320
+ h("span", { style: styles.badge }, bound ? `${t("device")}: ${status?.device?.idMasked ?? ""}` : t("notBound")),
321
+ status?.protocol ? h("span", { style: styles.badge }, `protocol v${status.protocol}`) : null
322
+ ),
323
+ h(
324
+ "div",
325
+ { style: { marginTop: 10 } },
326
+ h("span", { style: styles.label }, t("gateway")),
327
+ h(
328
+ "div",
329
+ { style: styles.row },
330
+ h("input", {
331
+ style: styles.input,
332
+ value: gatewayDraft,
333
+ placeholder: "wss://dsh.b.nps.qzsyzn.com/_gw/connect",
334
+ onChange: (e) => setGatewayDraft(e.target.value)
335
+ }),
336
+ h("button", { style: styles.btnPrimary, disabled: busy === "gateway" || !gatewayDraft, onClick: saveGateway }, t("save"))
337
+ )
338
+ ),
339
+ !adminToken ? h("div", { style: { ...styles.muted, marginTop: 8 } }, t("adminLocalOnly")) : null,
340
+ error ? h("div", { style: { ...styles.error, marginTop: 10 } }, `${t("lastError")}: ${error}`) : null,
341
+ notice ? h("div", { style: { ...styles.muted, marginTop: 8 } }, notice) : null
342
+ ),
343
+ h(
344
+ "div",
345
+ { style: styles.card },
346
+ h(
347
+ "div",
348
+ { style: styles.row },
349
+ h(
350
+ "div",
351
+ { style: { flex: 1, minWidth: 260 } },
352
+ h("div", { style: { fontSize: 13, fontWeight: 600 } }, t("device")),
353
+ h(
354
+ "div",
355
+ { style: styles.muted },
356
+ bound ? `${status?.device?.tunnel?.name || "DSH"} \xB7 ${status?.device?.tunnel?.publicId || ""}` : t("notBound")
357
+ )
358
+ ),
359
+ !bound && h("button", { style: styles.btnPrimary, disabled: Boolean(busy), onClick: requestCode }, t("getCode")),
360
+ bound && h("button", {
361
+ style: styles.btn,
362
+ disabled: Boolean(busy),
363
+ onClick: () => setConfirmUnbind(true)
364
+ }, t("unbind"))
365
+ ),
366
+ pairing && pairing.status === "pending" ? h(
367
+ "div",
368
+ { style: { marginTop: 12 } },
369
+ h("div", { style: styles.muted }, t("codeTitle")),
370
+ h("div", { style: styles.code }, pairing.code),
371
+ h("div", { style: styles.muted }, t("codeExpires")),
372
+ pairing.verificationUrl ? h("a", { href: pairing.verificationUrl, target: "_blank", rel: "noreferrer", style: { fontSize: 12 } }, t("openConsole")) : null
373
+ ) : null,
374
+ !bound ? h(
375
+ "div",
376
+ { style: { marginTop: 12 } },
377
+ h("span", { style: styles.label }, t("bindManual")),
378
+ h(
379
+ "div",
380
+ { style: styles.row },
381
+ h("input", { style: styles.input, value: codeDraft, onChange: (e) => setCodeDraft(e.target.value), placeholder: "ABCD-1234" }),
382
+ h("button", { style: styles.btn, disabled: busy === "bind" || !codeDraft, onClick: bindCode }, t("bind"))
383
+ )
384
+ ) : null,
385
+ confirmUnbind ? h(
386
+ "div",
387
+ { style: { marginTop: 12 } },
388
+ h("div", { style: { ...styles.muted, marginBottom: 8 } }, t("unbindConfirm")),
389
+ h(
390
+ "div",
391
+ { style: styles.row },
392
+ h("button", { style: styles.btnPrimary, disabled: busy === "unbind", onClick: unbind }, t("unbind")),
393
+ h("button", { style: styles.btn, onClick: () => setConfirmUnbind(false) }, t("cancel"))
394
+ )
395
+ ) : null
396
+ ),
397
+ h(
398
+ "div",
399
+ { style: styles.card },
400
+ h("div", { style: { fontSize: 13, fontWeight: 600 } }, t("password")),
401
+ h("div", { style: styles.muted }, passwordSet ? t("passwordSet") : t("passwordHint")),
402
+ h(
403
+ "div",
404
+ { style: { ...styles.row, marginTop: 8 } },
405
+ h("input", {
406
+ style: styles.input,
407
+ type: "password",
408
+ value: passwordDraft,
409
+ onChange: (e) => setPasswordDraft(e.target.value),
410
+ placeholder: t("passwordPlaceholder")
411
+ }),
412
+ h("button", { style: styles.btnPrimary, disabled: busy === "password" || passwordDraft.length < 6, onClick: savePassword }, t("savePassword"))
413
+ )
414
+ ),
415
+ h(
416
+ "div",
417
+ { style: styles.card },
418
+ h(
419
+ "div",
420
+ { style: styles.row },
421
+ h(
422
+ "div",
423
+ { style: { flex: 1, minWidth: 200 } },
424
+ h("div", { style: { fontSize: 13, fontWeight: 600 } }, t("tunnel")),
425
+ h("div", { style: styles.muted }, connected ? publicUrl : t("noPublicUrl"))
426
+ ),
427
+ connected ? h("button", { style: styles.btn, disabled: Boolean(busy), onClick: stopTunnel }, t("stop")) : h("button", { style: styles.btnPrimary, disabled: Boolean(busy) || !bound || !passwordSet, onClick: startTunnel }, t("start"))
428
+ ),
429
+ publicUrl ? h(
430
+ "div",
431
+ { style: { display: "flex", gap: 16, marginTop: 12, flexWrap: "wrap", alignItems: "flex-start" } },
432
+ h(
433
+ "div",
434
+ { style: { flex: "1 1 320px", minWidth: 260 } },
435
+ h(UrlRow, { label: t("publicUrl"), url: publicUrl, t }),
436
+ h(UrlRow, { label: t("accessUrl"), url: accessUrl, t })
437
+ ),
438
+ qrDataUrl ? h(
439
+ "div",
440
+ null,
441
+ h("div", { style: styles.label }, t("qr")),
442
+ h("img", { src: qrDataUrl, alt: t("qr"), style: styles.qr })
443
+ ) : null
444
+ ) : null
445
+ )
446
+ );
447
+ }
448
+ var ctxRpc = async () => ({ ok: false, error: { code: "no-context", message: "RPC not ready" } });
449
+ var serviceControl = null;
450
+ function apply(ctx) {
451
+ ctxRpc = (endpoint, payload, signal) => ctx.connection.rpc.call(RPC_CHANNEL, endpoint, payload, signal);
452
+ const panel = (props = {}) => h(BridgePanel, { ...props, key: "qzsy-dsh-bridge-panel" });
453
+ let visible = true;
454
+ let registration = null;
455
+ const register = () => {
456
+ if (!visible || registration) return;
457
+ try {
458
+ registration = ctx.slots.register({
459
+ name: "settings.section",
460
+ id: PLUGIN_ID,
461
+ order: 10,
462
+ label: () => createTranslator(ctx)("title")
463
+ }, panel);
464
+ } catch {
465
+ }
466
+ };
467
+ const unregister = () => {
468
+ if (!registration) return;
469
+ try {
470
+ if (typeof registration === "function") registration();
471
+ else registration.dispose?.();
472
+ } catch {
473
+ }
474
+ registration = null;
475
+ };
476
+ try {
477
+ ctx.slots.inject("settings.section", () => {
478
+ if (visible) register();
479
+ });
480
+ } catch {
481
+ register();
482
+ }
483
+ serviceControl = {
484
+ version: 1,
485
+ render: (props = {}) => h(BridgePanel, props),
486
+ setSettingsVisible: (next) => {
487
+ visible = Boolean(next);
488
+ if (visible) register();
489
+ else unregister();
490
+ },
491
+ settingsVisible: () => visible
492
+ };
493
+ if (typeof ctx.provide === "function") ctx.provide(PLUGIN_ID, serviceControl);
494
+ }
495
+
496
+ return module.exports;
497
+ }
498
+ });