@chatcode/cco-better-sidebar 0.18.0-rc.4 → 0.18.0-rc.5
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.md +14 -0
- package/README_EN.md +14 -0
- package/lib/client-editor.js +271 -260
- package/lib/client-mermaid.js +271 -260
- package/lib/client-registry.js +659 -509
- package/lib/client-terminal.js +271 -260
- package/lib/client.js +653 -503
- package/lib/index.js +115 -4
- package/lib/types/client/LogoutConfirmDialog.d.ts +5 -0
- package/lib/types/client/locales.d.ts +2 -0
- package/lib/types/client/ui-tweaks/branding.d.ts +1 -1
- package/lib/types/client/ui-tweaks/sidebar-header.d.ts +6 -0
- package/lib/types/login-page-script.d.ts +2 -0
- package/lib/types/login-route.d.ts +2 -0
- package/package.json +1 -1
- package/src/client/AccountMenu.tsx +12 -2
- package/src/client/LogoutConfirmDialog.tsx +39 -0
- package/src/client/locales-ja.ts +2 -0
- package/src/client/locales.ts +6 -0
- package/src/client/sidebar.module.css +81 -19
- package/src/client/ui-tweaks/branding.tsx +10 -9
- package/src/client/ui-tweaks/sidebar-header.ts +73 -0
- package/src/login-page-script.ts +107 -0
- package/src/login-route.ts +7 -4
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** Standalone sign-in recovery; each document starts once and always keeps a usable reopen action. */
|
|
2
|
+
export const CHATCODE_LOGIN_PAGE_SCRIPT = String.raw`
|
|
3
|
+
(() => {
|
|
4
|
+
const message = document.getElementById('message');
|
|
5
|
+
const button = document.getElementById('login');
|
|
6
|
+
const fallback = document.getElementById('login-fallback');
|
|
7
|
+
const link = document.getElementById('login-link');
|
|
8
|
+
const current = new URL(location.href);
|
|
9
|
+
const returnTo = (() => {
|
|
10
|
+
const value = current.searchParams.get('return');
|
|
11
|
+
if (!value || !value.startsWith('/') || value.startsWith('//')) return '/';
|
|
12
|
+
const target = new URL(value, location.origin);
|
|
13
|
+
return target.origin === location.origin ? target.pathname + target.search + target.hash : '/';
|
|
14
|
+
})();
|
|
15
|
+
let busy = false, checking = false, attempted = false, recheck = false, attempt, popup;
|
|
16
|
+
const call = async name => {
|
|
17
|
+
const response = await fetch('/sidebar/api/' + name, {
|
|
18
|
+
method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}',
|
|
19
|
+
});
|
|
20
|
+
const data = await response.json();
|
|
21
|
+
if (!response.ok || !data.ok) throw new Error('操作失败');
|
|
22
|
+
return data.value;
|
|
23
|
+
};
|
|
24
|
+
const showMessage = (text, error = false) => {
|
|
25
|
+
message.textContent = text;
|
|
26
|
+
message.classList.toggle('error', error);
|
|
27
|
+
};
|
|
28
|
+
async function login() {
|
|
29
|
+
if (busy) return;
|
|
30
|
+
attempted = true;
|
|
31
|
+
busy = true;
|
|
32
|
+
button.disabled = true;
|
|
33
|
+
button.textContent = '正在打开登录页…';
|
|
34
|
+
let tab = null;
|
|
35
|
+
try { tab = window.open('', '_blank'); if (tab) tab.opener = null; } catch {}
|
|
36
|
+
try {
|
|
37
|
+
if (!attempt) {
|
|
38
|
+
const started = await call('chatcode.start');
|
|
39
|
+
const url = new URL(started.url);
|
|
40
|
+
if (!['http:', 'https:'].includes(url.protocol) || typeof started.sessionId !== 'string') throw new Error('登录地址无效');
|
|
41
|
+
attempt = { sessionId: started.sessionId, url: url.href };
|
|
42
|
+
}
|
|
43
|
+
link.href = attempt.url;
|
|
44
|
+
link.textContent = attempt.url;
|
|
45
|
+
fallback.hidden = false;
|
|
46
|
+
if (tab && !tab.closed) {
|
|
47
|
+
try { tab.location.href = attempt.url; popup = tab; } catch { try { tab.close(); } catch {} }
|
|
48
|
+
}
|
|
49
|
+
showMessage('请在登录页完成登录;若网页已关闭,可随时重新打开。');
|
|
50
|
+
} catch {
|
|
51
|
+
try { if (tab) tab.close(); } catch {}
|
|
52
|
+
showMessage('登录页打开失败,请重试。', true);
|
|
53
|
+
} finally {
|
|
54
|
+
busy = false;
|
|
55
|
+
button.disabled = false;
|
|
56
|
+
button.textContent = attempt ? '重新打开登录页' : '去登录';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function status() {
|
|
60
|
+
if (checking || busy) return;
|
|
61
|
+
checking = true;
|
|
62
|
+
const observedAttempt = attempt, observedStarted = attempted;
|
|
63
|
+
try {
|
|
64
|
+
const state = await call('chatcode.status');
|
|
65
|
+
if (busy || attempt !== observedAttempt || attempted !== observedStarted) return;
|
|
66
|
+
if (state.available && state.validation === 'valid' && !state.expired) {
|
|
67
|
+
try { if (popup) popup.close(); window.focus(); } catch {}
|
|
68
|
+
location.replace(returnTo);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!state.available) {
|
|
72
|
+
recheck = true;
|
|
73
|
+
button.textContent = '重新检测';
|
|
74
|
+
showMessage('登录服务暂不可用,请稍后重试。', true);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (state.configured && state.validation === 'unavailable') {
|
|
78
|
+
recheck = true;
|
|
79
|
+
button.textContent = '重新检测';
|
|
80
|
+
showMessage('暂时无法验证已有账号,请检查网络后重新检测。', true);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
recheck = false;
|
|
84
|
+
button.textContent = attempt ? '重新打开登录页' : '去登录';
|
|
85
|
+
if (attempt && (state.login?.sessionId !== attempt.sessionId || state.login.state !== 'pending')) {
|
|
86
|
+
attempt = undefined;
|
|
87
|
+
fallback.hidden = true;
|
|
88
|
+
link.removeAttribute('href');
|
|
89
|
+
link.textContent = '';
|
|
90
|
+
button.textContent = '去登录';
|
|
91
|
+
showMessage('登录请求已失效,请重新登录。');
|
|
92
|
+
}
|
|
93
|
+
if (!attempted) await login();
|
|
94
|
+
} catch {
|
|
95
|
+
recheck = true;
|
|
96
|
+
button.textContent = '重新检测';
|
|
97
|
+
showMessage('无法读取登录状态,请稍后重试。', true);
|
|
98
|
+
} finally {
|
|
99
|
+
checking = false;
|
|
100
|
+
if (!busy) button.disabled = false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
button.addEventListener('click', () => { void (recheck ? status() : login()); });
|
|
104
|
+
void status();
|
|
105
|
+
setInterval(() => { void status(); }, 2000);
|
|
106
|
+
})();
|
|
107
|
+
`
|
package/src/login-route.ts
CHANGED
|
@@ -2,14 +2,17 @@ import { readFile } from 'node:fs/promises'
|
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
import type { Context, SidebarHttpRequest, SidebarHttpResponse } from './context-types.ts'
|
|
5
|
+
import { CHATCODE_LOGIN_PAGE_SCRIPT } from './login-page-script.ts'
|
|
5
6
|
|
|
6
7
|
const assetPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'assets', 'logo.svg')
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
/** Render the standalone login gate with one automatic attempt and persistent manual recovery. */
|
|
10
|
+
export function renderChatcodeLoginPage(logo: string): string {
|
|
9
11
|
return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>请登录 ChatCode</title><style>
|
|
10
12
|
*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh;display:grid;place-items:center;font-family:Inter,"Microsoft YaHei",system-ui,sans-serif;color:#172b4d;background:radial-gradient(circle at 18% 18%,#e5edff 0,transparent 30rem),linear-gradient(135deg,#edf2fb,#dfe8f7)}.card{width:min(490px,calc(100vw - 40px));padding:34px 44px 36px;border:1px solid #dce4ef;border-radius:18px;background:#fff;box-shadow:0 20px 48px rgba(36,62,103,.18),0 2px 8px rgba(36,62,103,.06)}.brand{display:flex;align-items:center;justify-content:center;gap:12px;padding-bottom:27px;border-bottom:1px solid #edf0f5}.brand img{width:34px;height:34px}.brand strong{font-size:22px;letter-spacing:-.02em}.content{padding-top:40px;text-align:center}.content h1{margin:0;font-size:28px;line-height:1.3;font-weight:650}.content p{min-height:24px;margin:12px 0 30px;color:#71809a;font-size:14px;line-height:1.7}.action{width:100%;min-height:46px;border:0;border-radius:8px;background:#9B0B09;color:#fff;font:600 15px/1 Inter,"Microsoft YaHei",sans-serif;cursor:pointer;box-shadow:0 8px 18px rgba(155,11,9,.25);transition:transform .18s ease,box-shadow .18s ease,background .18s ease}.action:hover:not(:disabled){transform:translateY(-2px);background:#b20d0a;box-shadow:0 12px 24px rgba(155,11,9,.38)}.action:focus-visible{outline:3px solid #f0b7b5;outline-offset:3px}.action:disabled{cursor:default;opacity:.7}.error{color:#b42318}@media(max-width:480px){.card{padding:30px 26px}.content{padding-top:32px}}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
.login-fallback{margin:18px 0 0;color:#71809a;font-size:12px;line-height:1.7;overflow-wrap:anywhere}.login-fallback a{display:block;margin-top:4px;color:#315da8;text-underline-offset:3px}
|
|
14
|
+
</style></head><body><main class="card"><div class="brand"><img src="data:image/svg+xml;base64,${logo}" alt=""><strong>ChatCode</strong></div><section class="content"><h1>请登录 ChatCode</h1><p id="message" aria-live="polite">正在检查登录状态…</p><button id="login" class="action" type="button" disabled>正在检查登录状态…</button><div id="login-fallback" class="login-fallback" hidden>若登录网页已关闭或未弹出,可再次访问:<a id="login-link" target="_blank" rel="noopener noreferrer"></a></div></section></main><script>
|
|
15
|
+
${CHATCODE_LOGIN_PAGE_SCRIPT}
|
|
13
16
|
</script></body></html>`
|
|
14
17
|
}
|
|
15
18
|
|
|
@@ -20,7 +23,7 @@ export function registerLoginRoute(ctx: Context, fence: (req: SidebarHttpRequest
|
|
|
20
23
|
try {
|
|
21
24
|
const logo = (await readFile(assetPath)).toString('base64')
|
|
22
25
|
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'content-security-policy': "default-src 'self'; img-src 'self' data:; style-src 'unsafe-inline'; script-src 'unsafe-inline'" })
|
|
23
|
-
res.end(req.method === 'HEAD' ? undefined :
|
|
26
|
+
res.end(req.method === 'HEAD' ? undefined : renderChatcodeLoginPage(logo))
|
|
24
27
|
} catch { res.writeHead(500); res.end('login page unavailable') }
|
|
25
28
|
} })
|
|
26
29
|
}
|