codex-webstrapper 0.9.0 → 0.9.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/package.json +1 -1
- package/src/assets.mjs +12 -1
- package/src/auth.mjs +21 -2
- package/src/bridge-shim.js +204 -2
- package/src/server.mjs +1 -1
package/package.json
CHANGED
package/src/assets.mjs
CHANGED
|
@@ -151,13 +151,24 @@ export async function ensureExtractedAssets({
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
export async function buildPatchedIndexHtml(indexPath) {
|
|
154
|
-
|
|
154
|
+
let html = await fsp.readFile(indexPath, "utf8");
|
|
155
155
|
const shimTag = '<script src="/__webstrapper/shim.js"></script>';
|
|
156
156
|
|
|
157
157
|
if (html.includes(shimTag)) {
|
|
158
158
|
return html;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
// Replace or inject mobile viewport meta — the Electron app's default
|
|
162
|
+
// viewport lacks maximum-scale and user-scalable=no, causing iOS zoom issues
|
|
163
|
+
const viewportMeta =
|
|
164
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">';
|
|
165
|
+
const existingViewport = html.match(/<meta\s+name=["']viewport["'][^>]*>/i);
|
|
166
|
+
if (existingViewport) {
|
|
167
|
+
html = html.replace(existingViewport[0], viewportMeta);
|
|
168
|
+
} else if (html.includes("</head>")) {
|
|
169
|
+
html = html.replace("</head>", ` ${viewportMeta}\n</head>`);
|
|
170
|
+
}
|
|
171
|
+
|
|
161
172
|
if (html.includes("</head>")) {
|
|
162
173
|
return html.replace("</head>", ` ${shimTag}\n</head>`);
|
|
163
174
|
}
|
package/src/auth.mjs
CHANGED
|
@@ -126,10 +126,29 @@ export function createAuthController({ token, sessionStore, cookieName = SESSION
|
|
|
126
126
|
);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
function requireAuth(req, res) {
|
|
129
|
+
function requireAuth(req, res, parsedUrl) {
|
|
130
130
|
if (hasValidSession(req)) {
|
|
131
131
|
return true;
|
|
132
132
|
}
|
|
133
|
+
|
|
134
|
+
// Auto-authenticate if a valid token is in the URL query string.
|
|
135
|
+
// This lets iOS "Add to Home Screen" bookmarks work — the saved URL
|
|
136
|
+
// includes ?token=X, so each launch re-authenticates automatically.
|
|
137
|
+
if (parsedUrl) {
|
|
138
|
+
const provided = parsedUrl.searchParams.get("token") || "";
|
|
139
|
+
if (provided && provided === token) {
|
|
140
|
+
const session = sessionStore.createSession();
|
|
141
|
+
const cookie = serializeCookie(cookieName, session.id, {
|
|
142
|
+
maxAgeSeconds: Math.floor(sessionStore.ttlMs / 1000),
|
|
143
|
+
httpOnly: true,
|
|
144
|
+
sameSite: "Lax",
|
|
145
|
+
path: "/"
|
|
146
|
+
});
|
|
147
|
+
res.setHeader("set-cookie", cookie);
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
133
152
|
rejectUnauthorized(res);
|
|
134
153
|
return false;
|
|
135
154
|
}
|
|
@@ -153,7 +172,7 @@ export function createAuthController({ token, sessionStore, cookieName = SESSION
|
|
|
153
172
|
|
|
154
173
|
res.statusCode = 302;
|
|
155
174
|
res.setHeader("set-cookie", cookie);
|
|
156
|
-
res.setHeader("location",
|
|
175
|
+
res.setHeader("location", `/?token=${encodeURIComponent(provided)}`);
|
|
157
176
|
res.end();
|
|
158
177
|
}
|
|
159
178
|
|
package/src/bridge-shim.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const mainMessageHistory = [];
|
|
8
8
|
const CONTEXT_MENU_ROOT_ID = "__codex-webstrap-context-menu-root";
|
|
9
9
|
const CONTEXT_MENU_STYLE_ID = "__codex-webstrap-context-menu-style";
|
|
10
|
+
const MOBILE_STYLE_ID = "__codex-webstrap-mobile-style";
|
|
10
11
|
|
|
11
12
|
let ws = null;
|
|
12
13
|
let connected = false;
|
|
@@ -317,6 +318,200 @@
|
|
|
317
318
|
document.head.appendChild(style);
|
|
318
319
|
}
|
|
319
320
|
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
// Mobile-responsive CSS overrides
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
|
|
325
|
+
function ensureMobileStyles() {
|
|
326
|
+
if (document.getElementById(MOBILE_STYLE_ID)) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const style = document.createElement("style");
|
|
331
|
+
style.id = MOBILE_STYLE_ID;
|
|
332
|
+
style.textContent = `
|
|
333
|
+
/* ==== Tablet & small screen fixes (max-width: 768px) ==== */
|
|
334
|
+
@media (max-width: 768px) {
|
|
335
|
+
|
|
336
|
+
/* Viewport stabilization */
|
|
337
|
+
html, body {
|
|
338
|
+
overflow-x: hidden !important;
|
|
339
|
+
-webkit-text-size-adjust: 100% !important;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/* Header overflow fix */
|
|
343
|
+
.h-toolbar {
|
|
344
|
+
max-width: 100vw !important;
|
|
345
|
+
overflow: hidden !important;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.h-toolbar button {
|
|
349
|
+
flex-shrink: 1 !important;
|
|
350
|
+
min-width: 0 !important;
|
|
351
|
+
overflow: hidden !important;
|
|
352
|
+
text-overflow: ellipsis !important;
|
|
353
|
+
white-space: nowrap !important;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* Prevent iOS auto-zoom on focus (triggers when font-size < 16px) */
|
|
357
|
+
input, textarea, select, [contenteditable="true"] {
|
|
358
|
+
font-size: 16px !important;
|
|
359
|
+
max-width: 100% !important;
|
|
360
|
+
box-sizing: border-box !important;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/* Disable double-tap zoom */
|
|
364
|
+
* {
|
|
365
|
+
touch-action: manipulation;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
[contenteditable="true"]:focus {
|
|
369
|
+
scroll-margin-bottom: 20px;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* Terminal — constrain height */
|
|
373
|
+
[class*="terminal"],
|
|
374
|
+
[class*="Terminal"] {
|
|
375
|
+
max-height: 40vh !important;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/* Dialogs/modals — fit screen */
|
|
379
|
+
[role="dialog"] {
|
|
380
|
+
max-width: calc(100vw - 16px) !important;
|
|
381
|
+
max-height: calc(100dvh - 32px) !important;
|
|
382
|
+
overflow-y: auto !important;
|
|
383
|
+
margin: 8px !important;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/* Context menu — responsive sizing */
|
|
387
|
+
#${CONTEXT_MENU_ROOT_ID} .cw-menu {
|
|
388
|
+
min-width: min(220px, calc(100vw - 24px)) !important;
|
|
389
|
+
max-width: calc(100vw - 24px) !important;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
#${CONTEXT_MENU_ROOT_ID} .cw-item--submenu > .cw-menu {
|
|
393
|
+
position: fixed !important;
|
|
394
|
+
left: 12px !important;
|
|
395
|
+
right: 12px !important;
|
|
396
|
+
top: auto !important;
|
|
397
|
+
width: auto !important;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/* Overflow prevention */
|
|
401
|
+
pre, code {
|
|
402
|
+
overflow-x: auto !important;
|
|
403
|
+
max-width: 100% !important;
|
|
404
|
+
word-break: break-word !important;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* ==== Phone layout (max-width: 600px) ==== */
|
|
409
|
+
@media (max-width: 600px) {
|
|
410
|
+
|
|
411
|
+
/* Safe area support for notched devices */
|
|
412
|
+
body {
|
|
413
|
+
padding-top: env(safe-area-inset-top) !important;
|
|
414
|
+
padding-bottom: env(safe-area-inset-bottom) !important;
|
|
415
|
+
padding-left: env(safe-area-inset-left) !important;
|
|
416
|
+
padding-right: env(safe-area-inset-right) !important;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* CRITICAL: Collapse sidebar token so main content gets full width.
|
|
420
|
+
The Codex app uses --spacing-token-sidebar with a 240px clamp minimum
|
|
421
|
+
which is far too wide on phones. Setting it to 0 makes the sidebar an
|
|
422
|
+
overlay instead of pushing the main content off-screen. */
|
|
423
|
+
:root {
|
|
424
|
+
--spacing-token-sidebar: 0px !important;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/* Sidebar becomes full-screen overlay when open */
|
|
428
|
+
.window-fx-sidebar-surface,
|
|
429
|
+
.w-token-sidebar {
|
|
430
|
+
width: 85vw !important;
|
|
431
|
+
max-width: 320px !important;
|
|
432
|
+
z-index: 50 !important;
|
|
433
|
+
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.5) !important;
|
|
434
|
+
background-color: rgb(24, 24, 24) !important;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/* Main content takes full width */
|
|
438
|
+
.main-surface,
|
|
439
|
+
.left-token-sidebar {
|
|
440
|
+
left: 0 !important;
|
|
441
|
+
width: 100vw !important;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/* Header left section — collapse to fit phone */
|
|
445
|
+
.app-header-left {
|
|
446
|
+
width: auto !important;
|
|
447
|
+
max-width: 50vw !important;
|
|
448
|
+
flex-shrink: 1 !important;
|
|
449
|
+
padding-left: 8px !important;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/* Use stable viewport height */
|
|
453
|
+
#root {
|
|
454
|
+
height: calc(var(--cw-vh, 1vh) * 100) !important;
|
|
455
|
+
height: 100dvh !important;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
`;
|
|
459
|
+
document.head.appendChild(style);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ---------------------------------------------------------------------------
|
|
463
|
+
// Viewport height stabilizer (mobile keyboard / chrome bar)
|
|
464
|
+
// ---------------------------------------------------------------------------
|
|
465
|
+
|
|
466
|
+
function installViewportStabilizer() {
|
|
467
|
+
if (!window.visualViewport) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const update = () => {
|
|
472
|
+
document.documentElement.style.setProperty(
|
|
473
|
+
"--cw-vh",
|
|
474
|
+
window.visualViewport.height * 0.01 + "px"
|
|
475
|
+
);
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
window.visualViewport.addEventListener("resize", update);
|
|
479
|
+
update();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ---------------------------------------------------------------------------
|
|
483
|
+
// Auto-collapse sidebar on mobile first load
|
|
484
|
+
// ---------------------------------------------------------------------------
|
|
485
|
+
|
|
486
|
+
function autoCollapseSidebarOnMobile() {
|
|
487
|
+
if (window.innerWidth > 600) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// The React app restores sidebar state from storage after mount.
|
|
492
|
+
// We poll briefly after DOM ready to catch the sidebar in its open state.
|
|
493
|
+
function tryCollapse(attempts) {
|
|
494
|
+
if (attempts <= 0) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const btn = document.querySelector('button[aria-label="Hide sidebar"]');
|
|
498
|
+
if (btn) {
|
|
499
|
+
btn.click();
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
setTimeout(() => tryCollapse(attempts - 1), 200);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (document.readyState === "loading") {
|
|
506
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
507
|
+
// Give React time to mount and restore sidebar state
|
|
508
|
+
setTimeout(() => tryCollapse(15), 500);
|
|
509
|
+
});
|
|
510
|
+
} else {
|
|
511
|
+
setTimeout(() => tryCollapse(15), 500);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
320
515
|
function normalizeContextMenuItems(items) {
|
|
321
516
|
if (!Array.isArray(items)) {
|
|
322
517
|
return [];
|
|
@@ -344,6 +539,7 @@
|
|
|
344
539
|
|
|
345
540
|
window.removeEventListener("keydown", current.onKeyDown, true);
|
|
346
541
|
window.removeEventListener("resize", current.onWindowChange, true);
|
|
542
|
+
clearTimeout(current.resizeDebounce);
|
|
347
543
|
|
|
348
544
|
current.root.remove();
|
|
349
545
|
current.resolve(result ?? null);
|
|
@@ -449,8 +645,10 @@
|
|
|
449
645
|
}
|
|
450
646
|
};
|
|
451
647
|
|
|
648
|
+
let resizeDebounce = null;
|
|
452
649
|
const onWindowChange = () => {
|
|
453
|
-
|
|
650
|
+
clearTimeout(resizeDebounce);
|
|
651
|
+
resizeDebounce = setTimeout(() => closeContextMenu(null), 300);
|
|
454
652
|
};
|
|
455
653
|
|
|
456
654
|
root.addEventListener("mousedown", onRootMouseDown);
|
|
@@ -462,7 +660,8 @@
|
|
|
462
660
|
root,
|
|
463
661
|
resolve,
|
|
464
662
|
onKeyDown,
|
|
465
|
-
onWindowChange
|
|
663
|
+
onWindowChange,
|
|
664
|
+
get resizeDebounce() { return resizeDebounce; }
|
|
466
665
|
};
|
|
467
666
|
|
|
468
667
|
window.addEventListener("keydown", onKeyDown, true);
|
|
@@ -675,6 +874,9 @@
|
|
|
675
874
|
window.codexWindowType = "electron";
|
|
676
875
|
window.electronBridge = electronBridge;
|
|
677
876
|
installBrowserCompatibilityShims();
|
|
877
|
+
ensureMobileStyles();
|
|
878
|
+
installViewportStabilizer();
|
|
879
|
+
autoCollapseSidebarOnMobile();
|
|
678
880
|
window.addEventListener("contextmenu", rememberContextMenuPosition, true);
|
|
679
881
|
|
|
680
882
|
connect();
|