dsh-zen-remote 1.0.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/AGENTS.md +51 -0
- package/LICENSE +38 -0
- package/README.md +237 -0
- package/assets/home.png +0 -0
- package/assets/info.png +0 -0
- package/assets/pairing.png +0 -0
- package/assets/session.png +0 -0
- package/assets/sheet.png +0 -0
- package/cordis.patch.yml +17 -0
- package/cordis.patch.yml.example +16 -0
- package/docs/interface.md +200 -0
- package/docs/remote-access.en.md +335 -0
- package/docs/remote-access.md +422 -0
- package/dsh-push.mjs +213 -0
- package/lan-gate.mjs +78 -0
- package/lib/client.js +6615 -0
- package/lib/index.js +310 -0
- package/lib/index.js.map +1 -0
- package/lib/lan-gate-server.cjs +626 -0
- package/lib/types/client/MobileAttachButton.d.ts +25 -0
- package/lib/types/client/MobileAttachButton.d.ts.map +1 -0
- package/lib/types/client/MobileAttachChips.d.ts +32 -0
- package/lib/types/client/MobileAttachChips.d.ts.map +1 -0
- package/lib/types/client/MobileDrawerFooter.d.ts +21 -0
- package/lib/types/client/MobileDrawerFooter.d.ts.map +1 -0
- package/lib/types/client/MobileHome.d.ts +24 -0
- package/lib/types/client/MobileHome.d.ts.map +1 -0
- package/lib/types/client/MobileHomeChips.d.ts +76 -0
- package/lib/types/client/MobileHomeChips.d.ts.map +1 -0
- package/lib/types/client/MobileNavOverlay.d.ts +15 -0
- package/lib/types/client/MobileNavOverlay.d.ts.map +1 -0
- package/lib/types/client/MobileNavToggle.d.ts +18 -0
- package/lib/types/client/MobileNavToggle.d.ts.map +1 -0
- package/lib/types/client/MobileSessionHeader.d.ts +58 -0
- package/lib/types/client/MobileSessionHeader.d.ts.map +1 -0
- package/lib/types/client/MobileSessionInfo.d.ts +41 -0
- package/lib/types/client/MobileSessionInfo.d.ts.map +1 -0
- package/lib/types/client/attach-upload.d.ts +78 -0
- package/lib/types/client/attach-upload.d.ts.map +1 -0
- package/lib/types/client/chips-store.d.ts +13 -0
- package/lib/types/client/chips-store.d.ts.map +1 -0
- package/lib/types/client/debug.d.ts +9 -0
- package/lib/types/client/debug.d.ts.map +1 -0
- package/lib/types/client/effects/aionui-compat.d.ts +4 -0
- package/lib/types/client/effects/aionui-compat.d.ts.map +1 -0
- package/lib/types/client/effects/gestures.d.ts +7 -0
- package/lib/types/client/effects/gestures.d.ts.map +1 -0
- package/lib/types/client/effects/header-status.d.ts +15 -0
- package/lib/types/client/effects/header-status.d.ts.map +1 -0
- package/lib/types/client/effects/phone-chrome.d.ts +106 -0
- package/lib/types/client/effects/phone-chrome.d.ts.map +1 -0
- package/lib/types/client/effects/turn-fold.d.ts +25 -0
- package/lib/types/client/effects/turn-fold.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +18 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/locales.d.ts +58 -0
- package/lib/types/client/locales.d.ts.map +1 -0
- package/lib/types/client/nav-store.d.ts +73 -0
- package/lib/types/client/nav-store.d.ts.map +1 -0
- package/lib/types/client/session-dot.d.ts +12 -0
- package/lib/types/client/session-dot.d.ts.map +1 -0
- package/lib/types/client/styles/base.css.d.ts +2 -0
- package/lib/types/client/styles/base.css.d.ts.map +1 -0
- package/lib/types/client/styles/chips.css.d.ts +2 -0
- package/lib/types/client/styles/chips.css.d.ts.map +1 -0
- package/lib/types/client/styles/compat.css.d.ts +2 -0
- package/lib/types/client/styles/compat.css.d.ts.map +1 -0
- package/lib/types/client/styles/composer.css.d.ts +2 -0
- package/lib/types/client/styles/composer.css.d.ts.map +1 -0
- package/lib/types/client/styles/header.css.d.ts +2 -0
- package/lib/types/client/styles/header.css.d.ts.map +1 -0
- package/lib/types/client/styles/home.css.d.ts +2 -0
- package/lib/types/client/styles/home.css.d.ts.map +1 -0
- package/lib/types/client/styles/index.d.ts +15 -0
- package/lib/types/client/styles/index.d.ts.map +1 -0
- package/lib/types/client/styles/info.css.d.ts +2 -0
- package/lib/types/client/styles/info.css.d.ts.map +1 -0
- package/lib/types/client/styles/layout.css.d.ts +2 -0
- package/lib/types/client/styles/layout.css.d.ts.map +1 -0
- package/lib/types/client/styles/misc.css.d.ts +2 -0
- package/lib/types/client/styles/misc.css.d.ts.map +1 -0
- package/lib/types/client/styles/turn-fold.css.d.ts +2 -0
- package/lib/types/client/styles/turn-fold.css.d.ts.map +1 -0
- package/lib/types/index.d.ts +83 -0
- package/lib/types/index.d.ts.map +1 -0
- package/llms.txt +20 -0
- package/package.json +128 -0
- package/pwa/app.css +95 -0
- package/pwa/icons/icon-192.png +0 -0
- package/pwa/icons/icon-512.png +0 -0
- package/pwa/icons/icon-maskable-512.png +0 -0
- package/pwa/icons/icon-maskable.svg +1 -0
- package/pwa/icons/icon.svg +1 -0
- package/pwa/inject.js +190 -0
- package/pwa/manifest.json +29 -0
- package/pwa/offline.html +38 -0
- package/pwa/sw.js +176 -0
- package/pwa/touch-gestures.js +94 -0
- package/scripts/build-client.mjs +101 -0
- package/scripts/cdp-probe.mjs +64 -0
- package/scripts/check-attach-upload.mjs +99 -0
- package/scripts/check-sunk-viewport.mjs +33 -0
- package/scripts/check-upload-endpoint.mjs +168 -0
- package/src/client/MobileAttachButton.tsx +125 -0
- package/src/client/MobileAttachChips.tsx +85 -0
- package/src/client/MobileDrawerFooter.tsx +56 -0
- package/src/client/MobileHome.tsx +348 -0
- package/src/client/MobileHomeChips.tsx +426 -0
- package/src/client/MobileNavOverlay.tsx +279 -0
- package/src/client/MobileNavToggle.tsx +52 -0
- package/src/client/MobileSessionHeader.tsx +242 -0
- package/src/client/MobileSessionInfo.tsx +303 -0
- package/src/client/attach-upload.ts +140 -0
- package/src/client/chips-store.ts +41 -0
- package/src/client/debug.ts +253 -0
- package/src/client/effects/aionui-compat.ts +183 -0
- package/src/client/effects/gestures.ts +265 -0
- package/src/client/effects/header-status.ts +30 -0
- package/src/client/effects/phone-chrome.ts +265 -0
- package/src/client/effects/turn-fold.ts +258 -0
- package/src/client/index.tsx +227 -0
- package/src/client/locales.ts +109 -0
- package/src/client/nav-store.ts +78 -0
- package/src/client/session-dot.ts +17 -0
- package/src/client/styles/base.css.ts +167 -0
- package/src/client/styles/chips.css.ts +224 -0
- package/src/client/styles/compat.css.ts +681 -0
- package/src/client/styles/composer.css.ts +504 -0
- package/src/client/styles/header.css.ts +368 -0
- package/src/client/styles/home.css.ts +477 -0
- package/src/client/styles/index.ts +25 -0
- package/src/client/styles/info.css.ts +238 -0
- package/src/client/styles/layout.css.ts +501 -0
- package/src/client/styles/misc.css.ts +128 -0
- package/src/client/styles/turn-fold.css.ts +106 -0
- package/src/index.ts +343 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-mobile-nav, node half.
|
|
3
|
+
*
|
|
4
|
+
* Was an empty apply (pure client UI plugin) until S7. It now owns ONE host
|
|
5
|
+
* route: the phone composer's attachment upload. The official file picker
|
|
6
|
+
* opens on the machine running DSH, which is useless from a phone, and the
|
|
7
|
+
* public client API has no upload verb at all — the only public browser->host
|
|
8
|
+
* byte channel is `session.prompt([{type:'image',…}])`, which is images only
|
|
9
|
+
* and lands as a sent message rather than a file on disk. So a non-image
|
|
10
|
+
* attachment needs a route of its own, and that route belongs here rather
|
|
11
|
+
* than in the gateway half of this plugin: the gateway authenticates and forwards
|
|
12
|
+
* verbatim, it does not know what a session or a workspace is.
|
|
13
|
+
*
|
|
14
|
+
* The browser half still ships via exports["./client"], discovered through
|
|
15
|
+
* the package.json dsh.client declaration.
|
|
16
|
+
*/
|
|
17
|
+
import { lstat, mkdir, open, realpath, rm } from 'node:fs/promises';
|
|
18
|
+
import { basename, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
19
|
+
/** Exact route the phone composer POSTs one file body to. */
|
|
20
|
+
export const UPLOAD_ROUTE = '/_dsh/mobile-nav/upload';
|
|
21
|
+
/** Workspace-relative directory uploads land in (also the `@` prefix the composer inserts). */
|
|
22
|
+
export const UPLOAD_DIR = '.dsh-uploads';
|
|
23
|
+
/** Body cap when the plugin row sets no `maxUploadBytes`. */
|
|
24
|
+
export const DEFAULT_MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
|
|
25
|
+
/** Longest filename, in bytes, that survives sanitization (ext4/APFS leaf limit is 255). */
|
|
26
|
+
const MAX_NAME_BYTES = 180;
|
|
27
|
+
/** Distinct leaf names tried before a collision is given up on. */
|
|
28
|
+
const MAX_COLLISION_TRIES = 100;
|
|
29
|
+
/**
|
|
30
|
+
* One rejection carrying the status the client should see.
|
|
31
|
+
*
|
|
32
|
+
* Fields are assigned in the body rather than declared as constructor
|
|
33
|
+
* parameter properties: `scripts/check-upload-endpoint.mjs` imports this
|
|
34
|
+
* module through Node's strip-only type stripping, which rejects that syntax.
|
|
35
|
+
*/
|
|
36
|
+
class UploadError extends Error {
|
|
37
|
+
status;
|
|
38
|
+
code;
|
|
39
|
+
constructor(status, code, message) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = 'UploadError';
|
|
42
|
+
this.status = status;
|
|
43
|
+
this.code = code;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function message(error) {
|
|
47
|
+
return error instanceof Error ? error.message : String(error);
|
|
48
|
+
}
|
|
49
|
+
function isErrnoCode(error, code) {
|
|
50
|
+
return error instanceof Error && 'code' in error && error.code === code;
|
|
51
|
+
}
|
|
52
|
+
function responseJson(res, status, body) {
|
|
53
|
+
const bytes = Buffer.from(JSON.stringify(body));
|
|
54
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
55
|
+
res.setHeader('Content-Length', String(bytes.length));
|
|
56
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
57
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
58
|
+
res.setHeader('Content-Security-Policy', "default-src 'none'; frame-ancestors 'none'");
|
|
59
|
+
res.writeHead(status);
|
|
60
|
+
res.end(bytes);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Accept a state-changing request only from this DSH Web application's origin.
|
|
64
|
+
*
|
|
65
|
+
* The gateway half rewrites `Origin`/`Host` to the upstream origin
|
|
66
|
+
* before forwarding (lan-gate-server.cjs `cleanHeaders`), so a phone request
|
|
67
|
+
* that already cleared the pairing wall presents here as same-origin; a
|
|
68
|
+
* request with neither header falls back to the Fetch metadata.
|
|
69
|
+
* @param req - the inbound request.
|
|
70
|
+
* @returns true when the request may mutate the workspace.
|
|
71
|
+
*/
|
|
72
|
+
export function sameOriginPost(req) {
|
|
73
|
+
const fetchSite = req.headers['sec-fetch-site'];
|
|
74
|
+
if (fetchSite === 'cross-site')
|
|
75
|
+
return false;
|
|
76
|
+
const origin = req.headers.origin;
|
|
77
|
+
if (origin === undefined)
|
|
78
|
+
return fetchSite === 'same-origin' || fetchSite === 'same-site' || fetchSite === 'none';
|
|
79
|
+
const host = req.headers.host;
|
|
80
|
+
if (host === undefined)
|
|
81
|
+
return false;
|
|
82
|
+
try {
|
|
83
|
+
const parsed = new URL(origin);
|
|
84
|
+
return (parsed.protocol === 'http:' || parsed.protocol === 'https:') && parsed.host === host;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Reject a resolved path that is not rooted below the expected directory.
|
|
92
|
+
* @param root - the directory the target must stay inside.
|
|
93
|
+
* @param target - the resolved candidate path.
|
|
94
|
+
* @throws when the target escapes the root.
|
|
95
|
+
*/
|
|
96
|
+
export function ensurePathInside(root, target) {
|
|
97
|
+
const rel = relative(root, target);
|
|
98
|
+
if (rel !== '' && (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel))) {
|
|
99
|
+
throw new UploadError(400, 'path-escape', `resolved upload path escapes its workspace root: ${target}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Convert an untrusted browser label into one portable leaf filename.
|
|
104
|
+
*
|
|
105
|
+
* Everything that could steer the write out of the upload directory is gone
|
|
106
|
+
* after this: only the basename survives (so `../../etc/passwd` becomes
|
|
107
|
+
* `passwd`), separators and control characters become `_`, leading dots are
|
|
108
|
+
* dropped, and the Windows reserved device names are prefixed. Whitespace
|
|
109
|
+
* folds to `_` rather than being kept: the client appends the result to the
|
|
110
|
+
* composer draft as an `@path` mention, and a mention with a space in it is
|
|
111
|
+
* broken for the agent reading it, not just for the chip parser. Length is
|
|
112
|
+
* capped in BYTES because the label arrives as UTF-8.
|
|
113
|
+
* @param raw - browser-supplied filename.
|
|
114
|
+
* @returns a single safe leaf name, never empty.
|
|
115
|
+
*/
|
|
116
|
+
export function safeUploadName(raw) {
|
|
117
|
+
const leaf = basename(raw.replaceAll('\\', '/')).normalize('NFC');
|
|
118
|
+
let cleaned = leaf
|
|
119
|
+
.replace(/[<>:"|?*\u0000-\u001f/\\]/gu, '_')
|
|
120
|
+
.replace(/\s+/gu, '_')
|
|
121
|
+
.replace(/^\.+/u, '')
|
|
122
|
+
.trim()
|
|
123
|
+
.replace(/[. ]+$/u, '');
|
|
124
|
+
if (/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(cleaned))
|
|
125
|
+
cleaned = `_${cleaned}`;
|
|
126
|
+
const candidate = cleaned === '' ? 'upload.bin' : cleaned;
|
|
127
|
+
if (Buffer.byteLength(candidate) <= MAX_NAME_BYTES)
|
|
128
|
+
return candidate;
|
|
129
|
+
const extension = extname(candidate).slice(0, 20);
|
|
130
|
+
const budget = Math.max(1, MAX_NAME_BYTES - Buffer.byteLength(extension));
|
|
131
|
+
let stem = candidate.slice(0, Math.max(1, candidate.length - extension.length));
|
|
132
|
+
while (Buffer.byteLength(stem) > budget)
|
|
133
|
+
stem = stem.slice(0, -1);
|
|
134
|
+
return `${stem}${extension}`;
|
|
135
|
+
}
|
|
136
|
+
function singleQuery(url, key) {
|
|
137
|
+
const values = url.searchParams.getAll(key);
|
|
138
|
+
const value = values[0];
|
|
139
|
+
if (values.length !== 1 || value === undefined || value === '') {
|
|
140
|
+
throw new UploadError(400, 'bad-request', `${key} is required exactly once`);
|
|
141
|
+
}
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
/** Create the directory if absent, then prove it is a real directory inside the workspace. */
|
|
145
|
+
async function ensureManagedDirectory(workspace, path) {
|
|
146
|
+
try {
|
|
147
|
+
await mkdir(path, { mode: 0o700 });
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (!isErrnoCode(error, 'EEXIST'))
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
const entry = await lstat(path);
|
|
154
|
+
// A symlink here would be the one way a prior workspace write could still
|
|
155
|
+
// redirect the bytes elsewhere — realpath alone would happily follow it.
|
|
156
|
+
if (entry.isSymbolicLink()) {
|
|
157
|
+
throw new UploadError(400, 'path-escape', `upload directory is a symbolic link: ${path}`);
|
|
158
|
+
}
|
|
159
|
+
if (!entry.isDirectory())
|
|
160
|
+
throw new UploadError(400, 'path-escape', `upload path is not a directory: ${path}`);
|
|
161
|
+
const canonical = await realpath(path);
|
|
162
|
+
ensurePathInside(workspace, canonical);
|
|
163
|
+
return canonical;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Resolve (and create) the upload directory of one live session.
|
|
167
|
+
* @param ctx - host context carrying the sessions service.
|
|
168
|
+
* @param sessionId - the session whose workspace receives the file.
|
|
169
|
+
* @returns the canonical and visible upload directories.
|
|
170
|
+
* @throws 404 when no live session has that id.
|
|
171
|
+
*/
|
|
172
|
+
async function sessionUploadRoot(ctx, sessionId) {
|
|
173
|
+
const session = ctx.sessions.get(sessionId);
|
|
174
|
+
if (session === undefined)
|
|
175
|
+
throw new UploadError(404, 'session-not-found', `live Session not found: ${sessionId}`);
|
|
176
|
+
const cwd = session.header.cwd;
|
|
177
|
+
if (cwd === undefined || !isAbsolute(cwd)) {
|
|
178
|
+
throw new UploadError(404, 'session-not-found', `Session has no absolute workspace: ${sessionId}`);
|
|
179
|
+
}
|
|
180
|
+
const visibleWorkspace = resolve(cwd);
|
|
181
|
+
const workspace = await realpath(visibleWorkspace);
|
|
182
|
+
const visibleRoot = join(visibleWorkspace, UPLOAD_DIR);
|
|
183
|
+
const writeRoot = await ensureManagedDirectory(workspace, visibleRoot);
|
|
184
|
+
return { writeRoot, visibleRoot };
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Claim one not-yet-existing leaf name, suffixing `-1`, `-2`, … on collision.
|
|
188
|
+
* `wx` makes the claim atomic, so two concurrent uploads of the same name
|
|
189
|
+
* cannot both win the same path.
|
|
190
|
+
*/
|
|
191
|
+
async function openUnique(directory, filename) {
|
|
192
|
+
const extension = extname(filename);
|
|
193
|
+
const stem = filename.slice(0, filename.length - extension.length) || 'upload';
|
|
194
|
+
for (let n = 0; n < MAX_COLLISION_TRIES; n += 1) {
|
|
195
|
+
const path = join(directory, n === 0 ? `${stem}${extension}` : `${stem}-${n}${extension}`);
|
|
196
|
+
ensurePathInside(directory, path);
|
|
197
|
+
try {
|
|
198
|
+
return { handle: await open(path, 'wx', 0o600), path };
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (!isErrnoCode(error, 'EEXIST'))
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
throw new UploadError(409, 'name-taken', `too many files named like ${filename}`);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Stream the request body onto disk under a running byte cap.
|
|
209
|
+
* @param req - the request whose body is the file.
|
|
210
|
+
* @param directory - canonical upload directory.
|
|
211
|
+
* @param filename - sanitized leaf name.
|
|
212
|
+
* @param maxBytes - hard cap; exceeding it aborts and unlinks.
|
|
213
|
+
* @returns the absolute path written and its byte count.
|
|
214
|
+
*/
|
|
215
|
+
async function writeUpload(req, directory, filename, maxBytes) {
|
|
216
|
+
const declared = req.headers['content-length'];
|
|
217
|
+
const expected = declared === undefined ? undefined : Number(declared);
|
|
218
|
+
if (expected !== undefined && (!Number.isSafeInteger(expected) || expected < 0)) {
|
|
219
|
+
throw new UploadError(400, 'bad-request', 'Content-Length is not a byte count');
|
|
220
|
+
}
|
|
221
|
+
// Reject the oversized upload before a single byte is read, so the phone
|
|
222
|
+
// gets its 413 without spending the whole body on the radio.
|
|
223
|
+
if (expected !== undefined && expected > maxBytes) {
|
|
224
|
+
throw new UploadError(413, 'too-large', `upload exceeds the ${maxBytes}-byte limit`);
|
|
225
|
+
}
|
|
226
|
+
const { handle, path } = await openUnique(directory, filename);
|
|
227
|
+
let received = 0;
|
|
228
|
+
try {
|
|
229
|
+
for await (const chunk of req) {
|
|
230
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
231
|
+
received += bytes.length;
|
|
232
|
+
// Chunked bodies declare no length, so the cap must also hold here.
|
|
233
|
+
if (received > maxBytes)
|
|
234
|
+
throw new UploadError(413, 'too-large', `upload exceeds the ${maxBytes}-byte limit`);
|
|
235
|
+
await handle.write(bytes);
|
|
236
|
+
}
|
|
237
|
+
if (expected !== undefined && received !== expected) {
|
|
238
|
+
throw new UploadError(400, 'truncated', `upload body size mismatch: expected ${expected}, received ${received}`);
|
|
239
|
+
}
|
|
240
|
+
await handle.close();
|
|
241
|
+
return { path, bytes: received };
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
await handle.close().catch(() => { });
|
|
245
|
+
await rm(path, { force: true }).catch(() => { });
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Handle one `POST {@link UPLOAD_ROUTE}?session=<id>&name=<file>` request.
|
|
251
|
+
*
|
|
252
|
+
* Exported so an integration check can drive it with a plain node:http server
|
|
253
|
+
* and a fake sessions service instead of booting a harness.
|
|
254
|
+
* @param ctx - host context carrying the sessions service and logger.
|
|
255
|
+
* @param maxBytes - body cap.
|
|
256
|
+
* @param req - inbound request; its body is the raw file.
|
|
257
|
+
* @param res - the response this call owns end to end.
|
|
258
|
+
*/
|
|
259
|
+
export async function handleUpload(ctx, maxBytes, req, res) {
|
|
260
|
+
if (req.method !== 'POST') {
|
|
261
|
+
res.setHeader('Allow', 'POST');
|
|
262
|
+
responseJson(res, 405, { ok: false, error: { code: 'method-not-allowed', message: 'Use POST' } });
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (!sameOriginPost(req)) {
|
|
266
|
+
const error = { code: 'origin-rejected', message: 'The request must originate from this DSH Web application' };
|
|
267
|
+
responseJson(res, 403, { ok: false, error });
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
const url = new URL(req.url ?? UPLOAD_ROUTE, 'http://dsh.internal');
|
|
272
|
+
const sessionId = singleQuery(url, 'session');
|
|
273
|
+
const filename = safeUploadName(singleQuery(url, 'name'));
|
|
274
|
+
const root = await sessionUploadRoot(ctx, sessionId);
|
|
275
|
+
const written = await writeUpload(req, root.writeRoot, filename, maxBytes);
|
|
276
|
+
const leaf = basename(written.path);
|
|
277
|
+
responseJson(res, 201, {
|
|
278
|
+
ok: true,
|
|
279
|
+
relPath: `${UPLOAD_DIR}/${leaf}`,
|
|
280
|
+
absolutePath: join(root.visibleRoot, leaf),
|
|
281
|
+
filename: leaf,
|
|
282
|
+
bytes: written.bytes,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
const status = error instanceof UploadError ? error.status : 400;
|
|
287
|
+
const code = error instanceof UploadError ? error.code : 'upload-rejected';
|
|
288
|
+
ctx.logger.warn('dsh-mobile-nav upload rejected: %s', message(error));
|
|
289
|
+
responseJson(res, status, { ok: false, error: { code, message: message(error) } });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Host half: mount the upload route wherever a webServer and live sessions
|
|
294
|
+
* exist. Both are injected INSIDE apply rather than declared as a top-level
|
|
295
|
+
* `inject`, so the plugin row still loads (and the browser half still ships)
|
|
296
|
+
* in a composition without them — Electron carries no webServer.
|
|
297
|
+
* @param ctx - host plugin context.
|
|
298
|
+
* @param config - optional body cap override.
|
|
299
|
+
*/
|
|
300
|
+
export function apply(ctx, config = {}) {
|
|
301
|
+
const maxBytes = config.maxUploadBytes ?? DEFAULT_MAX_UPLOAD_BYTES;
|
|
302
|
+
ctx.inject(['webServer', 'sessions'], (webCtx) => {
|
|
303
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
304
|
+
kind: 'exact',
|
|
305
|
+
path: UPLOAD_ROUTE,
|
|
306
|
+
handler: (req, res) => handleUpload(webCtx, maxBytes, req, res),
|
|
307
|
+
}), 'dsh-mobile-nav: upload route');
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|