@juspay/shooter 1.19.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/client/_app/immutable/assets/11.F10lvwyh.css +1 -0
- package/build/client/_app/immutable/assets/11.F10lvwyh.css.br +0 -0
- package/build/client/_app/immutable/assets/11.F10lvwyh.css.gz +0 -0
- package/build/client/_app/immutable/chunks/C_YNQL8b.js +3 -0
- package/build/client/_app/immutable/chunks/C_YNQL8b.js.br +0 -0
- package/build/client/_app/immutable/chunks/C_YNQL8b.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DA4Zt9Me.js → DIZ3Qst5.js} +1 -1
- package/build/client/_app/immutable/chunks/DIZ3Qst5.js.br +0 -0
- package/build/client/_app/immutable/chunks/{DA4Zt9Me.js.gz → DIZ3Qst5.js.gz} +0 -0
- package/build/client/_app/immutable/chunks/{DCDL_9ys.js → DT4H19pV.js} +1 -1
- package/build/client/_app/immutable/chunks/DT4H19pV.js.br +0 -0
- package/build/client/_app/immutable/chunks/DT4H19pV.js.gz +0 -0
- package/build/client/_app/immutable/chunks/J5-Cr5oR.js +6 -0
- package/build/client/_app/immutable/chunks/J5-Cr5oR.js.br +0 -0
- package/build/client/_app/immutable/chunks/J5-Cr5oR.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.D4TXlu7A.js → app.Bd-DfeJi.js} +2 -2
- package/build/client/_app/immutable/entry/app.Bd-DfeJi.js.br +0 -0
- package/build/client/_app/immutable/entry/app.Bd-DfeJi.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.evvp4tX7.js +1 -0
- package/build/client/_app/immutable/entry/start.evvp4tX7.js.br +2 -0
- package/build/client/_app/immutable/entry/start.evvp4tX7.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{0.1zylwAPT.js → 0.Bl-1LQWM.js} +1 -1
- package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.BVnLUSs-.js → 1.DT4dq6Ay.js} +1 -1
- package/build/client/_app/immutable/nodes/1.DT4dq6Ay.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.DT4dq6Ay.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{10.D1wl2wPX.js → 10.CF7RGXpe.js} +1 -1
- package/build/client/_app/immutable/nodes/10.CF7RGXpe.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.CF7RGXpe.js.gz +0 -0
- package/build/client/_app/immutable/nodes/11.BV_G7yLI.js +2 -0
- package/build/client/_app/immutable/nodes/11.BV_G7yLI.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.BV_G7yLI.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{2.D1Mm0DUX.js → 2.DcRhsjYp.js} +1 -1
- package/build/client/_app/immutable/nodes/2.DcRhsjYp.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DcRhsjYp.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{3.Wfz3TcJd.js → 3.0MMe3oxR.js} +1 -1
- package/build/client/_app/immutable/nodes/3.0MMe3oxR.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.0MMe3oxR.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{6.DtZAEPXb.js → 6.ComiWlV6.js} +1 -1
- package/build/client/_app/immutable/nodes/6.ComiWlV6.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.ComiWlV6.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{7.MfBRh32I.js → 7.vkPx1kVP.js} +1 -1
- package/build/client/_app/immutable/nodes/7.vkPx1kVP.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.vkPx1kVP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{8.DVE6LnOC.js → 8.Bmr3sWbS.js} +1 -1
- package/build/client/_app/immutable/nodes/8.Bmr3sWbS.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.Bmr3sWbS.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{9.BCel5OqI.js → 9.CAJucyeI.js} +1 -1
- package/build/client/_app/immutable/nodes/9.CAJucyeI.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.CAJucyeI.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/server/chunks/{0-DJqyZZTr.js → 0-DDGB6CRT.js} +2 -2
- package/build/server/chunks/{0-DJqyZZTr.js.map → 0-DDGB6CRT.js.map} +1 -1
- package/build/server/chunks/{1-2YUVen1F.js → 1-DEjonQXD.js} +2 -2
- package/build/server/chunks/{1-2YUVen1F.js.map → 1-DEjonQXD.js.map} +1 -1
- package/build/server/chunks/{10-D1X7LB3v.js → 10-BK1kiiiw.js} +2 -2
- package/build/server/chunks/{10-D1X7LB3v.js.map → 10-BK1kiiiw.js.map} +1 -1
- package/build/server/chunks/{11-qXSPdF5j.js → 11-CJPjkEF3.js} +4 -4
- package/build/server/chunks/11-CJPjkEF3.js.map +1 -0
- package/build/server/chunks/{2-BD7kj1mt.js → 2-RLnhlWh5.js} +2 -2
- package/build/server/chunks/{2-BD7kj1mt.js.map → 2-RLnhlWh5.js.map} +1 -1
- package/build/server/chunks/{3-oNjv-BhZ.js → 3-Dd4pJBqZ.js} +2 -2
- package/build/server/chunks/{3-oNjv-BhZ.js.map → 3-Dd4pJBqZ.js.map} +1 -1
- package/build/server/chunks/{6-DRJGUqHG.js → 6-DdRMnKNa.js} +2 -2
- package/build/server/chunks/{6-DRJGUqHG.js.map → 6-DdRMnKNa.js.map} +1 -1
- package/build/server/chunks/{7-_giJiu0L.js → 7-vLOMMetm.js} +2 -2
- package/build/server/chunks/{7-_giJiu0L.js.map → 7-vLOMMetm.js.map} +1 -1
- package/build/server/chunks/{8-zvWAVNT5.js → 8-rJyiQLFs.js} +2 -2
- package/build/server/chunks/{8-zvWAVNT5.js.map → 8-rJyiQLFs.js.map} +1 -1
- package/build/server/chunks/{9-DVyDL445.js → 9-CVSNNYED.js} +2 -2
- package/build/server/chunks/{9-DVyDL445.js.map → 9-CVSNNYED.js.map} +1 -1
- package/build/server/chunks/Banner-BgaAs1rs.js.map +1 -1
- package/build/server/chunks/Button-D0hZ7JYt.js.map +1 -1
- package/build/server/chunks/Icon-D0GBnDcs.js.map +1 -1
- package/build/server/chunks/Input-OmIiydSx.js.map +1 -1
- package/build/server/chunks/Pill-4xJ-VhAA.js.map +1 -1
- package/build/server/chunks/Shimmer-Dw2uvTC1.js.map +1 -1
- package/build/server/chunks/_error.svelte-CZnkxeLr.js.map +1 -1
- package/build/server/chunks/_page.svelte-BLo2v_8E.js.map +1 -1
- package/build/server/chunks/_page.svelte-BTlfUsBp.js.map +1 -1
- package/build/server/chunks/_page.svelte-BX2FMgSg.js.map +1 -1
- package/build/server/chunks/_page.svelte-C7B0qdrC.js.map +1 -1
- package/build/server/chunks/_page.svelte-CE7COWnF.js.map +1 -1
- package/build/server/chunks/_page.svelte-CWsjjd4l.js.map +1 -1
- package/build/server/chunks/_page.svelte-D5S2hkBk.js.map +1 -1
- package/build/server/chunks/_page.svelte-D_Ey8QRG.js.map +1 -1
- package/build/server/chunks/{_page.svelte-BUBLUSGo.js → _page.svelte-dabsQl9c.js} +206 -5
- package/build/server/chunks/_page.svelte-dabsQl9c.js.map +1 -0
- package/build/server/chunks/_page.svelte-tBuIq8Pg.js.map +1 -1
- package/build/server/chunks/{_server.ts-C_OOUqsd.js → _server.ts-AnBXfZXh.js} +2 -2
- package/build/server/chunks/{_server.ts-C_OOUqsd.js.map → _server.ts-AnBXfZXh.js.map} +1 -1
- package/build/server/chunks/_server.ts-B-evHL2q.js +13 -0
- package/build/server/chunks/_server.ts-B-evHL2q.js.map +1 -0
- package/build/server/chunks/_server.ts-B2wIgsW4.js +95 -0
- package/build/server/chunks/_server.ts-B2wIgsW4.js.map +1 -0
- package/build/server/chunks/{_server.ts-Bi0Oe4PF.js → _server.ts-CJGyN8mw.js} +14 -9
- package/build/server/chunks/_server.ts-CJGyN8mw.js.map +1 -0
- package/build/server/chunks/{_server.ts-DhJx0DLr.js → _server.ts-DEx9-epI.js} +16 -7
- package/build/server/chunks/_server.ts-DEx9-epI.js.map +1 -0
- package/build/server/chunks/{_server.ts-DxT9IlZF.js → _server.ts-DKNIsQeH.js} +3 -3
- package/build/server/chunks/{_server.ts-DxT9IlZF.js.map → _server.ts-DKNIsQeH.js.map} +1 -1
- package/build/server/chunks/_server.ts-DpRr0Tfh.js +68 -0
- package/build/server/chunks/_server.ts-DpRr0Tfh.js.map +1 -0
- package/build/server/chunks/{_server.ts-CRVNEOd2.js → _server.ts-Dz9Jd9Jh.js} +3 -3
- package/build/server/chunks/{_server.ts-CRVNEOd2.js.map → _server.ts-Dz9Jd9Jh.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Bjbr7glm.js → _server.ts-QN-Bo5ql.js} +12 -5
- package/build/server/chunks/_server.ts-QN-Bo5ql.js.map +1 -0
- package/build/server/chunks/{_server.ts-BrqaMMAa.js → _server.ts-W6i3EnGX.js} +29 -6
- package/build/server/chunks/_server.ts-W6i3EnGX.js.map +1 -0
- package/build/server/chunks/{_server.ts-DMm0hBP4.js → _server.ts-bk_EeAdY.js} +2 -2
- package/build/server/chunks/{_server.ts-DMm0hBP4.js.map → _server.ts-bk_EeAdY.js.map} +1 -1
- package/build/server/chunks/cache-BlMaDsHi.js.map +1 -1
- package/build/server/chunks/guest-registry-t0-7Zv5q.js +39 -0
- package/build/server/chunks/guest-registry-t0-7Zv5q.js.map +1 -0
- package/build/server/chunks/index-CoYB03g7.js.map +1 -1
- package/build/server/chunks/index2-dSGQ9Eaa.js.map +1 -1
- package/build/server/chunks/{pty-manager-41h3IK8K.js → pty-manager-CkZNoW1t.js} +6 -2
- package/build/server/chunks/pty-manager-CkZNoW1t.js.map +1 -0
- package/build/server/chunks/root-D4IoFC8F.js.map +1 -1
- package/build/server/chunks/share-auth-BS7JuiHf.js +27 -0
- package/build/server/chunks/share-auth-BS7JuiHf.js.map +1 -0
- package/build/server/chunks/share-store-B9jMpVg0.js +127 -0
- package/build/server/chunks/share-store-B9jMpVg0.js.map +1 -0
- package/build/server/chunks/state.svelte-CmHqngc_.js.map +1 -1
- package/build/server/chunks/stores-CRYxfF0o.js.map +1 -1
- package/build/server/index.js +1 -1
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +40 -19
- package/build/server/manifest.js.map +1 -1
- package/package.json +2 -2
- package/server.ts +8 -3
- package/src/lib/modules/client/terminal/ShareGate.svelte +96 -0
- package/src/lib/modules/client/terminal/ShareSheet.svelte +395 -0
- package/src/lib/modules/client/terminal/xterm-wrapper.ts +19 -2
- package/src/lib/modules/server/terminal/pty-manager.ts +6 -0
- package/src/lib/modules/server/terminal/share-auth.ts +37 -0
- package/src/lib/modules/server/terminal/share-store.ts +172 -0
- package/src/lib/modules/server/ws/guest-registry.ts +49 -0
- package/src/lib/modules/server/ws/server.ts +22 -3
- package/src/lib/modules/server/ws/session-handler.ts +18 -4
- package/src/lib/modules/server/ws/terminal-handler.ts +21 -2
- package/src/lib/modules/server/ws/ticket-store.ts +18 -10
- package/src/lib/types/generated/Client.ts +25 -1
- package/src/lib/types/generated/Share.ts +404 -0
- package/src/lib/types/generated/WsProtocol.ts +73 -2
- package/src/lib/types/generated/index.ts +1 -0
- package/src/lib/types/terminal-client.ts +19 -2
- package/src/lib/types/ws.ts +1 -0
- package/src/routes/api/terminals/[id]/+server.ts +14 -3
- package/src/routes/api/terminals/[id]/paste-image/+server.ts +8 -4
- package/src/routes/api/terminals/[id]/resize/+server.ts +8 -4
- package/src/routes/api/terminals/[id]/share/+server.ts +98 -0
- package/src/routes/api/terminals/[id]/share/auth/+server.ts +81 -0
- package/src/routes/api/terminals/[id]/share/status/+server.ts +11 -0
- package/src/routes/api/ws-ticket/+server.ts +26 -5
- package/src/routes/terminals/[id]/+page.svelte +184 -43
- package/build/client/_app/immutable/assets/11.v5KA95xm.css +0 -1
- package/build/client/_app/immutable/assets/11.v5KA95xm.css.br +0 -0
- package/build/client/_app/immutable/assets/11.v5KA95xm.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BcqA7eKM.js +0 -3
- package/build/client/_app/immutable/chunks/BcqA7eKM.js.br +0 -0
- package/build/client/_app/immutable/chunks/BcqA7eKM.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CR6bkGJW.js +0 -6
- package/build/client/_app/immutable/chunks/CR6bkGJW.js.br +0 -0
- package/build/client/_app/immutable/chunks/CR6bkGJW.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DA4Zt9Me.js.br +0 -0
- package/build/client/_app/immutable/chunks/DCDL_9ys.js.br +0 -0
- package/build/client/_app/immutable/chunks/DCDL_9ys.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.D4TXlu7A.js.br +0 -0
- package/build/client/_app/immutable/entry/app.D4TXlu7A.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BBQhtURO.js +0 -1
- package/build/client/_app/immutable/entry/start.BBQhtURO.js.br +0 -0
- package/build/client/_app/immutable/entry/start.BBQhtURO.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.1zylwAPT.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.1zylwAPT.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.BVnLUSs-.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.BVnLUSs-.js.gz +0 -0
- package/build/client/_app/immutable/nodes/10.D1wl2wPX.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.D1wl2wPX.js.gz +0 -0
- package/build/client/_app/immutable/nodes/11.C18nMGmp.js +0 -2
- package/build/client/_app/immutable/nodes/11.C18nMGmp.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.C18nMGmp.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.D1Mm0DUX.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.D1Mm0DUX.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.Wfz3TcJd.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.Wfz3TcJd.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.DtZAEPXb.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.DtZAEPXb.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.MfBRh32I.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.MfBRh32I.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.DVE6LnOC.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.DVE6LnOC.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.BCel5OqI.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.BCel5OqI.js.gz +0 -0
- package/build/server/chunks/11-qXSPdF5j.js.map +0 -1
- package/build/server/chunks/_page.svelte-BUBLUSGo.js.map +0 -1
- package/build/server/chunks/_server.ts-Bi0Oe4PF.js.map +0 -1
- package/build/server/chunks/_server.ts-Bjbr7glm.js.map +0 -1
- package/build/server/chunks/_server.ts-BrqaMMAa.js.map +0 -1
- package/build/server/chunks/_server.ts-DhJx0DLr.js.map +0 -1
- package/build/server/chunks/events-handler-Dm1mNPQP.js +0 -20
- package/build/server/chunks/events-handler-Dm1mNPQP.js.map +0 -1
- package/build/server/chunks/pty-manager-41h3IK8K.js.map +0 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// /api/terminals/[id]/share — owner management of a terminal's share.
|
|
2
|
+
// GET: current state. PUT: create/update. DELETE: revoke.
|
|
3
|
+
// All methods require the API key (owners only).
|
|
4
|
+
|
|
5
|
+
import type { ShareConfigRequest, ShareInfoResponse, ShareMode } from '$lib/types';
|
|
6
|
+
|
|
7
|
+
import { validateAuth } from '$lib/modules/server/auth';
|
|
8
|
+
import { ptyManager } from '$lib/modules/server/terminal/pty-manager.js';
|
|
9
|
+
import { hashPassword, shareStore } from '$lib/modules/server/terminal/share-store';
|
|
10
|
+
import { closeGuests } from '$lib/modules/server/ws/guest-registry';
|
|
11
|
+
import { json } from '@sveltejs/kit';
|
|
12
|
+
|
|
13
|
+
import type { RequestHandler } from './$types';
|
|
14
|
+
|
|
15
|
+
const MIN_PASSWORD_LENGTH = 6;
|
|
16
|
+
const MODES: ShareMode[] = ['view', 'control'];
|
|
17
|
+
|
|
18
|
+
function toInfo(terminalId: string): ShareInfoResponse {
|
|
19
|
+
const share = shareStore.getShare(terminalId);
|
|
20
|
+
if (!share) {
|
|
21
|
+
return { active: false, createdAt: null, mode: null, updatedAt: null };
|
|
22
|
+
}
|
|
23
|
+
return { active: true, createdAt: share.createdAt, mode: share.mode, updatedAt: share.updatedAt };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const GET: RequestHandler = ({ params, request }) => {
|
|
27
|
+
const authError = validateAuth(request);
|
|
28
|
+
if (authError) {
|
|
29
|
+
return authError;
|
|
30
|
+
}
|
|
31
|
+
return json(toInfo(params.id));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const PUT: RequestHandler = async ({ params, request }) => {
|
|
35
|
+
const authError = validateAuth(request);
|
|
36
|
+
if (authError) {
|
|
37
|
+
return authError;
|
|
38
|
+
}
|
|
39
|
+
if (!ptyManager.get(params.id)) {
|
|
40
|
+
return json({ error: 'Terminal not found' }, { status: 404 });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let body: ShareConfigRequest;
|
|
44
|
+
try {
|
|
45
|
+
body = (await request.json()) as ShareConfigRequest;
|
|
46
|
+
} catch {
|
|
47
|
+
return json({ error: 'Invalid JSON' }, { status: 400 });
|
|
48
|
+
}
|
|
49
|
+
if (!MODES.includes(body.mode)) {
|
|
50
|
+
return json({ error: "mode must be 'view' or 'control'" }, { status: 400 });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const existing = shareStore.getShare(params.id);
|
|
54
|
+
const password = typeof body.password === 'string' ? body.password : '';
|
|
55
|
+
if (!existing && password.length < MIN_PASSWORD_LENGTH) {
|
|
56
|
+
return json(
|
|
57
|
+
{ error: `password is required (min ${String(MIN_PASSWORD_LENGTH)} chars)` },
|
|
58
|
+
{ status: 400 }
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (password && password.length < MIN_PASSWORD_LENGTH) {
|
|
62
|
+
return json(
|
|
63
|
+
{ error: `password must be at least ${String(MIN_PASSWORD_LENGTH)} chars` },
|
|
64
|
+
{ status: 400 }
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
shareStore.setShare({
|
|
70
|
+
createdAt: existing?.createdAt ?? now,
|
|
71
|
+
mode: body.mode,
|
|
72
|
+
// `existing` is guaranteed non-null when password is empty (validated above).
|
|
73
|
+
passwordHash: password ? hashPassword(password) : (existing?.passwordHash ?? ''),
|
|
74
|
+
terminalId: params.id,
|
|
75
|
+
updatedAt: now,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// A new password invalidates existing guest sessions; any change to the
|
|
79
|
+
// share forces connected guests to reconnect under the new scope.
|
|
80
|
+
if (password) {
|
|
81
|
+
shareStore.deleteSessions(params.id);
|
|
82
|
+
}
|
|
83
|
+
if (password || existing?.mode !== body.mode) {
|
|
84
|
+
closeGuests(params.id);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return json(toInfo(params.id));
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const DELETE: RequestHandler = ({ params, request }) => {
|
|
91
|
+
const authError = validateAuth(request);
|
|
92
|
+
if (authError) {
|
|
93
|
+
return authError;
|
|
94
|
+
}
|
|
95
|
+
shareStore.deleteShare(params.id);
|
|
96
|
+
const closed = closeGuests(params.id);
|
|
97
|
+
return json({ closedConnections: closed, success: true });
|
|
98
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// POST /api/terminals/[id]/share/auth — exchange the share password for a
|
|
2
|
+
// guest session token. Public endpoint; brute-force-limited per IP+terminal.
|
|
3
|
+
|
|
4
|
+
import type { ShareAuthRequest } from '$lib/types';
|
|
5
|
+
|
|
6
|
+
import { shareStore, verifyPassword } from '$lib/modules/server/terminal/share-store';
|
|
7
|
+
import { json } from '@sveltejs/kit';
|
|
8
|
+
|
|
9
|
+
import type { RequestHandler } from './$types';
|
|
10
|
+
|
|
11
|
+
const RATE_LIMIT_WINDOW_MS = 60_000;
|
|
12
|
+
const RATE_LIMIT_MAX = 10;
|
|
13
|
+
|
|
14
|
+
/** Maps "ip:terminalId" -> attempt timestamps (epoch ms). */
|
|
15
|
+
const attempts = new Map<string, number[]>();
|
|
16
|
+
|
|
17
|
+
function checkRateLimit(key: string): boolean {
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
const recent = (attempts.get(key) ?? []).filter((t) => t > now - RATE_LIMIT_WINDOW_MS);
|
|
20
|
+
attempts.set(key, recent);
|
|
21
|
+
if (recent.length >= RATE_LIMIT_MAX) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
recent.push(now);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Cleanup stale rate limit entries every 5 minutes
|
|
29
|
+
setInterval(() => {
|
|
30
|
+
const cutoff = Date.now() - RATE_LIMIT_WINDOW_MS;
|
|
31
|
+
for (const [key, timestamps] of attempts) {
|
|
32
|
+
const recent = timestamps.filter((t) => t > cutoff);
|
|
33
|
+
if (recent.length === 0) {
|
|
34
|
+
attempts.delete(key);
|
|
35
|
+
} else {
|
|
36
|
+
attempts.set(key, recent);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}, 300_000).unref();
|
|
40
|
+
|
|
41
|
+
export const POST: RequestHandler = async (event) => {
|
|
42
|
+
const { params, request } = event;
|
|
43
|
+
|
|
44
|
+
const share = shareStore.getShare(params.id);
|
|
45
|
+
if (!share) {
|
|
46
|
+
return json({ error: 'Not shared' }, { status: 404 });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Behind Cloudflare Tunnel the connecting IP arrives in headers.
|
|
50
|
+
let ip = 'unknown';
|
|
51
|
+
const cfIp = request.headers.get('cf-connecting-ip');
|
|
52
|
+
const fwd = request.headers.get('x-forwarded-for');
|
|
53
|
+
if (cfIp) {
|
|
54
|
+
ip = cfIp;
|
|
55
|
+
} else if (fwd) {
|
|
56
|
+
ip = fwd.split(',')[0].trim();
|
|
57
|
+
} else {
|
|
58
|
+
try {
|
|
59
|
+
ip = event.getClientAddress();
|
|
60
|
+
} catch {
|
|
61
|
+
// Keep 'unknown' — the rate limit still applies per terminal.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!checkRateLimit(`${ip}:${params.id}`)) {
|
|
66
|
+
return json({ error: 'Too many attempts. Try again in a minute.' }, { status: 429 });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let body: ShareAuthRequest;
|
|
70
|
+
try {
|
|
71
|
+
body = (await request.json()) as ShareAuthRequest;
|
|
72
|
+
} catch {
|
|
73
|
+
return json({ error: 'Invalid JSON' }, { status: 400 });
|
|
74
|
+
}
|
|
75
|
+
if (typeof body.password !== 'string' || !verifyPassword(body.password, share.passwordHash)) {
|
|
76
|
+
return json({ error: 'Invalid password' }, { status: 401 });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { expiresAt, token } = shareStore.createSession(params.id);
|
|
80
|
+
return json({ expiresAt, mode: share.mode, token });
|
|
81
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// GET /api/terminals/[id]/share/status — public probe used by the page to
|
|
2
|
+
// decide whether to show the password gate. Reveals only a boolean.
|
|
3
|
+
|
|
4
|
+
import { shareStore } from '$lib/modules/server/terminal/share-store';
|
|
5
|
+
import { json } from '@sveltejs/kit';
|
|
6
|
+
|
|
7
|
+
import type { RequestHandler } from './$types';
|
|
8
|
+
|
|
9
|
+
export const GET: RequestHandler = ({ params }) => {
|
|
10
|
+
return json({ shared: shareStore.getShare(params.id) !== null });
|
|
11
|
+
};
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// Rate limited to 30 requests per minute per API key.
|
|
9
9
|
|
|
10
10
|
import { validateAuth } from '$lib/modules/server/auth';
|
|
11
|
+
import { shareStore } from '$lib/modules/server/terminal/share-store';
|
|
11
12
|
import { generateTicket } from '$lib/modules/server/ws/ticket-store';
|
|
12
13
|
import { json } from '@sveltejs/kit';
|
|
13
14
|
|
|
@@ -63,15 +64,35 @@ setInterval(() => {
|
|
|
63
64
|
// ── Endpoint ────────────────────────────────────────────────────────
|
|
64
65
|
|
|
65
66
|
export const POST: RequestHandler = ({ request }) => {
|
|
67
|
+
const bearer = (
|
|
68
|
+
request.headers.get('authorization') ??
|
|
69
|
+
request.headers.get('Authorization') ??
|
|
70
|
+
''
|
|
71
|
+
)
|
|
72
|
+
.replace(/^Bearer\s+/i, '')
|
|
73
|
+
.trim();
|
|
74
|
+
|
|
66
75
|
const authError = validateAuth(request);
|
|
67
76
|
if (authError) {
|
|
68
|
-
|
|
77
|
+
// Not the API key — maybe a guest share token (issues a scoped ticket).
|
|
78
|
+
const session = bearer ? shareStore.resolveToken(bearer) : null;
|
|
79
|
+
if (!session) {
|
|
80
|
+
return authError;
|
|
81
|
+
}
|
|
82
|
+
if (!checkRateLimit(bearer)) {
|
|
83
|
+
return json(
|
|
84
|
+
{ error: 'Rate limit exceeded. Maximum 30 ticket requests per minute.' },
|
|
85
|
+
{ status: 429 }
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const ticket = generateTicket({
|
|
89
|
+
readOnly: session.mode === 'view',
|
|
90
|
+
terminalId: session.terminalId,
|
|
91
|
+
});
|
|
92
|
+
return json({ expiresIn: 30, ticket });
|
|
69
93
|
}
|
|
70
94
|
|
|
71
|
-
|
|
72
|
-
const apiKey = (request.headers.get('authorization') ?? '').substring(7).trim();
|
|
73
|
-
|
|
74
|
-
if (!checkRateLimit(apiKey)) {
|
|
95
|
+
if (!checkRateLimit(bearer)) {
|
|
75
96
|
return json(
|
|
76
97
|
{ error: 'Rate limit exceeded. Maximum 30 ticket requests per minute.' },
|
|
77
98
|
{ status: 429 }
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import type {
|
|
3
3
|
ConversationMessage,
|
|
4
4
|
MessagePart,
|
|
5
|
+
ShareAuthResponse,
|
|
6
|
+
ShareMode,
|
|
7
|
+
ShareStatusResponse,
|
|
5
8
|
ShooterConfig,
|
|
6
9
|
TerminalDetailView,
|
|
7
10
|
ToolUsePart,
|
|
@@ -17,6 +20,8 @@
|
|
|
17
20
|
import ConnectionStatus from '$lib/modules/client/terminal/ConnectionStatus.svelte';
|
|
18
21
|
import { createShortcutManager } from '$lib/modules/client/terminal/keyboard-shortcuts';
|
|
19
22
|
import QuickKeys from '$lib/modules/client/terminal/QuickKeys.svelte';
|
|
23
|
+
import ShareGate from '$lib/modules/client/terminal/ShareGate.svelte';
|
|
24
|
+
import ShareSheet from '$lib/modules/client/terminal/ShareSheet.svelte';
|
|
20
25
|
import ShortcutsHelp from '$lib/modules/client/terminal/ShortcutsHelp.svelte';
|
|
21
26
|
import {
|
|
22
27
|
Button,
|
|
@@ -46,6 +51,10 @@
|
|
|
46
51
|
let inputText = $state('');
|
|
47
52
|
let chatMessages = $state<ConversationMessage[]>([]);
|
|
48
53
|
let chatSessionEnded = $state(false);
|
|
54
|
+
let authMode = $state<'guest' | 'owner' | null>(null);
|
|
55
|
+
let guestMode = $state<null | ShareMode>(null);
|
|
56
|
+
let shareGateVisible = $state(false);
|
|
57
|
+
let shareSheetOpen = $state(false);
|
|
49
58
|
|
|
50
59
|
// DOM references
|
|
51
60
|
let termContainer = $state<HTMLDivElement | null>(null);
|
|
@@ -80,6 +89,11 @@
|
|
|
80
89
|
);
|
|
81
90
|
const tabActiveIndex = $derived(viewMode === 'raw' ? 0 : 1);
|
|
82
91
|
const displayCwd = $derived(shortenPath(currentCwd || terminal?.cwd || ''));
|
|
92
|
+
const isOwner = $derived(authMode === 'owner');
|
|
93
|
+
const viewOnly = $derived(authMode === 'guest' && guestMode === 'view');
|
|
94
|
+
const shareUrl = $derived(
|
|
95
|
+
typeof window !== 'undefined' ? `${window.location.origin}/terminals/${terminalId}` : ''
|
|
96
|
+
);
|
|
83
97
|
const paletteCommands = $derived.by((): { action: () => void; label: string }[] => {
|
|
84
98
|
const cmds: { action: () => void; label: string }[] = [
|
|
85
99
|
{ action: (): void => void goto('/'), label: 'Go to Home' },
|
|
@@ -92,7 +106,7 @@
|
|
|
92
106
|
label: 'Show keyboard shortcuts',
|
|
93
107
|
},
|
|
94
108
|
];
|
|
95
|
-
if (isRunning) {
|
|
109
|
+
if (isRunning && isOwner) {
|
|
96
110
|
cmds.push({ action: (): void => void killTerminal(), label: 'Kill terminal' });
|
|
97
111
|
}
|
|
98
112
|
return cmds;
|
|
@@ -142,6 +156,47 @@
|
|
|
142
156
|
}
|
|
143
157
|
}
|
|
144
158
|
|
|
159
|
+
// ------- Guest share tokens -------
|
|
160
|
+
|
|
161
|
+
const SHARE_TOKENS_KEY = 'shooter_share_tokens';
|
|
162
|
+
|
|
163
|
+
function getShareToken(): null | string {
|
|
164
|
+
const id = terminalId;
|
|
165
|
+
if (!id) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const raw = localStorage.getItem(SHARE_TOKENS_KEY);
|
|
170
|
+
if (!raw) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const map = JSON.parse(raw) as Record<string, string>;
|
|
174
|
+
return typeof map[id] === 'string' ? map[id] : null;
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function storeShareToken(token: string): void {
|
|
181
|
+
const id = terminalId;
|
|
182
|
+
if (!id) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
let map: Record<string, string> = {};
|
|
186
|
+
try {
|
|
187
|
+
map = JSON.parse(localStorage.getItem(SHARE_TOKENS_KEY) ?? '{}') as Record<string, string>;
|
|
188
|
+
} catch {
|
|
189
|
+
// Corrupt entry — start fresh.
|
|
190
|
+
}
|
|
191
|
+
map[id] = token;
|
|
192
|
+
localStorage.setItem(SHARE_TOKENS_KEY, JSON.stringify(map));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Bearer for API calls: the owner's API key, or this terminal's guest token. */
|
|
196
|
+
function getBearer(): null | string {
|
|
197
|
+
return getConfig()?.apiKey ?? getShareToken();
|
|
198
|
+
}
|
|
199
|
+
|
|
145
200
|
// ------- API calls -------
|
|
146
201
|
|
|
147
202
|
async function fetchTerminal(): Promise<void> {
|
|
@@ -150,36 +205,92 @@
|
|
|
150
205
|
}
|
|
151
206
|
|
|
152
207
|
const config = getConfig();
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
208
|
+
const bearer = config?.apiKey ?? getShareToken();
|
|
209
|
+
if (!bearer) {
|
|
210
|
+
await checkShareAccess();
|
|
156
211
|
return;
|
|
157
212
|
}
|
|
158
213
|
|
|
159
214
|
try {
|
|
160
215
|
const res = await fetch(`/api/terminals/${terminalId}`, {
|
|
161
|
-
headers: { Authorization: `Bearer ${
|
|
216
|
+
headers: { Authorization: `Bearer ${bearer}` },
|
|
162
217
|
});
|
|
218
|
+
if (res.status === 401 && !config) {
|
|
219
|
+
// Stale/revoked guest token — fall back to the password gate.
|
|
220
|
+
await checkShareAccess();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
163
223
|
if (!res.ok) {
|
|
164
224
|
error = res.status === 404 ? 'Terminal not found' : 'Failed to load terminal';
|
|
165
225
|
loading = false;
|
|
166
226
|
return;
|
|
167
227
|
}
|
|
168
228
|
terminal = (await res.json()) as TerminalDetailView;
|
|
229
|
+
if (config) {
|
|
230
|
+
authMode = 'owner';
|
|
231
|
+
} else {
|
|
232
|
+
authMode = 'guest';
|
|
233
|
+
guestMode = terminal.shareMode ?? 'view';
|
|
234
|
+
}
|
|
169
235
|
} catch {
|
|
170
236
|
error = 'Failed to connect to server';
|
|
171
237
|
}
|
|
172
238
|
loading = false;
|
|
173
239
|
}
|
|
174
240
|
|
|
241
|
+
async function checkShareAccess(): Promise<void> {
|
|
242
|
+
try {
|
|
243
|
+
const res = await fetch(`/api/terminals/${terminalId}/share/status`);
|
|
244
|
+
if (res.ok) {
|
|
245
|
+
const data = (await res.json()) as ShareStatusResponse;
|
|
246
|
+
if (data.shared) {
|
|
247
|
+
shareGateVisible = true;
|
|
248
|
+
loading = false;
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} catch {
|
|
253
|
+
// Fall through to the configuration error.
|
|
254
|
+
}
|
|
255
|
+
error = 'No configuration found. Please configure settings first.';
|
|
256
|
+
loading = false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function submitSharePassword(password: string): Promise<null | string> {
|
|
260
|
+
try {
|
|
261
|
+
const res = await fetch(`/api/terminals/${terminalId}/share/auth`, {
|
|
262
|
+
body: JSON.stringify({ password }),
|
|
263
|
+
headers: { 'Content-Type': 'application/json' },
|
|
264
|
+
method: 'POST',
|
|
265
|
+
});
|
|
266
|
+
if (res.status === 429) {
|
|
267
|
+
return 'Too many attempts — try again in a minute.';
|
|
268
|
+
}
|
|
269
|
+
if (!res.ok) {
|
|
270
|
+
return 'Incorrect password.';
|
|
271
|
+
}
|
|
272
|
+
const data = (await res.json()) as ShareAuthResponse;
|
|
273
|
+
storeShareToken(data.token);
|
|
274
|
+
shareGateVisible = false;
|
|
275
|
+
loading = true;
|
|
276
|
+
await fetchTerminal();
|
|
277
|
+
if (terminal && !error) {
|
|
278
|
+
initViews();
|
|
279
|
+
}
|
|
280
|
+
return null;
|
|
281
|
+
} catch {
|
|
282
|
+
return 'Failed to reach the server.';
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
175
286
|
async function getWsTicket(): Promise<null | string> {
|
|
176
|
-
const
|
|
177
|
-
if (!
|
|
287
|
+
const bearer = getBearer();
|
|
288
|
+
if (!bearer) {
|
|
178
289
|
return null;
|
|
179
290
|
}
|
|
180
291
|
try {
|
|
181
292
|
const res = await fetch('/api/ws-ticket', {
|
|
182
|
-
headers: { Authorization: `Bearer ${
|
|
293
|
+
headers: { Authorization: `Bearer ${bearer}` },
|
|
183
294
|
method: 'POST',
|
|
184
295
|
});
|
|
185
296
|
if (!res.ok) {
|
|
@@ -269,10 +380,12 @@
|
|
|
269
380
|
}
|
|
270
381
|
|
|
271
382
|
const instance = await createTerminal({
|
|
272
|
-
apiKey:
|
|
383
|
+
apiKey: getBearer() ?? undefined,
|
|
273
384
|
container: termContainer,
|
|
274
385
|
fontSize: window.innerWidth < 768 ? 12 : 14,
|
|
275
386
|
getTicket,
|
|
387
|
+
initialCols: terminal.cols ?? undefined,
|
|
388
|
+
initialRows: terminal.rows ?? undefined,
|
|
276
389
|
onActivity: (active: boolean) => {
|
|
277
390
|
if (!disposed) {
|
|
278
391
|
isActive = active;
|
|
@@ -304,6 +417,7 @@
|
|
|
304
417
|
rawConnectionStatus = 'connected';
|
|
305
418
|
}
|
|
306
419
|
},
|
|
420
|
+
readOnly: viewOnly,
|
|
307
421
|
terminalId,
|
|
308
422
|
wsUrl,
|
|
309
423
|
});
|
|
@@ -606,23 +720,7 @@
|
|
|
606
720
|
|
|
607
721
|
// ------- Lifecycle -------
|
|
608
722
|
|
|
609
|
-
|
|
610
|
-
await fetchTerminal();
|
|
611
|
-
if (disposed) {
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// Set up keyboard shortcuts
|
|
616
|
-
shortcutManager = createShortcutManager({
|
|
617
|
-
onHelp: () => {
|
|
618
|
-
showShortcutsHelp = !showShortcutsHelp;
|
|
619
|
-
},
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
if (!terminal || error) {
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
|
|
723
|
+
function initViews(): void {
|
|
626
724
|
// Default view: Chat on mobile for AI sessions, Raw on desktop
|
|
627
725
|
if (isAI && window.innerWidth < 768) {
|
|
628
726
|
viewMode = 'chat';
|
|
@@ -640,6 +738,26 @@
|
|
|
640
738
|
void connectSessionWs();
|
|
641
739
|
chatInitialized = true;
|
|
642
740
|
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
onMount(async () => {
|
|
744
|
+
await fetchTerminal();
|
|
745
|
+
if (disposed) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Set up keyboard shortcuts
|
|
750
|
+
shortcutManager = createShortcutManager({
|
|
751
|
+
onHelp: () => {
|
|
752
|
+
showShortcutsHelp = !showShortcutsHelp;
|
|
753
|
+
},
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
if (!terminal || error) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
initViews();
|
|
643
761
|
});
|
|
644
762
|
|
|
645
763
|
onDestroy(() => {
|
|
@@ -664,6 +782,10 @@
|
|
|
664
782
|
<div class="skeleton" style="width: 100%; height: 100%;"></div>
|
|
665
783
|
</div>
|
|
666
784
|
</div>
|
|
785
|
+
{:else if shareGateVisible}
|
|
786
|
+
<div class="term-page">
|
|
787
|
+
<ShareGate onSubmit={submitSharePassword} />
|
|
788
|
+
</div>
|
|
667
789
|
{:else if error}
|
|
668
790
|
<main class="main">
|
|
669
791
|
<div class="session-back-row">
|
|
@@ -681,7 +803,9 @@
|
|
|
681
803
|
<!-- Top Bar -->
|
|
682
804
|
<div class="term-topbar">
|
|
683
805
|
<div class="term-topbar-left">
|
|
684
|
-
|
|
806
|
+
{#if isOwner}
|
|
807
|
+
<a href="/terminals" class="term-back" aria-label="Back to terminals">←</a>
|
|
808
|
+
{/if}
|
|
685
809
|
<span class="term-command-name">{commandName}</span>
|
|
686
810
|
<Pill text={badgeLabel} classes={badgeClass} />
|
|
687
811
|
{#if isRunning}
|
|
@@ -695,7 +819,7 @@
|
|
|
695
819
|
</Tooltip>
|
|
696
820
|
{/if}
|
|
697
821
|
<ConnectionStatus status={connectionStatus} onretry={handleRetry} />
|
|
698
|
-
{#if isAI && (terminal as TerminalDetailView & { sessionFile?: string })?.sessionFile}
|
|
822
|
+
{#if isOwner && isAI && (terminal as TerminalDetailView & { sessionFile?: string })?.sessionFile}
|
|
699
823
|
{@const sessionFile =
|
|
700
824
|
(terminal as TerminalDetailView & { sessionFile?: string }).sessionFile ?? ''}
|
|
701
825
|
<a
|
|
@@ -729,22 +853,31 @@
|
|
|
729
853
|
ariaLabel="Keyboard shortcuts"
|
|
730
854
|
/>
|
|
731
855
|
|
|
732
|
-
{#if
|
|
733
|
-
<Button
|
|
734
|
-
classes="btn-danger btn-sm"
|
|
735
|
-
onclick={killTerminal}
|
|
736
|
-
disabled={killing}
|
|
737
|
-
showLoader={killing}
|
|
738
|
-
text="Kill"
|
|
739
|
-
/>
|
|
740
|
-
{:else}
|
|
856
|
+
{#if isOwner}
|
|
741
857
|
<Button
|
|
742
858
|
classes="btn-secondary btn-sm"
|
|
743
|
-
onclick={
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
text="
|
|
859
|
+
onclick={(): void => {
|
|
860
|
+
shareSheetOpen = true;
|
|
861
|
+
}}
|
|
862
|
+
text="Share"
|
|
747
863
|
/>
|
|
864
|
+
{#if isRunning}
|
|
865
|
+
<Button
|
|
866
|
+
classes="btn-danger btn-sm"
|
|
867
|
+
onclick={killTerminal}
|
|
868
|
+
disabled={killing}
|
|
869
|
+
showLoader={killing}
|
|
870
|
+
text="Kill"
|
|
871
|
+
/>
|
|
872
|
+
{:else}
|
|
873
|
+
<Button
|
|
874
|
+
classes="btn-secondary btn-sm"
|
|
875
|
+
onclick={removeTerminal}
|
|
876
|
+
disabled={removing}
|
|
877
|
+
showLoader={removing}
|
|
878
|
+
text="Remove"
|
|
879
|
+
/>
|
|
880
|
+
{/if}
|
|
748
881
|
{/if}
|
|
749
882
|
</div>
|
|
750
883
|
</div>
|
|
@@ -759,7 +892,7 @@
|
|
|
759
892
|
></div>
|
|
760
893
|
|
|
761
894
|
<!-- Raw Input Bar + Quick Keys -->
|
|
762
|
-
{#if isRunning && viewMode === 'raw'}
|
|
895
|
+
{#if isRunning && viewMode === 'raw' && !viewOnly}
|
|
763
896
|
<div class="term-input-area">
|
|
764
897
|
<QuickKeys onKey={handleQuickKey} />
|
|
765
898
|
<div class="term-input-bar">
|
|
@@ -788,7 +921,7 @@
|
|
|
788
921
|
messages={chatMessages}
|
|
789
922
|
connectionState={connectionStatus}
|
|
790
923
|
sessionEnded={chatSessionEnded}
|
|
791
|
-
showInput={isRunning}
|
|
924
|
+
showInput={isRunning && !viewOnly}
|
|
792
925
|
onSendInput={handleChatSendInput}
|
|
793
926
|
onCancel={handleChatCancel}
|
|
794
927
|
/>
|
|
@@ -815,6 +948,14 @@
|
|
|
815
948
|
showShortcutsHelp = false;
|
|
816
949
|
}}
|
|
817
950
|
/>
|
|
951
|
+
<ShareSheet
|
|
952
|
+
open={shareSheetOpen}
|
|
953
|
+
terminalId={terminalId ?? ''}
|
|
954
|
+
{shareUrl}
|
|
955
|
+
onClose={(): void => {
|
|
956
|
+
shareSheetOpen = false;
|
|
957
|
+
}}
|
|
958
|
+
/>
|
|
818
959
|
<CommandPalette
|
|
819
960
|
bind:open={showCommandPalette}
|
|
820
961
|
commands={paletteCommands}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.command-palette{--command-menu-overlay-background: rgba(0, 0, 0, .5);--command-menu-background: var(--component-bg);--command-menu-border: 1px solid var(--border);--command-menu-border-radius: var(--radius-lg);--command-menu-width: 500px;--command-menu-max-width: 90vw;--command-menu-input-color: var(--text-primary);--command-menu-input-placeholder-color: var(--text-tertiary);--command-menu-input-font-family: var(--font-mono);--command-menu-separator-color: var(--border);--command-menu-item-color: var(--text-secondary);--command-menu-item-active-background: var(--component-bg-hover);--command-menu-item-active-color: var(--text-primary);--command-menu-empty-color: var(--text-tertiary);--command-menu-z-index: 1001}.connection-status.svelte-1cg9pai{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:500;line-height:1;flex-shrink:0}.connection-status.connected.svelte-1cg9pai{color:var(--ds-green-700)}.connection-status.reconnecting.svelte-1cg9pai{color:var(--ds-amber-900)}.connection-status.disconnected.svelte-1cg9pai{color:var(--ds-red-900)}.conn-dot.svelte-1cg9pai{width:8px;height:8px;border-radius:50%;flex-shrink:0}.conn-dot.connected.svelte-1cg9pai{background:var(--ds-green-700)}.conn-dot.reconnecting.svelte-1cg9pai{background:var(--ds-amber-700);animation:pulse-dot 1.5s ease-in-out infinite}.conn-dot.disconnected.svelte-1cg9pai{background:var(--ds-red-700)}.status-label.svelte-1cg9pai{white-space:nowrap}.btn-retry{--button-height: auto;--button-padding: 2px 8px;--button-font-size: 12px;--button-border: 1px solid currentColor;--button-text-color: inherit;margin-left:2px}@media(max-width:480px){.status-label.svelte-1cg9pai{display:none}}.quick-keys.svelte-64qat5{display:flex;overflow-x:auto;gap:6px;padding:var(--space-2) var(--space-3);scrollbar-width:none;-webkit-overflow-scrolling:touch;flex-shrink:0}.quick-keys.svelte-64qat5::-webkit-scrollbar{display:none}.btn-quick-key{--button-color: var(--ds-gray-200);--button-text-color: var(--ds-gray-700);--button-border: 1px solid var(--ds-gray-400);--button-hover-color: var(--ds-gray-300);--button-hover-text-color: var(--text-primary);--button-hover-border: 1px solid var(--ds-gray-400);--button-height: 44px;--button-padding: 0 var(--space-3);--button-border-radius: var(--radius-md);--button-font-family: var(--font-mono);--button-font-size: var(--text-xs);min-width:52px;flex-shrink:0;white-space:nowrap;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent;touch-action:manipulation}.shortcuts-modal{--modal-content-background-color: var(--component-bg);--modal-border-radius: var(--radius-lg);--modal-header-background-color: var(--component-bg);--modal-header-padding: var(--space-4) var(--space-5);--modal-header-border-bottom: 1px solid var(--border);--header-text-size: var(--text-lg);--modal-header-text-weight: 600;--background-color: rgba(0, 0, 0, .5);--modal-z-index: 1000}.shortcuts-modal .modal-content{max-width:420px;width:90vw;min-width:320px}.shortcuts-list.svelte-1u7lstk{display:flex;flex-direction:column;gap:var(--space-2);padding:var(--space-4) var(--space-5)}.shortcut-row.svelte-1u7lstk{display:flex;justify-content:space-between;align-items:center;padding:var(--space-2) 0}.shortcut-desc.svelte-1u7lstk{font-size:var(--text-sm);color:var(--text-secondary)}.shortcut-kbd{--keyboard-input-key-color: var(--text-primary);--keyboard-input-key-background: var(--ds-gray-200);--keyboard-input-key-border: 1px solid var(--ds-gray-400);--keyboard-input-key-box-shadow: 0 1px 0 var(--ds-gray-400);--keyboard-input-font-family: var(--font-mono);--keyboard-input-font-size: var(--text-xs)}.term-page.svelte-1tubujq{display:flex;flex-direction:column;height:calc(100vh - var(--header-height) - 64px);height:calc(100dvh - var(--header-height) - 64px);overflow:hidden;background:var(--ds-background-200)}.term-topbar.svelte-1tubujq{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-2) var(--space-4);background:var(--ds-background-100);border-bottom:1px solid var(--border);flex-shrink:0;min-height:48px}.term-topbar-left.svelte-1tubujq{display:flex;align-items:center;gap:var(--space-2);min-width:0;overflow:hidden}.term-topbar-right.svelte-1tubujq{display:flex;align-items:center;gap:var(--space-3);flex-shrink:1}.term-back.svelte-1tubujq{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:var(--radius-md);background:transparent;color:var(--text-secondary);text-decoration:none;font-size:18px;transition:background var(--transition-fast),color var(--transition-fast);flex-shrink:0}.term-back.svelte-1tubujq:hover{background:var(--ds-gray-alpha-100);color:var(--text-primary)}.term-command-name.svelte-1tubujq{font-family:var(--font-mono);font-size:var(--text-base);font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.term-tabs{--tabs-bar-border-bottom: none;--tabs-bar-background: var(--ds-gray-200);--tabs-bar-padding: 2px;--tabs-item-padding: 6px 12px;--tabs-item-font-size: var(--text-xs);--tabs-indicator-height: 0;--tabs-active-color: var(--text-primary);--tabs-item-color: var(--text-tertiary);border-radius:var(--radius-md);border:1px solid var(--ds-gray-400);overflow:hidden}.term-shortcuts-btn{--button-height: 28px;--button-width: 28px;--button-padding: 0;--button-border-radius: var(--radius-sm);--button-border: 1px solid var(--border);--button-color: transparent;--button-text-color: var(--text-tertiary);--button-font-size: 14px;--button-font-weight: 600;--button-hover-color: var(--component-bg-hover);--button-hover-text-color: var(--text-primary);flex-shrink:0}.term-cwd.svelte-1tubujq{font-family:var(--font-mono);font-size:11px;color:var(--text-tertiary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px}.term-session-link.svelte-1tubujq{font-size:11px;font-weight:500;color:var(--ds-blue-900);text-decoration:none;white-space:nowrap;padding:2px 8px;border-radius:var(--radius-sm);border:1px solid var(--ds-blue-400);transition:background var(--transition-fast),color var(--transition-fast)}.term-session-link.svelte-1tubujq:hover{background:var(--ds-blue-100);color:var(--ds-blue-1000)}.activity-dot.svelte-1tubujq{width:8px;height:8px;border-radius:50%;flex-shrink:0}.activity-active.svelte-1tubujq{background:var(--ds-green-500);animation:activity-pulse .6s ease-in-out infinite}.activity-idle.svelte-1tubujq{background:var(--ds-gray-600)}.term-body.svelte-1tubujq{flex:1;flex-direction:column;min-height:0;overflow:hidden;padding:var(--space-1);background:var(--ds-background-200, #0a0a0f)}.term-body.svelte-1tubujq .xterm{height:100%}.term-body.svelte-1tubujq .xterm-viewport{overflow-y:auto!important}.term-body-loading.svelte-1tubujq{flex:1;padding:var(--space-4);min-height:200px}.term-chat-body.svelte-1tubujq{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.term-input-area.svelte-1tubujq{flex-shrink:0;background:var(--ds-background-100);border-top:1px solid var(--border);padding-bottom:env(safe-area-inset-bottom,0px)}.term-input-bar.svelte-1tubujq{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3)}.term-input-field{--input-container-margin: 0;--input-height: 44px;flex:1}.btn-send{--button-height: 44px;--button-width: 44px;--button-padding: 0;--button-font-size: 18px;flex-shrink:0}.term-exited-bar.svelte-1tubujq{display:flex;align-items:center;justify-content:center;gap:var(--space-2);padding:var(--space-2) var(--space-3);padding-bottom:calc(var(--space-2) + env(safe-area-inset-bottom,0px));background:var(--ds-gray-200);border-top:1px solid var(--border);font-size:var(--text-sm);color:var(--text-tertiary);flex-shrink:0}@media(max-width:768px){.term-topbar.svelte-1tubujq{padding:var(--space-2) var(--space-3);gap:var(--space-2)}.term-command-name.svelte-1tubujq{font-size:var(--text-sm);max-width:100px}}@media(max-width:480px){.term-topbar.svelte-1tubujq{min-height:44px;padding:var(--space-1) var(--space-2)}.term-command-name.svelte-1tubujq{max-width:80px;text-overflow:ellipsis}.term-topbar-right.svelte-1tubujq{flex-shrink:1}}
|
|
Binary file
|
|
Binary file
|