dsh-remote-control 1.1.0 → 2.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.
- package/cordis.patch.yml +3 -5
- package/dist/bundle/client.cjs +485 -76
- package/dist/bundle/index.js +1218 -686
- package/package.json +6 -5
package/cordis.patch.yml
CHANGED
|
@@ -19,9 +19,7 @@
|
|
|
19
19
|
enabled: true
|
|
20
20
|
idleReleaseSec: 300
|
|
21
21
|
keepDisplay: false
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
sidebarQr:
|
|
22
|
+
# 状态栏那颗 pill:配对的**唯一**入口(`/drc pair` 与右栏自动弹码都在 2026-10-03 删了)。
|
|
23
|
+
# 关掉它这台主机就没有配对入口了——所以关掉会在 status.json 的 problems 里留一条 warn:pill。
|
|
24
|
+
pill:
|
|
26
25
|
enabled: true
|
|
27
|
-
refreshMs: 2000
|
package/dist/bundle/client.cjs
CHANGED
|
@@ -31,112 +31,521 @@ __export(main_exports, {
|
|
|
31
31
|
name: () => name
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(main_exports);
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
// client/src/pill.ts
|
|
36
|
+
var STATUS_ROUTE = "/plugins/dsh-remote-control/status";
|
|
37
|
+
var NEW_ROUTE = "/plugins/dsh-remote-control/pairing/new";
|
|
38
|
+
var IMAGE_ROUTE = "/plugins/dsh-remote-control/pairing.png";
|
|
39
|
+
var UNPAIR_ROUTE = "/plugins/dsh-remote-control/unpair";
|
|
40
|
+
var SLOT_NAME = "conversation.composer.dock";
|
|
41
|
+
var PILL_ID = "dsh-remote-control";
|
|
42
|
+
var PILL_ORDER = 20;
|
|
43
|
+
var STYLE_ID = "dsh-remote-control/pill.css";
|
|
35
44
|
var POLL_MS = 2e3;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
reported.add(message);
|
|
45
|
-
try {
|
|
46
|
-
const detail = error instanceof Error ? error.stack ?? error.message : error === void 0 ? "" : String(error);
|
|
47
|
-
console.error(`[dsh-remote-control sidebar] ${message} ${detail}`);
|
|
48
|
-
} catch {
|
|
45
|
+
function panelViewFor(answer, httpStatus) {
|
|
46
|
+
if (!answer || typeof answer !== "object") return { kind: "failed", detail: `HTTP ${httpStatus}` };
|
|
47
|
+
if (answer.state === "ready") {
|
|
48
|
+
const token = typeof answer.token === "string" ? answer.token : "";
|
|
49
|
+
const epoch = typeof answer.epoch === "string" ? answer.epoch : "";
|
|
50
|
+
const expiresInMs = typeof answer.expiresInMs === "number" ? answer.expiresInMs : 0;
|
|
51
|
+
if (token === "" || expiresInMs <= 0) return { kind: "failed", detail: "\u53D1\u7801\u56DE\u7B54\u91CC\u7684\u7801\u4E0D\u5B8C\u6574" };
|
|
52
|
+
return { kind: "qr", token, expiresInMs, imageSrc: `${IMAGE_ROUTE}?e=${encodeURIComponent(epoch)}` };
|
|
49
53
|
}
|
|
54
|
+
if (answer.state === "unavailable") {
|
|
55
|
+
return {
|
|
56
|
+
kind: "unavailable",
|
|
57
|
+
reason: answer.reason === "relay-offline" ? "\u4E2D\u7EE7\u8FD8\u6CA1\u8FDE\u4E0A\uFF0C\u6682\u65F6\u65E0\u6CD5\u914D\u5BF9\u3002" : "\u73B0\u5728\u53D1\u4E0D\u51FA\u914D\u5BF9\u7801\u3002"
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
kind: "failed",
|
|
62
|
+
// 把宿主说的那道守卫一起印出来:这条路由在浏览器里点,屏幕上的这句话就是唯一的现场。
|
|
63
|
+
detail: typeof answer.error === "string" ? typeof answer.guard === "string" ? `${answer.error}\uFF08${answer.guard}\uFF09` : answer.error : `HTTP ${httpStatus}`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function pillLabel(status) {
|
|
67
|
+
const relay = status?.relay;
|
|
68
|
+
if (relay === "idle") return { text: "\u8FDC\u7A0B\u672A\u542F\u52A8", tone: "off" };
|
|
69
|
+
if (relay === "offline") return { text: "\u5DF2\u65AD\u5F00\u8FDE\u63A5", tone: "error" };
|
|
70
|
+
if (relay === "connecting") return { text: "\u8FDE\u63A5\u4E2D", tone: "wait" };
|
|
71
|
+
if (relay !== "online") return { text: "\u8FDC\u7A0B\u63A7\u5236", tone: "off" };
|
|
72
|
+
const paired = typeof status?.paired === "number" ? status.paired : 0;
|
|
73
|
+
if (paired > 0) {
|
|
74
|
+
const waiting = waitingCount(status);
|
|
75
|
+
if (waiting > 0) return { text: `\u7B49 ${waiting} \u4EF6\u4E8B`, tone: "wait" };
|
|
76
|
+
return { text: "\u5DF2\u914D\u5BF9", tone: "on" };
|
|
77
|
+
}
|
|
78
|
+
return { text: "\u672A\u914D\u5BF9", tone: "off" };
|
|
79
|
+
}
|
|
80
|
+
function waitingCount(status) {
|
|
81
|
+
const count = status?.waiting;
|
|
82
|
+
return typeof count === "number" && count > 0 ? Math.min(Math.floor(count), 99) : 0;
|
|
83
|
+
}
|
|
84
|
+
function humanDuration(sec) {
|
|
85
|
+
const total = typeof sec === "number" && sec > 0 ? Math.floor(sec) : 0;
|
|
86
|
+
if (total < 60) return `${total} \u79D2`;
|
|
87
|
+
const minutes = Math.floor(total / 60);
|
|
88
|
+
return minutes >= 10 ? `${minutes} \u5206` : `${minutes} \u5206 ${total % 60} \u79D2`;
|
|
89
|
+
}
|
|
90
|
+
function waitingRow(status) {
|
|
91
|
+
const count = waitingCount(status);
|
|
92
|
+
if (count === 0) return "";
|
|
93
|
+
const oldest = typeof status?.waitingOldestSec === "number" ? status.waitingOldestSec : 0;
|
|
94
|
+
return `${count} \u4EF6${oldest > 0 ? ` \xB7 \u6700\u4E45 ${humanDuration(oldest)}` : ""}`.slice(0, 40);
|
|
50
95
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
96
|
+
function relayAddress(url) {
|
|
97
|
+
if (typeof url !== "string" || url.trim() === "") return "";
|
|
98
|
+
const withoutScheme = url.trim().replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
|
|
99
|
+
const authority = /^[^/?]*/.exec(withoutScheme)?.[0] ?? "";
|
|
100
|
+
return authority.slice(0, 40);
|
|
101
|
+
}
|
|
102
|
+
function relayRow(status) {
|
|
103
|
+
return relayAddress(status?.serverUrl);
|
|
104
|
+
}
|
|
105
|
+
function relayStateRow(status) {
|
|
106
|
+
const relay = status?.relay;
|
|
107
|
+
if (relay === "online") return "\u5DF2\u8FDE\u63A5";
|
|
108
|
+
if (relay === "connecting") return "\u8FDE\u63A5\u4E2D";
|
|
109
|
+
if (relay === "offline") return "\u5DF2\u65AD\u5F00";
|
|
110
|
+
if (relay === "idle") return "\u672A\u542F\u52A8";
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
function versionRow(status) {
|
|
114
|
+
const version = status?.version;
|
|
115
|
+
return typeof version === "string" && version.trim() !== "" ? version.trim().slice(0, 24) : "";
|
|
116
|
+
}
|
|
117
|
+
function secondsText(ms) {
|
|
118
|
+
const whole = Math.max(0, Math.round(ms / 1e3));
|
|
119
|
+
if (whole < 60) return `${whole} \u79D2`;
|
|
120
|
+
return `${Math.floor(whole / 60)} \u5206 ${whole % 60} \u79D2`;
|
|
121
|
+
}
|
|
122
|
+
var CSS = `
|
|
123
|
+
.drc-pill { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; padding: 2px 8px;
|
|
124
|
+
border: 0; border-radius: 999px; background: transparent; color: inherit; font: inherit;
|
|
125
|
+
font-size: 12px; line-height: 18px; cursor: pointer;
|
|
126
|
+
/* \u5BBF\u4E3B\u90A3\u4E00\u6392\u6324\u4E0D\u4E0B\u65F6\u4F1A\u81EA\u5DF1\u622A\u65AD\uFF08\u300C6 \u8F6E \u2026\u300D\u90A3\u79CD\uFF09\uFF0C\u6211\u4EEC\u4E5F\u8DDF\u8FD9\u4E2A\u5F62\u72B6\u8D70\uFF1A
|
|
127
|
+
\u4E00\u884C\u5199\u5B8C\uFF0C\u5199\u4E0D\u4E0B\u5C31\u7701\u7565\u53F7\u3002**\u6CA1\u6709\u8FD9\u4E24\u6761**\u7684\u8BDD\uFF0Cflex \u9879\u4F1A\u88AB\u538B\u5230\u300C\u4E2D\u6587\u6700\u5C0F\u5185\u5BB9\u5BBD\u5EA6
|
|
128
|
+
= \u4E00\u4E2A\u5B57\u300D\uFF0C\u4E8E\u662F\u300C\u5DF2\u8FDE 1 \u53F0\u300D\u7AD6\u7740\u6392\u6210\u56DB\u884C\uFF082026-10-03 \u771F\u5C4F\u5E55\u622A\u56FE\uFF09\u3002 */
|
|
129
|
+
flex: 0 1 auto; white-space: nowrap; }
|
|
130
|
+
.drc-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
131
|
+
.drc-pill:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04)); }
|
|
132
|
+
.drc-dot { width: 6px; height: 6px; flex: 0 0 auto; border-radius: 50%; background: var(--dsw-alias-label-dimmed, #9aa0a6); }
|
|
133
|
+
.drc-dot[data-tone="wait"] { background: var(--dsw-alias-state-warn-primary, #faad14); }
|
|
134
|
+
.drc-dot[data-tone="on"] { background: var(--dsw-alias-state-success-primary, #52c41a); }
|
|
135
|
+
.drc-dot[data-tone="error"] { background: var(--dsw-alias-state-error-primary, #ff4d4f); }
|
|
136
|
+
.drc-panel { position: absolute; bottom: calc(100% + 8px); right: 0; z-index: 30; width: 240px;
|
|
137
|
+
padding: 10px 12px; border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.1)); border-radius: 12px;
|
|
138
|
+
background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-1, #ffffff));
|
|
139
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14), 0 2px 6px rgba(0, 0, 0, 0.06);
|
|
140
|
+
color: var(--dsw-alias-label-primary); font-size: 12px; line-height: 18px; }
|
|
141
|
+
/* \u62AC\u5934\u4E00\u884C\uFF1A\u5DE6\u4FA7\u72B6\u6001\uFF0C\u53F3\u4FA7\u52A8\u4F5C\u3002\u4E24\u9897\u6309\u94AE\u90FD\u6536\u5728\u8FD9\u4E2A\u53F3\u4E0A\u89D2\uFF082026-10-03 \u91CD\u8BBE\u8BA1\u7684\u8981\u6C42\uFF09\u3002 */
|
|
142
|
+
.drc-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
143
|
+
.drc-head-label { font-weight: 600; }
|
|
144
|
+
.drc-head-label[data-tone="off"] { color: var(--dsw-alias-label-secondary, #6b7280); }
|
|
145
|
+
.drc-head-label[data-tone="wait"] { color: var(--dsw-alias-state-warn-primary, #faad14); }
|
|
146
|
+
.drc-head-label[data-tone="on"] { color: var(--dsw-alias-state-success-primary, #52c41a); }
|
|
147
|
+
.drc-head-label[data-tone="error"] { color: var(--dsw-alias-state-error-primary, #ff4d4f); }
|
|
148
|
+
.drc-actions { display: flex; gap: 6px; flex: 0 0 auto; }
|
|
149
|
+
.drc-btn { padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.1));
|
|
150
|
+
border-radius: 8px; background: transparent; color: inherit; font: inherit; font-size: 12px; cursor: pointer;
|
|
151
|
+
white-space: nowrap; }
|
|
152
|
+
.drc-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04)); }
|
|
153
|
+
/* \u6B63\u6587\u90A3\u4E09\u884C\uFF08\u4E2D\u7EE7 / \u72B6\u6001 / \u7248\u672C\uFF09\uFF1A\u952E\u5DE6\u503C\u53F3\uFF08space-between\uFF09\uFF0C\u503C\u88AB\u622A\u65AD\u65F6\u7701\u7565\u53F7\u2014\u2014
|
|
154
|
+
\u548C\u90A3\u9897 pill \u540C\u4E00\u5957"\u6324\u4E0D\u4E0B\u5C31\u622A\u6587\u5B57\u4E0D\u6491\u5BB9\u5668"\u3002\u7B2C\u4E00\u884C\u5E26\u90A3\u6761\u5206\u9694\u7EBF\uFF0C\u540E\u4E24\u884C\u53EA\u7559\u884C\u8DDD\u3002 */
|
|
155
|
+
.drc-info { display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
|
|
156
|
+
white-space: nowrap; margin-top: 8px; padding-top: 6px;
|
|
157
|
+
border-top: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08)); }
|
|
158
|
+
.drc-info + .drc-info { margin-top: 2px; padding-top: 0; border-top: 0; }
|
|
159
|
+
.drc-key { flex: 0 0 auto; color: var(--dsw-alias-label-tertiary); }
|
|
160
|
+
.drc-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
161
|
+
.drc-qr { display: block; width: 200px; height: 200px; margin: 8px auto 0; image-rendering: pixelated; }
|
|
162
|
+
.drc-code { margin: 8px 0 0; text-align: center; font-weight: 600; font-size: 14px; letter-spacing: 2px; }
|
|
163
|
+
.drc-note { margin: 4px 0 0; text-align: center; color: var(--dsw-alias-label-tertiary); }
|
|
164
|
+
.drc-note[data-kind="failed"] { color: var(--dsw-alias-state-error-primary, #ff4d4f); }
|
|
165
|
+
`;
|
|
166
|
+
function isSlots(value) {
|
|
167
|
+
if (!value || typeof value !== "object") return false;
|
|
168
|
+
const record = value;
|
|
169
|
+
return typeof record.inject === "function" && typeof record.register === "function";
|
|
170
|
+
}
|
|
171
|
+
function pickSlots(source) {
|
|
172
|
+
if (isSlots(source)) return source;
|
|
173
|
+
const asGettable = source;
|
|
174
|
+
if (asGettable && typeof asGettable.get === "function") {
|
|
175
|
+
try {
|
|
176
|
+
const got = asGettable.get("slots", true);
|
|
177
|
+
if (isSlots(got)) return got;
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return void 0;
|
|
54
182
|
}
|
|
55
|
-
|
|
56
|
-
if (stopped || inFlight) return;
|
|
183
|
+
function findCreateElement() {
|
|
57
184
|
try {
|
|
58
|
-
if (typeof
|
|
185
|
+
if (typeof require !== "function") return void 0;
|
|
186
|
+
const moduleId = "react";
|
|
187
|
+
const react = require(moduleId);
|
|
188
|
+
const create = react?.createElement ?? react?.default?.createElement;
|
|
189
|
+
return typeof create === "function" ? create : void 0;
|
|
59
190
|
} catch {
|
|
191
|
+
return void 0;
|
|
60
192
|
}
|
|
61
|
-
|
|
193
|
+
}
|
|
194
|
+
function warn(message, error) {
|
|
62
195
|
try {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
if (id === null) return;
|
|
66
|
-
sessionId = id;
|
|
196
|
+
const detail = error instanceof Error ? error.message : error === void 0 ? "" : String(error);
|
|
197
|
+
console.warn(`[dsh-remote-control pill] ${message} ${detail}`);
|
|
67
198
|
} catch {
|
|
68
|
-
return;
|
|
69
199
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
lastEpoch = null;
|
|
73
|
-
}
|
|
74
|
-
inFlight = true;
|
|
200
|
+
}
|
|
201
|
+
function ensureStyle(doc) {
|
|
75
202
|
try {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
203
|
+
const existing = doc.querySelector(`style[data-plugin-css="${STYLE_ID}"]`);
|
|
204
|
+
if (existing) {
|
|
205
|
+
if (existing.textContent !== CSS) existing.textContent = CSS;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const element = doc.createElement("style");
|
|
209
|
+
element.setAttribute("data-plugin-css", STYLE_ID);
|
|
210
|
+
element.textContent = CSS;
|
|
211
|
+
doc.head.appendChild(element);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
warn("\u6837\u5F0F\u6CE8\u5165\u5931\u8D25\uFF08pill \u4ECD\u53EF\u7528\uFF0C\u53EA\u662F\u6CA1\u914D\u8272\uFF09", error);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function buildPill(root, deps) {
|
|
217
|
+
const doc = root.ownerDocument;
|
|
218
|
+
ensureStyle(doc);
|
|
219
|
+
const button = doc.createElement("button");
|
|
220
|
+
button.type = "button";
|
|
221
|
+
button.className = "drc-pill";
|
|
222
|
+
button.setAttribute("aria-haspopup", "dialog");
|
|
223
|
+
button.setAttribute("aria-expanded", "false");
|
|
224
|
+
const dot = doc.createElement("span");
|
|
225
|
+
dot.className = "drc-dot";
|
|
226
|
+
dot.setAttribute("aria-hidden", "true");
|
|
227
|
+
const label = doc.createElement("span");
|
|
228
|
+
label.className = "drc-label";
|
|
229
|
+
button.appendChild(dot);
|
|
230
|
+
button.appendChild(label);
|
|
231
|
+
root.appendChild(button);
|
|
232
|
+
let panel;
|
|
233
|
+
let statusTimer;
|
|
234
|
+
let countTimer;
|
|
235
|
+
let view = { kind: "info" };
|
|
236
|
+
let expiresInMs = 0;
|
|
237
|
+
let lastStatus;
|
|
238
|
+
let disposed = false;
|
|
239
|
+
const pairedNow = () => typeof lastStatus?.paired === "number" ? lastStatus.paired : 0;
|
|
240
|
+
const write = () => {
|
|
241
|
+
const next = pillLabel(lastStatus);
|
|
242
|
+
label.textContent = next.text;
|
|
243
|
+
dot.setAttribute("data-tone", next.tone);
|
|
244
|
+
button.setAttribute("title", `dsh-remote-control\uFF1A${next.text}`);
|
|
245
|
+
button.setAttribute("aria-label", next.text);
|
|
246
|
+
};
|
|
247
|
+
const text = (tag, className, content) => {
|
|
248
|
+
const node = doc.createElement(tag);
|
|
249
|
+
node.className = className;
|
|
250
|
+
node.textContent = content;
|
|
251
|
+
return node;
|
|
252
|
+
};
|
|
253
|
+
const buttonOf = (content, onClick) => {
|
|
254
|
+
const node = doc.createElement("button");
|
|
255
|
+
node.type = "button";
|
|
256
|
+
node.className = "drc-btn";
|
|
257
|
+
node.textContent = content;
|
|
258
|
+
node.addEventListener("click", onClick);
|
|
259
|
+
return node;
|
|
260
|
+
};
|
|
261
|
+
const qrNote = () => expiresInMs > 0 ? `\u626B\u7801\u914D\u5BF9 \xB7 ${secondsText(expiresInMs)}\u540E\u8FC7\u671F` : "\u914D\u5BF9\u7801\u5DF2\u8FC7\u671F\uFF0C\u70B9\u53F3\u4E0A\u89D2\u300C\u5237\u65B0\u300D\u91CD\u65B0\u751F\u6210";
|
|
262
|
+
const header = () => {
|
|
263
|
+
const head = doc.createElement("div");
|
|
264
|
+
head.className = "drc-head";
|
|
265
|
+
const state = pillLabel(lastStatus);
|
|
266
|
+
const stateText = text("span", "drc-head-label", state.text);
|
|
267
|
+
stateText.setAttribute("data-tone", state.tone);
|
|
268
|
+
head.appendChild(stateText);
|
|
269
|
+
const actions = doc.createElement("div");
|
|
270
|
+
actions.className = "drc-actions";
|
|
271
|
+
const action = pairedNow() > 0 ? { label: "\u9000\u51FA\u914D\u5BF9", run: requestUnpair } : { label: "\u5237\u65B0", run: requestPairing };
|
|
272
|
+
actions.appendChild(buttonOf(action.label, () => void action.run()));
|
|
273
|
+
head.appendChild(actions);
|
|
274
|
+
return head;
|
|
275
|
+
};
|
|
276
|
+
const infoRows = () => {
|
|
277
|
+
if (!lastStatus) return [];
|
|
278
|
+
const rows = [
|
|
279
|
+
// 排第一:它是唯一一条"看完要做点什么"的,其余三条都是"知道就行"。
|
|
280
|
+
["\u5F85\u5904\u7406", waitingRow(lastStatus)],
|
|
281
|
+
["\u4E2D\u7EE7", relayRow(lastStatus)],
|
|
282
|
+
["\u72B6\u6001", relayStateRow(lastStatus)],
|
|
283
|
+
["\u7248\u672C", versionRow(lastStatus)]
|
|
284
|
+
];
|
|
285
|
+
return rows.filter(([, value]) => value !== "").map(([key, value]) => {
|
|
286
|
+
const row = doc.createElement("div");
|
|
287
|
+
row.className = "drc-info";
|
|
288
|
+
row.appendChild(text("span", "drc-key", key));
|
|
289
|
+
row.appendChild(text("span", "drc-value", value));
|
|
290
|
+
return row;
|
|
79
291
|
});
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
292
|
+
};
|
|
293
|
+
const paint = () => {
|
|
294
|
+
if (!panel) return;
|
|
295
|
+
panel.replaceChildren();
|
|
296
|
+
panel.appendChild(header());
|
|
297
|
+
if (view.kind === "loading") {
|
|
298
|
+
panel.appendChild(text("p", "drc-note", "\u6B63\u5728\u751F\u6210\u914D\u5BF9\u7801\u2026"));
|
|
299
|
+
} else if (view.kind === "qr") {
|
|
300
|
+
const image = doc.createElement("img");
|
|
301
|
+
image.className = "drc-qr";
|
|
302
|
+
image.setAttribute("alt", "\u914D\u5BF9\u4E8C\u7EF4\u7801");
|
|
303
|
+
image.src = view.imageSrc;
|
|
304
|
+
panel.appendChild(image);
|
|
305
|
+
panel.appendChild(text("p", "drc-code", view.token));
|
|
306
|
+
panel.appendChild(text("p", "drc-note", qrNote()));
|
|
307
|
+
} else if (view.kind === "unavailable" || view.kind === "failed") {
|
|
308
|
+
const note = view.kind === "unavailable" ? text("p", "drc-note", view.reason) : (() => {
|
|
309
|
+
const node = text("p", "drc-note", `\u914D\u5BF9\u8BF7\u6C42\u6CA1\u6210\u529F\uFF1A${view.detail}`);
|
|
310
|
+
node.setAttribute("data-kind", "failed");
|
|
311
|
+
return node;
|
|
312
|
+
})();
|
|
313
|
+
panel.appendChild(note);
|
|
314
|
+
}
|
|
315
|
+
for (const row of infoRows()) panel.appendChild(row);
|
|
316
|
+
};
|
|
317
|
+
const closePanel = () => {
|
|
318
|
+
if (countTimer !== void 0) {
|
|
319
|
+
clearInterval(countTimer);
|
|
320
|
+
countTimer = void 0;
|
|
321
|
+
}
|
|
322
|
+
if (panel) {
|
|
323
|
+
panel.remove();
|
|
324
|
+
panel = void 0;
|
|
325
|
+
}
|
|
326
|
+
button.setAttribute("aria-expanded", "false");
|
|
327
|
+
};
|
|
328
|
+
async function requestPairing() {
|
|
329
|
+
if (disposed || !panel) return;
|
|
330
|
+
view = { kind: "loading" };
|
|
331
|
+
paint();
|
|
332
|
+
let answer;
|
|
333
|
+
let httpStatus = 0;
|
|
334
|
+
try {
|
|
335
|
+
const response = await deps.fetchImpl(NEW_ROUTE, {
|
|
336
|
+
method: "POST",
|
|
337
|
+
headers: { "x-drc-pair": "1" },
|
|
338
|
+
credentials: "same-origin"
|
|
339
|
+
});
|
|
340
|
+
httpStatus = typeof response?.status === "number" ? response.status : 0;
|
|
341
|
+
answer = await response?.json?.();
|
|
342
|
+
} catch (error) {
|
|
343
|
+
view = { kind: "failed", detail: error instanceof Error ? error.message : String(error) };
|
|
344
|
+
paint();
|
|
83
345
|
return;
|
|
84
346
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
347
|
+
if (disposed || !panel) return;
|
|
348
|
+
view = panelViewFor(answer, httpStatus);
|
|
349
|
+
if (view.kind === "qr") {
|
|
350
|
+
expiresInMs = view.expiresInMs;
|
|
351
|
+
if (countTimer === void 0) {
|
|
352
|
+
countTimer = setInterval(() => {
|
|
353
|
+
expiresInMs -= 1e3;
|
|
354
|
+
if (expiresInMs <= 0) {
|
|
355
|
+
clearInterval(countTimer);
|
|
356
|
+
countTimer = void 0;
|
|
357
|
+
}
|
|
358
|
+
paint();
|
|
359
|
+
}, 1e3);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
paint();
|
|
363
|
+
}
|
|
364
|
+
async function requestUnpair() {
|
|
365
|
+
if (disposed || !panel) return;
|
|
366
|
+
try {
|
|
367
|
+
await deps.fetchImpl(UNPAIR_ROUTE, {
|
|
368
|
+
method: "POST",
|
|
369
|
+
headers: { "x-drc-pair": "1" },
|
|
370
|
+
credentials: "same-origin"
|
|
371
|
+
});
|
|
372
|
+
} catch (error) {
|
|
373
|
+
warn("\u9000\u51FA\u914D\u5BF9\u8BF7\u6C42\u5931\u8D25\uFF08\u9762\u677F\u4F1A\u9760\u4E0B\u4E00\u6B21\u8F6E\u8BE2\u5BF9\u8D26\uFF09", error);
|
|
374
|
+
}
|
|
375
|
+
if (disposed || !panel) return;
|
|
376
|
+
if (lastStatus) lastStatus = { ...lastStatus, paired: 0 };
|
|
377
|
+
write();
|
|
378
|
+
view = { kind: "loading" };
|
|
379
|
+
paint();
|
|
380
|
+
void requestPairing();
|
|
381
|
+
}
|
|
382
|
+
const openPanel = () => {
|
|
383
|
+
if (panel || disposed) return;
|
|
384
|
+
panel = doc.createElement("div");
|
|
385
|
+
panel.className = "drc-panel";
|
|
386
|
+
panel.setAttribute("role", "dialog");
|
|
387
|
+
panel.setAttribute("aria-label", "dsh-remote-control \u914D\u5BF9");
|
|
388
|
+
panel.tabIndex = -1;
|
|
389
|
+
root.appendChild(panel);
|
|
390
|
+
button.setAttribute("aria-expanded", "true");
|
|
391
|
+
if (pairedNow() > 0) {
|
|
392
|
+
view = { kind: "info" };
|
|
393
|
+
paint();
|
|
394
|
+
} else {
|
|
395
|
+
void requestPairing();
|
|
396
|
+
}
|
|
92
397
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
398
|
+
panel.focus();
|
|
399
|
+
} catch {
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
const inside = (candidate) => {
|
|
403
|
+
if (!candidate || typeof candidate !== "object") return false;
|
|
404
|
+
try {
|
|
405
|
+
return root.contains(candidate) || (panel?.contains(candidate) ?? false);
|
|
406
|
+
} catch {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const onPointerDown = (event) => {
|
|
411
|
+
if (!panel) return;
|
|
412
|
+
if (inside(event?.target)) return;
|
|
413
|
+
closePanel();
|
|
414
|
+
};
|
|
415
|
+
const onKeyDown = (event) => {
|
|
416
|
+
if (event.key === "Escape") closePanel();
|
|
417
|
+
};
|
|
418
|
+
async function pollStatus() {
|
|
419
|
+
if (disposed) return;
|
|
420
|
+
try {
|
|
421
|
+
if (doc.visibilityState === "hidden") return;
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
const response = await deps.fetchImpl(`${STATUS_ROUTE}?t=${Date.now()}`, {
|
|
426
|
+
headers: { accept: "application/json" },
|
|
427
|
+
credentials: "same-origin"
|
|
428
|
+
});
|
|
429
|
+
if (!response || response.status !== 200) return;
|
|
430
|
+
const body = await response.json?.();
|
|
431
|
+
if (!body || typeof body !== "object") return;
|
|
432
|
+
lastStatus = body;
|
|
433
|
+
if (view.kind === "qr" && pairedNow() > 0) {
|
|
434
|
+
view = { kind: "info" };
|
|
435
|
+
if (countTimer !== void 0) {
|
|
436
|
+
clearInterval(countTimer);
|
|
437
|
+
countTimer = void 0;
|
|
438
|
+
}
|
|
439
|
+
if (panel) paint();
|
|
440
|
+
}
|
|
441
|
+
write();
|
|
95
442
|
} catch (error) {
|
|
96
|
-
|
|
443
|
+
deps.warn?.("status poll failed");
|
|
444
|
+
warn("status poll failed\uFF08pill \u4F1A\u505C\u5728\u9ED8\u8BA4\u6587\u6848\u4E0A\uFF09", error);
|
|
97
445
|
}
|
|
98
|
-
} catch (error) {
|
|
99
|
-
report("poll failed", error);
|
|
100
|
-
} finally {
|
|
101
|
-
inFlight = false;
|
|
102
446
|
}
|
|
447
|
+
button.addEventListener("click", () => {
|
|
448
|
+
if (panel) closePanel();
|
|
449
|
+
else openPanel();
|
|
450
|
+
});
|
|
451
|
+
doc.addEventListener("pointerdown", onPointerDown, true);
|
|
452
|
+
doc.addEventListener("keydown", onKeyDown);
|
|
453
|
+
write();
|
|
454
|
+
void pollStatus();
|
|
455
|
+
statusTimer = setInterval(() => void pollStatus(), POLL_MS);
|
|
456
|
+
return () => {
|
|
457
|
+
if (disposed) return;
|
|
458
|
+
disposed = true;
|
|
459
|
+
if (statusTimer !== void 0) clearInterval(statusTimer);
|
|
460
|
+
closePanel();
|
|
461
|
+
doc.removeEventListener("pointerdown", onPointerDown, true);
|
|
462
|
+
doc.removeEventListener("keydown", onKeyDown);
|
|
463
|
+
button.remove();
|
|
464
|
+
};
|
|
103
465
|
}
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
timer = void 0;
|
|
466
|
+
function mountPill(ctx, createElement) {
|
|
467
|
+
if (!createElement) {
|
|
468
|
+
warn("\u88C5\u8F7D\u5668\u7ED9\u4E0D\u51FA react\uFF1A\u914D\u5BF9\u90A3\u9897 pill \u4E0D\u4F1A\u51FA\u73B0");
|
|
469
|
+
return false;
|
|
109
470
|
}
|
|
471
|
+
let mounted = false;
|
|
472
|
+
const mount = (slots) => {
|
|
473
|
+
if (mounted) return;
|
|
474
|
+
let cleanup;
|
|
475
|
+
const attach = (node) => {
|
|
476
|
+
const element = node;
|
|
477
|
+
if (element && typeof element.appendChild === "function" && element.ownerDocument) {
|
|
478
|
+
if (cleanup) return;
|
|
479
|
+
element.style.position = "relative";
|
|
480
|
+
cleanup = buildPill(element, {
|
|
481
|
+
fetchImpl: (input, init) => globalThis.fetch(input, init)
|
|
482
|
+
});
|
|
483
|
+
} else if (cleanup) {
|
|
484
|
+
cleanup();
|
|
485
|
+
cleanup = void 0;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
const component = () => createElement("span", { ref: attach });
|
|
489
|
+
mounted = true;
|
|
490
|
+
try {
|
|
491
|
+
slots.inject(SLOT_NAME, () => {
|
|
492
|
+
try {
|
|
493
|
+
slots.register({ name: SLOT_NAME, id: PILL_ID, order: PILL_ORDER, inject: () => ({}) }, component);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
warn("\u69FD\u4F4D\u6CE8\u518C\u5931\u8D25\uFF08\u90A3\u9897 pill \u4E0D\u51FA\u73B0\uFF09", error);
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
} catch (error) {
|
|
499
|
+
mounted = false;
|
|
500
|
+
warn("\u69FD\u4F4D\u6CE8\u5165\u5931\u8D25\uFF08\u90A3\u9897 pill \u4E0D\u51FA\u73B0\uFF09", error);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
try {
|
|
504
|
+
ctx.effect?.(() => () => {
|
|
505
|
+
if (cleanup) {
|
|
506
|
+
cleanup();
|
|
507
|
+
cleanup = void 0;
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
} catch {
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
let early;
|
|
110
514
|
try {
|
|
111
|
-
|
|
515
|
+
early = pickSlots(ctx.slots) ?? pickSlots(ctx.get?.("slots", true));
|
|
112
516
|
} catch {
|
|
113
517
|
}
|
|
518
|
+
if (early) mount(early);
|
|
519
|
+
try {
|
|
520
|
+
ctx.inject?.(["slots"], ((...args) => {
|
|
521
|
+
for (const candidate of [...args, ctx]) {
|
|
522
|
+
const found = pickSlots(candidate);
|
|
523
|
+
if (found) {
|
|
524
|
+
mount(found);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}));
|
|
529
|
+
} catch (error) {
|
|
530
|
+
warn("ctx.inject \u53E3\u4E0D\u53EF\u7528\uFF1Aslots \u518D\u665A\u5230\u4E5F\u6CA1\u4EBA\u8865\u6302", error);
|
|
531
|
+
}
|
|
532
|
+
if (!mounted) {
|
|
533
|
+
warn("apply \u65F6\u6CA1\u63A2\u5230 slots\uFF08\u5DF2\u6302\u6CE8\u5165\u56DE\u8C03\u7B49\u5B83\u665A\u5230\uFF09\uFF1Bpill \u4E00\u76F4\u4E0D\u51FA\u73B0\u5C31\u662F\u8FD9\u4EE3\u5BBF\u4E3B\u6CA1\u8FD9\u4E2A\u670D\u52A1");
|
|
534
|
+
}
|
|
535
|
+
return mounted;
|
|
114
536
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (current && !stopped) void poll(current);
|
|
118
|
-
}
|
|
537
|
+
|
|
538
|
+
// client/src/main.ts
|
|
119
539
|
var name = "dsh-remote-control";
|
|
120
|
-
var inject = ["
|
|
540
|
+
var inject = ["slots"];
|
|
121
541
|
function apply(ctx) {
|
|
122
542
|
try {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
reported.clear();
|
|
543
|
+
mountPill(ctx, findCreateElement());
|
|
544
|
+
} catch (error) {
|
|
126
545
|
try {
|
|
127
|
-
|
|
546
|
+
console.error("[dsh-remote-control] pill \u6302\u8F7D\u629B\u9519\uFF08\u5DF2\u541E\u6389\uFF09\uFF1A", error);
|
|
128
547
|
} catch {
|
|
129
548
|
}
|
|
130
|
-
timer = setInterval(() => void poll(ctx), POLL_MS);
|
|
131
|
-
void poll(ctx);
|
|
132
|
-
try {
|
|
133
|
-
ctx.effect?.(() => stop);
|
|
134
|
-
} catch (error) {
|
|
135
|
-
report("ctx.effect unavailable; poll timer will outlive this fiber", error);
|
|
136
|
-
}
|
|
137
|
-
} catch (error) {
|
|
138
|
-
report("apply failed", error);
|
|
139
|
-
stop();
|
|
140
549
|
}
|
|
141
550
|
}
|
|
142
551
|
|