codex-webstrapper 0.9.0 → 0.9.7
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 +245 -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,241 @@
|
|
|
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 — constrain to viewport */
|
|
343
|
+
.h-toolbar {
|
|
344
|
+
max-width: 100vw !important;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.h-toolbar button {
|
|
348
|
+
flex-shrink: 1 !important;
|
|
349
|
+
min-width: 0 !important;
|
|
350
|
+
overflow: hidden !important;
|
|
351
|
+
text-overflow: ellipsis !important;
|
|
352
|
+
white-space: nowrap !important;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* Prevent iOS auto-zoom on focus (triggers when font-size < 16px) */
|
|
356
|
+
input, textarea, select {
|
|
357
|
+
font-size: 16px !important;
|
|
358
|
+
max-width: 100% !important;
|
|
359
|
+
box-sizing: border-box !important;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
[contenteditable="true"] {
|
|
363
|
+
max-width: 100% !important;
|
|
364
|
+
box-sizing: border-box !important;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
[contenteditable="true"]:focus {
|
|
368
|
+
font-size: 16px !important;
|
|
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
|
+
When collapsed the app sets opacity-0 but the element still covers
|
|
429
|
+
the screen (translate is 0 because we zeroed the token). We must
|
|
430
|
+
disable pointer events so it doesn't block taps on main content. */
|
|
431
|
+
.window-fx-sidebar-surface,
|
|
432
|
+
.w-token-sidebar {
|
|
433
|
+
width: 85vw !important;
|
|
434
|
+
max-width: 320px !important;
|
|
435
|
+
z-index: 50 !important;
|
|
436
|
+
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.5) !important;
|
|
437
|
+
background-color: rgb(24, 24, 24) !important;
|
|
438
|
+
pointer-events: none !important;
|
|
439
|
+
transition: opacity 0.3s ease, pointer-events 0s linear 0.3s !important;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/* Re-enable pointer events only when sidebar is visible (open) */
|
|
443
|
+
.window-fx-sidebar-surface.opacity-100,
|
|
444
|
+
.w-token-sidebar.opacity-100 {
|
|
445
|
+
pointer-events: auto !important;
|
|
446
|
+
transition: opacity 0.3s ease, pointer-events 0s linear 0s !important;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/* Main content takes full width */
|
|
450
|
+
.main-surface,
|
|
451
|
+
.left-token-sidebar {
|
|
452
|
+
left: 0 !important;
|
|
453
|
+
width: 100vw !important;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/* Header toolbar — swipeable so all buttons remain accessible.
|
|
457
|
+
When the sidebar is collapsed the portal area expands and can
|
|
458
|
+
push right-side buttons off-screen; scrolling keeps them
|
|
459
|
+
reachable via horizontal swipe. */
|
|
460
|
+
.h-toolbar {
|
|
461
|
+
overflow-x: auto !important;
|
|
462
|
+
overflow-y: hidden !important;
|
|
463
|
+
-webkit-overflow-scrolling: touch !important;
|
|
464
|
+
scrollbar-width: none !important;
|
|
465
|
+
}
|
|
466
|
+
.h-toolbar::-webkit-scrollbar {
|
|
467
|
+
display: none !important;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/* Prevent header-left from consuming more than half the bar.
|
|
471
|
+
The app sets an inline min-width via CSS vars that over-allocates
|
|
472
|
+
space for the portal area — override it so buttons fit. */
|
|
473
|
+
.app-header-left {
|
|
474
|
+
width: auto !important;
|
|
475
|
+
min-width: 0 !important;
|
|
476
|
+
max-width: 50vw !important;
|
|
477
|
+
flex-shrink: 0 !important;
|
|
478
|
+
padding-left: 4px !important;
|
|
479
|
+
padding-right: 0 !important;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* Collapse the empty portal gap when sidebar is hidden */
|
|
483
|
+
.app-header-left-portal {
|
|
484
|
+
gap: 0 !important;
|
|
485
|
+
padding-right: 2px !important;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/* Keep right-side buttons from shrinking below usable size */
|
|
489
|
+
.h-toolbar button {
|
|
490
|
+
flex-shrink: 0 !important;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/* Use stable viewport height */
|
|
494
|
+
#root {
|
|
495
|
+
height: calc(var(--cw-vh, 1vh) * 100) !important;
|
|
496
|
+
height: 100dvh !important;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
`;
|
|
500
|
+
document.head.appendChild(style);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// ---------------------------------------------------------------------------
|
|
504
|
+
// Viewport height stabilizer (mobile keyboard / chrome bar)
|
|
505
|
+
// ---------------------------------------------------------------------------
|
|
506
|
+
|
|
507
|
+
function installViewportStabilizer() {
|
|
508
|
+
if (!window.visualViewport) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const update = () => {
|
|
513
|
+
document.documentElement.style.setProperty(
|
|
514
|
+
"--cw-vh",
|
|
515
|
+
window.visualViewport.height * 0.01 + "px"
|
|
516
|
+
);
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
window.visualViewport.addEventListener("resize", update);
|
|
520
|
+
update();
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
// Auto-collapse sidebar on mobile first load
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
526
|
+
|
|
527
|
+
function autoCollapseSidebarOnMobile() {
|
|
528
|
+
if (window.innerWidth > 600) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// The React app restores sidebar state from storage after mount.
|
|
533
|
+
// We poll briefly after DOM ready to catch the sidebar in its open state.
|
|
534
|
+
function tryCollapse(attempts) {
|
|
535
|
+
if (attempts <= 0) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const btn = document.querySelector('button[aria-label="Hide sidebar"]');
|
|
539
|
+
if (btn) {
|
|
540
|
+
btn.click();
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
setTimeout(() => tryCollapse(attempts - 1), 200);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (document.readyState === "loading") {
|
|
547
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
548
|
+
// Give React time to mount and restore sidebar state
|
|
549
|
+
setTimeout(() => tryCollapse(15), 500);
|
|
550
|
+
});
|
|
551
|
+
} else {
|
|
552
|
+
setTimeout(() => tryCollapse(15), 500);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
320
556
|
function normalizeContextMenuItems(items) {
|
|
321
557
|
if (!Array.isArray(items)) {
|
|
322
558
|
return [];
|
|
@@ -344,6 +580,7 @@
|
|
|
344
580
|
|
|
345
581
|
window.removeEventListener("keydown", current.onKeyDown, true);
|
|
346
582
|
window.removeEventListener("resize", current.onWindowChange, true);
|
|
583
|
+
clearTimeout(current.resizeDebounce);
|
|
347
584
|
|
|
348
585
|
current.root.remove();
|
|
349
586
|
current.resolve(result ?? null);
|
|
@@ -449,8 +686,10 @@
|
|
|
449
686
|
}
|
|
450
687
|
};
|
|
451
688
|
|
|
689
|
+
let resizeDebounce = null;
|
|
452
690
|
const onWindowChange = () => {
|
|
453
|
-
|
|
691
|
+
clearTimeout(resizeDebounce);
|
|
692
|
+
resizeDebounce = setTimeout(() => closeContextMenu(null), 300);
|
|
454
693
|
};
|
|
455
694
|
|
|
456
695
|
root.addEventListener("mousedown", onRootMouseDown);
|
|
@@ -462,7 +701,8 @@
|
|
|
462
701
|
root,
|
|
463
702
|
resolve,
|
|
464
703
|
onKeyDown,
|
|
465
|
-
onWindowChange
|
|
704
|
+
onWindowChange,
|
|
705
|
+
get resizeDebounce() { return resizeDebounce; }
|
|
466
706
|
};
|
|
467
707
|
|
|
468
708
|
window.addEventListener("keydown", onKeyDown, true);
|
|
@@ -675,6 +915,9 @@
|
|
|
675
915
|
window.codexWindowType = "electron";
|
|
676
916
|
window.electronBridge = electronBridge;
|
|
677
917
|
installBrowserCompatibilityShims();
|
|
918
|
+
ensureMobileStyles();
|
|
919
|
+
installViewportStabilizer();
|
|
920
|
+
autoCollapseSidebarOnMobile();
|
|
678
921
|
window.addEventListener("contextmenu", rememberContextMenuPosition, true);
|
|
679
922
|
|
|
680
923
|
connect();
|