linkshell-cli 0.10.49 → 0.10.51
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/dist/cli/src/commands/desktop-workspace.js +5 -6
- package/dist/cli/src/commands/desktop-workspace.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/host/src/index.d.ts +1 -0
- package/dist/host/src/index.js +1 -0
- package/dist/host/src/index.js.map +1 -1
- package/dist/host/src/local-transport.d.ts +3 -1
- package/dist/host/src/local-transport.js +13 -8
- package/dist/host/src/local-transport.js.map +1 -1
- package/dist/host/src/screen-loading.d.ts +3 -0
- package/dist/host/src/screen-loading.js +93 -0
- package/dist/host/src/screen-loading.js.map +1 -0
- package/dist/host/src/screen-viewer.js +55 -22
- package/dist/host/src/screen-viewer.js.map +1 -1
- package/package.json +4 -4
- package/src/commands/desktop-workspace.ts +5 -6
package/dist/host/src/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { defaultHome, startHost, hostPaths, isHostRunning, type HostOptions, type HostPaths, type RunningHost } from "./host.js";
|
|
2
2
|
export { connectHost, type HostClient } from "./rpc/client.js";
|
|
3
|
+
export { localWebSocket, windowsPipe } from "./local-transport.js";
|
|
3
4
|
export { SessionHub, type Subscriber } from "./hub.js";
|
|
4
5
|
export { HostStore } from "./store.js";
|
|
5
6
|
export { CodexDriver } from "./drivers/codex/driver.js";
|
package/dist/host/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { defaultHome, startHost, hostPaths, isHostRunning } from "./host.js";
|
|
2
2
|
export { connectHost } from "./rpc/client.js";
|
|
3
|
+
export { localWebSocket, windowsPipe } from "./local-transport.js";
|
|
3
4
|
export { SessionHub } from "./hub.js";
|
|
4
5
|
export { HostStore } from "./store.js";
|
|
5
6
|
export { CodexDriver } from "./drivers/codex/driver.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../host/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAsD,MAAM,WAAW,CAAC;AACjI,OAAO,EAAE,WAAW,EAAmB,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAmB,MAAM,UAAU,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAGxD,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAqB,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEnE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../host/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAsD,MAAM,WAAW,CAAC;AACjI,OAAO,EAAE,WAAW,EAAmB,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,UAAU,EAAmB,MAAM,UAAU,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAGxD,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAqB,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEnE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -2,4 +2,6 @@ import WebSocket from "ws";
|
|
|
2
2
|
export declare const isNamedPipe: (path: string) => boolean;
|
|
3
3
|
/** Stable across callers, separate for each state directory, without the Unix path limit. */
|
|
4
4
|
export declare function windowsPipe(home: string, name: string): string;
|
|
5
|
-
export declare function localWebSocket(target: string, timeout?: number
|
|
5
|
+
export declare function localWebSocket(target: string, timeout?: number, limits?: {
|
|
6
|
+
maxPayload?: number;
|
|
7
|
+
}): WebSocket;
|
|
@@ -9,14 +9,19 @@ export function windowsPipe(home, name) {
|
|
|
9
9
|
const tag = createHash("sha256").update(key).digest("hex").slice(0, 24);
|
|
10
10
|
return `\\\\.\\pipe\\linkshell-${tag}-${name}`;
|
|
11
11
|
}
|
|
12
|
-
export function localWebSocket(target, timeout = 10_000) {
|
|
13
|
-
const options = { perMessageDeflate: false, handshakeTimeout: timeout };
|
|
12
|
+
export function localWebSocket(target, timeout = 10_000, limits = {}) {
|
|
13
|
+
const options = { perMessageDeflate: false, handshakeTimeout: timeout, ...limits };
|
|
14
14
|
// URL parsers treat Windows backslashes and drive letters as URL syntax. Connect directly.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
const socket = isNamedPipe(target) ? new WebSocket("ws://localhost/", {
|
|
16
|
+
...options,
|
|
17
|
+
createConnection: () => connect(target),
|
|
18
|
+
}) : new WebSocket(/^wss?:\/\//.test(target) ? target : `ws+unix://${target}:/`, options);
|
|
19
|
+
// Node's HTTP request timeout does not reliably fire on Windows pipe handshakes.
|
|
20
|
+
const deadline = setTimeout(() => socket.terminate(), timeout);
|
|
21
|
+
deadline.unref();
|
|
22
|
+
socket.once("open", () => clearTimeout(deadline));
|
|
23
|
+
socket.once("close", () => clearTimeout(deadline));
|
|
24
|
+
socket.once("error", () => clearTimeout(deadline));
|
|
25
|
+
return socket;
|
|
21
26
|
}
|
|
22
27
|
//# sourceMappingURL=local-transport.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-transport.js","sourceRoot":"","sources":["../../../../host/src/local-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAClC,OAAO,SAAS,MAAM,IAAI,CAAC;AAE3B,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AAEvF,6FAA6F;AAC7F,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,IAAY;IACpD,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACrE,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxE,OAAO,0BAA0B,GAAG,IAAI,IAAI,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,OAAO,GAAG,MAAM;
|
|
1
|
+
{"version":3,"file":"local-transport.js","sourceRoot":"","sources":["../../../../host/src/local-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAClC,OAAO,SAAS,MAAM,IAAI,CAAC;AAE3B,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AAEvF,6FAA6F;AAC7F,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,IAAY;IACpD,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACrE,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxE,OAAO,0BAA0B,GAAG,IAAI,IAAI,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,OAAO,GAAG,MAAM,EAAE,SAAkC,EAAE;IACnG,MAAM,OAAO,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC;IACnF,2FAA2F;IAC3F,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,iBAAiB,EAAE;QACpE,GAAG,OAAO;QACV,gBAAgB,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;KACxC,CAAC,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,MAAM,IAAI,EAAE,OAAO,CAAC,CAAC;IAC1F,iFAAiF;IACjF,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC;IAC/D,QAAQ,CAAC,KAAK,EAAE,CAAC;IACjB,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClD,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IACnD,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IACnD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Shared by the browser viewer and the phone's WebView, including native video reception.
|
|
2
|
+
export const SCREEN_LOADING_STYLE = String.raw `
|
|
3
|
+
#note { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
|
4
|
+
padding: max(28px, env(safe-area-inset-top)) 24px max(88px, env(safe-area-inset-bottom));
|
|
5
|
+
color: #f4f4f7; text-align: center; pointer-events: none; background: rgba(8,9,14,.94); }
|
|
6
|
+
.screen-loading-card { width: min(100%, 340px); }
|
|
7
|
+
.screen-loading-art { position: relative; width: 104px; height: 84px; margin: 0 auto 28px; color: #a5a9ef; }
|
|
8
|
+
.screen-loading-art::before { content: ''; position: absolute; inset: -32px; border-radius: 50%;
|
|
9
|
+
background: radial-gradient(ellipse, rgba(129,140,248,.16), transparent 68%); animation: screen-breathe 2.8s ease-in-out infinite; }
|
|
10
|
+
.screen-loading-monitor { position: absolute; inset: 6px 4px 17px; border: 1px solid rgba(165,169,239,.5); border-radius: 10px;
|
|
11
|
+
background: rgba(129,140,248,.05); overflow: hidden; }
|
|
12
|
+
.screen-loading-monitor::before { content: ''; position: absolute; top: 12px; left: 12px; width: 23px; height: 4px;
|
|
13
|
+
border-radius: 2px; background: rgba(165,169,239,.4); box-shadow: 0 11px 0 rgba(165,169,239,.12), 0 22px 0 rgba(165,169,239,.12); }
|
|
14
|
+
.screen-loading-monitor::after { content: ''; position: absolute; top: 0; bottom: 0; left: -45%; width: 45%;
|
|
15
|
+
background: linear-gradient(90deg, transparent, rgba(165,169,239,.16), transparent); animation: screen-scan 2.4s ease-in-out infinite; }
|
|
16
|
+
.screen-loading-stand { position: absolute; bottom: 8px; left: 50%; width: 26px; height: 10px; transform: translateX(-50%);
|
|
17
|
+
border-bottom: 1px solid rgba(165,169,239,.5); }
|
|
18
|
+
.screen-loading-stand::before { content: ''; position: absolute; left: 50%; height: 9px; border-left: 1px solid rgba(165,169,239,.5); }
|
|
19
|
+
#screen-loading-title { margin: 0; font-size: calc(18px * var(--text-scale, 1)); font-weight: 600; letter-spacing: -.3px; }
|
|
20
|
+
#screen-loading-detail { margin: 10px 0 0; color: rgba(255,255,255,.52); font-size: calc(13px * var(--text-scale, 1)); line-height: 1.7; overflow-wrap: anywhere; }
|
|
21
|
+
.screen-loading-steps { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 24px;
|
|
22
|
+
color: rgba(255,255,255,.3); font-size: calc(11px * var(--text-scale, 1)); }
|
|
23
|
+
.screen-loading-steps i { width: 24px; height: 1px; background: rgba(255,255,255,.12); }
|
|
24
|
+
.screen-loading-steps .current { color: #bbbffb; }
|
|
25
|
+
.screen-loading-steps .complete { color: rgba(255,255,255,.68); }
|
|
26
|
+
#note[data-state="error"] .screen-loading-steps { display: none; }
|
|
27
|
+
#screen-loading-retry { pointer-events: auto; margin-top: 22px; min-height: 44px; padding: 0 20px; border-radius: 10px;
|
|
28
|
+
border: 1px solid rgba(255,255,255,.18); background: rgba(255,255,255,.07); color: #eeeef4; font: inherit; font-size: 13px; cursor: pointer; }
|
|
29
|
+
#screen-loading-retry:hover { background: rgba(255,255,255,.13); }
|
|
30
|
+
#screen-loading-retry:focus-visible { outline: 2px solid #a5a9ef; outline-offset: 4px; }
|
|
31
|
+
#note[data-state="error"] .screen-loading-art::before, #note[data-state="error"] .screen-loading-monitor::after,
|
|
32
|
+
#note[data-state="blocked"] .screen-loading-art::before, #note[data-state="blocked"] .screen-loading-monitor::after,
|
|
33
|
+
#note[data-state="paused"] .screen-loading-art::before, #note[data-state="paused"] .screen-loading-monitor::after { animation: none; opacity: .3; }
|
|
34
|
+
@keyframes screen-scan { to { transform: translateX(340%); } }
|
|
35
|
+
@keyframes screen-breathe { 50% { opacity: .4; transform: scale(.92); } }
|
|
36
|
+
@media (prefers-reduced-motion: reduce) { .screen-loading-art::before, .screen-loading-monitor::after { animation: none; } }
|
|
37
|
+
@media (max-height: 420px) { #note { padding: 16px 72px; } .screen-loading-art { height: 64px; margin-bottom: 12px; }
|
|
38
|
+
.screen-loading-steps { margin-top: 14px; } #screen-loading-retry { margin-top: 12px; } }
|
|
39
|
+
`;
|
|
40
|
+
export const SCREEN_LOADING_HTML = String.raw `
|
|
41
|
+
<div id="note" data-state="connecting">
|
|
42
|
+
<div class="screen-loading-card">
|
|
43
|
+
<div class="screen-loading-art" aria-hidden="true"><div class="screen-loading-monitor"></div><div class="screen-loading-stand"></div></div>
|
|
44
|
+
<div role="status" aria-live="polite" aria-atomic="true">
|
|
45
|
+
<h2 id="screen-loading-title">正在连接电脑</h2>
|
|
46
|
+
<p id="screen-loading-detail">正在建立屏幕连接,画面会自动显示。</p>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="screen-loading-steps" aria-hidden="true"><span id="screen-step-connect" class="current">连接电脑</span><i></i><span id="screen-step-picture">接收画面</span><i></i><span>显示桌面</span></div>
|
|
49
|
+
<button id="screen-loading-retry" class="gone" type="button">重新连接</button>
|
|
50
|
+
</div>
|
|
51
|
+
</div>`;
|
|
52
|
+
export const SCREEN_LOADING_SCRIPT = String.raw `
|
|
53
|
+
function createScreenLoading(root, retry, report) {
|
|
54
|
+
const title = root.querySelector('#screen-loading-title');
|
|
55
|
+
const detail = root.querySelector('#screen-loading-detail');
|
|
56
|
+
const button = root.querySelector('#screen-loading-retry');
|
|
57
|
+
const connectStep = root.querySelector('#screen-step-connect');
|
|
58
|
+
const pictureStep = root.querySelector('#screen-step-picture');
|
|
59
|
+
let phase = '', timer = 0, slow = false, disposed = false;
|
|
60
|
+
const pending = () => phase === 'connecting' || phase === 'receiving' || phase === 'recovering' || phase === 'adjusting';
|
|
61
|
+
const description = () => slow ? '这次连接耗时较长。可以继续等待,或重新连接。' :
|
|
62
|
+
phase === 'receiving' ? '电脑已响应,画面准备好后会自动显示。' :
|
|
63
|
+
phase === 'adjusting' ? '正在应用屏幕设置,请稍候。' :
|
|
64
|
+
phase === 'recovering' ? '正在尝试恢复画面,请稍候。' : '正在建立屏幕连接,画面会自动显示。';
|
|
65
|
+
function set(next, message) {
|
|
66
|
+
if (disposed || (phase === next && !message)) return;
|
|
67
|
+
const wasPending = pending();
|
|
68
|
+
phase = next;
|
|
69
|
+
root.dataset.state = phase;
|
|
70
|
+
root.classList.toggle('gone', phase === 'ready');
|
|
71
|
+
if (!pending()) { clearTimeout(timer); timer = 0; slow = false; }
|
|
72
|
+
else if (!wasPending) {
|
|
73
|
+
timer = setTimeout(() => {
|
|
74
|
+
if (!pending() || disposed) return;
|
|
75
|
+
slow = true;
|
|
76
|
+
detail.textContent = description();
|
|
77
|
+
button.classList.remove('gone');
|
|
78
|
+
}, 12000);
|
|
79
|
+
}
|
|
80
|
+
title.textContent = phase === 'connecting' ? '正在连接电脑' : phase === 'receiving' ? '正在接收画面' :
|
|
81
|
+
phase === 'recovering' ? '正在恢复画面' : phase === 'adjusting' ? '正在调整屏幕' : phase === 'paused' ? '屏幕已暂停' : phase === 'blocked' ? '画面已就绪,轻点开始' : phase === 'error' ? '暂时无法显示屏幕' : '画面已连接';
|
|
82
|
+
detail.textContent = message || (pending() ? description() : phase === 'paused' ? '返回屏幕后会自动继续。' : '');
|
|
83
|
+
button.classList.toggle('gone', phase !== 'error' && !slow);
|
|
84
|
+
connectStep.className = phase === 'connecting' ? 'current' : 'complete';
|
|
85
|
+
pictureStep.className = phase === 'receiving' || phase === 'recovering' ? 'current' : '';
|
|
86
|
+
report(phase);
|
|
87
|
+
}
|
|
88
|
+
button.addEventListener('click', (event) => { event.stopPropagation(); retry(); });
|
|
89
|
+
set('connecting');
|
|
90
|
+
return { set, get phase() { return phase; }, dispose() { disposed = true; clearTimeout(timer); } };
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
//# sourceMappingURL=screen-loading.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screen-loading.js","sourceRoot":"","sources":["../../../../host/src/screen-loading.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqC7C,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;OAWtC,CAAC;AAER,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwC9C,CAAC"}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// device can reach each other, it is a video track from the app, with the hands and the pointer on
|
|
18
18
|
// data channels beside it; the page draws the pointer, which that picture does not have in it.
|
|
19
19
|
// Otherwise it is H.264 down the page's socket, decoded here onto a canvas, as it always was.
|
|
20
|
+
import { SCREEN_LOADING_HTML, SCREEN_LOADING_SCRIPT, SCREEN_LOADING_STYLE } from "./screen-loading.js";
|
|
20
21
|
const icon = (body) => `<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`;
|
|
21
22
|
const ICONS = {
|
|
22
23
|
view: icon('<path d="M2.5 12s3.5-6.5 9.5-6.5 9.5 6.5 9.5 6.5-3.5 6.5-9.5 6.5S2.5 12 2.5 12Z"/><circle cx="12" cy="12" r="2.8"/>'),
|
|
@@ -52,8 +53,6 @@ const STYLE = String.raw `
|
|
|
52
53
|
#pointer.pressed { background: rgba(255,255,255,0.4); }
|
|
53
54
|
#pointer.own { border: 0; border-radius: 0; box-shadow: none; background: transparent center / 100% 100% no-repeat; }
|
|
54
55
|
#stage.own { cursor: none; }
|
|
55
|
-
#note { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; padding: 32px;
|
|
56
|
-
color: rgba(255,255,255,0.72); text-align: center; pointer-events: none; }
|
|
57
56
|
.glass { background: rgba(38,38,42,0.74); -webkit-backdrop-filter: blur(22px) saturate(1.6); backdrop-filter: blur(22px) saturate(1.6);
|
|
58
57
|
border: 0.5px solid rgba(255,255,255,0.16); box-shadow: 0 8px 28px rgba(0,0,0,0.38); }
|
|
59
58
|
#control-shell { position: absolute; pointer-events: none; z-index: 1; will-change: width, height, transform, border-radius, opacity; }
|
|
@@ -597,8 +596,22 @@ const mods = new Set();
|
|
|
597
596
|
|
|
598
597
|
// ---- The picture ----------------------------------------------------------
|
|
599
598
|
|
|
600
|
-
let sessionError = "";
|
|
601
|
-
const
|
|
599
|
+
let sessionError = "", sessionPhase = "", hasPicture = false;
|
|
600
|
+
const loading = createScreenLoading(note, () => {
|
|
601
|
+
if (app && chrome.canRetryScreen) tellApp({ type: "screenRetry" });
|
|
602
|
+
else if (typeof window.__linkshellReload === "function") window.__linkshellReload({});
|
|
603
|
+
else location.reload();
|
|
604
|
+
}, (phase) => window.__linkshellScreenState?.(phase));
|
|
605
|
+
addEventListener("pagehide", () => loading.dispose(), { once: true });
|
|
606
|
+
const say = (text) => {
|
|
607
|
+
if (!text) hasPicture = true;
|
|
608
|
+
if (sessionError) loading.set("error", sessionError);
|
|
609
|
+
else if (text) loading.set("error", text);
|
|
610
|
+
else if (sessionPhase) loading.set(sessionPhase);
|
|
611
|
+
else loading.set("ready");
|
|
612
|
+
};
|
|
613
|
+
// Transport updates must not hide a configuration failure or a paused retained session.
|
|
614
|
+
const showProgress = (phase) => { if (!sessionError && !sessionPhase) loading.set(phase); };
|
|
602
615
|
const round = (value, digits) => (value == null || !isFinite(value) ? null : +value.toFixed(digits === undefined ? 1 : digits));
|
|
603
616
|
const hex = (n) => n.toString(16).padStart(2, "0");
|
|
604
617
|
function sps(unit) {
|
|
@@ -694,11 +707,13 @@ const session = createScreenSession({ active: true, display: Number(query.get("d
|
|
|
694
707
|
changed: (state) => {
|
|
695
708
|
width = state.width;
|
|
696
709
|
const previousError = sessionError;
|
|
710
|
+
const previousPhase = sessionPhase;
|
|
697
711
|
sessionError = state.error;
|
|
712
|
+
sessionPhase = state.paused ? "paused" : !state.inputReady ? "adjusting" : "";
|
|
698
713
|
if (sessionError) say(sessionError);
|
|
699
|
-
else if (
|
|
700
|
-
|
|
701
|
-
else if (previousError ||
|
|
714
|
+
else if (sessionPhase) loading.set(sessionPhase);
|
|
715
|
+
// Configuration acknowledgement enables input; only received media proves the first picture exists.
|
|
716
|
+
else if (previousError || previousPhase) loading.set(hasPicture ? "ready" : "receiving");
|
|
702
717
|
if (state.inputReady && mode !== "view") askControl();
|
|
703
718
|
refreshWidths();
|
|
704
719
|
refresh();
|
|
@@ -725,7 +740,7 @@ function waitForCapabilities() {
|
|
|
725
740
|
if (!session.known()) session.receive({ t: "capabilities", version: 1, configure: false, pause: false, retainMs: 0 });
|
|
726
741
|
}, 10000);
|
|
727
742
|
}
|
|
728
|
-
ws.onopen = () => { session.open(); waitForCapabilities(); if (mode !== "view") askControl(); };
|
|
743
|
+
ws.onopen = () => { if (loading.phase === "connecting") showProgress("receiving"); session.open(); waitForCapabilities(); if (mode !== "view") askControl(); };
|
|
729
744
|
// Out of sight, a page answers late (its timers slow to one a second) and shows nothing: the host sends no
|
|
730
745
|
// picture down the socket meanwhile, and starts again at a keyframe.
|
|
731
746
|
document.addEventListener("visibilitychange", () => session.visibility(document.hidden));
|
|
@@ -803,7 +818,7 @@ ws.onclose = () => {
|
|
|
803
818
|
drop();
|
|
804
819
|
if (requestRelay()) return;
|
|
805
820
|
tellApp({ type: "screenClosed" });
|
|
806
|
-
if (
|
|
821
|
+
if (loading.phase !== "error") say("屏幕连接已断开,请确认电脑在线后重新连接。");
|
|
807
822
|
blocked = false;
|
|
808
823
|
};
|
|
809
824
|
|
|
@@ -936,15 +951,17 @@ function watch(mine) {
|
|
|
936
951
|
const state = mine.pc.connectionState || mine.pc.iceConnectionState;
|
|
937
952
|
if (state === "failed" || mine.pc.iceConnectionState === "failed") return giveUp(mine.up ? "the connection failed" : "no path between the two (ICE failed)");
|
|
938
953
|
if (state === "disconnected") {
|
|
954
|
+
showProgress("recovering");
|
|
939
955
|
// A path that drops for a moment comes back by itself; one gone for longer is given up.
|
|
940
956
|
if (mine.up && !mine.away) mine.away = setTimeout(() => away(mine, performance.now()), 4000);
|
|
941
957
|
} else if (state === "connected" || state === "completed") {
|
|
958
|
+
if (loading.phase !== "ready") showProgress("receiving");
|
|
942
959
|
clearTimeout(mine.away);
|
|
943
960
|
mine.away = 0;
|
|
944
961
|
if (mine.up) return;
|
|
945
962
|
mine.up = true;
|
|
946
963
|
clearTimeout(patience);
|
|
947
|
-
if (
|
|
964
|
+
if (loading.phase !== "ready") patience = setTimeout(frameless, 4000);
|
|
948
965
|
}
|
|
949
966
|
}
|
|
950
967
|
|
|
@@ -973,6 +990,7 @@ function frameless() {
|
|
|
973
990
|
function giveUp(reason) {
|
|
974
991
|
if (gaveUp) return;
|
|
975
992
|
gaveUp = true;
|
|
993
|
+
showProgress("recovering");
|
|
976
994
|
const pc = rtc && rtc.pc;
|
|
977
995
|
const told = (detail) => {
|
|
978
996
|
drop();
|
|
@@ -989,7 +1007,7 @@ function requestRelay() {
|
|
|
989
1007
|
if (!relayRequested) {
|
|
990
1008
|
relayRequested = true;
|
|
991
1009
|
tellApp({ type: "screenFallback" });
|
|
992
|
-
|
|
1010
|
+
showProgress("recovering");
|
|
993
1011
|
}
|
|
994
1012
|
return true;
|
|
995
1013
|
}
|
|
@@ -1010,7 +1028,7 @@ function fallBack() {
|
|
|
1010
1028
|
gaveUp = true;
|
|
1011
1029
|
drop();
|
|
1012
1030
|
if (requestRelay()) return;
|
|
1013
|
-
|
|
1031
|
+
showProgress("recovering");
|
|
1014
1032
|
blocked = false;
|
|
1015
1033
|
if (!windowsHost && !("VideoDecoder" in window)) return unseeable();
|
|
1016
1034
|
if (tried) hint(live ? "直连断开了:已改用兼容方式传画面,延迟会高一些" : "没能和电脑直连:已改用兼容方式传画面,延迟会高一些", 5000);
|
|
@@ -1024,12 +1042,12 @@ function play() {
|
|
|
1024
1042
|
started.then(() => {
|
|
1025
1043
|
if (!blocked) return;
|
|
1026
1044
|
blocked = false;
|
|
1027
|
-
|
|
1045
|
+
showProgress("receiving");
|
|
1028
1046
|
}, (error) => {
|
|
1029
1047
|
// A new stream replacing the one being started rejects the first call too; that is not a refusal.
|
|
1030
1048
|
if (error.name === "AbortError" || !rtc) return;
|
|
1031
1049
|
blocked = true;
|
|
1032
|
-
|
|
1050
|
+
if (!sessionError && !sessionPhase) loading.set("blocked", "浏览器需要你的确认,轻点屏幕即可开始观看。");
|
|
1033
1051
|
});
|
|
1034
1052
|
}
|
|
1035
1053
|
// What a browser takes as someone's say-so to start a video.
|
|
@@ -1041,19 +1059,34 @@ function sized() {
|
|
|
1041
1059
|
// While this page presents the frames itself, the size that counts is the frame's own: the
|
|
1042
1060
|
// <video> is kept running as the fallback but is not what is on screen.
|
|
1043
1061
|
if (presenter) {
|
|
1044
|
-
clearTimeout(patience);
|
|
1062
|
+
if (presenter.shown) clearTimeout(patience);
|
|
1045
1063
|
if (!live) show(true);
|
|
1046
1064
|
return;
|
|
1047
1065
|
}
|
|
1048
1066
|
content.w = video.videoWidth;
|
|
1049
1067
|
content.h = video.videoHeight;
|
|
1050
|
-
if (!blocked) say("");
|
|
1051
1068
|
if (live) return layout();
|
|
1052
|
-
clearTimeout(patience);
|
|
1053
1069
|
show(true);
|
|
1054
1070
|
}
|
|
1055
1071
|
video.addEventListener("resize", sized);
|
|
1056
1072
|
video.addEventListener("loadedmetadata", sized);
|
|
1073
|
+
// Metadata and a connected socket do not mean a frame is visible yet.
|
|
1074
|
+
function videoShown() {
|
|
1075
|
+
if (!rtc || blocked || video.readyState < 2 || presenter) return;
|
|
1076
|
+
clearTimeout(patience);
|
|
1077
|
+
sized();
|
|
1078
|
+
say("");
|
|
1079
|
+
}
|
|
1080
|
+
if ("requestVideoFrameCallback" in video) {
|
|
1081
|
+
const nextFrame = () => {
|
|
1082
|
+
if (loading.phase !== "ready") videoShown();
|
|
1083
|
+
video.requestVideoFrameCallback(nextFrame);
|
|
1084
|
+
};
|
|
1085
|
+
video.requestVideoFrameCallback(nextFrame);
|
|
1086
|
+
} else {
|
|
1087
|
+
video.addEventListener("playing", videoShown);
|
|
1088
|
+
video.addEventListener("timeupdate", videoShown);
|
|
1089
|
+
}
|
|
1057
1090
|
|
|
1058
1091
|
// ---- Presenting the track's frames this page's own way --------------------
|
|
1059
1092
|
|
|
@@ -1129,6 +1162,7 @@ async function presentTrack(track) {
|
|
|
1129
1162
|
draws.end();
|
|
1130
1163
|
device.queue.submit([pass.finish()]);
|
|
1131
1164
|
mine.shown += 1;
|
|
1165
|
+
say("");
|
|
1132
1166
|
if (mine.w !== frame.displayWidth || mine.h !== frame.displayHeight) {
|
|
1133
1167
|
mine.w = frame.displayWidth;
|
|
1134
1168
|
mine.h = frame.displayHeight;
|
|
@@ -1138,7 +1172,6 @@ async function presentTrack(track) {
|
|
|
1138
1172
|
// the <video> beside it was allowed to start playing.
|
|
1139
1173
|
clearTimeout(patience);
|
|
1140
1174
|
blocked = false;
|
|
1141
|
-
say("");
|
|
1142
1175
|
if (!live) show(true);
|
|
1143
1176
|
else layout();
|
|
1144
1177
|
}
|
|
@@ -3108,7 +3141,7 @@ if (nativePicture) {
|
|
|
3108
3141
|
session.reconnect();
|
|
3109
3142
|
waitForCapabilities();
|
|
3110
3143
|
Object.assign(control, { asked: false, known: false, trusted: false });
|
|
3111
|
-
|
|
3144
|
+
showProgress("connecting");
|
|
3112
3145
|
if (mode !== "view") askControl();
|
|
3113
3146
|
},
|
|
3114
3147
|
};
|
|
@@ -3126,16 +3159,16 @@ if (nativePicture) {
|
|
|
3126
3159
|
}
|
|
3127
3160
|
tellApp({ type: "ready", mode });
|
|
3128
3161
|
`;
|
|
3129
|
-
const SCRIPT = `(() => {${VIEWER_LOGIC}${BODY}})();`;
|
|
3162
|
+
const SCRIPT = `(() => {${VIEWER_LOGIC}${SCREEN_LOADING_SCRIPT}${BODY}})();`;
|
|
3130
3163
|
export function viewerPage(platform = "darwin") {
|
|
3131
3164
|
return `<!doctype html>
|
|
3132
3165
|
<html><head><meta charset="utf-8">
|
|
3133
3166
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
|
|
3134
3167
|
<title>屏幕</title>
|
|
3135
|
-
<style>${STYLE}</style></head>
|
|
3168
|
+
<style>${STYLE}${SCREEN_LOADING_STYLE}</style></head>
|
|
3136
3169
|
<body data-platform="${platform === "win32" ? "win32" : "darwin"}">
|
|
3137
3170
|
<div id="stage"><div id="picture"><canvas id="screen"></canvas><canvas id="gpu" class="gone"></canvas><video id="video" class="gone" autoplay playsinline muted></video><div id="pointer"></div></div></div>
|
|
3138
|
-
|
|
3171
|
+
${SCREEN_LOADING_HTML}
|
|
3139
3172
|
<div id="toast" class="glass"></div>
|
|
3140
3173
|
<div id="menu" class="glass gone">
|
|
3141
3174
|
<div class="choice" role="button" aria-label="只看,不操作电脑" data-mode="view"><i></i><div><b>只看</b><span>不操作电脑</span></div></div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen-viewer.js","sourceRoot":"","sources":["../../../../host/src/screen-viewer.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,2EAA2E;AAC3E,6EAA6E;AAC7E,mEAAmE;AACnE,EAAE;AACF,4DAA4D;AAC5D,yEAAyE;AACzE,2CAA2C;AAC3C,6EAA6E;AAC7E,+EAA+E;AAC/E,2CAA2C;AAC3C,qGAAqG;AACrG,+EAA+E;AAC/E,6FAA6F;AAC7F,EAAE;AACF,oGAAoG;AACpG,mGAAmG;AACnG,+FAA+F;AAC/F,8FAA8F;AAE9F,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAC5B,uJAAuJ,IAAI,QAAQ,CAAC;AAEtK,MAAM,KAAK,GAAG;IACZ,IAAI,EAAE,IAAI,CAAC,qHAAqH,CAAC;IACjI,QAAQ,EAAE,IAAI,CAAC,6DAA6D,CAAC;IAC7E,KAAK,EAAE,IAAI,CAAC,oGAAoG,CAAC;IACjH,QAAQ,EAAE,IAAI,CAAC,iJAAiJ,CAAC;IACjK,GAAG,EAAE,IAAI,CAAC,0EAA0E,CAAC;IACrF,MAAM,EAAE,IAAI,CAAC,oDAAoD,CAAC;IAClE,MAAM,EAAE,IAAI,CAAC,oDAAoD,CAAC;IAClE,IAAI,EAAE,IAAI,CAAC,4BAA4B,CAAC;IACxC,KAAK,EAAE,IAAI,CAAC,qDAAqD,CAAC;IAClE,OAAO,EAAE,IAAI,CAAC,sEAAsE,CAAC;IACrF,IAAI,EAAE,IAAI,CAAC,iEAAiE,CAAC;IAC7E,KAAK,EAAE,IAAI,CAAC,4CAA4C,CAAC;CAC1D,CAAC;AAEF,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"screen-viewer.js","sourceRoot":"","sources":["../../../../host/src/screen-viewer.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,2EAA2E;AAC3E,6EAA6E;AAC7E,mEAAmE;AACnE,EAAE;AACF,4DAA4D;AAC5D,yEAAyE;AACzE,2CAA2C;AAC3C,6EAA6E;AAC7E,+EAA+E;AAC/E,2CAA2C;AAC3C,qGAAqG;AACrG,+EAA+E;AAC/E,6FAA6F;AAC7F,EAAE;AACF,oGAAoG;AACpG,mGAAmG;AACnG,+FAA+F;AAC/F,8FAA8F;AAE9F,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEvG,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAC5B,uJAAuJ,IAAI,QAAQ,CAAC;AAEtK,MAAM,KAAK,GAAG;IACZ,IAAI,EAAE,IAAI,CAAC,qHAAqH,CAAC;IACjI,QAAQ,EAAE,IAAI,CAAC,6DAA6D,CAAC;IAC7E,KAAK,EAAE,IAAI,CAAC,oGAAoG,CAAC;IACjH,QAAQ,EAAE,IAAI,CAAC,iJAAiJ,CAAC;IACjK,GAAG,EAAE,IAAI,CAAC,0EAA0E,CAAC;IACrF,MAAM,EAAE,IAAI,CAAC,oDAAoD,CAAC;IAClE,MAAM,EAAE,IAAI,CAAC,oDAAoD,CAAC;IAClE,IAAI,EAAE,IAAI,CAAC,4BAA4B,CAAC;IACxC,KAAK,EAAE,IAAI,CAAC,qDAAqD,CAAC;IAClE,OAAO,EAAE,IAAI,CAAC,sEAAsE,CAAC;IACrF,IAAI,EAAE,IAAI,CAAC,iEAAiE,CAAC;IAC7E,KAAK,EAAE,IAAI,CAAC,4CAA4C,CAAC;CAC1D,CAAC;AAEF,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkHvB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0YrC,CAAC;AAEF,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmjFtB,CAAC;AAEF,MAAM,MAAM,GAAG,WAAW,YAAY,GAAG,qBAAqB,GAAG,IAAI,OAAO,CAAC;AAE7E,MAAM,UAAU,UAAU,CAAC,WAA4B,QAAQ;IAC7D,OAAO;;;;SAIA,KAAK,GAAG,oBAAoB;uBACd,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ;;EAE9D,mBAAmB;;;;;;;;iHAQ4F,KAAK,CAAC,KAAK;;;;;;;;;;;;;;;kEAe1D,KAAK,CAAC,QAAQ;iEACf,KAAK,CAAC,KAAK;+DACb,KAAK,CAAC,GAAG;gEACR,KAAK,CAAC,IAAI;;;;;;;;;;;;;;;4CAe9B,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,aAAa,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ;;4CAEtF,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,aAAa,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;oCAuBnG,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG;oCAClC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG;;;;;;;;;;6CAUzB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;UACxD,MAAM;eACD,CAAC;AAChB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linkshell-cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.51",
|
|
4
4
|
"description": "Follow, steer and approve the coding agents on your computer (Claude Code, Codex, Gemini, Copilot and more) from your phone",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli/src/index.js",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"node-pty": "^1.1.0",
|
|
35
35
|
"qrcode-terminal": "^0.12.0",
|
|
36
36
|
"ws": "^8.21.0",
|
|
37
|
-
"@linkshell/gateway": "0.6.
|
|
38
|
-
"@linkshell/
|
|
39
|
-
"@linkshell/
|
|
37
|
+
"@linkshell/gateway": "0.6.28",
|
|
38
|
+
"@linkshell/wire": "0.2.17",
|
|
39
|
+
"@linkshell/host": "0.4.49"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/ws": "^8.5.14",
|
|
@@ -6,6 +6,7 @@ import { homedir } from "node:os";
|
|
|
6
6
|
import type { Readable } from "node:stream";
|
|
7
7
|
import type { Socket } from "node:net";
|
|
8
8
|
import WebSocket, { WebSocketServer } from "ws";
|
|
9
|
+
import { localWebSocket } from "@linkshell/host/local-transport";
|
|
9
10
|
import { isMethodName, machineInfoSchema, rpcMessageSchema, type MachineEntry } from "@linkshell/wire";
|
|
10
11
|
import { getValidToken, loadAuth } from "../auth.js";
|
|
11
12
|
import { runLogin } from "./login.js";
|
|
@@ -85,7 +86,7 @@ async function loadAssets(directory: string): Promise<Map<string, Buffer>> {
|
|
|
85
86
|
async function localComputer(target: string, upstreams: Set<WebSocket>): Promise<LocalComputer | undefined> {
|
|
86
87
|
// Query the existing socket directly: discovering this computer never starts a host.
|
|
87
88
|
return new Promise((resolve) => {
|
|
88
|
-
const client =
|
|
89
|
+
const client = localWebSocket(target, 1_500, { maxPayload: MAX_PROXY_BYTES });
|
|
89
90
|
upstreams.add(client);
|
|
90
91
|
const finish = (computer?: LocalComputer) => {
|
|
91
92
|
clearTimeout(timeout);
|
|
@@ -107,7 +108,7 @@ async function localComputer(target: string, upstreams: Set<WebSocket>): Promise
|
|
|
107
108
|
key: "desktop:local", gateway: "desktop:local",
|
|
108
109
|
machine: {
|
|
109
110
|
id: info.machineId, name: info.hostname.replace(/\.local$/, ""), platform: info.platform,
|
|
110
|
-
// Local transport uses
|
|
111
|
+
// Local transport uses the host's Unix socket or Windows named pipe.
|
|
111
112
|
signKey: "", boxKey: "", online: true, via: "paired",
|
|
112
113
|
},
|
|
113
114
|
});
|
|
@@ -127,9 +128,7 @@ async function account(signal: AbortSignal) {
|
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
function proxyHost(client: WebSocket, target: string, upstreams: Set<WebSocket>): void {
|
|
130
|
-
const host =
|
|
131
|
-
perMessageDeflate: false, handshakeTimeout: 3_000, maxPayload: MAX_PROXY_BYTES,
|
|
132
|
-
});
|
|
131
|
+
const host = localWebSocket(target, 3_000, { maxPayload: MAX_PROXY_BYTES });
|
|
133
132
|
upstreams.add(host);
|
|
134
133
|
let pending: string[] = [];
|
|
135
134
|
let pendingBytes = 0;
|
|
@@ -271,7 +270,7 @@ export async function startDesktopWorkspace(options: { assets: string; home?: st
|
|
|
271
270
|
}
|
|
272
271
|
if (!["GET", "HEAD"].includes(request.method ?? "")) throw new HttpError(405, "不支持此请求方法");
|
|
273
272
|
const assetPath = path === "/" ? "/index.html" : path;
|
|
274
|
-
const content = path === "/config.js" ? Buffer.from(`window.__LINKSHELL_CONFIG__ = ${JSON.stringify({ desktop: true, deployment: "official", previewOrigin })};\n`) : assets.get(assetPath);
|
|
273
|
+
const content = path === "/config.js" ? Buffer.from(`window.__LINKSHELL_CONFIG__ = ${JSON.stringify({ desktop: true, desktopPlatform: process.platform, deployment: "official", previewOrigin })};\n`) : assets.get(assetPath);
|
|
275
274
|
if (!content) throw new HttpError(404, "未找到资源");
|
|
276
275
|
response.writeHead(200, {
|
|
277
276
|
"content-type": MIME[extname(assetPath)] ?? "application/octet-stream", "content-length": content.byteLength,
|