aicodeman 1.8.1 → 1.8.2
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/dist/config/webview-limits.d.ts +36 -0
- package/dist/config/webview-limits.d.ts.map +1 -0
- package/dist/config/webview-limits.js +40 -0
- package/dist/config/webview-limits.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/webview.d.ts +84 -0
- package/dist/types/webview.d.ts.map +1 -0
- package/dist/types/webview.js +21 -0
- package/dist/types/webview.js.map +1 -0
- package/dist/web/middleware/auth.d.ts.map +1 -1
- package/dist/web/middleware/auth.js +75 -3
- package/dist/web/middleware/auth.js.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.adc992cd.js +38 -0
- package/dist/web/public/app.adc992cd.js.br +0 -0
- package/dist/web/public/app.adc992cd.js.gz +0 -0
- package/dist/web/public/{constants.21a0a83e.js → constants.e7175b95.js} +3 -0
- package/dist/web/public/constants.e7175b95.js.br +0 -0
- package/dist/web/public/{constants.21a0a83e.js.gz → constants.e7175b95.js.gz} +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/i18n.4dcf1b87.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +70 -6
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
- package/dist/web/public/mobile-handlers.bc2b8c1d.js.gz +0 -0
- package/dist/web/public/mobile.2ebdbb03.css.gz +0 -0
- package/dist/web/public/notification-manager.fca811fd.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.58582d4c.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-ui.fd39c53c.js.gz +0 -0
- package/dist/web/public/settings-ui.478c8242.js.gz +0 -0
- package/dist/web/public/styles.3f797179.css +1 -0
- package/dist/web/public/styles.3f797179.css.br +0 -0
- package/dist/web/public/styles.3f797179.css.gz +0 -0
- package/dist/web/public/subagent-windows.69b60c35.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.2d582bf6.js +3 -0
- package/dist/web/public/terminal-ui.2d582bf6.js.br +0 -0
- package/dist/web/public/terminal-ui.2d582bf6.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
- package/dist/web/public/webview-tabs.js +445 -0
- package/dist/web/public/webview-tabs.js.br +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/index.d.ts +1 -0
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +1 -0
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/routes/webview-routes.d.ts +50 -0
- package/dist/web/routes/webview-routes.d.ts.map +1 -0
- package/dist/web/routes/webview-routes.js +541 -0
- package/dist/web/routes/webview-routes.js.map +1 -0
- package/dist/web/schemas.d.ts +26 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +34 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +11 -5
- package/dist/web/server.js.map +1 -1
- package/dist/web/sse-events.d.ts +7 -1
- package/dist/web/sse-events.d.ts.map +1 -1
- package/dist/web/sse-events.js +8 -1
- package/dist/web/sse-events.js.map +1 -1
- package/dist/web/webview-proxy.d.ts +181 -0
- package/dist/web/webview-proxy.d.ts.map +1 -0
- package/dist/web/webview-proxy.js +491 -0
- package/dist/web/webview-proxy.js.map +1 -0
- package/dist/webview-capabilities.d.ts +55 -0
- package/dist/webview-capabilities.d.ts.map +1 -0
- package/dist/webview-capabilities.js +102 -0
- package/dist/webview-capabilities.js.map +1 -0
- package/dist/webview-store.d.ts +16 -0
- package/dist/webview-store.d.ts.map +1 -0
- package/dist/webview-store.js +34 -0
- package/dist/webview-store.js.map +1 -0
- package/package.json +2 -1
- package/dist/web/public/app.2e95c82d.js +0 -38
- package/dist/web/public/app.2e95c82d.js.br +0 -0
- package/dist/web/public/app.2e95c82d.js.gz +0 -0
- package/dist/web/public/constants.21a0a83e.js.br +0 -0
- package/dist/web/public/styles.5339e2f0.css +0 -1
- package/dist/web/public/styles.5339e2f0.css.br +0 -0
- package/dist/web/public/styles.5339e2f0.css.gz +0 -0
- package/dist/web/public/terminal-ui.9ea677e9.js +0 -3
- package/dist/web/public/terminal-ui.9ea677e9.js.br +0 -0
- package/dist/web/public/terminal-ui.9ea677e9.js.gz +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Web tabs: saved dashboard URLs rendered as tabs beside agent
|
|
3
|
+
* sessions, so Codeman is one mission control instead of Codeman plus a pile of
|
|
4
|
+
* browser tabs.
|
|
5
|
+
*
|
|
6
|
+
* Each open dashboard is an <iframe> inside #webviewLayer, which covers the
|
|
7
|
+
* terminal while a web tab is active. Frames stay MOUNTED while hidden, because a
|
|
8
|
+
* dashboard that reloads and re-authenticates on every tab switch is worse than
|
|
9
|
+
* the browser tab it replaced. `maxLiveFrames` (from the server) bounds that with
|
|
10
|
+
* least-recently-viewed eviction.
|
|
11
|
+
*
|
|
12
|
+
* Sandboxing: a proxied dashboard is served from Codeman's own origin, so the
|
|
13
|
+
* iframe deliberately omits `allow-same-origin` unless the dashboard is marked
|
|
14
|
+
* trusted. Without that omission the page could read this document and call the
|
|
15
|
+
* API that spawns agents.
|
|
16
|
+
*
|
|
17
|
+
* @mixin Extends CodemanApp.prototype via Object.assign
|
|
18
|
+
* @dependency app.js, api-client.js, constants.js (escapeHtml)
|
|
19
|
+
* @loadorder 12.5 of 16, after session-ui.js (needs the tab strip), before api-client.js
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
Object.assign(CodemanApp.prototype, {
|
|
23
|
+
// ── State ─────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/** Load the saved list and restore which tabs were open. */
|
|
26
|
+
async initWebviews() {
|
|
27
|
+
this.webviews = this.webviews || new Map();
|
|
28
|
+
this.webviewOrder = this.webviewOrder || [];
|
|
29
|
+
this.activeWebviewId = this.activeWebviewId || null;
|
|
30
|
+
this._webviewMaxFrames = this._webviewMaxFrames || 6;
|
|
31
|
+
this._webviewFrameLru = this._webviewFrameLru || [];
|
|
32
|
+
|
|
33
|
+
await this.refreshWebviews();
|
|
34
|
+
|
|
35
|
+
// Restore the previously open web tabs (per device: which dashboards you keep
|
|
36
|
+
// open is a workspace-layout choice, not something to sync across machines).
|
|
37
|
+
let saved = [];
|
|
38
|
+
try {
|
|
39
|
+
saved = JSON.parse(localStorage.getItem('codeman-webview-order') || '[]');
|
|
40
|
+
} catch {
|
|
41
|
+
saved = [];
|
|
42
|
+
}
|
|
43
|
+
this.webviewOrder = saved.filter((id) => this.webviews.has(id));
|
|
44
|
+
this.renderSessionTabs();
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
async refreshWebviews() {
|
|
48
|
+
const data = await this._apiJson('/api/webviews');
|
|
49
|
+
if (!data) return;
|
|
50
|
+
this.webviews = new Map((data.webviews || []).map((w) => [w.id, w]));
|
|
51
|
+
if (typeof data.maxLiveFrames === 'number') this._webviewMaxFrames = data.maxLiveFrames;
|
|
52
|
+
this.renderWebviewMenuItems();
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/** SSE: the saved list changed (possibly on another device). */
|
|
56
|
+
async _onWebviewChanged(data) {
|
|
57
|
+
await this.refreshWebviews();
|
|
58
|
+
// A dashboard deleted elsewhere must not linger as a dead tab here.
|
|
59
|
+
if (data && data.action === 'deleted' && data.id) this._removeWebviewTab(data.id);
|
|
60
|
+
this.renderSessionTabs();
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
_persistWebviewOrder() {
|
|
64
|
+
try {
|
|
65
|
+
localStorage.setItem('codeman-webview-order', JSON.stringify(this.webviewOrder || []));
|
|
66
|
+
} catch {
|
|
67
|
+
/* private mode / quota, order is a convenience, never fatal */
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// ── Tab strip ─────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Tab HTML for every OPEN web tab, appended by _fullRenderSessionTabs().
|
|
75
|
+
* `startIndex` continues the Alt+N numbering after the session tabs.
|
|
76
|
+
*/
|
|
77
|
+
renderWebviewTabs(startIndex) {
|
|
78
|
+
if (!this.webviewOrder || this.webviewOrder.length === 0) return '';
|
|
79
|
+
const parts = [];
|
|
80
|
+
let idx = startIndex;
|
|
81
|
+
|
|
82
|
+
for (const id of this.webviewOrder) {
|
|
83
|
+
const webview = this.webviews.get(id);
|
|
84
|
+
if (!webview) continue;
|
|
85
|
+
const isActive = id === this.activeWebviewId;
|
|
86
|
+
const jsonId = escapeHtml(JSON.stringify(id));
|
|
87
|
+
const icon = webview.icon ? escapeHtml(webview.icon) : '';
|
|
88
|
+
|
|
89
|
+
parts.push(`<div class="session-tab session-tab--web ${isActive ? 'active' : ''}" data-webview-id="${escapeHtml(id)}"
|
|
90
|
+
onclick="app.handleWebviewTabClick(event, ${jsonId})"
|
|
91
|
+
tabindex="0" role="tab" aria-selected="${isActive ? 'true' : 'false'}"
|
|
92
|
+
aria-label="${escapeHtml(webview.name)} web tab" title="${escapeHtml(webview.url)}">
|
|
93
|
+
${idx < 9 ? '<span class="tab-number">' + (idx + 1) + '</span>' : ''}
|
|
94
|
+
<span class="tab-web-icon" aria-hidden="true">${icon || this._webviewGlobeIcon()}</span>
|
|
95
|
+
<span class="tab-info">
|
|
96
|
+
<span class="tab-name-row">
|
|
97
|
+
<span class="tab-name">${escapeHtml(webview.name)}</span>
|
|
98
|
+
</span>
|
|
99
|
+
</span>
|
|
100
|
+
<span class="tab-gear" onclick="event.stopPropagation(); app.showWebviewModal(${jsonId})" title="URL settings" aria-label="URL settings" tabindex="0">⚙</span>
|
|
101
|
+
<span class="tab-close" onclick="event.stopPropagation(); app.closeWebviewTab(${jsonId})" title="Close tab" aria-label="Close web tab" tabindex="0">×</span>
|
|
102
|
+
</div>`);
|
|
103
|
+
idx++;
|
|
104
|
+
}
|
|
105
|
+
return parts.join('');
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
_webviewGlobeIcon() {
|
|
109
|
+
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20"/></svg>';
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
handleWebviewTabClick(event, id) {
|
|
113
|
+
event?.preventDefault?.();
|
|
114
|
+
return this.openWebview(id);
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
/** Mark exactly one tab active across BOTH tab kinds. */
|
|
118
|
+
_updateActiveWebviewTab() {
|
|
119
|
+
const container = this.$('sessionTabs');
|
|
120
|
+
if (!container) return;
|
|
121
|
+
for (const tab of container.querySelectorAll('.session-tab[data-webview-id]')) {
|
|
122
|
+
tab.classList.toggle('active', tab.dataset.webviewId === this.activeWebviewId);
|
|
123
|
+
}
|
|
124
|
+
if (this.activeWebviewId) {
|
|
125
|
+
// A web tab is active, so no session tab may also look active.
|
|
126
|
+
for (const tab of container.querySelectorAll('.session-tab[data-id]')) tab.classList.remove('active');
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// ── Opening / closing ─────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Open (or focus) a dashboard tab. Mints a fresh capability every time: they are
|
|
134
|
+
* memory-only and expire, so a tab reopened after a server restart must not reuse
|
|
135
|
+
* the dead URL from the previous run.
|
|
136
|
+
*/
|
|
137
|
+
async openWebview(id) {
|
|
138
|
+
const webview = this.webviews.get(id);
|
|
139
|
+
if (!webview) return;
|
|
140
|
+
|
|
141
|
+
if (!this.webviewOrder.includes(id)) {
|
|
142
|
+
this.webviewOrder.push(id);
|
|
143
|
+
this._persistWebviewOrder();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const data = await this._apiJson(`/api/webviews/${encodeURIComponent(id)}/open`, { method: 'POST' });
|
|
147
|
+
if (!data) {
|
|
148
|
+
this.showToast?.('Could not open URL', 'error');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (data.webview) this.webviews.set(id, data.webview);
|
|
152
|
+
|
|
153
|
+
const src = data.embedUrl || data.webview?.url || webview.url;
|
|
154
|
+
this._mountWebviewFrame(id, src, data.webview || webview);
|
|
155
|
+
this.activeWebviewId = id;
|
|
156
|
+
this.hideWelcome?.();
|
|
157
|
+
document.querySelector('.main')?.classList.add('webview-active');
|
|
158
|
+
this.renderSessionTabs();
|
|
159
|
+
this._updateActiveWebviewTab();
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
/** Create the frame if absent, then reveal it and hide its siblings. */
|
|
163
|
+
_mountWebviewFrame(id, src, webview) {
|
|
164
|
+
const layer = document.getElementById('webviewLayer');
|
|
165
|
+
if (!layer) return;
|
|
166
|
+
|
|
167
|
+
let wrap = layer.querySelector(`.webview-frame[data-webview-id="${CSS.escape(id)}"]`);
|
|
168
|
+
if (!wrap) {
|
|
169
|
+
wrap = document.createElement('div');
|
|
170
|
+
wrap.className = 'webview-frame';
|
|
171
|
+
wrap.dataset.webviewId = id;
|
|
172
|
+
|
|
173
|
+
const frame = document.createElement('iframe');
|
|
174
|
+
frame.className = 'webview-iframe';
|
|
175
|
+
frame.setAttribute('title', webview.name);
|
|
176
|
+
// No allow-same-origin unless explicitly trusted: a proxied page is served
|
|
177
|
+
// from THIS origin, so granting it would let the dashboard read this document
|
|
178
|
+
// and drive the Codeman API.
|
|
179
|
+
const sandbox = ['allow-scripts', 'allow-forms', 'allow-popups', 'allow-downloads', 'allow-modals'];
|
|
180
|
+
if (webview.trusted) sandbox.push('allow-same-origin');
|
|
181
|
+
frame.setAttribute('sandbox', sandbox.join(' '));
|
|
182
|
+
frame.setAttribute('referrerpolicy', 'no-referrer-when-downgrade');
|
|
183
|
+
frame.src = src;
|
|
184
|
+
|
|
185
|
+
const failure = document.createElement('div');
|
|
186
|
+
failure.className = 'webview-failure';
|
|
187
|
+
failure.innerHTML = this._webviewFailureHtml(id);
|
|
188
|
+
|
|
189
|
+
wrap.appendChild(frame);
|
|
190
|
+
wrap.appendChild(failure);
|
|
191
|
+
layer.appendChild(wrap);
|
|
192
|
+
|
|
193
|
+
// A frame that never fires `load` is the normal symptom of a refused embed or
|
|
194
|
+
// an unreachable host. Show an actionable panel instead of a blank rectangle.
|
|
195
|
+
const timer = setTimeout(() => wrap.classList.add('webview-frame--failed'), 8000);
|
|
196
|
+
frame.addEventListener('load', () => {
|
|
197
|
+
clearTimeout(timer);
|
|
198
|
+
wrap.classList.remove('webview-frame--failed');
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
this._touchWebviewFrame(id);
|
|
203
|
+
for (const other of layer.querySelectorAll('.webview-frame')) {
|
|
204
|
+
other.classList.toggle('active', other.dataset.webviewId === id);
|
|
205
|
+
}
|
|
206
|
+
// Chart libraries measure on resize; a frame revealed from display:none needs the nudge.
|
|
207
|
+
requestAnimationFrame(() => window.dispatchEvent(new Event('resize')));
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
_webviewFailureHtml(id) {
|
|
211
|
+
const jsonId = escapeHtml(JSON.stringify(id));
|
|
212
|
+
return `<div class="webview-failure-inner">
|
|
213
|
+
<h3>This URL did not load</h3>
|
|
214
|
+
<p>It may be unreachable from the Codeman server, or it may refuse to be embedded.</p>
|
|
215
|
+
<div class="webview-failure-actions">
|
|
216
|
+
<button class="btn-secondary" onclick="app.reloadWebview(${jsonId})">Reload</button>
|
|
217
|
+
<button class="btn-secondary" onclick="app.openWebviewExternal(${jsonId})">Open in new tab</button>
|
|
218
|
+
<button class="btn-secondary" onclick="app.showWebviewModal(${jsonId})">Edit</button>
|
|
219
|
+
</div>
|
|
220
|
+
</div>`;
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
/** Least-recently-viewed eviction so N open dashboards cannot pin N live pages. */
|
|
224
|
+
_touchWebviewFrame(id) {
|
|
225
|
+
this._webviewFrameLru = (this._webviewFrameLru || []).filter((x) => x !== id);
|
|
226
|
+
this._webviewFrameLru.push(id);
|
|
227
|
+
const layer = document.getElementById('webviewLayer');
|
|
228
|
+
if (!layer) return;
|
|
229
|
+
while (this._webviewFrameLru.length > this._webviewMaxFrames) {
|
|
230
|
+
const evict = this._webviewFrameLru.shift();
|
|
231
|
+
if (evict === this.activeWebviewId) continue;
|
|
232
|
+
layer.querySelector(`.webview-frame[data-webview-id="${CSS.escape(evict)}"]`)?.remove();
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
reloadWebview(id) {
|
|
237
|
+
const target = id || this.activeWebviewId;
|
|
238
|
+
if (!target) return;
|
|
239
|
+
document
|
|
240
|
+
.getElementById('webviewLayer')
|
|
241
|
+
?.querySelector(`.webview-frame[data-webview-id="${CSS.escape(target)}"]`)
|
|
242
|
+
?.remove();
|
|
243
|
+
this._webviewFrameLru = (this._webviewFrameLru || []).filter((x) => x !== target);
|
|
244
|
+
return this.openWebview(target);
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
openWebviewExternal(id) {
|
|
248
|
+
const webview = this.webviews.get(id || this.activeWebviewId);
|
|
249
|
+
if (webview) window.open(webview.url, '_blank', 'noopener');
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
closeWebviewTab(id) {
|
|
253
|
+
this._removeWebviewTab(id);
|
|
254
|
+
this.renderSessionTabs();
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
_removeWebviewTab(id) {
|
|
258
|
+
this.webviewOrder = (this.webviewOrder || []).filter((x) => x !== id);
|
|
259
|
+
this._webviewFrameLru = (this._webviewFrameLru || []).filter((x) => x !== id);
|
|
260
|
+
this._persistWebviewOrder();
|
|
261
|
+
document
|
|
262
|
+
.getElementById('webviewLayer')
|
|
263
|
+
?.querySelector(`.webview-frame[data-webview-id="${CSS.escape(id)}"]`)
|
|
264
|
+
?.remove();
|
|
265
|
+
|
|
266
|
+
if (this.activeWebviewId === id) {
|
|
267
|
+
this.activeWebviewId = null;
|
|
268
|
+
const next = this.webviewOrder[0];
|
|
269
|
+
if (next) {
|
|
270
|
+
this.openWebview(next);
|
|
271
|
+
} else {
|
|
272
|
+
this._hideWebviewLayer();
|
|
273
|
+
// Fall back to whatever session was last shown, or the welcome screen.
|
|
274
|
+
if (this.activeSessionId) this._updateActiveTabImmediate(this.activeSessionId);
|
|
275
|
+
else this.showWelcome?.();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
/** Called by selectSession(): a session tab takes the stage back from a web tab. */
|
|
281
|
+
_hideWebviewLayer() {
|
|
282
|
+
if (!this.activeWebviewId && !document.querySelector('.main.webview-active')) return;
|
|
283
|
+
this.activeWebviewId = null;
|
|
284
|
+
document.querySelector('.main')?.classList.remove('webview-active');
|
|
285
|
+
for (const frame of document.querySelectorAll('#webviewLayer .webview-frame')) {
|
|
286
|
+
frame.classList.remove('active');
|
|
287
|
+
}
|
|
288
|
+
this._updateActiveWebviewTab();
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
// ── Run-menu entries ──────────────────────────────────────────────────────
|
|
292
|
+
|
|
293
|
+
/** Saved dashboards listed inside the Run dropdown, under "Web / URL". */
|
|
294
|
+
renderWebviewMenuItems() {
|
|
295
|
+
const container = document.getElementById('runModeWebviews');
|
|
296
|
+
if (!container) return;
|
|
297
|
+
const list = [...(this.webviews?.values() || [])];
|
|
298
|
+
if (list.length === 0) {
|
|
299
|
+
container.innerHTML = '<div class="run-mode-empty">No URLs yet</div>';
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
container.innerHTML = list
|
|
303
|
+
.map(
|
|
304
|
+
(w) => `<button class="run-mode-option run-mode-option--web" onclick="app.openWebviewFromMenu(${escapeHtml(
|
|
305
|
+
JSON.stringify(w.id)
|
|
306
|
+
)})" title="${escapeHtml(w.url)}">
|
|
307
|
+
<span class="run-mode-menu-icon">${w.icon ? escapeHtml(w.icon) : '<span class="run-mode-dot web"></span>'}</span>${escapeHtml(
|
|
308
|
+
w.name
|
|
309
|
+
)}
|
|
310
|
+
</button>`
|
|
311
|
+
)
|
|
312
|
+
.join('');
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
openWebviewFromMenu(id) {
|
|
316
|
+
document.getElementById('runModeMenu')?.classList.remove('active');
|
|
317
|
+
return this.openWebview(id);
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
// ── Icon picker ───────────────────────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
/** Common dashboard/service glyphs. The text field stays open for anything else. */
|
|
323
|
+
_webviewIconChoices() {
|
|
324
|
+
return ['📊', '📈', '🖥️', '🎛️', '📡', '🐳', '🗄️', '🔒', '🌐', '📁', '🧪', '🧬', '⚡', '🔔', '📝', '🎧'];
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
_renderWebviewIconPicker(selected) {
|
|
328
|
+
const picker = document.getElementById('webviewIconPicker');
|
|
329
|
+
if (!picker) return;
|
|
330
|
+
picker.innerHTML = this._webviewIconChoices()
|
|
331
|
+
.map(
|
|
332
|
+
(icon) =>
|
|
333
|
+
`<button type="button" class="webview-icon-choice${icon === selected ? ' selected' : ''}"
|
|
334
|
+
onclick="app.pickWebviewIcon(${escapeHtml(JSON.stringify(icon))})"
|
|
335
|
+
aria-label="Use ${escapeHtml(icon)} as the icon">${escapeHtml(icon)}</button>`
|
|
336
|
+
)
|
|
337
|
+
.join('');
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
/** Clicking the selected icon again clears it, so there is a way back to no icon. */
|
|
341
|
+
pickWebviewIcon(icon) {
|
|
342
|
+
const field = document.getElementById('webviewIcon');
|
|
343
|
+
if (!field) return;
|
|
344
|
+
field.value = field.value === icon ? '' : icon;
|
|
345
|
+
this._renderWebviewIconPicker(field.value);
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
// ── Editor modal ──────────────────────────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
showWebviewModal(id) {
|
|
351
|
+
const modal = document.getElementById('webviewModal');
|
|
352
|
+
if (!modal) return;
|
|
353
|
+
const webview = id ? this.webviews.get(id) : null;
|
|
354
|
+
this._editingWebviewId = webview ? webview.id : null;
|
|
355
|
+
|
|
356
|
+
document.getElementById('webviewModalTitle').textContent = webview ? 'Edit URL' : 'Add URL';
|
|
357
|
+
this._renderWebviewIconPicker(webview?.icon || '');
|
|
358
|
+
document.getElementById('webviewName').value = webview?.name || '';
|
|
359
|
+
document.getElementById('webviewUrl').value = webview?.url || '';
|
|
360
|
+
document.getElementById('webviewIcon').value = webview?.icon || '';
|
|
361
|
+
document.getElementById('webviewSandboxed').checked = !webview?.trusted;
|
|
362
|
+
document.getElementById('webviewProbeResult').textContent = '';
|
|
363
|
+
document.getElementById('webviewDeleteBtn').style.display = webview ? '' : 'none';
|
|
364
|
+
|
|
365
|
+
document.getElementById('runModeMenu')?.classList.remove('active');
|
|
366
|
+
modal.classList.add('active');
|
|
367
|
+
document.getElementById('webviewName').focus();
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
closeWebviewModal() {
|
|
371
|
+
document.getElementById('webviewModal')?.classList.remove('active');
|
|
372
|
+
this._editingWebviewId = null;
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
/** Server-side probe: it runs from the network position the proxy will use. */
|
|
376
|
+
async testWebviewUrl() {
|
|
377
|
+
const url = document.getElementById('webviewUrl').value.trim();
|
|
378
|
+
const out = document.getElementById('webviewProbeResult');
|
|
379
|
+
if (!url) {
|
|
380
|
+
out.textContent = 'Enter a URL first.';
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
out.textContent = 'Testing...';
|
|
384
|
+
const probe = await this._apiJson('/api/webviews/probe', { method: 'POST', body: { url } });
|
|
385
|
+
if (!probe) {
|
|
386
|
+
out.textContent = 'Test failed (invalid URL?).';
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
out.textContent = probe.reachable
|
|
390
|
+
? `Reachable (HTTP ${probe.status}). ${probe.reason}`
|
|
391
|
+
: `Not reachable. ${probe.reason}`;
|
|
392
|
+
out.className = 'form-hint webview-probe-result ' + (probe.reachable ? 'ok' : 'bad');
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
async saveWebview() {
|
|
396
|
+
const name = document.getElementById('webviewName').value.trim();
|
|
397
|
+
const url = document.getElementById('webviewUrl').value.trim();
|
|
398
|
+
const icon = document.getElementById('webviewIcon').value.trim();
|
|
399
|
+
const trusted = !document.getElementById('webviewSandboxed').checked;
|
|
400
|
+
if (!name || !url) {
|
|
401
|
+
this.showToast?.('Name and URL are required', 'error');
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// `icon: undefined` rather than null, the schema uses .optional(), which
|
|
406
|
+
// rejects an explicit null on the wire.
|
|
407
|
+
const body = { name, url, icon: icon || undefined, trusted };
|
|
408
|
+
const editing = this._editingWebviewId;
|
|
409
|
+
const data = editing
|
|
410
|
+
? await this._apiJson(`/api/webviews/${encodeURIComponent(editing)}`, { method: 'PATCH', body })
|
|
411
|
+
: await this._apiJson('/api/webviews', { method: 'POST', body });
|
|
412
|
+
|
|
413
|
+
if (!data) {
|
|
414
|
+
this.showToast?.('Could not save (check the URL)', 'error');
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
await this.refreshWebviews();
|
|
419
|
+
this.closeWebviewModal();
|
|
420
|
+
if (editing) {
|
|
421
|
+
// The capability was revoked server-side by the edit, so a mounted frame is
|
|
422
|
+
// now pointing at a dead URL. Remount it.
|
|
423
|
+
if (this.webviewOrder.includes(editing)) this.reloadWebview(editing);
|
|
424
|
+
} else {
|
|
425
|
+
this.openWebview(data.id);
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
async deleteWebview() {
|
|
430
|
+
const id = this._editingWebviewId;
|
|
431
|
+
if (!id) return;
|
|
432
|
+
const webview = this.webviews.get(id);
|
|
433
|
+
if (!confirm(`Delete "${webview?.name || id}"?`)) return;
|
|
434
|
+
const res = await this._apiDelete(`/api/webviews/${encodeURIComponent(id)}`);
|
|
435
|
+
if (!res || !res.ok) {
|
|
436
|
+
this.showToast?.('Could not delete URL', 'error');
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
this._removeWebviewTab(id);
|
|
440
|
+
this.webviews.delete(id);
|
|
441
|
+
this.closeWebviewModal();
|
|
442
|
+
this.renderWebviewMenuItems();
|
|
443
|
+
this.renderSessionTabs();
|
|
444
|
+
},
|
|
445
|
+
});
|
|
Binary file
|
|
Binary file
|
|
@@ -21,4 +21,5 @@ export { registerSearchRoutes } from './search-routes.js';
|
|
|
21
21
|
export { registerMeRoutes } from './me-routes.js';
|
|
22
22
|
export { registerAdminRoutes } from './admin-routes.js';
|
|
23
23
|
export { registerWsRoutes } from './ws-routes.js';
|
|
24
|
+
export { registerWebviewRoutes, tryWebviewRefererFallback } from './webview-routes.js';
|
|
24
25
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/web/routes/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/web/routes/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/web/routes/index.js
CHANGED
|
@@ -21,4 +21,5 @@ export { registerSearchRoutes } from './search-routes.js';
|
|
|
21
21
|
export { registerMeRoutes } from './me-routes.js';
|
|
22
22
|
export { registerAdminRoutes } from './admin-routes.js';
|
|
23
23
|
export { registerWsRoutes } from './ws-routes.js';
|
|
24
|
+
export { registerWebviewRoutes, tryWebviewRefererFallback } from './webview-routes.js';
|
|
24
25
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/web/routes/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/web/routes/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Web tabs: saved dashboard URLs, plus the reverse proxy that makes
|
|
3
|
+
* them embeddable.
|
|
4
|
+
*
|
|
5
|
+
* Two distinct surfaces live here, and the split matters:
|
|
6
|
+
*
|
|
7
|
+
* 1. `/api/webviews/*`, ordinary authenticated CRUD, owner-scoped like every
|
|
8
|
+
* other resource, returning the `ApiResponse` envelope.
|
|
9
|
+
* 2. `/webview/:cap/*`, the proxy. NOT an API surface. It authenticates on an
|
|
10
|
+
* unguessable capability in the path instead of Codeman's session cookie, and
|
|
11
|
+
* is correspondingly exempt from the cookie and Origin checks in
|
|
12
|
+
* `middleware/auth.ts`. See `src/webview-capabilities.ts` for why a cookie
|
|
13
|
+
* cannot work here (sandboxed iframes are opaque-origin, so their requests are
|
|
14
|
+
* cross-site and arrive with `Origin: null`).
|
|
15
|
+
*
|
|
16
|
+
* The proxy is registered inside an ENCAPSULATED plugin scope with its own
|
|
17
|
+
* catch-all content-type parser. Fastify scopes parsers to the plugin that
|
|
18
|
+
* registers them, which is what lets the proxy forward raw request bodies
|
|
19
|
+
* upstream while the rest of the app keeps its JSON parsing (and, critically,
|
|
20
|
+
* keeps `text/plain` raw, auto-parsing that was a real CSRF hole once).
|
|
21
|
+
*
|
|
22
|
+
* Endpoints:
|
|
23
|
+
* GET /api/webviews
|
|
24
|
+
* POST /api/webviews
|
|
25
|
+
* PATCH /api/webviews/:id
|
|
26
|
+
* DELETE /api/webviews/:id
|
|
27
|
+
* POST /api/webviews/probe
|
|
28
|
+
* POST /api/webviews/:id/open
|
|
29
|
+
* ALL /webview/:cap/* (+ WebSocket upgrade on GET)
|
|
30
|
+
*/
|
|
31
|
+
import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
|
|
32
|
+
import type { EventPort } from '../ports/index.js';
|
|
33
|
+
export declare function registerWebviewRoutes(app: FastifyInstance, ctx: EventPort): void;
|
|
34
|
+
/**
|
|
35
|
+
* Last-resort handler for a dashboard asset requested with a ROOT-ABSOLUTE URL.
|
|
36
|
+
*
|
|
37
|
+
* `<base href>` fixes relative URLs and the HTML rewrite fixes `src`/`href`/`action`
|
|
38
|
+
* attributes, but neither can reach a URL built at runtime: `fetch('/api/data')`,
|
|
39
|
+
* `import('/chunk.js')`, `url(/img.png)` inside a stylesheet. Those arrive at
|
|
40
|
+
* Codeman's root and 404.
|
|
41
|
+
*
|
|
42
|
+
* The `Referer` identifies which dashboard asked, so the request can be routed to
|
|
43
|
+
* the right upstream. Wiring it into the 404 handler rather than a catch-all route
|
|
44
|
+
* is what keeps it contained: every real Codeman route matches first, and this only
|
|
45
|
+
* ever sees requests that were going to fail anyway.
|
|
46
|
+
*
|
|
47
|
+
* @returns true when the request was handled (caller must not also reply).
|
|
48
|
+
*/
|
|
49
|
+
export declare function tryWebviewRefererFallback(req: FastifyRequest, reply: FastifyReply): Promise<boolean>;
|
|
50
|
+
//# sourceMappingURL=webview-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webview-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/webview-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAqB7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AA6CnD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,SAAS,GAAG,IAAI,CAGhF;AA+XD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAY1G"}
|