@linxin666/dsh-remote-web-ui 0.4.1 → 0.4.3
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/README.i18n.yaml +2 -2
- package/README.md +9 -7
- package/README.zh.md +9 -7
- package/lib/client.js +282 -619
- package/lib/client.js.map +1 -1
- package/lib/index.js +418 -901
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +9 -10
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +1 -1
- package/lib/types/client/index.d.ts +32 -3
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +56 -11
- package/lib/types/client/locales.d.ts +0 -84
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +2 -86
- package/lib/types/client/pair-api.d.ts +4 -0
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +11 -7
- package/lib/types/client/remote-channel.d.ts +27 -7
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +25 -17
- package/lib/types/client/settings-entry-form.d.ts +50 -0
- package/lib/types/client/settings-entry-form.d.ts.map +1 -0
- package/lib/types/client/settings-entry-form.js +98 -0
- package/lib/types/index.d.ts +99 -7
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +104 -137
- package/lib/types/lan-bind-plan.d.ts +14 -0
- package/lib/types/lan-bind-plan.d.ts.map +1 -1
- package/lib/types/lan-bind-plan.js +16 -0
- package/lib/types/lan-bind.d.ts +5 -1
- package/lib/types/lan-bind.d.ts.map +1 -1
- package/lib/types/lan-bind.js +65 -2
- package/lib/types/mount-once.d.ts +38 -7
- package/lib/types/mount-once.d.ts.map +1 -1
- package/lib/types/mount-once.js +96 -12
- package/lib/types/pairing.d.ts +15 -2
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +26 -14
- package/lib/types/pairing.persist.test.js +43 -3
- package/lib/types/remote-channel-boot.d.ts.map +1 -1
- package/lib/types/remote-channel-boot.js +13 -3
- package/lib/types/remote-channel-rules.d.ts +74 -0
- package/lib/types/remote-channel-rules.d.ts.map +1 -1
- package/lib/types/remote-channel-rules.js +104 -0
- package/lib/types/tunnel.d.ts +60 -3
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +136 -3
- package/package.json +21 -20
- package/src/client/RemoteEntry.tsx +8 -10
- package/src/client/RemoteSettingsCard.tsx +0 -2
- package/src/client/index.ts +58 -12
- package/src/client/locales.ts +2 -86
- package/src/client/pair-api.ts +11 -7
- package/src/client/remote-channel.ts +38 -15
- package/src/client/remote.module.css +61 -193
- package/src/client/settings-entry-form.ts +142 -0
- package/src/index.ts +156 -152
- package/src/lan-bind-plan.ts +22 -0
- package/src/lan-bind.ts +63 -2
- package/src/mount-once.ts +107 -13
- package/src/pairing.persist.test.ts +47 -3
- package/src/pairing.ts +26 -12
- package/src/remote-channel-boot.ts +13 -3
- package/src/remote-channel-rules.ts +113 -0
- package/src/tunnel.ts +142 -3
- package/lib/types/client/UpdateEntry.d.ts +0 -14
- package/lib/types/client/UpdateEntry.d.ts.map +0 -1
- package/lib/types/client/UpdateEntry.js +0 -112
- package/lib/types/client/UpdatePanel.d.ts +0 -42
- package/lib/types/client/UpdatePanel.d.ts.map +0 -1
- package/lib/types/client/UpdatePanel.js +0 -101
- package/lib/types/client/update-api.d.ts +0 -29
- package/lib/types/client/update-api.d.ts.map +0 -1
- package/lib/types/client/update-api.js +0 -47
- package/lib/types/console-output.d.ts +0 -41
- package/lib/types/console-output.d.ts.map +0 -1
- package/lib/types/console-output.js +0 -89
- package/lib/types/update-routes.d.ts +0 -30
- package/lib/types/update-routes.d.ts.map +0 -1
- package/lib/types/update-routes.js +0 -47
- package/lib/types/update.d.ts +0 -272
- package/lib/types/update.d.ts.map +0 -1
- package/lib/types/update.js +0 -744
- package/src/client/UpdateEntry.tsx +0 -144
- package/src/client/UpdatePanel.tsx +0 -256
- package/src/client/update-api.ts +0 -49
- package/src/console-output.ts +0 -97
- package/src/update-routes.ts +0 -65
- package/src/update.ts +0 -882
package/lib/client.js
CHANGED
|
@@ -1231,7 +1231,7 @@ window.__ModuleLoader__.load({
|
|
|
1231
1231
|
//#region src/client/pair-api.ts
|
|
1232
1232
|
/** Read the host-authoritative desktop pairing policy. */
|
|
1233
1233
|
async function readPairGatePolicy() {
|
|
1234
|
-
const response = await fetch("
|
|
1234
|
+
const response = await fetch("api/pair/status");
|
|
1235
1235
|
if (!response.ok) throw new Error(`remote-web-ui: status failed with ${String(response.status)}`);
|
|
1236
1236
|
const value = await response.json();
|
|
1237
1237
|
if (typeof value.requirePairingForLan !== "boolean") throw new Error("remote-web-ui: status omitted requirePairingForLan");
|
|
@@ -1248,7 +1248,7 @@ window.__ModuleLoader__.load({
|
|
|
1248
1248
|
* origin — the panel is a desktop control endpoint).
|
|
1249
1249
|
*/
|
|
1250
1250
|
async function issuePair(address) {
|
|
1251
|
-
const response = await fetch("
|
|
1251
|
+
const response = await fetch("api/pair/issue", {
|
|
1252
1252
|
method: "POST",
|
|
1253
1253
|
headers: { "content-type": "application/json" },
|
|
1254
1254
|
body: JSON.stringify({ ...address !== void 0 ? { address } : {} })
|
|
@@ -1277,7 +1277,7 @@ window.__ModuleLoader__.load({
|
|
|
1277
1277
|
* @returns the wire result.
|
|
1278
1278
|
*/
|
|
1279
1279
|
async function acceptPair(token) {
|
|
1280
|
-
const response = await fetch("
|
|
1280
|
+
const response = await fetch("api/pair/accept", {
|
|
1281
1281
|
method: "POST",
|
|
1282
1282
|
headers: { "content-type": "application/json" },
|
|
1283
1283
|
body: JSON.stringify({ token })
|
|
@@ -1294,7 +1294,7 @@ window.__ModuleLoader__.load({
|
|
|
1294
1294
|
}
|
|
1295
1295
|
/** Revoke mobile access (paired devices + the current token). */
|
|
1296
1296
|
async function stopPair() {
|
|
1297
|
-
const response = await fetch("
|
|
1297
|
+
const response = await fetch("api/pair/stop", { method: "POST" });
|
|
1298
1298
|
if (!response.ok) throw new Error(`remote-web-ui: stop failed with ${String(response.status)}`);
|
|
1299
1299
|
}
|
|
1300
1300
|
/**
|
|
@@ -1302,7 +1302,7 @@ window.__ModuleLoader__.load({
|
|
|
1302
1302
|
* @param deviceId - the session id of the row to drop.
|
|
1303
1303
|
*/
|
|
1304
1304
|
async function revokePair(deviceId) {
|
|
1305
|
-
const response = await fetch("
|
|
1305
|
+
const response = await fetch("api/pair/revoke", {
|
|
1306
1306
|
method: "POST",
|
|
1307
1307
|
headers: { "content-type": "application/json" },
|
|
1308
1308
|
body: JSON.stringify({ deviceId })
|
|
@@ -1316,7 +1316,7 @@ window.__ModuleLoader__.load({
|
|
|
1316
1316
|
* proves this page is not paired (see {@link shouldStopHeartbeat}).
|
|
1317
1317
|
*/
|
|
1318
1318
|
async function sendHeartbeat() {
|
|
1319
|
-
return (await fetch("
|
|
1319
|
+
return (await fetch("api/pair/heartbeat", { method: "POST" })).status;
|
|
1320
1320
|
}
|
|
1321
1321
|
/**
|
|
1322
1322
|
* Whether a heartbeat answer means "this page can never be accepted again" and
|
|
@@ -1349,7 +1349,7 @@ window.__ModuleLoader__.load({
|
|
|
1349
1349
|
};
|
|
1350
1350
|
/** Read the LAN-bind facts (loopback-only endpoint). */
|
|
1351
1351
|
async function readLanBindStatus() {
|
|
1352
|
-
const response = await fetch("
|
|
1352
|
+
const response = await fetch("api/pair/lan-bind");
|
|
1353
1353
|
if (!response.ok) throw new LanBindStatusError(response.status, `remote-web-ui: lan-bind status failed with ${String(response.status)}`);
|
|
1354
1354
|
return await response.json();
|
|
1355
1355
|
}
|
|
@@ -1409,7 +1409,7 @@ window.__ModuleLoader__.load({
|
|
|
1409
1409
|
}
|
|
1410
1410
|
//#endregion
|
|
1411
1411
|
//#region \0dsh-css:packages/dsh-remote-web-ui/src/client/remote.module.css.mjs
|
|
1412
|
-
const css$1 = ".fThDlq_overlay{z-index:1000;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.fThDlq_mask{background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur);position:absolute;inset:0}.fThDlq_trigger{width:36px;height:36px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;transition:background-color .12s,color .12s,box-shadow .12s;display:inline-flex;position:relative}.fThDlq_trigger[data-update-available]{color:var(--dsw-alias-brand-primary)}.fThDlq_trigger[data-update-available]:after{box-sizing:border-box;border:2px solid var(--dsw-alias-bg-layer-1);background:var(--dsw-alias-brand-primary);content:\"\";pointer-events:none;border-radius:50%;width:8px;height:8px;position:absolute;top:4px;right:4px}.fThDlq_trigger[data-wide=wide]{border-radius:999px;flex:auto;justify-content:flex-start;gap:8px;width:auto;min-width:0;padding:0 10px}.fThDlq_trigger[data-update-available][data-wide=wide]{flex:none;gap:6px}.fThDlq_trigger[data-update-available][data-wide=wide]:after{display:none}.fThDlq_updateBadgeText{white-space:nowrap;font-size:13px;font-weight:500;line-height:1}.fThDlq_trigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.fThDlq_trigger:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.fThDlq_trigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}.fThDlq_trigger:disabled{opacity:.5;cursor:default}.fThDlq_panel{z-index:1;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);width:560px;max-width:calc(100vw - 48px);max-height:calc(100vh - 48px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:24px;flex-direction:column;gap:14px;padding:24px;font-size:14px;line-height:22px;display:flex;position:relative;overflow:auto}.fThDlq_header{align-items:flex-start;gap:12px;display:flex}.fThDlq_heading{flex:1;min-width:0}.fThDlq_title{margin:0;font-size:18px;font-weight:600;line-height:26px}.fThDlq_subtitle{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:13px}.fThDlq_close{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;transition:background-color .12s,color .12s,box-shadow .12s;display:inline-flex}.fThDlq_close:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_close:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.fThDlq_close:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}.fThDlq_close:disabled{opacity:.5;cursor:default}.fThDlq_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:16px;flex-direction:column;align-items:center;gap:12px;padding:16px;display:flex}.fThDlq_cardHeader{justify-content:space-between;align-items:center;gap:12px;width:100%;display:flex}.fThDlq_cardTitle{font-weight:500}.fThDlq_badge{white-space:nowrap;border-radius:999px;flex:none;align-items:center;gap:6px;min-width:0;padding:2px 10px;font-size:12px;line-height:18px;display:inline-flex}.fThDlq_badge:before{content:\"\";background:currentColor;border-radius:50%;width:8px;height:8px}.fThDlq_badge-waiting{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badge-connected{color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badge-disconnected{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badge-stopped{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badgePublic{color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badges{flex:none;align-items:center;gap:6px;display:inline-flex}.fThDlq_qrWrap{background:var(--dsw-alias-bg-base);border-radius:12px;justify-content:center;align-items:center;padding:12px;display:flex}.fThDlq_qr{display:block}.fThDlq_expired{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px}.fThDlq_expiry{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px}.fThDlq_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px}.fThDlq_link{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-caption);font-family:var(--dsw-font-mono,ui-monospace, monospace);margin:0;font-size:12px;display:block;overflow:hidden}.fThDlq_pairLinks{flex-direction:column;gap:8px;display:flex}.fThDlq_pairLinkRow{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:10px;align-items:center;gap:10px;min-width:0;padding:10px 12px;display:flex}.fThDlq_pairLinkText{flex:1;min-width:0}.fThDlq_pairLinkLabel{color:var(--dsw-alias-label-secondary);margin-bottom:3px;font-size:12px;display:block}.fThDlq_copyLink{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-button-elevated-fill);min-height:30px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:8px;flex:none;align-items:center;gap:5px;padding:0 10px;display:inline-flex}.fThDlq_oneTimeHint{color:var(--dsw-alias-label-caption);margin:0;font-size:12px}.fThDlq_stoppedHint{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px}.fThDlq_tunnelNote{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px}.fThDlq_tunnelFailed{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px}.fThDlq_actions{gap:8px;display:flex}.fThDlq_action{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-button-elevated-fill);height:34px;color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;border-radius:10px;justify-content:center;align-items:center;gap:6px;padding:0 14px;font-size:13px;transition:background-color .12s,border-color .12s,box-shadow .12s;display:inline-flex}.fThDlq_action:hover:not(:disabled){background:var(--dsw-alias-button-floating-hover)}.fThDlq_action:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.fThDlq_action:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}.fThDlq_action:disabled{opacity:.5;cursor:default}.fThDlq_banner{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:16px;padding:16px}.fThDlq_bannerTitle{color:var(--dsw-alias-state-warn-primary);margin:0;font-weight:500}.fThDlq_bannerHint{color:var(--dsw-alias-label-secondary);margin:6px 0 0;font-size:13px}.fThDlq_fencePage{z-index:2000;box-sizing:border-box;background:var(--dsw-alias-bg-base);text-align:center;flex-direction:column;justify-content:center;align-items:center;padding:40px 24px;display:flex;position:fixed;inset:0;overflow:auto}.fThDlq_fenceCard{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:min(520px,100%);box-shadow:var(--dsw-shadow-lv3);text-align:center;border-radius:20px;margin-inline:auto;padding:36px 40px}.fThDlq_fenceMark{background:var(--dsw-alias-state-error-secondary);width:44px;height:44px;color:var(--dsw-alias-state-error-primary);border-radius:50%;place-items:center;margin-inline:auto;font-size:24px;line-height:1;display:grid}.fThDlq_fenceEyebrow{color:var(--dsw-alias-state-error-primary);margin:22px 0 8px;font-size:13px;font-weight:600}.fThDlq_fenceTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:24px;line-height:1.35}.fThDlq_fenceDetail{color:var(--dsw-alias-label-secondary);margin:12px 0 0;font-size:14px;line-height:1.65}.fThDlq_fenceSteps{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);text-align:left;border-radius:12px;margin:24px auto 0;padding:20px 20px 20px 42px;font-size:14px;line-height:1.65}.fThDlq_fenceSteps li+li{margin-top:8px}.fThDlq_fenceForm{width:100%;margin-top:20px}.fThDlq_fenceInputRow{gap:8px;width:100%;display:flex}.fThDlq_fenceInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-width:0;color:var(--dsw-alias-label-primary);font:inherit;border-radius:10px;flex:1;padding:10px 14px;font-size:13px;transition:border-color .12s,box-shadow .12s}.fThDlq_fenceInput:focus{border-color:var(--dsw-alias-brand-primary);outline:none;box-shadow:0 0 0 2px #0066ff26}.fThDlq_fencePairButton{border:1px solid var(--dsw-alias-button-primary-fill);background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);font:inherit;cursor:pointer;border-radius:10px;flex-shrink:0;padding:10px 18px;font-size:13px;font-weight:500;transition:filter .12s,opacity .12s}.fThDlq_fencePairButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill));border-color:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill))}.fThDlq_fencePairButton:disabled{opacity:.55;cursor:not-allowed}.fThDlq_fencePairButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.fThDlq_fenceError{color:var(--dsw-alias-state-error-primary);text-align:left;margin:10px 0 0;font-size:13px;line-height:1.4}.fThDlq_fenceRetry{border:1px solid var(--dsw-alias-border-l2);width:100%;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border-radius:10px;margin-top:12px;padding:10px 16px;font-weight:500;transition:background-color .12s,color .12s}.fThDlq_fenceRetry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.fThDlq_fenceRetry:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.fThDlq_fenceFootnote{color:var(--dsw-alias-label-tertiary);margin:14px 0 0;font-size:12px;line-height:1.55}.fThDlq_addresses{border:none;margin:12px 0 0;padding:0}.fThDlq_addresses legend{color:var(--dsw-alias-label-secondary);padding:0;font-size:13px}.fThDlq_address{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;cursor:pointer;border-radius:6px;align-items:center;gap:8px;margin-top:6px;padding:4px 6px;font-size:13px;transition:background-color .12s;display:flex}.fThDlq_address:hover{background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_address input:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);border-radius:50%;outline:none}.fThDlq_addressValue{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);flex:1;font-size:12px;overflow:hidden}.fThDlq_addressHint{color:var(--dsw-alias-label-tertiary);margin:6px 0 0;font-size:12px}.fThDlq_devices{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:16px;flex-direction:column;gap:8px;padding:12px 16px 14px;display:flex}.fThDlq_devicesTitle{margin:0;font-size:13px;font-weight:500;line-height:20px}.fThDlq_devicesEmpty{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px}.fThDlq_deviceList{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}.fThDlq_deviceRow{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.fThDlq_deviceMeta{flex-direction:column;gap:2px;min-width:0;display:flex}.fThDlq_deviceName{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.fThDlq_devicePresence{font-size:12px;line-height:18px}.fThDlq_deviceOnline{color:var(--dsw-alias-state-success-primary)}.fThDlq_deviceOffline{color:var(--dsw-alias-label-secondary)}.fThDlq_deviceSeen{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:12px}.fThDlq_deviceRevoke{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex:none;padding:6px 10px;font-size:12px;transition:background-color .12s,color .12s}.fThDlq_deviceRevoke:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.fThDlq_deviceRevoke:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}@media (prefers-reduced-motion:reduce){.fThDlq_trigger,.fThDlq_close,.fThDlq_action,.fThDlq_address,.fThDlq_deviceRevoke{transition:none}}.fThDlq_entryRow{flex:none;align-items:center;gap:6px;min-width:0;display:flex}.fThDlq_entryRow[data-rail=rail]{flex-direction:column-reverse;gap:4px}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footArea]{flex-flow:wrap;align-items:center}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footArea]>:not([class*=settingsArea]):not([class*=footerActions]){flex:100%;min-width:0}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=settingsArea]{flex:auto;order:1;width:auto;min-width:0}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footerActions]{flex:none;order:2;align-items:center;width:auto;min-width:0}.fThDlq_updateStatus{margin:0;font-weight:600}.fThDlq_updateDetail{color:var(--dsw-alias-label-secondary);margin:6px 0 0;font-size:13px}.fThDlq_updateError{color:var(--dsw-alias-text-danger,var(--dsw-alias-label-primary));margin:0;font-weight:600}.fThDlq_updateOutput{background:var(--dsw-alias-bg-layer-1);max-height:180px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-all;border-radius:10px;margin:10px 0 0;padding:10px 12px;font-size:12px;line-height:18px;overflow:auto}.fThDlq_updateList{flex-direction:column;gap:6px;margin:12px 0 0;padding:0;list-style:none;display:flex}.fThDlq_updateListItem{justify-content:space-between;align-items:baseline;gap:12px;font-size:13px;display:flex}.fThDlq_updateListName{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);overflow:hidden}.fThDlq_updateListVersions{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none}.fThDlq_updateNotes{margin-top:14px}.fThDlq_updateNotesTitle{margin:0 0 8px;font-size:14px;font-weight:600}.fThDlq_updateNoteSection{flex-direction:column;gap:4px;margin-top:10px;display:flex}.fThDlq_updateNoteHeading{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px;font-weight:600}.fThDlq_updateNoteList{flex-direction:column;gap:4px;margin:0;padding-left:18px;font-size:13px;display:flex}.fThDlq_updateNoteItem{color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}.fThDlq_updateVersions{margin-top:12px}.fThDlq_updateVersionsSummary{cursor:pointer;color:var(--dsw-alias-label-secondary);user-select:none;font-size:12px}.fThDlq_updateActions{justify-content:flex-end;margin-top:16px;display:flex}.fThDlq_updateRetry{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:10px;align-items:center;gap:6px;padding:8px 14px;font-size:13px;transition:background-color .12s,color .12s;display:inline-flex}.fThDlq_updateRetry:hover{background:var(--dsw-alias-interactive-bg-active)}@media (prefers-reduced-motion:reduce){.fThDlq_updateRetry{transition:none}}";
|
|
1412
|
+
const css$1 = ".fThDlq_overlay{z-index:1000;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.fThDlq_mask{background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur);position:absolute;inset:0}.fThDlq_trigger{width:36px;height:36px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;transition:background-color .12s,color .12s,box-shadow .12s;display:inline-flex;position:relative}.fThDlq_trigger[data-wide=wide]{border-radius:999px;flex:auto;justify-content:flex-start;gap:8px;width:auto;min-width:0;padding:0 10px}.fThDlq_trigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.fThDlq_trigger:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.fThDlq_trigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}.fThDlq_trigger:disabled{opacity:.5;cursor:default}.fThDlq_panel{z-index:1;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);width:560px;max-width:calc(100vw - 48px);max-height:calc(100vh - 48px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:24px;flex-direction:column;gap:14px;padding:24px;font-size:14px;line-height:22px;display:flex;position:relative;overflow:auto}.fThDlq_header{align-items:flex-start;gap:12px;display:flex}.fThDlq_heading{flex:1;min-width:0}.fThDlq_title{margin:0;font-size:18px;font-weight:600;line-height:26px}.fThDlq_subtitle{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:13px}.fThDlq_close{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;transition:background-color .12s,color .12s,box-shadow .12s;display:inline-flex}.fThDlq_close:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_close:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.fThDlq_close:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}.fThDlq_close:disabled{opacity:.5;cursor:default}.fThDlq_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:16px;flex-direction:column;align-items:center;gap:12px;padding:16px;display:flex}.fThDlq_cardHeader{justify-content:space-between;align-items:center;gap:12px;width:100%;display:flex}.fThDlq_cardTitle{font-weight:500}.fThDlq_badge{white-space:nowrap;border-radius:999px;flex:none;align-items:center;gap:6px;min-width:0;padding:2px 10px;font-size:12px;line-height:18px;display:inline-flex}.fThDlq_badge:before{content:\"\";background:currentColor;border-radius:50%;width:8px;height:8px}.fThDlq_badge-waiting{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badge-connected{color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badge-disconnected{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badge-stopped{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badgePublic{color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_badges{flex:none;align-items:center;gap:6px;display:inline-flex}.fThDlq_qrWrap{background:var(--dsw-alias-bg-base);border-radius:12px;justify-content:center;align-items:center;padding:12px;display:flex}.fThDlq_qr{display:block}.fThDlq_expired{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px}.fThDlq_expiry{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px}.fThDlq_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px}.fThDlq_link{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-caption);font-family:var(--dsw-font-mono,ui-monospace, monospace);margin:0;font-size:12px;display:block;overflow:hidden}.fThDlq_pairLinks{flex-direction:column;gap:8px;display:flex}.fThDlq_pairLinkRow{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:10px;align-items:center;gap:10px;min-width:0;padding:10px 12px;display:flex}.fThDlq_pairLinkText{flex:1;min-width:0}.fThDlq_pairLinkLabel{color:var(--dsw-alias-label-secondary);margin-bottom:3px;font-size:12px;display:block}.fThDlq_copyLink{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-button-elevated-fill);min-height:30px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:8px;flex:none;align-items:center;gap:5px;padding:0 10px;display:inline-flex}.fThDlq_oneTimeHint{color:var(--dsw-alias-label-caption);margin:0;font-size:12px}.fThDlq_stoppedHint{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px}.fThDlq_tunnelNote{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px}.fThDlq_tunnelFailed{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px}.fThDlq_actions{gap:8px;display:flex}.fThDlq_action{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-button-elevated-fill);height:34px;color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;border-radius:10px;justify-content:center;align-items:center;gap:6px;padding:0 14px;font-size:13px;transition:background-color .12s,border-color .12s,box-shadow .12s;display:inline-flex}.fThDlq_action:hover:not(:disabled){background:var(--dsw-alias-button-floating-hover)}.fThDlq_action:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.fThDlq_action:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}.fThDlq_action:disabled{opacity:.5;cursor:default}.fThDlq_banner{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:16px;padding:16px}.fThDlq_bannerTitle{color:var(--dsw-alias-state-warn-primary);margin:0;font-weight:500}.fThDlq_bannerHint{color:var(--dsw-alias-label-secondary);margin:6px 0 0;font-size:13px}.fThDlq_fencePage{z-index:2000;box-sizing:border-box;background:var(--dsw-alias-bg-base);text-align:center;flex-direction:column;justify-content:center;align-items:center;padding:40px 24px;display:flex;position:fixed;inset:0;overflow:auto}.fThDlq_fenceCard{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:min(520px,100%);box-shadow:var(--dsw-shadow-lv3);text-align:center;border-radius:20px;margin-inline:auto;padding:36px 40px}.fThDlq_fenceMark{background:var(--dsw-alias-state-error-secondary);width:44px;height:44px;color:var(--dsw-alias-state-error-primary);border-radius:50%;place-items:center;margin-inline:auto;font-size:24px;line-height:1;display:grid}.fThDlq_fenceEyebrow{color:var(--dsw-alias-state-error-primary);margin:22px 0 8px;font-size:13px;font-weight:600}.fThDlq_fenceTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:24px;line-height:1.35}.fThDlq_fenceDetail{color:var(--dsw-alias-label-secondary);margin:12px 0 0;font-size:14px;line-height:1.65}.fThDlq_fenceSteps{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);text-align:left;border-radius:12px;margin:24px auto 0;padding:20px 20px 20px 42px;font-size:14px;line-height:1.65}.fThDlq_fenceSteps li+li{margin-top:8px}.fThDlq_fenceForm{width:100%;margin-top:20px}.fThDlq_fenceInputRow{gap:8px;width:100%;display:flex}.fThDlq_fenceInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-width:0;color:var(--dsw-alias-label-primary);font:inherit;border-radius:10px;flex:1;padding:10px 14px;font-size:13px;transition:border-color .12s,box-shadow .12s}.fThDlq_fenceInput:focus{border-color:var(--dsw-alias-brand-primary);outline:none;box-shadow:0 0 0 2px #0066ff26}.fThDlq_fencePairButton{border:1px solid var(--dsw-alias-button-primary-fill);background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);font:inherit;cursor:pointer;border-radius:10px;flex-shrink:0;padding:10px 18px;font-size:13px;font-weight:500;transition:filter .12s,opacity .12s}.fThDlq_fencePairButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill));border-color:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill))}.fThDlq_fencePairButton:disabled{opacity:.55;cursor:not-allowed}.fThDlq_fencePairButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.fThDlq_fenceError{color:var(--dsw-alias-state-error-primary);text-align:left;margin:10px 0 0;font-size:13px;line-height:1.4}.fThDlq_fenceRetry{border:1px solid var(--dsw-alias-border-l2);width:100%;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border-radius:10px;margin-top:12px;padding:10px 16px;font-weight:500;transition:background-color .12s,color .12s}.fThDlq_fenceRetry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.fThDlq_fenceRetry:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.fThDlq_fenceFootnote{color:var(--dsw-alias-label-tertiary);margin:14px 0 0;font-size:12px;line-height:1.55}.fThDlq_addresses{border:none;margin:12px 0 0;padding:0}.fThDlq_addresses legend{color:var(--dsw-alias-label-secondary);padding:0;font-size:13px}.fThDlq_address{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;cursor:pointer;border-radius:6px;align-items:center;gap:8px;margin-top:6px;padding:4px 6px;font-size:13px;transition:background-color .12s;display:flex}.fThDlq_address:hover{background:var(--dsw-alias-interactive-bg-hover)}.fThDlq_address input:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);border-radius:50%;outline:none}.fThDlq_addressValue{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);flex:1;font-size:12px;overflow:hidden}.fThDlq_addressHint{color:var(--dsw-alias-label-tertiary);margin:6px 0 0;font-size:12px}.fThDlq_devices{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:16px;flex-direction:column;gap:8px;padding:12px 16px 14px;display:flex}.fThDlq_devicesTitle{margin:0;font-size:13px;font-weight:500;line-height:20px}.fThDlq_devicesEmpty{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px}.fThDlq_deviceList{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}.fThDlq_deviceRow{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.fThDlq_deviceMeta{flex-direction:column;gap:2px;min-width:0;display:flex}.fThDlq_deviceName{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.fThDlq_devicePresence{font-size:12px;line-height:18px}.fThDlq_deviceOnline{color:var(--dsw-alias-state-success-primary)}.fThDlq_deviceOffline{color:var(--dsw-alias-label-secondary)}.fThDlq_deviceSeen{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:12px}.fThDlq_deviceRevoke{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex:none;padding:6px 10px;font-size:12px;transition:background-color .12s,color .12s}.fThDlq_deviceRevoke:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.fThDlq_deviceRevoke:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2), 0 0 0 4px var(--dsw-alias-brand-primary);outline:none}@media (prefers-reduced-motion:reduce){.fThDlq_trigger,.fThDlq_close,.fThDlq_action,.fThDlq_address,.fThDlq_deviceRevoke{transition:none}}.fThDlq_entryRow{flex:none;align-items:center;gap:6px;min-width:0;display:flex}.fThDlq_entryRow[data-rail=rail]{flex-direction:column-reverse;gap:4px}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footArea]{flex-flow:wrap;align-items:center}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footArea]>:not([class*=settingsArea]):not([class*=footerActions]){flex:100%;min-width:0}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footArea]>[class*=footerActions]{display:contents}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footerActions]>[data-slot=\"sidebar.footer.action\"]>:not([data-dsh-part=entry]):not([class*=entryRow]){flex:100%;order:1;min-width:0}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footerActions]>[data-slot=\"sidebar.footer.action\"]>[data-dsh-part=entry],[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=footerActions]>[data-slot=\"sidebar.footer.action\"]>[class*=entryRow]{flex:none;order:4;min-width:0}[data-dsh-frame]:not([data-sidebar-collapsed]) [class*=settingsArea]{flex:auto;order:3;width:auto;min-width:0}[data-dsh-frame][data-sidebar-collapsed] [class*=footerActions]:has([data-rail=rail]){flex-direction:column;align-items:center;gap:4px}";
|
|
1413
1413
|
const tagId$1 = "@linxin666/dsh-remote-web-ui/packages/dsh-remote-web-ui/src/client/remote.module.css";
|
|
1414
1414
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
1415
1415
|
const tag = document.createElement("style");
|
|
@@ -1488,26 +1488,7 @@ window.__ModuleLoader__.load({
|
|
|
1488
1488
|
"title": "fThDlq_title",
|
|
1489
1489
|
"trigger": "fThDlq_trigger",
|
|
1490
1490
|
"tunnelFailed": "fThDlq_tunnelFailed",
|
|
1491
|
-
"tunnelNote": "fThDlq_tunnelNote"
|
|
1492
|
-
"updateActions": "fThDlq_updateActions",
|
|
1493
|
-
"updateBadgeText": "fThDlq_updateBadgeText",
|
|
1494
|
-
"updateDetail": "fThDlq_updateDetail",
|
|
1495
|
-
"updateError": "fThDlq_updateError",
|
|
1496
|
-
"updateList": "fThDlq_updateList",
|
|
1497
|
-
"updateListItem": "fThDlq_updateListItem",
|
|
1498
|
-
"updateListName": "fThDlq_updateListName",
|
|
1499
|
-
"updateListVersions": "fThDlq_updateListVersions",
|
|
1500
|
-
"updateNoteHeading": "fThDlq_updateNoteHeading",
|
|
1501
|
-
"updateNoteItem": "fThDlq_updateNoteItem",
|
|
1502
|
-
"updateNoteList": "fThDlq_updateNoteList",
|
|
1503
|
-
"updateNoteSection": "fThDlq_updateNoteSection",
|
|
1504
|
-
"updateNotes": "fThDlq_updateNotes",
|
|
1505
|
-
"updateNotesTitle": "fThDlq_updateNotesTitle",
|
|
1506
|
-
"updateOutput": "fThDlq_updateOutput",
|
|
1507
|
-
"updateRetry": "fThDlq_updateRetry",
|
|
1508
|
-
"updateStatus": "fThDlq_updateStatus",
|
|
1509
|
-
"updateVersions": "fThDlq_updateVersions",
|
|
1510
|
-
"updateVersionsSummary": "fThDlq_updateVersionsSummary"
|
|
1491
|
+
"tunnelNote": "fThDlq_tunnelNote"
|
|
1511
1492
|
};
|
|
1512
1493
|
//#endregion
|
|
1513
1494
|
//#region src/client/RemotePanel.tsx
|
|
@@ -1861,481 +1842,15 @@ window.__ModuleLoader__.load({
|
|
|
1861
1842
|
});
|
|
1862
1843
|
}
|
|
1863
1844
|
//#endregion
|
|
1864
|
-
//#region src/client/update-api.ts
|
|
1865
|
-
/**
|
|
1866
|
-
* Error thrown when the update status probe fails: carries the HTTP status
|
|
1867
|
-
* so the panel can tell "the update route is not mounted" (404 — the host
|
|
1868
|
-
* process runs an older plugin build) apart from real network failures.
|
|
1869
|
-
*/
|
|
1870
|
-
var UpdateStatusError = class extends Error {
|
|
1871
|
-
/** HTTP status of the failed response (0 when the fetch never returned). */
|
|
1872
|
-
status;
|
|
1873
|
-
constructor(status) {
|
|
1874
|
-
super("update status unavailable (HTTP " + String(status) + ")");
|
|
1875
|
-
this.status = status;
|
|
1876
|
-
}
|
|
1877
|
-
};
|
|
1878
|
-
/**
|
|
1879
|
-
* Probe the update status: install mode, owning profile, and the
|
|
1880
|
-
* current-vs-latest comparison for every family package.
|
|
1881
|
-
* @returns the status snapshot.
|
|
1882
|
-
*/
|
|
1883
|
-
async function fetchUpdateStatus() {
|
|
1884
|
-
let response;
|
|
1885
|
-
try {
|
|
1886
|
-
response = await fetch("/api/update/status");
|
|
1887
|
-
} catch {
|
|
1888
|
-
throw new UpdateStatusError(0);
|
|
1889
|
-
}
|
|
1890
|
-
if (!response.ok) throw new UpdateStatusError(response.status);
|
|
1891
|
-
return await response.json();
|
|
1892
|
-
}
|
|
1893
|
-
/**
|
|
1894
|
-
* Run the update (pnpm update in the owning profile). Blocks until pnpm
|
|
1895
|
-
* exits — the panel shows an in-flight state meanwhile.
|
|
1896
|
-
* @returns the run outcome.
|
|
1897
|
-
*/
|
|
1898
|
-
async function runUpdate() {
|
|
1899
|
-
const response = await fetch("/api/update/run", { method: "POST" });
|
|
1900
|
-
if (!response.ok) throw new Error("update run unavailable");
|
|
1901
|
-
return await response.json();
|
|
1902
|
-
}
|
|
1903
|
-
//#endregion
|
|
1904
|
-
//#region src/client/UpdatePanel.tsx
|
|
1905
|
-
/** The anchor package name (aggregate first) for copy purposes. */
|
|
1906
|
-
function anchorName(status) {
|
|
1907
|
-
return status?.anchor ?? status?.packages[0]?.name;
|
|
1908
|
-
}
|
|
1909
|
-
/** The latest npm release of the anchor, for reference copy. */
|
|
1910
|
-
function anchorLatest(status) {
|
|
1911
|
-
return status?.packages[0]?.latest;
|
|
1912
|
-
}
|
|
1913
|
-
/**
|
|
1914
|
-
* Render the update panel.
|
|
1915
|
-
* @param props - copy, view state, and actions.
|
|
1916
|
-
* @returns the panel element tree.
|
|
1917
|
-
*/
|
|
1918
|
-
function UpdatePanel({ t, view, onClose, onRecheck, onStartUpdate }) {
|
|
1919
|
-
const status = view.kind === "result" || view.kind === "updating" ? view.status : void 0;
|
|
1920
|
-
const title = view.kind === "done" && view.result.ok ? t("update.done") : t("update.title");
|
|
1921
|
-
const subtitle = subtitleOf(t, view);
|
|
1922
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1923
|
-
className: remote_module_css_default.panel,
|
|
1924
|
-
role: "dialog",
|
|
1925
|
-
"aria-modal": "true",
|
|
1926
|
-
"aria-label": title,
|
|
1927
|
-
children: [
|
|
1928
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1929
|
-
className: remote_module_css_default.header,
|
|
1930
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1931
|
-
className: remote_module_css_default.heading,
|
|
1932
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1933
|
-
className: remote_module_css_default.title,
|
|
1934
|
-
children: title
|
|
1935
|
-
}), subtitle !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1936
|
-
className: remote_module_css_default.subtitle,
|
|
1937
|
-
children: subtitle
|
|
1938
|
-
})]
|
|
1939
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1940
|
-
type: "button",
|
|
1941
|
-
className: remote_module_css_default.close,
|
|
1942
|
-
"aria-label": t("update.close"),
|
|
1943
|
-
onClick: onClose,
|
|
1944
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, {})
|
|
1945
|
-
})]
|
|
1946
|
-
}),
|
|
1947
|
-
view.kind === "checking" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1948
|
-
className: remote_module_css_default.updateStatus,
|
|
1949
|
-
children: t("update.checking")
|
|
1950
|
-
}),
|
|
1951
|
-
view.kind === "result" && status !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResultBody, {
|
|
1952
|
-
t,
|
|
1953
|
-
status
|
|
1954
|
-
}),
|
|
1955
|
-
view.kind === "result" && status !== void 0 && status.mode === "npm" && status.outdated && status.error === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1956
|
-
className: remote_module_css_default.updateActions,
|
|
1957
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1958
|
-
type: "button",
|
|
1959
|
-
className: remote_module_css_default.updateRetry,
|
|
1960
|
-
onClick: () => onStartUpdate(status),
|
|
1961
|
-
children: t("update.start")
|
|
1962
|
-
})
|
|
1963
|
-
}),
|
|
1964
|
-
view.kind === "updating" && status !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1965
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1966
|
-
className: remote_module_css_default.updateStatus,
|
|
1967
|
-
children: t("update.updating", {
|
|
1968
|
-
name: anchorName(status) ?? "",
|
|
1969
|
-
version: anchorLatest(status) ?? ""
|
|
1970
|
-
})
|
|
1971
|
-
}),
|
|
1972
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1973
|
-
className: remote_module_css_default.updateDetail,
|
|
1974
|
-
children: t("update.cooldownNotice")
|
|
1975
|
-
}),
|
|
1976
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageSummary, {
|
|
1977
|
-
t,
|
|
1978
|
-
status
|
|
1979
|
-
})
|
|
1980
|
-
] }),
|
|
1981
|
-
view.kind === "done" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoneBody, {
|
|
1982
|
-
t,
|
|
1983
|
-
result: view.result
|
|
1984
|
-
}),
|
|
1985
|
-
view.kind === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1986
|
-
className: remote_module_css_default.updateError,
|
|
1987
|
-
children: view.message
|
|
1988
|
-
}), view.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
1989
|
-
className: remote_module_css_default.updateOutput,
|
|
1990
|
-
children: view.detail
|
|
1991
|
-
})] }),
|
|
1992
|
-
(view.kind === "done" || view.kind === "error") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1993
|
-
className: remote_module_css_default.updateActions,
|
|
1994
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1995
|
-
type: "button",
|
|
1996
|
-
className: remote_module_css_default.updateRetry,
|
|
1997
|
-
onClick: onRecheck,
|
|
1998
|
-
children: [
|
|
1999
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, {}),
|
|
2000
|
-
" ",
|
|
2001
|
-
t("update.retry")
|
|
2002
|
-
]
|
|
2003
|
-
})
|
|
2004
|
-
})
|
|
2005
|
-
]
|
|
2006
|
-
});
|
|
2007
|
-
}
|
|
2008
|
-
/** The subtitle copy per view state (absent on plain results). */
|
|
2009
|
-
function subtitleOf(t, view) {
|
|
2010
|
-
switch (view.kind) {
|
|
2011
|
-
case "checking": return t("update.checking");
|
|
2012
|
-
case "updating": return t("update.updatingTitle");
|
|
2013
|
-
case "result": return;
|
|
2014
|
-
case "done": return view.result.ok ? t("update.doneDetail") : t("update.error");
|
|
2015
|
-
case "error": return;
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
/** The checked result body: mode banner + version list. */
|
|
2019
|
-
function ResultBody({ t, status }) {
|
|
2020
|
-
const anchor = anchorName(status);
|
|
2021
|
-
const latest = anchorLatest(status);
|
|
2022
|
-
if (status.mode === "link") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2023
|
-
className: remote_module_css_default.updateStatus,
|
|
2024
|
-
children: t("update.linkMode")
|
|
2025
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2026
|
-
className: remote_module_css_default.updateDetail,
|
|
2027
|
-
children: t("update.linkModeDetail", { version: latest ?? "-" })
|
|
2028
|
-
})] });
|
|
2029
|
-
if (status.mode === "missing") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2030
|
-
className: remote_module_css_default.updateStatus,
|
|
2031
|
-
children: t("update.missing")
|
|
2032
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2033
|
-
className: remote_module_css_default.updateDetail,
|
|
2034
|
-
children: t("update.missingDetail")
|
|
2035
|
-
})] });
|
|
2036
|
-
if (status.error === "registry-unreachable") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2037
|
-
className: remote_module_css_default.updateStatus,
|
|
2038
|
-
children: t("update.offline")
|
|
2039
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2040
|
-
className: remote_module_css_default.updateDetail,
|
|
2041
|
-
children: t("update.offlineDetail")
|
|
2042
|
-
})] });
|
|
2043
|
-
if (status.outdated) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
2044
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2045
|
-
className: remote_module_css_default.updateStatus,
|
|
2046
|
-
children: t("update.found")
|
|
2047
|
-
}),
|
|
2048
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2049
|
-
className: remote_module_css_default.updateDetail,
|
|
2050
|
-
children: anchor !== void 0 ? t("update.foundDetail", {
|
|
2051
|
-
name: anchor,
|
|
2052
|
-
version: latest ?? ""
|
|
2053
|
-
}) : ""
|
|
2054
|
-
}),
|
|
2055
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2056
|
-
className: remote_module_css_default.updateDetail,
|
|
2057
|
-
children: t("update.cooldownNotice")
|
|
2058
|
-
}),
|
|
2059
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageSummary, {
|
|
2060
|
-
t,
|
|
2061
|
-
status
|
|
2062
|
-
})
|
|
2063
|
-
] });
|
|
2064
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
2065
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2066
|
-
className: remote_module_css_default.updateStatus,
|
|
2067
|
-
children: t("update.upToDate")
|
|
2068
|
-
}),
|
|
2069
|
-
anchor !== void 0 && latest !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2070
|
-
className: remote_module_css_default.updateDetail,
|
|
2071
|
-
children: t("update.upToDateDetail", {
|
|
2072
|
-
name: anchor,
|
|
2073
|
-
version: latest
|
|
2074
|
-
})
|
|
2075
|
-
}),
|
|
2076
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageSummary, {
|
|
2077
|
-
t,
|
|
2078
|
-
status
|
|
2079
|
-
})
|
|
2080
|
-
] });
|
|
2081
|
-
}
|
|
2082
|
-
/** Release-note summary when available; otherwise fall back to the package list. */
|
|
2083
|
-
function PackageSummary({ t, status }) {
|
|
2084
|
-
if (status.notes === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageList, { status });
|
|
2085
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReleaseNotes, {
|
|
2086
|
-
t,
|
|
2087
|
-
notes: status.notes
|
|
2088
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
2089
|
-
className: remote_module_css_default.updateVersions,
|
|
2090
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
|
|
2091
|
-
className: remote_module_css_default.updateVersionsSummary,
|
|
2092
|
-
children: t("update.componentVersions")
|
|
2093
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PackageList, { status })]
|
|
2094
|
-
})] });
|
|
2095
|
-
}
|
|
2096
|
-
/** Render GitHub Release sections as a compact three-group list. */
|
|
2097
|
-
function ReleaseNotes({ t, notes }) {
|
|
2098
|
-
const sections = [
|
|
2099
|
-
{
|
|
2100
|
-
key: "features",
|
|
2101
|
-
title: t("update.releaseFeatures"),
|
|
2102
|
-
items: notes.features
|
|
2103
|
-
},
|
|
2104
|
-
{
|
|
2105
|
-
key: "fixes",
|
|
2106
|
-
title: t("update.releaseFixes"),
|
|
2107
|
-
items: notes.fixes
|
|
2108
|
-
},
|
|
2109
|
-
{
|
|
2110
|
-
key: "other",
|
|
2111
|
-
title: t("update.releaseOther"),
|
|
2112
|
-
items: notes.other
|
|
2113
|
-
}
|
|
2114
|
-
].filter((section) => section.items.length > 0);
|
|
2115
|
-
if (sections.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2116
|
-
className: remote_module_css_default.updateDetail,
|
|
2117
|
-
children: t("update.releaseUnavailable")
|
|
2118
|
-
});
|
|
2119
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2120
|
-
className: remote_module_css_default.updateNotes,
|
|
2121
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2122
|
-
className: remote_module_css_default.updateNotesTitle,
|
|
2123
|
-
children: t("update.releaseNotes", { version: notes.version })
|
|
2124
|
-
}), sections.map((section) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2125
|
-
className: remote_module_css_default.updateNoteSection,
|
|
2126
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
2127
|
-
className: remote_module_css_default.updateNoteHeading,
|
|
2128
|
-
children: section.title
|
|
2129
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2130
|
-
className: remote_module_css_default.updateNoteList,
|
|
2131
|
-
children: section.items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
2132
|
-
className: remote_module_css_default.updateNoteItem,
|
|
2133
|
-
children: item
|
|
2134
|
-
}, index))
|
|
2135
|
-
})]
|
|
2136
|
-
}, section.key))]
|
|
2137
|
-
});
|
|
2138
|
-
}
|
|
2139
|
-
/** The per-package current → latest comparison list. */
|
|
2140
|
-
function PackageList({ status }) {
|
|
2141
|
-
if (status.packages.length === 0) return null;
|
|
2142
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2143
|
-
className: remote_module_css_default.updateList,
|
|
2144
|
-
children: status.packages.map((packageStatus) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
2145
|
-
className: remote_module_css_default.updateListItem,
|
|
2146
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2147
|
-
className: remote_module_css_default.updateListName,
|
|
2148
|
-
children: packageStatus.name
|
|
2149
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2150
|
-
className: remote_module_css_default.updateListVersions,
|
|
2151
|
-
children: [packageStatus.current, packageStatus.latest !== void 0 && packageStatus.latest !== packageStatus.current && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [" → ", packageStatus.latest] })]
|
|
2152
|
-
})]
|
|
2153
|
-
}, packageStatus.name))
|
|
2154
|
-
});
|
|
2155
|
-
}
|
|
2156
|
-
/** The outcome body: success + restart hint, or the translated failure. */
|
|
2157
|
-
function DoneBody({ t, result }) {
|
|
2158
|
-
if (result.ok) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2159
|
-
className: remote_module_css_default.updateDetail,
|
|
2160
|
-
children: t("update.doneDetail")
|
|
2161
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2162
|
-
className: remote_module_css_default.updateDetail,
|
|
2163
|
-
children: t("update.restartHint")
|
|
2164
|
-
})] });
|
|
2165
|
-
const message = errorMessageOf(t, result);
|
|
2166
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2167
|
-
className: remote_module_css_default.updateError,
|
|
2168
|
-
children: message
|
|
2169
|
-
}), result.output.trim() !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
2170
|
-
className: remote_module_css_default.updateOutput,
|
|
2171
|
-
children: result.output.trim()
|
|
2172
|
-
})] });
|
|
2173
|
-
}
|
|
2174
|
-
/** Translate a structured failure code; fall back to the raw message. */
|
|
2175
|
-
function errorMessageOf(t, result) {
|
|
2176
|
-
switch (result.errorCode) {
|
|
2177
|
-
case "pnpm-missing": return t("update.error.pnpmMissing");
|
|
2178
|
-
case "timeout": return t("update.error.timeout");
|
|
2179
|
-
case "not-found": return t("update.error.notFound");
|
|
2180
|
-
case "link": return t("update.error.link");
|
|
2181
|
-
case "pnpm-failed": return t("update.error.pnpmFailed", { code: String(result.exitCode ?? "?") });
|
|
2182
|
-
case "stale": return t("update.error.stale");
|
|
2183
|
-
case "verify-failed": return t("update.error.verifyFailed");
|
|
2184
|
-
default: return result.error ?? t("update.error.unknown");
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
//#endregion
|
|
2188
|
-
//#region src/client/UpdateEntry.tsx
|
|
2189
|
-
/**
|
|
2190
|
-
* The sidebar update seat: the download trigger beside the remote-control
|
|
2191
|
-
* trigger plus the update panel modal. Owns the flow — probe the registry
|
|
2192
|
-
* on open, show the result, and let the user start the update from the
|
|
2193
|
-
* result view (#507), then report the outcome (restart hint on success,
|
|
2194
|
-
* translated failure on error).
|
|
2195
|
-
* Component-local state per the client stack rules.
|
|
2196
|
-
*/
|
|
2197
|
-
/**
|
|
2198
|
-
* Render the update trigger and panel.
|
|
2199
|
-
* @param props - column state and locale seat.
|
|
2200
|
-
* @returns the entry element tree.
|
|
2201
|
-
*/
|
|
2202
|
-
function UpdateEntry({ wide, t }) {
|
|
2203
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
2204
|
-
const [view, setView] = (0, react.useState)({ kind: "checking" });
|
|
2205
|
-
const [updateAvailable, setUpdateAvailable] = (0, react.useState)(false);
|
|
2206
|
-
const runToken = (0, react.useRef)(0);
|
|
2207
|
-
const availabilityToken = (0, react.useRef)(0);
|
|
2208
|
-
const mounted = (0, react.useRef)(false);
|
|
2209
|
-
const probeAvailability = (0, react.useCallback)(async () => {
|
|
2210
|
-
const token = ++availabilityToken.current;
|
|
2211
|
-
try {
|
|
2212
|
-
const status = await fetchUpdateStatus();
|
|
2213
|
-
if (token === availabilityToken.current) setUpdateAvailable(status.mode === "npm" && status.outdated);
|
|
2214
|
-
} catch {
|
|
2215
|
-
if (token === availabilityToken.current) setUpdateAvailable(false);
|
|
2216
|
-
}
|
|
2217
|
-
}, []);
|
|
2218
|
-
const check = (0, react.useCallback)(async () => {
|
|
2219
|
-
const availabilityCheck = ++availabilityToken.current;
|
|
2220
|
-
setView({ kind: "checking" });
|
|
2221
|
-
let status;
|
|
2222
|
-
try {
|
|
2223
|
-
status = await fetchUpdateStatus();
|
|
2224
|
-
} catch (error) {
|
|
2225
|
-
if (availabilityCheck === availabilityToken.current) setUpdateAvailable(false);
|
|
2226
|
-
if (error instanceof UpdateStatusError && error.status === 404) {
|
|
2227
|
-
setView({
|
|
2228
|
-
kind: "error",
|
|
2229
|
-
message: t("update.unmounted"),
|
|
2230
|
-
detail: t("update.unmountedDetail")
|
|
2231
|
-
});
|
|
2232
|
-
return;
|
|
2233
|
-
}
|
|
2234
|
-
setView({
|
|
2235
|
-
kind: "error",
|
|
2236
|
-
message: t("update.offline"),
|
|
2237
|
-
detail: t("update.offlineDetail")
|
|
2238
|
-
});
|
|
2239
|
-
return;
|
|
2240
|
-
}
|
|
2241
|
-
if (availabilityCheck === availabilityToken.current) setUpdateAvailable(status.mode === "npm" && status.outdated);
|
|
2242
|
-
if (status.error === "registry-unreachable") {
|
|
2243
|
-
setView({
|
|
2244
|
-
kind: "result",
|
|
2245
|
-
status
|
|
2246
|
-
});
|
|
2247
|
-
return;
|
|
2248
|
-
}
|
|
2249
|
-
setView({
|
|
2250
|
-
kind: "result",
|
|
2251
|
-
status
|
|
2252
|
-
});
|
|
2253
|
-
}, [t]);
|
|
2254
|
-
const startUpdate = (0, react.useCallback)(async (status) => {
|
|
2255
|
-
if (status.mode !== "npm" || !status.outdated) return;
|
|
2256
|
-
setView({
|
|
2257
|
-
kind: "updating",
|
|
2258
|
-
status
|
|
2259
|
-
});
|
|
2260
|
-
const token = ++runToken.current;
|
|
2261
|
-
try {
|
|
2262
|
-
const result = await runUpdate();
|
|
2263
|
-
if (result.ok && mounted.current) setUpdateAvailable(false);
|
|
2264
|
-
if (token !== runToken.current) return;
|
|
2265
|
-
setView({
|
|
2266
|
-
kind: "done",
|
|
2267
|
-
result
|
|
2268
|
-
});
|
|
2269
|
-
} catch {
|
|
2270
|
-
if (token !== runToken.current) return;
|
|
2271
|
-
setView({
|
|
2272
|
-
kind: "error",
|
|
2273
|
-
message: t("update.error"),
|
|
2274
|
-
detail: t("update.offlineDetail")
|
|
2275
|
-
});
|
|
2276
|
-
}
|
|
2277
|
-
}, [t]);
|
|
2278
|
-
const openPanel = (0, react.useCallback)(() => {
|
|
2279
|
-
setOpen(true);
|
|
2280
|
-
check();
|
|
2281
|
-
}, [check]);
|
|
2282
|
-
const closePanel = (0, react.useCallback)(() => {
|
|
2283
|
-
runToken.current++;
|
|
2284
|
-
setOpen(false);
|
|
2285
|
-
}, []);
|
|
2286
|
-
(0, react.useEffect)(() => {
|
|
2287
|
-
mounted.current = true;
|
|
2288
|
-
probeAvailability();
|
|
2289
|
-
return () => {
|
|
2290
|
-
mounted.current = false;
|
|
2291
|
-
runToken.current++;
|
|
2292
|
-
availabilityToken.current++;
|
|
2293
|
-
};
|
|
2294
|
-
}, [probeAvailability]);
|
|
2295
|
-
const updateLabel = updateAvailable ? t("update.availableLabel") : t("update.label");
|
|
2296
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2297
|
-
type: "button",
|
|
2298
|
-
className: remote_module_css_default.trigger,
|
|
2299
|
-
"data-wide": wide ? updateAvailable ? "wide" : void 0 : "rail",
|
|
2300
|
-
"data-update-available": updateAvailable ? "true" : void 0,
|
|
2301
|
-
"aria-label": updateLabel,
|
|
2302
|
-
"aria-expanded": open,
|
|
2303
|
-
title: updateLabel,
|
|
2304
|
-
onClick: openPanel,
|
|
2305
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutlineRegular, { size: wide ? 16 : 18 }), updateAvailable && wide && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2306
|
-
className: remote_module_css_default.updateBadgeText,
|
|
2307
|
-
children: t("update.badge")
|
|
2308
|
-
})]
|
|
2309
|
-
}), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2310
|
-
className: remote_module_css_default.overlay,
|
|
2311
|
-
role: "presentation",
|
|
2312
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2313
|
-
className: remote_module_css_default.mask,
|
|
2314
|
-
"aria-hidden": "true",
|
|
2315
|
-
onClick: closePanel
|
|
2316
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UpdatePanel, {
|
|
2317
|
-
t,
|
|
2318
|
-
view,
|
|
2319
|
-
onClose: closePanel,
|
|
2320
|
-
onRecheck: () => {
|
|
2321
|
-
check();
|
|
2322
|
-
},
|
|
2323
|
-
onStartUpdate: (status) => {
|
|
2324
|
-
startUpdate(status);
|
|
2325
|
-
}
|
|
2326
|
-
})]
|
|
2327
|
-
}), document.body)] });
|
|
2328
|
-
}
|
|
2329
|
-
//#endregion
|
|
2330
1845
|
//#region src/client/RemoteEntry.tsx
|
|
2331
1846
|
/**
|
|
2332
|
-
* The sidebar remote-control seat: the
|
|
2333
|
-
*
|
|
2334
|
-
*
|
|
2335
|
-
*
|
|
2336
|
-
*
|
|
2337
|
-
*
|
|
2338
|
-
*
|
|
1847
|
+
* The sidebar remote-control seat: the phone-icon trigger beside the
|
|
1848
|
+
* settings button, and the pairing panel modal. Owns the panel behavior —
|
|
1849
|
+
* token minting on open, the status SSE subscription, stop/refresh/copy — and
|
|
1850
|
+
* renders the pure {@link RemotePanel} body. The family self-update seat is
|
|
1851
|
+
* its own plugin (dsh-update) and no longer rides this row. Component-local
|
|
1852
|
+
* state per the client stack rules: nothing here survives remounts or crosses
|
|
1853
|
+
* entries.
|
|
2339
1854
|
*/
|
|
2340
1855
|
/**
|
|
2341
1856
|
* Apply one status frame onto the current state: the ready state mirrors
|
|
@@ -2415,7 +1930,7 @@ window.__ModuleLoader__.load({
|
|
|
2415
1930
|
if (seq !== openSeq.current) return;
|
|
2416
1931
|
setState(next);
|
|
2417
1932
|
if (next.kind !== "ready" && next.kind !== "lan-required") return;
|
|
2418
|
-
const source = new EventSource("
|
|
1933
|
+
const source = new EventSource("api/pair/events");
|
|
2419
1934
|
eventSource.current = source;
|
|
2420
1935
|
source.onmessage = (event) => {
|
|
2421
1936
|
try {
|
|
@@ -2503,18 +2018,15 @@ window.__ModuleLoader__.load({
|
|
|
2503
2018
|
}, 1500);
|
|
2504
2019
|
});
|
|
2505
2020
|
}, []);
|
|
2506
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2021
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2507
2022
|
className: remote_module_css_default.entryRow,
|
|
2508
2023
|
"data-rail": wide ? void 0 : "rail",
|
|
2509
|
-
children:
|
|
2510
|
-
wide,
|
|
2511
|
-
t
|
|
2512
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipAnchor, {
|
|
2024
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipAnchor, {
|
|
2513
2025
|
wide,
|
|
2514
2026
|
label: t("entry.label"),
|
|
2515
2027
|
onClick: openPanel,
|
|
2516
2028
|
expanded: open
|
|
2517
|
-
})
|
|
2029
|
+
})
|
|
2518
2030
|
}), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2519
2031
|
className: remote_module_css_default.overlay,
|
|
2520
2032
|
role: "presentation",
|
|
@@ -3468,8 +2980,6 @@ window.__ModuleLoader__.load({
|
|
|
3468
2980
|
descriptionKey: "settings.description",
|
|
3469
2981
|
defaultOpen: false,
|
|
3470
2982
|
state,
|
|
3471
|
-
renderChildrenWhenNotExposed: true,
|
|
3472
|
-
hideNotExposedNotice: true,
|
|
3473
2983
|
onSave: props.save,
|
|
3474
2984
|
onDiscard: props.discard,
|
|
3475
2985
|
children: [
|
|
@@ -3722,7 +3232,7 @@ window.__ModuleLoader__.load({
|
|
|
3722
3232
|
"status.disconnected": "已配对设备离线",
|
|
3723
3233
|
"status.stopped": "已停止远程访问",
|
|
3724
3234
|
"status.lanRequired": "此功能需要局域网绑定或公网地址才能使用",
|
|
3725
|
-
"status.lanRequiredHint": "当前服务仅绑定在 127.0.0.1
|
|
3235
|
+
"status.lanRequiredHint": "当前服务仅绑定在 127.0.0.1 且未配置公网地址,手机无法访问。请在本插件的设置卡片中打开“局域网访问”(装在全家桶里时在「设置 → Web 插件 → 远程访问设置」,单独安装时在设置 → 插件 → 本插件行),或用 dsh web --host 0.0.0.0 重新启动,或填写内网穿透的公网地址。",
|
|
3726
3236
|
"status.loopbackRequired": "配对面板仅限本机使用",
|
|
3727
3237
|
"status.loopbackRequiredHint": "请通过 http://127.0.0.1 打开此页面后重试;手机请使用配对链接访问。",
|
|
3728
3238
|
"status.unreachable": "无法连接配对服务",
|
|
@@ -3827,49 +3337,7 @@ window.__ModuleLoader__.load({
|
|
|
3827
3337
|
"settings.discard": "放弃",
|
|
3828
3338
|
"settings.unsaved": "未保存",
|
|
3829
3339
|
"settings.saveFailed": "部署未接受这些值,已保留供你修改。",
|
|
3830
|
-
"settings.invalidNumber": "请输入数字,留空则使用默认值。"
|
|
3831
|
-
"update.label": "检查更新",
|
|
3832
|
-
"update.badge": "有更新",
|
|
3833
|
-
"update.availableLabel": "发现新版本,检查更新",
|
|
3834
|
-
"update.title": "检查更新",
|
|
3835
|
-
"update.checking": "正在检查远程仓库中的新版本…",
|
|
3836
|
-
"update.upToDate": "当前已是最新版本",
|
|
3837
|
-
"update.upToDateDetail": "{name} 已是最新版本 v{version}。",
|
|
3838
|
-
"update.found": "发现新版本",
|
|
3839
|
-
"update.foundDetail": "{name} 有新版本 v{version}。确认后开始更新。",
|
|
3840
|
-
"update.start": "开始更新",
|
|
3841
|
-
"update.updating": "正在更新 {name} 至 v{version},可能需要 1-2 分钟…",
|
|
3842
|
-
"update.cooldownNotice": "本次更新由你主动触发,将跳过 pnpm 的 24 小时发布冷却期(供应链保护)直接安装所选版本。",
|
|
3843
|
-
"update.updatingTitle": "正在更新…",
|
|
3844
|
-
"update.done": "更新完成",
|
|
3845
|
-
"update.doneDetail": "所有组件已更新到最新版本。",
|
|
3846
|
-
"update.restartHint": "请重启 dsh web 使新版本生效:在终端按 Ctrl+C 停止后重新运行 dsh web。",
|
|
3847
|
-
"update.linkMode": "当前为本地开发模式",
|
|
3848
|
-
"update.linkModeDetail": "dsh-web 或其组件通过本地链接安装(开发模式),无法自动更新。npm 上最新版本:{version}。请同步本地仓库,或移除与全家桶重复的独立链接。",
|
|
3849
|
-
"update.missing": "未找到 dsh-web 安装",
|
|
3850
|
-
"update.missingDetail": "无法定位已安装的 dsh-web 聚合包。请先运行 dsh plugin --profile <profile> add @linxin666/dsh-web-all。",
|
|
3851
|
-
"update.offline": "无法连接更新源",
|
|
3852
|
-
"update.offlineDetail": "网络不可用或更新服务异常,请稍后重试。",
|
|
3853
|
-
"update.unmounted": "更新服务未加载",
|
|
3854
|
-
"update.unmountedDetail": "当前 dsh web 进程运行的插件版本不含更新服务,请重启 dsh web(Ctrl+C 后重新运行)后再试。",
|
|
3855
|
-
"update.error": "更新失败",
|
|
3856
|
-
"update.packages": "组件版本",
|
|
3857
|
-
"update.releaseNotes": "v{version} 更新说明",
|
|
3858
|
-
"update.releaseFeatures": "新增功能",
|
|
3859
|
-
"update.releaseFixes": "修复",
|
|
3860
|
-
"update.releaseOther": "其他变动",
|
|
3861
|
-
"update.releaseUnavailable": "本次发布说明暂不可用,可前往 GitHub Release 查看。",
|
|
3862
|
-
"update.componentVersions": "查看组件版本",
|
|
3863
|
-
"update.retry": "重新检查",
|
|
3864
|
-
"update.close": "关闭更新面板",
|
|
3865
|
-
"update.error.pnpmMissing": "未找到 pnpm,已尝试 pnpm / corepack / npx 仍不可用。请安装 pnpm 后重启应用(Windows: npm install -g pnpm 或 corepack enable;macOS: brew install pnpm 或 corepack enable)。",
|
|
3866
|
-
"update.error.timeout": "更新超时,已终止安装进程,请重试。",
|
|
3867
|
-
"update.error.notFound": "未找到 dsh-web 聚合包安装,无法更新。",
|
|
3868
|
-
"update.error.link": "dsh-web 或其组件使用本地链接,无法自动更新;请同步本地仓库,或移除与全家桶重复的独立链接。",
|
|
3869
|
-
"update.error.pnpmFailed": "更新执行失败(pnpm 退出码 {code}),详见下方输出。",
|
|
3870
|
-
"update.error.stale": "更新命令已执行,但安装版本未变化。常见原因:pnpm 11 的 minimumReleaseAge 门禁会静默跳过发布不足 24 小时的新版本。请在 profile 目录(~/.dsh/profiles/<profile>)的 pnpm-workspace.yaml 中配置 minimumReleaseAgeExclude(例如 \"@linxin666/*\")或 minimumReleaseAge: 0 后重新点击更新。",
|
|
3871
|
-
"update.error.verifyFailed": "更新命令已执行,但无法核对安装后的版本(可能是 registry 探测失败或安装路径变化)。请查看下方输出,或稍后重试。",
|
|
3872
|
-
"update.error.unknown": "更新失败,请重试。"
|
|
3340
|
+
"settings.invalidNumber": "请输入数字,留空则使用默认值。"
|
|
3873
3341
|
};
|
|
3874
3342
|
/** English dictionary, checked complete against the zh key set. */
|
|
3875
3343
|
const en = {
|
|
@@ -3886,7 +3354,7 @@ window.__ModuleLoader__.load({
|
|
|
3886
3354
|
"status.disconnected": "Paired devices offline",
|
|
3887
3355
|
"status.stopped": "Remote access stopped",
|
|
3888
3356
|
"status.lanRequired": "This feature needs a LAN bind or a public address",
|
|
3889
|
-
"status.lanRequiredHint": "The server is bound to 127.0.0.1 and no public address is configured, so a phone cannot reach it. Turn on LAN access in
|
|
3357
|
+
"status.lanRequiredHint": "The server is bound to 127.0.0.1 and no public address is configured, so a phone cannot reach it. Turn on LAN access in this plugin settings card (under Settings → Web Plugins → Remote access when the full family is installed, or on this plugin row under Settings → Plugins for a standalone install), or restart with dsh web --host 0.0.0.0, or set the tunneled public address.",
|
|
3890
3358
|
"status.loopbackRequired": "The pairing panel works on this machine only",
|
|
3891
3359
|
"status.loopbackRequiredHint": "Open this page at http://127.0.0.1 to mint a QR code; phones use the paired link.",
|
|
3892
3360
|
"status.unreachable": "Cannot reach the pairing service",
|
|
@@ -3991,49 +3459,7 @@ window.__ModuleLoader__.load({
|
|
|
3991
3459
|
"settings.discard": "Discard",
|
|
3992
3460
|
"settings.unsaved": "Unsaved",
|
|
3993
3461
|
"settings.saveFailed": "The deployment did not accept these values; they were left for you to correct.",
|
|
3994
|
-
"settings.invalidNumber": "Enter a number, or leave blank to use the default."
|
|
3995
|
-
"update.label": "Check for updates",
|
|
3996
|
-
"update.badge": "Update available",
|
|
3997
|
-
"update.availableLabel": "New version available. Check for updates",
|
|
3998
|
-
"update.title": "Check for updates",
|
|
3999
|
-
"update.checking": "Checking the remote repository for newer releases…",
|
|
4000
|
-
"update.upToDate": "Everything is up to date",
|
|
4001
|
-
"update.upToDateDetail": "{name} is at the latest version v{version}.",
|
|
4002
|
-
"update.found": "A new version is available",
|
|
4003
|
-
"update.foundDetail": "{name} has a newer release v{version}. Confirm to start the update.",
|
|
4004
|
-
"update.start": "Update now",
|
|
4005
|
-
"update.updating": "Updating {name} to v{version}; this may take 1-2 minutes…",
|
|
4006
|
-
"update.cooldownNotice": "This user-initiated update bypasses pnpm's 24-hour release cooldown (supply-chain protection) and installs the selected version immediately.",
|
|
4007
|
-
"update.updatingTitle": "Updating…",
|
|
4008
|
-
"update.done": "Update complete",
|
|
4009
|
-
"update.doneDetail": "All components are up to date.",
|
|
4010
|
-
"update.restartHint": "Restart dsh web for the new version to take effect: stop it with Ctrl+C in the terminal, then run dsh web again.",
|
|
4011
|
-
"update.linkMode": "Local development mode",
|
|
4012
|
-
"update.linkModeDetail": "dsh-web or one of its components is installed via a local link and cannot be updated remotely. Latest npm release: {version}. Sync the local checkout, or remove a standalone link duplicated by the aggregate.",
|
|
4013
|
-
"update.missing": "dsh-web not found",
|
|
4014
|
-
"update.missingDetail": "Could not locate the installed dsh-web aggregate package. Install it with: dsh plugin --profile <profile> add @linxin666/dsh-web-all",
|
|
4015
|
-
"update.offline": "Cannot reach the update source",
|
|
4016
|
-
"update.offlineDetail": "The network is unavailable or the update service is failing; try again later.",
|
|
4017
|
-
"update.unmounted": "The update service is not loaded",
|
|
4018
|
-
"update.unmountedDetail": "The running dsh web process loads a plugin build without the update service; restart dsh web (Ctrl+C, then run dsh web again) and retry.",
|
|
4019
|
-
"update.error": "Update failed",
|
|
4020
|
-
"update.packages": "Component versions",
|
|
4021
|
-
"update.releaseNotes": "v{version} release notes",
|
|
4022
|
-
"update.releaseFeatures": "New Features",
|
|
4023
|
-
"update.releaseFixes": "Bug Fixes",
|
|
4024
|
-
"update.releaseOther": "Other Changes",
|
|
4025
|
-
"update.releaseUnavailable": "Release notes are unavailable for this version; see the GitHub release instead.",
|
|
4026
|
-
"update.componentVersions": "View component versions",
|
|
4027
|
-
"update.retry": "Check again",
|
|
4028
|
-
"update.close": "Close update panel",
|
|
4029
|
-
"update.error.pnpmMissing": "pnpm was not found (tried pnpm / corepack / npx). Install pnpm and restart the app (Windows: npm install -g pnpm or corepack enable; macOS: brew install pnpm or corepack enable).",
|
|
4030
|
-
"update.error.timeout": "The update timed out and the install process was killed; try again.",
|
|
4031
|
-
"update.error.notFound": "The dsh-web aggregate package is not installed; nothing to update.",
|
|
4032
|
-
"update.error.link": "dsh-web or one of its components uses a local link. Sync the checkout, or remove a standalone link duplicated by the aggregate.",
|
|
4033
|
-
"update.error.pnpmFailed": "The update failed (pnpm exited with code {code}); see the output below.",
|
|
4034
|
-
"update.error.stale": "The update command ran but the installed versions did not change. Likely cause: the pnpm 11 minimumReleaseAge gate silently skips releases published less than 24 hours ago. Add minimumReleaseAgeExclude (e.g. \"@linxin666/*\") or set minimumReleaseAge: 0 in pnpm-workspace.yaml under the profile directory (~/.dsh/profiles/<profile>), then run the update again.",
|
|
4035
|
-
"update.error.verifyFailed": "The update command ran but the installed versions could not be verified afterwards (registry probe failed or the install path changed). Inspect the output below, or retry later.",
|
|
4036
|
-
"update.error.unknown": "Update failed; try again."
|
|
3462
|
+
"settings.invalidNumber": "Enter a number, or leave blank to use the default."
|
|
4037
3463
|
};
|
|
4038
3464
|
//#endregion
|
|
4039
3465
|
//#region src/client/deep-link.ts
|
|
@@ -4104,6 +3530,102 @@ window.__ModuleLoader__.load({
|
|
|
4104
3530
|
*/
|
|
4105
3531
|
/** The gated mirror prefix (must match src/remote-methods.ts). */
|
|
4106
3532
|
const REMOTE_PREFIX = "/remote";
|
|
3533
|
+
/**
|
|
3534
|
+
* The schemes that can deliver a page a remote party controls: the network
|
|
3535
|
+
* transports, plus the documents a network page can mint. Every other scheme
|
|
3536
|
+
* is registered and served by an application on this machine, so its page is
|
|
3537
|
+
* the machine's own page whatever the application calls the scheme.
|
|
3538
|
+
*
|
|
3539
|
+
* The list is deliberately the *web* side rather than an allowlist of known
|
|
3540
|
+
* desktop shells. The official DSH Desktop shell serves its Web GUI from
|
|
3541
|
+
* `dsh-app://app/` (`location.hostname === 'app'`), which no hostname
|
|
3542
|
+
* predicate can recognise; a scheme allowlist fixed that instance (#1682) but
|
|
3543
|
+
* left the next shell — or a `file:` page — fenced behind a pairing page it
|
|
3544
|
+
* can never complete, because a pairing link is reachable only over the
|
|
3545
|
+
* network. Naming the web side instead makes an unknown application scheme
|
|
3546
|
+
* local by construction.
|
|
3547
|
+
*
|
|
3548
|
+
* `blob:`, `data:`, `about:` and `filesystem:` stay on the web side: a
|
|
3549
|
+
* network page mints those documents, so they must keep the fence.
|
|
3550
|
+
*/
|
|
3551
|
+
const WEB_PAGE_PROTOCOLS = [
|
|
3552
|
+
"http:",
|
|
3553
|
+
"https:",
|
|
3554
|
+
"blob:",
|
|
3555
|
+
"data:",
|
|
3556
|
+
"about:",
|
|
3557
|
+
"filesystem:"
|
|
3558
|
+
];
|
|
3559
|
+
/**
|
|
3560
|
+
* Whether one page scheme can carry a document a remote party controls.
|
|
3561
|
+
* @param protocol - `location.protocol` of the page (for example `https:`).
|
|
3562
|
+
* @returns true for the network transports and the documents they mint.
|
|
3563
|
+
*/
|
|
3564
|
+
function isWebPageProtocol(protocol) {
|
|
3565
|
+
return WEB_PAGE_PROTOCOLS.includes(protocol);
|
|
3566
|
+
}
|
|
3567
|
+
/**
|
|
3568
|
+
* Hostname-only loopback classification: localhost, the IPv6 loopback literal
|
|
3569
|
+
* (WHATWG keeps its brackets) and any 127/8 IPv4 literal. Kept string-in,
|
|
3570
|
+
* boolean-out and dependency-free so the browser half and the inlined boot
|
|
3571
|
+
* script can both run it verbatim.
|
|
3572
|
+
* @param hostname - a page/cookie hostname, IPv6 literals bracketed.
|
|
3573
|
+
* @returns true for a loopback name or literal.
|
|
3574
|
+
*/
|
|
3575
|
+
function isLoopbackHostname(hostname) {
|
|
3576
|
+
if (hostname === "localhost" || hostname === "::1" || hostname === "[::1]") return true;
|
|
3577
|
+
const parts = hostname.split(".");
|
|
3578
|
+
return parts.length === 4 && parts[0] === "127" && parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
|
|
3579
|
+
}
|
|
3580
|
+
/**
|
|
3581
|
+
* Whether the current page is served from the machine running the host, so
|
|
3582
|
+
* its same-origin traffic must NOT be fenced behind the pairing channel.
|
|
3583
|
+
* Three independent facts all describe the page itself and any one is enough:
|
|
3584
|
+
*
|
|
3585
|
+
* - the page's own hostname is loopback;
|
|
3586
|
+
* - the page's protocol is not a {@link WEB_PAGE_PROTOCOLS} web scheme, so an
|
|
3587
|
+
* application on this machine delivered it — the desktop shell's
|
|
3588
|
+
* `dsh-app://app/` is one such page, and a scheme this build has never
|
|
3589
|
+
* heard of is another;
|
|
3590
|
+
* - the official connection transport already declared this shell the host
|
|
3591
|
+
* owner (`__DSH_TRANSPORT__.ownsHost === true`) AND the page is not a
|
|
3592
|
+
* network origin. The official client reads the same hook to derive
|
|
3593
|
+
* `connection.isLoopback`, and the desktop shell sets it before any boot
|
|
3594
|
+
* entry — without this term the plugin's predicate contradicts the host's
|
|
3595
|
+
* own conclusion (#1682). The network-origin guard is deliberate: this
|
|
3596
|
+
* plugin's own device-gated landing (a LAN or tunnel page) publishes the
|
|
3597
|
+
* same hook to grant a paired remote the full UI, and that page must keep
|
|
3598
|
+
* riding the gated channel — `ownsHost` buys the presentation, never an
|
|
3599
|
+
* exemption from the pairing fence.
|
|
3600
|
+
*
|
|
3601
|
+
* The order matters: loopback is checked first so a loopback page keeps its
|
|
3602
|
+
* exemption even on a scheme no list carries, and the scheme test runs before
|
|
3603
|
+
* the hook test so a web page always reaches the network-origin guard. A
|
|
3604
|
+
* missing or empty scheme is not evidence of a local page - the fence stays
|
|
3605
|
+
* up - because a real document always reports its own scheme.
|
|
3606
|
+
*
|
|
3607
|
+
* @param hostname - `location.hostname` of the page.
|
|
3608
|
+
* @param protocol - `location.protocol` of the page (for example `https:`).
|
|
3609
|
+
* @param transportOwnsHost - `__DSH_TRANSPORT__?.ownsHost` as read by the caller.
|
|
3610
|
+
* @returns true when the page is local and needs no remote channel.
|
|
3611
|
+
*/
|
|
3612
|
+
function isLocalPage(hostname, protocol, transportOwnsHost) {
|
|
3613
|
+
if (isLoopbackHostname(hostname)) return true;
|
|
3614
|
+
if (protocol !== void 0 && protocol !== "" && !isWebPageProtocol(protocol)) return true;
|
|
3615
|
+
return transportOwnsHost === true && !isNetworkOrigin(hostname);
|
|
3616
|
+
}
|
|
3617
|
+
/**
|
|
3618
|
+
* Whether a hostname can only be reached across a network, so a page on it is
|
|
3619
|
+
* remote whatever transport hook it carries: any IPv4/IPv6 literal (a dot or a
|
|
3620
|
+
* colon) or any dotted DNS name. A hostname with no dot and no colon is a
|
|
3621
|
+
* scheme-local authority (the desktop shell's `app`), which is why this test,
|
|
3622
|
+
* not a loopback test, is what the `ownsHost` term is gated on.
|
|
3623
|
+
* @param hostname - the page hostname.
|
|
3624
|
+
* @returns true when the page cannot be the machine's own local page.
|
|
3625
|
+
*/
|
|
3626
|
+
function isNetworkOrigin(hostname) {
|
|
3627
|
+
return hostname.includes(".") || hostname.includes(":");
|
|
3628
|
+
}
|
|
4107
3629
|
/** The live rule set. */
|
|
4108
3630
|
const REMOTE_CHANNEL_RULES = {
|
|
4109
3631
|
remotePrefix: REMOTE_PREFIX,
|
|
@@ -4126,6 +3648,7 @@ window.__ModuleLoader__.load({
|
|
|
4126
3648
|
deviceQuery: REMOTE_DEVICE_QUERY,
|
|
4127
3649
|
uploadPath: "/api/session/uploadFileBinary",
|
|
4128
3650
|
uploadHookGlobal: "__DSH_FILE_UPLOAD__",
|
|
3651
|
+
webProtocols: WEB_PAGE_PROTOCOLS,
|
|
4129
3652
|
hostGrantGlobal: "__DSH_REMOTE_HOST_GRANT__"
|
|
4130
3653
|
};
|
|
4131
3654
|
/** The window global the boot patch publishes its seat under. */
|
|
@@ -4156,24 +3679,29 @@ window.__ModuleLoader__.load({
|
|
|
4156
3679
|
* the given window and returns their restore.
|
|
4157
3680
|
*/
|
|
4158
3681
|
const RULES = REMOTE_CHANNEL_RULES;
|
|
3682
|
+
/**
|
|
3683
|
+
* Read the page-origin facts from a window-like object: the hostname and
|
|
3684
|
+
* protocol of the page itself plus the official transport's host-owner flag
|
|
3685
|
+
* (`__DSH_TRANSPORT__.ownsHost`, set by the desktop shell before any boot
|
|
3686
|
+
* entry, and by this plugin's own paired landing).
|
|
3687
|
+
* @param win - the window to read (defaults to the real one).
|
|
3688
|
+
* @returns the facts `isLocalPage`/`remoteChannelRequired` decide from.
|
|
3689
|
+
*/
|
|
3690
|
+
function pageOriginFacts(win = window) {
|
|
3691
|
+
const transport = win.__DSH_TRANSPORT__;
|
|
3692
|
+
return {
|
|
3693
|
+
hostname: win.location.hostname,
|
|
3694
|
+
protocol: win.location.protocol,
|
|
3695
|
+
...transport?.ownsHost === true ? { transportOwnsHost: true } : {}
|
|
3696
|
+
};
|
|
3697
|
+
}
|
|
4159
3698
|
/** Decide whether a remote desktop channel is required from local or host policy. */
|
|
4160
|
-
function remoteChannelRequired(
|
|
4161
|
-
if (
|
|
3699
|
+
function remoteChannelRequired(origin, snapshot, hostPairingPolicy) {
|
|
3700
|
+
if (isLocalPage(origin.hostname, origin.protocol, origin.transportOwnsHost)) return false;
|
|
4162
3701
|
if (snapshot.status === "ready") return (snapshot.value?.enabled ?? true) && (snapshot.value?.requirePairingForLan ?? true);
|
|
4163
3702
|
return hostPairingPolicy !== false;
|
|
4164
3703
|
}
|
|
4165
3704
|
/**
|
|
4166
|
-
* Browser-safe loopback classification for the page origin (the SDK client
|
|
4167
|
-
* exports its own; this copy keeps the module dependency-free).
|
|
4168
|
-
* @param hostname - a location hostname (IPv6 without brackets).
|
|
4169
|
-
* @returns true for localhost, IPv6 loopback, or any 127/8 literal.
|
|
4170
|
-
*/
|
|
4171
|
-
function isLoopbackHostname(hostname) {
|
|
4172
|
-
if (hostname === "localhost" || hostname === "::1" || hostname === "[::1]") return true;
|
|
4173
|
-
const parts = hostname.split(".");
|
|
4174
|
-
return parts.length === 4 && parts[0] === "127" && parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
|
|
4175
|
-
}
|
|
4176
|
-
/**
|
|
4177
3705
|
* Whether one same-origin path must ride the gated channel (fetch, EventSource,
|
|
4178
3706
|
* img/script/iframe src).
|
|
4179
3707
|
* @param pathname - the request URL pathname.
|
|
@@ -4553,7 +4081,7 @@ window.__ModuleLoader__.load({
|
|
|
4553
4081
|
/** The building package's version, when the bundle carries it. */
|
|
4554
4082
|
function bakedVersion() {
|
|
4555
4083
|
try {
|
|
4556
|
-
return "0.4.
|
|
4084
|
+
return "0.4.3";
|
|
4557
4085
|
} catch {
|
|
4558
4086
|
return;
|
|
4559
4087
|
}
|
|
@@ -5677,6 +5205,96 @@ window.__ModuleLoader__.load({
|
|
|
5677
5205
|
reconcile();
|
|
5678
5206
|
}
|
|
5679
5207
|
//#endregion
|
|
5208
|
+
//#region src/client/settings-entry-form.ts
|
|
5209
|
+
/** The snapshot a form reports before the Host has answered with this entry. */
|
|
5210
|
+
function pendingSnapshot() {
|
|
5211
|
+
return {
|
|
5212
|
+
status: "loading",
|
|
5213
|
+
value: void 0,
|
|
5214
|
+
base: void 0,
|
|
5215
|
+
user: void 0,
|
|
5216
|
+
revision: void 0,
|
|
5217
|
+
writable: false,
|
|
5218
|
+
mode: "host"
|
|
5219
|
+
};
|
|
5220
|
+
}
|
|
5221
|
+
/**
|
|
5222
|
+
* Create a settings form bound to the profile entry id this Host serves, and
|
|
5223
|
+
* rebound whenever the shared describe mirror names a different one of this
|
|
5224
|
+
* package's rows.
|
|
5225
|
+
* @param options - the shared forms service and this package's candidate row ids.
|
|
5226
|
+
* @returns a form delegating to the currently resolved entry's own form.
|
|
5227
|
+
*/
|
|
5228
|
+
function createServedEntryForm(options) {
|
|
5229
|
+
const { forms, entryIds } = options;
|
|
5230
|
+
const fallbackId = entryIds[0];
|
|
5231
|
+
const namespaceId = entryIds[entryIds.length - 1];
|
|
5232
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
5233
|
+
let boundId;
|
|
5234
|
+
let bound;
|
|
5235
|
+
let offBound;
|
|
5236
|
+
let snapshot = pendingSnapshot();
|
|
5237
|
+
/** Republish: the delegated snapshot when one is bound, the pending one otherwise. */
|
|
5238
|
+
const publish = () => {
|
|
5239
|
+
if (bound !== void 0) snapshot = bound.getSnapshot();
|
|
5240
|
+
for (const listener of [...listeners]) listener();
|
|
5241
|
+
};
|
|
5242
|
+
/**
|
|
5243
|
+
* The entry id the mirror currently justifies. An unanswered or empty mirror
|
|
5244
|
+
* is not evidence of absence, so it keeps the first candidate; a mirror that
|
|
5245
|
+
* answers without any of this package's rows leaves the namespace itself.
|
|
5246
|
+
* @returns the entry id to bind.
|
|
5247
|
+
*/
|
|
5248
|
+
const resolve = () => {
|
|
5249
|
+
let served;
|
|
5250
|
+
try {
|
|
5251
|
+
served = forms.describe().getSnapshot().view?.namespaces.map((row) => row.ns);
|
|
5252
|
+
} catch {
|
|
5253
|
+
served = void 0;
|
|
5254
|
+
}
|
|
5255
|
+
if (served === void 0 || served.length === 0) return fallbackId;
|
|
5256
|
+
return entryIds.find((id) => served.includes(id)) ?? namespaceId;
|
|
5257
|
+
};
|
|
5258
|
+
/** Bind (or rebind) the resolved entry's form; a no-op while it is unchanged. */
|
|
5259
|
+
const bind = () => {
|
|
5260
|
+
const target = resolve();
|
|
5261
|
+
if (target === boundId) return;
|
|
5262
|
+
offBound?.();
|
|
5263
|
+
offBound = void 0;
|
|
5264
|
+
let form;
|
|
5265
|
+
try {
|
|
5266
|
+
form = forms.get(target);
|
|
5267
|
+
} catch {
|
|
5268
|
+
boundId = void 0;
|
|
5269
|
+
return;
|
|
5270
|
+
}
|
|
5271
|
+
boundId = target;
|
|
5272
|
+
bound = form;
|
|
5273
|
+
offBound = form.subscribe(() => {
|
|
5274
|
+
publish();
|
|
5275
|
+
});
|
|
5276
|
+
publish();
|
|
5277
|
+
};
|
|
5278
|
+
try {
|
|
5279
|
+
forms.describe().subscribe(() => {
|
|
5280
|
+
bind();
|
|
5281
|
+
});
|
|
5282
|
+
} catch {}
|
|
5283
|
+
bind();
|
|
5284
|
+
return {
|
|
5285
|
+
getSnapshot: () => snapshot,
|
|
5286
|
+
subscribe: (listener) => {
|
|
5287
|
+
listeners.add(listener);
|
|
5288
|
+
return () => {
|
|
5289
|
+
listeners.delete(listener);
|
|
5290
|
+
};
|
|
5291
|
+
},
|
|
5292
|
+
set: (field, value) => bound?.set(field, value) ?? Promise.resolve(false),
|
|
5293
|
+
unset: (field) => bound?.unset(field) ?? Promise.resolve(false),
|
|
5294
|
+
mutate: (ops, expectedRevision) => bound?.mutate(ops, expectedRevision) ?? Promise.resolve(false)
|
|
5295
|
+
};
|
|
5296
|
+
}
|
|
5297
|
+
//#endregion
|
|
5680
5298
|
//#region src/client/index.ts
|
|
5681
5299
|
/**
|
|
5682
5300
|
* Remote control — browser half. Registers the `remote` dictionaries, the
|
|
@@ -5689,13 +5307,36 @@ window.__ModuleLoader__.load({
|
|
|
5689
5307
|
*/
|
|
5690
5308
|
/** Dictionary namespace owned by this plugin. */
|
|
5691
5309
|
const NS = "remote";
|
|
5692
|
-
/**
|
|
5310
|
+
/**
|
|
5311
|
+
* Settings namespace the remote-control card edits: the family identity of
|
|
5312
|
+
* this plugin's own settings form, and the row id a standalone bundle install
|
|
5313
|
+
* carries.
|
|
5314
|
+
*/
|
|
5693
5315
|
const REMOTE_WEB_UI_NS = "remote-web-ui";
|
|
5316
|
+
/** Profile entry id the family aggregate's generated row carries. */
|
|
5317
|
+
const AGGREGATE_ENTRY_ID = "web-ui-remote-web-ui";
|
|
5318
|
+
/** Profile entry ids this package's patch rows carry, most specific first. */
|
|
5694
5319
|
const REMOTE_WEB_UI_ENTRY_IDS = [
|
|
5695
|
-
|
|
5320
|
+
AGGREGATE_ENTRY_ID,
|
|
5696
5321
|
"ui-remote-web-ui",
|
|
5697
5322
|
REMOTE_WEB_UI_NS
|
|
5698
5323
|
];
|
|
5324
|
+
/**
|
|
5325
|
+
* The profile entry id this package's own row carries, for a page that serves
|
|
5326
|
+
* no family binder.
|
|
5327
|
+
*
|
|
5328
|
+
* The shared describe mirror answers asynchronously, so at plugin activation it
|
|
5329
|
+
* usually holds nothing yet — and an unanswered or empty mirror is not evidence
|
|
5330
|
+
* of absence. Binding the bare namespace there left the card bound to an entry
|
|
5331
|
+
* the Host does not serve, and the form is bound once per session, so the card
|
|
5332
|
+
* never recovered (every save answered `No configurable plugin entry`). The
|
|
5333
|
+
* aggregate row id matches nearly every deployment; only a mirror that answers
|
|
5334
|
+
* with OTHER plugins' rows falls back to the namespace itself (the pre-0.1.7
|
|
5335
|
+
* keying shape), because that answer is the one that actually proves this
|
|
5336
|
+
* package's own row is not served.
|
|
5337
|
+
* @param forms - the shared configuration forms service.
|
|
5338
|
+
* @returns the entry id to bind.
|
|
5339
|
+
*/
|
|
5699
5340
|
function servedEntryId(forms) {
|
|
5700
5341
|
let served;
|
|
5701
5342
|
try {
|
|
@@ -5703,9 +5344,29 @@ window.__ModuleLoader__.load({
|
|
|
5703
5344
|
} catch {
|
|
5704
5345
|
served = void 0;
|
|
5705
5346
|
}
|
|
5706
|
-
if (
|
|
5347
|
+
if (served === void 0 || served.length === 0) return AGGREGATE_ENTRY_ID;
|
|
5707
5348
|
return REMOTE_WEB_UI_ENTRY_IDS.find((id) => served.includes(id)) ?? REMOTE_WEB_UI_NS;
|
|
5708
5349
|
}
|
|
5350
|
+
/**
|
|
5351
|
+
* Bind the settings form the remote-control card reads and writes.
|
|
5352
|
+
*
|
|
5353
|
+
* The family binder comes first: it resolves this package's family namespace
|
|
5354
|
+
* onto the profile entry id the Host serves the form under, and keeps the
|
|
5355
|
+
* loopback bridge as its own fallback. A page without that group (or one where
|
|
5356
|
+
* its client half has not applied yet) binds through the shared forms service,
|
|
5357
|
+
* on the entry id the describe mirror justifies and rebound as soon as the
|
|
5358
|
+
* mirror answers — see {@link createServedEntryForm}.
|
|
5359
|
+
* @param ctx - client context carrying the family binder and/or the shared forms service.
|
|
5360
|
+
* @returns the form the card stages and saves through.
|
|
5361
|
+
*/
|
|
5362
|
+
function bindSettingsForm(ctx) {
|
|
5363
|
+
const family = ctx.get("webUiSettings");
|
|
5364
|
+
if (family !== void 0 && typeof family.bind === "function") return family.bind({ namespace: REMOTE_WEB_UI_NS });
|
|
5365
|
+
return createServedEntryForm({
|
|
5366
|
+
forms: ctx.configForms,
|
|
5367
|
+
entryIds: REMOTE_WEB_UI_ENTRY_IDS
|
|
5368
|
+
});
|
|
5369
|
+
}
|
|
5709
5370
|
/** Heartbeat cadence from a paired phone (presence + revocation liveness). */
|
|
5710
5371
|
const HEARTBEAT_INTERVAL_MS = 1e4;
|
|
5711
5372
|
/** Services required by this plugin. */
|
|
@@ -5756,8 +5417,7 @@ window.__ModuleLoader__.load({
|
|
|
5756
5417
|
};
|
|
5757
5418
|
const t = ctx.locale.bind(NS);
|
|
5758
5419
|
if (adapt !== void 0) adapt.translate = t;
|
|
5759
|
-
const
|
|
5760
|
-
const settingsForm = family !== void 0 && typeof family.bind === "function" ? family.bind({ namespace: REMOTE_WEB_UI_NS }) : ctx.configForms.get(servedEntryId(ctx.configForms));
|
|
5420
|
+
const settingsForm = bindSettingsForm(ctx);
|
|
5761
5421
|
const enabled = () => {
|
|
5762
5422
|
const snapshot = settingsForm.getSnapshot();
|
|
5763
5423
|
return snapshot.status === "ready" ? snapshot.value?.enabled ?? true : snapshot.status === "unavailable";
|
|
@@ -5860,7 +5520,7 @@ window.__ModuleLoader__.load({
|
|
|
5860
5520
|
}
|
|
5861
5521
|
showFenceNotice();
|
|
5862
5522
|
};
|
|
5863
|
-
const channelActive = () => remoteChannelRequired(window
|
|
5523
|
+
const channelActive = () => remoteChannelRequired(pageOriginFacts(window), settingsForm.getSnapshot(), hostPairingPolicy);
|
|
5864
5524
|
const bootSeat = () => window[REMOTE_CHANNEL_BOOT_GLOBAL];
|
|
5865
5525
|
const syncChannel = () => {
|
|
5866
5526
|
const transition = channelTransition(channelActive(), disposeChannel !== void 0);
|
|
@@ -5892,7 +5552,8 @@ window.__ModuleLoader__.load({
|
|
|
5892
5552
|
};
|
|
5893
5553
|
settingsForm.subscribe(syncChannel);
|
|
5894
5554
|
syncChannel();
|
|
5895
|
-
|
|
5555
|
+
const pageOrigin = pageOriginFacts(window);
|
|
5556
|
+
if (!isLocalPage(pageOrigin.hostname, pageOrigin.protocol, pageOrigin.transportOwnsHost) && settingsForm.getSnapshot().status !== "ready") readPairGatePolicy().then((policy) => {
|
|
5896
5557
|
hostPairingPolicy = policy.requirePairingForLan;
|
|
5897
5558
|
syncChannel();
|
|
5898
5559
|
if (hostPairingPolicy && unpairedWhilePolicyPending) showFenceNotice();
|
|
@@ -5918,7 +5579,9 @@ window.__ModuleLoader__.load({
|
|
|
5918
5579
|
}
|
|
5919
5580
|
//#endregion
|
|
5920
5581
|
exports.apply = apply;
|
|
5582
|
+
exports.bindSettingsForm = bindSettingsForm;
|
|
5921
5583
|
exports.inject = inject;
|
|
5584
|
+
exports.servedEntryId = servedEntryId;
|
|
5922
5585
|
return module.exports;
|
|
5923
5586
|
}
|
|
5924
5587
|
});
|