cicy-desktop 2.1.342 → 2.1.358
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/package.json +1 -1
- package/scripts/build-homepage.cjs +16 -1
- package/src/app-updater.js +30 -0
- package/src/backends/homepage-preload.js +3 -1
- package/src/backends/homepage-react/assets/index-Buyk0RlW.js +655 -0
- package/src/backends/homepage-react/assets/index-CVk0uc2g.css +1 -0
- package/src/backends/homepage-react/index.html +5 -3
- package/src/backends/homepage-react/panel/facebook-matrix.html +305 -87
- package/src/backends/homepage-react/panel/ipcheck.html +112 -0
- package/src/backends/homepage-react/panel/matrix.html +595 -0
- package/src/backends/homepage-react/panel/telegram-matrix.html +96 -22
- package/src/backends/homepage-react/panel/tiktok-matrix.html +599 -0
- package/src/backends/hub-client.js +108 -2
- package/src/backends/sidecar-ipc.js +33 -6
- package/src/main.js +67 -11
- package/src/sidecar/host-mihomo.js +47 -2
- package/src/tabbrowser/facebook-identity.js +12 -1
- package/src/tabbrowser/facebook-matrix.html +305 -87
- package/src/tabbrowser/fb-login.js +115 -0
- package/src/tabbrowser/newtab-protocol.js +6 -1
- package/src/tabbrowser/panel-cells.js +14 -1
- package/src/tabbrowser/telegram-matrix.html +96 -22
- package/src/tabbrowser/telegram-web-preferences.js +5 -1
- package/src/tabbrowser/tiktok-matrix.html +305 -87
- package/src/tools/tab-browser-tools.js +21 -100
- package/src/tray.js +22 -0
- package/test/bootstrap-failure-reporting.test.js +1 -1
- package/test/facebook-identity.test.js +3 -1
- package/test/facebook-matrix.test.js +2 -1
- package/test/telegram-matrix.test.js +2 -2
- package/test/tiktok-matrix.test.js +2 -1
- package/tools/arm-fblogin.js +32 -0
- package/tools/wsd-ws +114 -0
- package/workers/render/index.html +3 -1
- package/workers/render/package.json +2 -3
- package/workers/render/public/panel/facebook-matrix.html +305 -87
- package/workers/render/public/panel/ipcheck.html +112 -0
- package/workers/render/public/panel/matrix.html +595 -0
- package/workers/render/public/panel/telegram-matrix.html +96 -22
- package/workers/render/public/panel/tiktok-matrix.html +599 -0
- package/workers/render/src/App.css +4 -0
- package/workers/render/src/App.jsx +917 -52
- package/workers/render/stamp-panel-version.mjs +16 -0
- package/workers/render/worker.js +1 -1
- package/src/backends/homepage-react/assets/index-C6Zrdgc0.css +0 -1
- package/src/backends/homepage-react/assets/index-OG6JB73F.js +0 -411
- package/src/tabbrowser/cicy-code-tab-restore.js +0 -10
- package/test/cicy-code-tab-restore.test.js +0 -21
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<meta name="pv" content="7aad42d12a05">
|
|
6
7
|
<title>Telegram 矩阵</title>
|
|
7
8
|
<style>
|
|
8
9
|
:root { color-scheme: dark; --bg:#0A0A0A; --cell:#141416; --head:#1a1a1c; --line:rgba(255,255,255,.08); --fg:#e6e6e9; --muted:#8b8b92; --accent:#3b82f6; --hover:rgba(255,255,255,.06); font-family: Inter, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; }
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
.line2 .phone { flex: none; color: #b4b4bb; font: 11px ui-monospace, monospace; white-space: nowrap; }
|
|
46
47
|
.line2 .phone.none { display: none; }
|
|
47
48
|
button.code { background: #1f6feb; padding: 3px 8px; font-size: 11px; }
|
|
49
|
+
button.code.on { background: #c0142e; }
|
|
48
50
|
.ghead .phone { color: #b4b4bb; font: 11px ui-monospace, monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
|
|
49
51
|
/* 代理设置弹窗 */
|
|
50
52
|
.modal { position: fixed; inset: 0; z-index: 60; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,.55); }
|
|
@@ -84,32 +86,35 @@
|
|
|
84
86
|
/* 批量登录:未登录的会话一次全部铺开(最多 10 格),每格一个独立 BrowserView */
|
|
85
87
|
#grid-title { display: none; width: 100%; margin-bottom: 6px; align-items: center; justify-content: space-between; gap: 8px; color: #b4b4bb; font-size: 12px; }
|
|
86
88
|
#grid-title b { color: var(--fg); font-size: 13px; }
|
|
87
|
-
#grid { display: none; width: 100%; flex: 1 1 auto; min-height: 0; gap: 8px;
|
|
89
|
+
#grid { display: none; width: 100%; flex: 1 1 auto; min-height: 0; gap: 8px; flex-direction: row; flex-wrap: wrap; align-content: flex-start; overflow-x: hidden; overflow-y: auto; }
|
|
88
90
|
/* 批量模式 = 整页只有 webview:侧栏整个隐藏,顶部一条细工具栏,其余全是格子 */
|
|
89
91
|
body.batch { grid-template-columns: 1fr; }
|
|
90
92
|
body.batch aside { display: none; }
|
|
91
93
|
main.batch { padding: 8px 10px 10px; }
|
|
92
94
|
main.batch #preview-title, main.batch #phone { display: none; }
|
|
93
95
|
main.batch #grid-title { display: flex; }
|
|
94
|
-
main.batch #grid { display:
|
|
95
|
-
.gcell { display: flex; flex-direction: column;
|
|
96
|
+
main.batch #grid { display: flex; }
|
|
97
|
+
.gcell { display: flex; flex-direction: column; flex: none; width: 360px; height: 50vh; min-height: 0; border: 1px solid var(--line); border-radius: 14px; background: var(--cell); overflow: hidden; }
|
|
96
98
|
.gcell.done { border-color: rgba(63,185,80,.6); }
|
|
97
99
|
.gcell.failed { border-color: rgba(255,92,92,.6); }
|
|
98
100
|
.ghead { flex: none; height: 30px; padding: 0 8px; display: flex; align-items: center; gap: 6px; font-size: 12px; border-bottom: 1px solid var(--line); background: var(--head); min-width: 0; }
|
|
99
101
|
.ghead .name { font-size: 12px; }
|
|
100
102
|
.ghead .st { margin-left: auto; }
|
|
101
103
|
.gcell.done .ghead .st { color: #3fb950; font-weight: 600; }
|
|
102
|
-
.gbody { flex: 1 1 auto; min-height: 0; background: #fff; }
|
|
104
|
+
.gbody { flex: 1 1 auto; min-height: 0; width: 100%; background: #fff; }
|
|
103
105
|
#grid-empty { grid-column: 1 / -1; display: grid; place-items: center; color: var(--muted); }
|
|
104
106
|
.spinner { width: 28px; height: 28px; border: 3px solid rgba(255,255,255,.12); border-top-color: var(--accent); border-radius: 50%; animation: spin .9s linear infinite; margin: 0 auto 12px; }
|
|
105
107
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
108
|
+
/* 机器 id 徽章:机器多了一眼认出这是哪台。列表/批量两种模式都显示。 */
|
|
109
|
+
.mid{display:inline-block;margin-right:8px;padding:2px 9px;border-radius:7px;font:700 13px ui-monospace,monospace;
|
|
110
|
+
background:#1f6feb;color:#fff;letter-spacing:.5px;vertical-align:middle;box-shadow:0 1px 4px rgba(0,0,0,.4)}
|
|
106
111
|
</style>
|
|
107
112
|
</head>
|
|
108
113
|
<body>
|
|
109
114
|
<aside>
|
|
110
115
|
<header>
|
|
111
|
-
<h1>Telegram 矩阵 <small id="count"></small></h1>
|
|
112
|
-
<div class="actions"><button class="ghost" id="batch-login" title="
|
|
116
|
+
<h1><span id="mid-list" class="mid" hidden></span>Telegram 矩阵 <small id="count"></small></h1>
|
|
117
|
+
<div class="actions"><button class="ghost" id="batch-login" title="平铺所有 Profile 的会话到网格(最多 10 个)">批量网格</button><button id="add-profile" title="新建一个独立会话(persist:sandbox-N)">+ 添加 Profile</button></div>
|
|
113
118
|
</header>
|
|
114
119
|
<div class="list">
|
|
115
120
|
<div id="rows"></div>
|
|
@@ -119,13 +124,13 @@
|
|
|
119
124
|
</aside>
|
|
120
125
|
<main>
|
|
121
126
|
<div id="grid-title">
|
|
122
|
-
<div class="left"><b>批量登录</b><span id="grid-count"></span></div>
|
|
123
|
-
<div class="actions"><button class="ghost" id="grid-reload" title="重新加载所有格子">全部重载</button><button
|
|
127
|
+
<div class="left"><span id="mid-batch" class="mid" hidden></span><b>批量登录</b><span id="grid-count"></span></div>
|
|
128
|
+
<div class="actions"><button class="ghost" id="grid-reload" title="重新加载所有格子">全部重载</button><button id="grid-add-profile" title="新建一个独立会话(persist:sandbox-N)">+ 添加 Profile</button></div>
|
|
124
129
|
</div>
|
|
125
130
|
<div id="grid"></div>
|
|
126
131
|
<div id="preview-title">
|
|
127
132
|
<div class="left"><span class="dot" id="sel-dot"></span><span id="selected-name">未选择 Profile</span><span id="status"></span></div>
|
|
128
|
-
<div class="actions"><button class="code" id="open-code" title="
|
|
133
|
+
<div class="actions"><button class="code" id="open-code" title="在格子上叠加/关闭接码 webview(同代理出口)">接码</button><button class="ghost" id="probe-ip" title="经该 Profile 的代理探测出口 IP / 地区">测 IP</button><button class="ghost" id="reload" title="重新加载 Telegram Web">重新加载</button></div>
|
|
129
134
|
</div>
|
|
130
135
|
<div id="phone">
|
|
131
136
|
<div id="phone-preview"></div>
|
|
@@ -169,6 +174,7 @@
|
|
|
169
174
|
let profiles = [];
|
|
170
175
|
let selected = null;
|
|
171
176
|
const openedProfiles = new Set(); // 打开过的会话保留在后台(登录态不丢),只有选中的贴到预览区
|
|
177
|
+
const codeOpen = new Set(); // 已叠开接码 overlay 的 accountIdx(同 partition 的接码 webview 覆盖在格子上)
|
|
172
178
|
const cellState = new Map(); // accountIdx -> { loading, failed, error, url, title }
|
|
173
179
|
const rows = new Map(); // accountIdx -> row element
|
|
174
180
|
const BATCH_MAX = 10; // 一次最多铺开多少个登录格
|
|
@@ -180,6 +186,14 @@
|
|
|
180
186
|
const gcells = new Map(); // accountIdx -> { el, body }
|
|
181
187
|
|
|
182
188
|
const esc = (value) => String(value || '').replace(/[&<>"']/g, (c) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
189
|
+
// 机器 id:app 打开面板时把本机 id 拼进 URL(?team=xs-3007)。两处徽章都填上。
|
|
190
|
+
(function(){ try {
|
|
191
|
+
const t = new URLSearchParams(location.search).get('team');
|
|
192
|
+
if (t) for (const id of ['mid-list','mid-batch']) {
|
|
193
|
+
const el = document.getElementById(id); if (el) { el.textContent = t; el.hidden = false; el.title = '本机: ' + t; }
|
|
194
|
+
}
|
|
195
|
+
if (t) document.title = t + ' · Telegram 矩阵';
|
|
196
|
+
} catch (e) {} })();
|
|
183
197
|
const cellId = (idx) => `telegram-preview-${idx}`;
|
|
184
198
|
const stateOf = (idx) => cellState.get(Number(idx)) || {};
|
|
185
199
|
const dotClass = (st) => st.loading ? 'loading' : st.failed ? 'failed' : (st.url ? 'ok' : '');
|
|
@@ -215,10 +229,22 @@
|
|
|
215
229
|
el.classList.toggle('none', !ph); el.textContent = ph; el.title = codeUrlOf(p) ? `接码:${codeUrlOf(p)}` : '';
|
|
216
230
|
const g = gcells.get(p.accountIdx); if (g) { g.el.querySelector('.phone').textContent = ph; g.el.querySelector('.code').disabled = !codeUrlOf(p); }
|
|
217
231
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
232
|
+
// 接码 = 在当前格子的 Telegram webview 上方叠一个同尺寸、同 partition(persist:sandbox-idx,
|
|
233
|
+
// 走该 profile 的代理出口)、更高 z 层的接码 webview。再点一次关闭。与 tg-autologin 自动化里
|
|
234
|
+
// 的 overlay 完全一致的呈现,只是重载/关闭放在格子 header(BrowserView 之上无法放页面按钮)。
|
|
235
|
+
function openCode(p) {
|
|
236
|
+
if (!codeUrlOf(p)) { flash(`#${p.accountIdx} 还没有接码 URL,点 ⚙ 填写`, 4000); return; }
|
|
237
|
+
if (!openedProfiles.has(p.accountIdx)) { openedProfiles.add(p.accountIdx); cellState.set(p.accountIdx, { loading: true }); }
|
|
238
|
+
if (codeOpen.has(p.accountIdx)) { codeOpen.delete(p.accountIdx); flash(`#${p.accountIdx} 已关闭接码`); }
|
|
239
|
+
else { codeOpen.add(p.accountIdx); flash(`#${p.accountIdx} 接码已叠加在格子上`); }
|
|
240
|
+
updateCodeButtons(p.accountIdx);
|
|
241
|
+
syncPreview();
|
|
242
|
+
}
|
|
243
|
+
function updateCodeButtons(idx) {
|
|
244
|
+
const on = codeOpen.has(idx);
|
|
245
|
+
const g = gcells.get(idx);
|
|
246
|
+
if (g) { const b = g.el.querySelector('.code'); if (b) { b.classList.toggle('on', on); b.textContent = on ? '关闭接码' : '接码'; } }
|
|
247
|
+
if (selected && selected.accountIdx === idx) { const b = document.getElementById('open-code'); if (b) { b.classList.toggle('on', on); b.textContent = on ? '关闭接码' : '接码'; } }
|
|
222
248
|
}
|
|
223
249
|
function renderIdentity(p) {
|
|
224
250
|
renderPhone(p);
|
|
@@ -246,15 +272,21 @@
|
|
|
246
272
|
if (!window.panelAPI) return;
|
|
247
273
|
const rect = preview.getBoundingClientRect();
|
|
248
274
|
const box = (r) => ({ x: r.x, y: r.y, w: r.width, h: r.height });
|
|
249
|
-
const cells = [
|
|
275
|
+
const cells = []; const overlays = [];
|
|
276
|
+
for (const profileId of openedProfiles) {
|
|
250
277
|
let visible = false, r = null;
|
|
251
278
|
if (batchMode) {
|
|
252
279
|
const g = gcells.get(profileId);
|
|
253
280
|
if (g && batch.has(profileId)) { visible = true; r = box(g.body.getBoundingClientRect()); }
|
|
254
281
|
} else if (selected && profileId === selected.accountIdx) { visible = true; r = box(rect); }
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
282
|
+
cells.push({ id: cellId(profileId), url: TELEGRAM_URL, profile: profileId, visible, rect: r });
|
|
283
|
+
// 接码叠加层:同 rect、同 profile(=同 partition/代理),排在 tg 格子之后 → attach 更晚 → 叠最上层
|
|
284
|
+
if (visible && codeOpen.has(profileId)) {
|
|
285
|
+
const p = profiles.find((x) => x.accountIdx === profileId); const cu = p && codeUrlOf(p);
|
|
286
|
+
if (cu) overlays.push({ id: 'code-' + profileId, url: cu, profile: profileId, visible: true, rect: r });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
try { window.panelAPI.sync(cells.concat(overlays)); } catch (e) {}
|
|
258
290
|
}
|
|
259
291
|
|
|
260
292
|
function describeError(p, st) {
|
|
@@ -272,6 +304,7 @@
|
|
|
272
304
|
if (selected && st.failed) errDetail.innerHTML = describeError(selected, st);
|
|
273
305
|
selDot.className = `dot ${selected ? dotClass(st) : ''}`;
|
|
274
306
|
if (selected && !statusEl.dataset.sticky) statusEl.textContent = stateText(st, true);
|
|
307
|
+
if (selected) updateCodeButtons(selected.accountIdx);
|
|
275
308
|
}
|
|
276
309
|
|
|
277
310
|
function renderRowState(idx) {
|
|
@@ -289,7 +322,7 @@
|
|
|
289
322
|
function buildGridCell(p) {
|
|
290
323
|
const el = document.createElement('div');
|
|
291
324
|
el.className = 'gcell'; el.dataset.id = String(p.accountIdx);
|
|
292
|
-
el.innerHTML = `<div class="ghead"><span class="dot"></span><span class="name">${esc(p.name)}</span><span class="chip">#${p.accountIdx}</span><span class="phone">${esc(phoneOf(p))}</span><span class="st"></span><button class="code" title="
|
|
325
|
+
el.innerHTML = `<div class="ghead"><span class="dot"></span><span class="name">${esc(p.name)}</span><span class="chip">#${p.accountIdx}</span><span class="phone">${esc(phoneOf(p))}</span><span class="st"></span><button class="code" title="在格子上叠加/关闭接码 webview(同代理出口)">接码</button><button class="icon reload" title="重新加载">⟳</button></div><div class="gbody"></div>`;
|
|
293
326
|
el.querySelector('.reload').onclick = () => reloadProfile(p.accountIdx);
|
|
294
327
|
el.querySelector('.code').onclick = () => openCode(p);
|
|
295
328
|
el.querySelector('.code').disabled = !codeUrlOf(p);
|
|
@@ -304,11 +337,13 @@
|
|
|
304
337
|
g.el.classList.toggle('failed', !!st.failed && !st.loading);
|
|
305
338
|
g.el.querySelector('.dot').className = `dot ${dotClass(st)}`;
|
|
306
339
|
g.el.querySelector('.st').textContent = done ? '✓ 已登录' : stateText(st, true);
|
|
340
|
+
updateCodeButtons(idx);
|
|
307
341
|
if (batchMode) { const n = [...batch].filter((i) => { const q = profiles.find((x) => x.accountIdx === i); return q && hasIdentity(q); }).length; gridCount.textContent = `${batch.size} 个会话 · ${n} 已登录`; }
|
|
308
342
|
}
|
|
309
343
|
function enterBatch() {
|
|
310
|
-
|
|
311
|
-
|
|
344
|
+
// 默认网格 = 平铺全部 profile 的会话(不再只挑未登录的);仍受 BATCH_MAX 上限保护。
|
|
345
|
+
const todo = profiles.slice(0, BATCH_MAX);
|
|
346
|
+
if (!todo.length) { flash('没有 Profile'); return; }
|
|
312
347
|
batch.clear(); gridEl.innerHTML = ''; gcells.clear();
|
|
313
348
|
for (const p of todo) {
|
|
314
349
|
batch.add(p.accountIdx);
|
|
@@ -319,17 +354,18 @@
|
|
|
319
354
|
gridEl.style.gridTemplateColumns = `repeat(${gridCols(todo.length)}, minmax(0, 1fr))`;
|
|
320
355
|
batchMode = true; mainEl.classList.add('batch'); document.body.classList.add('batch'); document.getElementById('batch-login').textContent = '退出批量';
|
|
321
356
|
render();
|
|
357
|
+
syncPreview(); // 后台 tab 里 rAF 不会触发,先直接同步一次把格子建出来
|
|
322
358
|
requestAnimationFrame(syncPreview);
|
|
323
359
|
setTimeout(pullStates, 300);
|
|
324
360
|
}
|
|
325
361
|
function exitBatch() {
|
|
326
362
|
if (!batchMode) return;
|
|
327
|
-
batchMode = false; mainEl.classList.remove('batch'); document.body.classList.remove('batch'); document.getElementById('batch-login').textContent = '
|
|
363
|
+
batchMode = false; mainEl.classList.remove('batch'); document.body.classList.remove('batch'); document.getElementById('batch-login').textContent = '批量网格';
|
|
328
364
|
batch.clear(); gcells.clear(); gridEl.innerHTML = '';
|
|
329
365
|
render(); renderPreviewState(); syncPreview(); // 会话留在后台,登录态不丢
|
|
330
366
|
}
|
|
331
367
|
document.getElementById('batch-login').onclick = () => (batchMode ? exitBatch() : enterBatch());
|
|
332
|
-
document.getElementById('grid-
|
|
368
|
+
document.getElementById('grid-add-profile').onclick = () => document.getElementById('add-profile').click();
|
|
333
369
|
document.getElementById('grid-reload').onclick = () => { for (const idx of batch) reloadProfile(idx); };
|
|
334
370
|
new ResizeObserver(() => requestAnimationFrame(syncPreview)).observe(gridEl);
|
|
335
371
|
|
|
@@ -347,6 +383,8 @@
|
|
|
347
383
|
}
|
|
348
384
|
|
|
349
385
|
function reloadProfile(idx) {
|
|
386
|
+
// 接码 overlay 开着时,重载指向接码 webview(用户看到的最上层就是它)
|
|
387
|
+
if (codeOpen.has(idx)) { try { window.panelAPI.reload('code-' + idx); } catch (e) {} return; }
|
|
350
388
|
const fresh = !openedProfiles.has(idx);
|
|
351
389
|
openedProfiles.add(idx);
|
|
352
390
|
cellState.set(idx, { loading: true }); renderRowState(idx);
|
|
@@ -511,11 +549,47 @@
|
|
|
511
549
|
|
|
512
550
|
new ResizeObserver(() => requestAnimationFrame(syncPreview)).observe(preview);
|
|
513
551
|
window.addEventListener('resize', syncPreview);
|
|
552
|
+
document.addEventListener('visibilitychange', () => { if (!document.hidden) { syncPreview(); setTimeout(syncPreview, 500); setTimeout(pullStates, 800); } });
|
|
514
553
|
try { window.panelAPI.onCellState(applyCellState); } catch (e) {}
|
|
515
554
|
pullStates({ adopt: true })
|
|
516
555
|
.catch(() => {})
|
|
517
556
|
.then(() => loadProfiles())
|
|
557
|
+
// 打开面板即默认进批量网格:把所有 profile 的 webview 平铺出来。
|
|
558
|
+
// 崩溃循环保护:若上次开机自动批量后没在几秒内清除标记(说明渲染进程崩溃重载了),
|
|
559
|
+
// 这次跳过自动批量、停在列表,避免弱机反复「崩溃→自动批量→再崩」的刷新循环。
|
|
560
|
+
.then(() => {
|
|
561
|
+
try {
|
|
562
|
+
const K = 'tg-matrix-autobatch-pending';
|
|
563
|
+
let prev = null; try { prev = localStorage.getItem(K); } catch (e) {}
|
|
564
|
+
if (prev && (Date.now() - Number(prev)) < 20000) {
|
|
565
|
+
try { localStorage.removeItem(K); } catch (e) {}
|
|
566
|
+
flash('上次批量疑似导致崩溃,已跳过自动批量。点「批量网格」可手动打开。', 6000);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
try { localStorage.setItem(K, String(Date.now())); } catch (e) {}
|
|
570
|
+
enterBatch();
|
|
571
|
+
setTimeout(() => { try { localStorage.removeItem(K); } catch (e) {} }, 8000);
|
|
572
|
+
} catch (e) {}
|
|
573
|
+
})
|
|
518
574
|
.catch((e) => { flash(e.message || String(e), 8000); render(); });
|
|
575
|
+
|
|
576
|
+
// 自动更新:构建时给本页打了内容版本号(<meta name="pv">)。每 60s 拉一次自身
|
|
577
|
+
// (同源 cicyui://panel,无跨域;cicyui handler 每次都从远端取最新),版本变了就 reload。
|
|
578
|
+
// 这样服务端一部署,所有开着的面板自己就切到新版,不用手动逐台重载。DEV 态不轮询。
|
|
579
|
+
(function autoUpdate() {
|
|
580
|
+
const meta = document.querySelector('meta[name="pv"]');
|
|
581
|
+
const PV = meta ? meta.content : '';
|
|
582
|
+
if (!PV || PV === 'DEV') return;
|
|
583
|
+
setInterval(async () => {
|
|
584
|
+
try {
|
|
585
|
+
const r = await fetch(location.href, { cache: 'no-store' });
|
|
586
|
+
if (!r.ok) return;
|
|
587
|
+
const t = await r.text();
|
|
588
|
+
const mm = t.match(/<meta name="pv" content="([^"]*)">/);
|
|
589
|
+
if (mm && mm[1] && mm[1] !== PV) location.reload();
|
|
590
|
+
} catch (e) {}
|
|
591
|
+
}, 60000);
|
|
592
|
+
})();
|
|
519
593
|
</script>
|
|
520
594
|
</body>
|
|
521
595
|
</html>
|