bitwrench 2.0.17 → 2.0.19
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/README.md +169 -75
- package/dist/bitwrench-bccl.cjs.js +228 -55
- package/dist/bitwrench-bccl.cjs.min.js +3 -3
- package/dist/bitwrench-bccl.esm.js +228 -55
- package/dist/bitwrench-bccl.esm.min.js +3 -3
- package/dist/bitwrench-bccl.umd.js +228 -55
- package/dist/bitwrench-bccl.umd.min.js +3 -3
- package/dist/bitwrench-code-edit.cjs.js +7 -9
- package/dist/bitwrench-code-edit.cjs.min.js +5 -7
- package/dist/bitwrench-code-edit.es5.js +6 -8
- package/dist/bitwrench-code-edit.es5.min.js +5 -7
- package/dist/bitwrench-code-edit.esm.js +7 -9
- package/dist/bitwrench-code-edit.esm.min.js +5 -7
- package/dist/bitwrench-code-edit.umd.js +7 -9
- package/dist/bitwrench-code-edit.umd.min.js +5 -7
- package/dist/bitwrench-debug.js +268 -0
- package/dist/bitwrench-debug.min.js +3 -0
- package/dist/bitwrench-lean.cjs.js +1190 -2348
- package/dist/bitwrench-lean.cjs.min.js +20 -20
- package/dist/bitwrench-lean.es5.js +1285 -2551
- package/dist/bitwrench-lean.es5.min.js +18 -18
- package/dist/bitwrench-lean.esm.js +1190 -2348
- package/dist/bitwrench-lean.esm.min.js +20 -20
- package/dist/bitwrench-lean.umd.js +1190 -2348
- package/dist/bitwrench-lean.umd.min.js +20 -20
- package/dist/bitwrench-util-css.cjs.js +236 -0
- package/dist/bitwrench-util-css.cjs.min.js +22 -0
- package/dist/bitwrench-util-css.es5.js +414 -0
- package/dist/bitwrench-util-css.es5.min.js +21 -0
- package/dist/bitwrench-util-css.esm.js +230 -0
- package/dist/bitwrench-util-css.esm.min.js +21 -0
- package/dist/bitwrench-util-css.umd.js +242 -0
- package/dist/bitwrench-util-css.umd.min.js +21 -0
- package/dist/bitwrench.cjs.js +1404 -2388
- package/dist/bitwrench.cjs.min.js +21 -21
- package/dist/bitwrench.css +503 -132
- package/dist/bitwrench.es5.js +1588 -2659
- package/dist/bitwrench.es5.min.js +19 -19
- package/dist/bitwrench.esm.js +1405 -2389
- package/dist/bitwrench.esm.min.js +21 -21
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +1404 -2388
- package/dist/bitwrench.umd.min.js +21 -21
- package/dist/builds.json +214 -104
- package/dist/bwserve.cjs.js +514 -68
- package/dist/bwserve.esm.js +513 -69
- package/dist/sri.json +46 -36
- package/package.json +6 -3
- package/readme.html +183 -85
- package/src/bitwrench-bccl-entry.js +3 -4
- package/src/bitwrench-bccl.js +224 -50
- package/src/bitwrench-code-edit.js +6 -8
- package/src/bitwrench-color-utils.js +31 -9
- package/src/bitwrench-debug.js +245 -0
- package/src/bitwrench-esm-entry.js +11 -0
- package/src/bitwrench-styles.js +474 -240
- package/src/bitwrench-util-css.js +229 -0
- package/src/bitwrench.js +689 -2042
- package/src/bwserve/attach.js +57 -0
- package/src/bwserve/bwclient.js +141 -0
- package/src/bwserve/bwshell.js +102 -0
- package/src/bwserve/client.js +151 -1
- package/src/bwserve/index.js +127 -28
- package/src/cli/attach.js +587 -0
- package/src/cli/convert.js +2 -5
- package/src/cli/index.js +7 -0
- package/src/cli/inject.js +1 -1
- package/src/cli/serve.js +185 -5
- package/src/generate-css.js +11 -4
- package/src/vendor/html2canvas.min.js +20 -0
- package/src/version.js +3 -3
- package/src/bwserve/shell.js +0 -106
package/dist/bwserve.esm.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
/*! bwserve v2.0.
|
|
1
|
+
/*! bwserve v2.0.19 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import { dirname, resolve, join, extname } from 'path';
|
|
4
4
|
import { createServer } from 'http';
|
|
5
5
|
import { existsSync, statSync, readFileSync } from 'fs';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Auto-generated version file from package.json
|
|
9
|
+
* DO NOT EDIT DIRECTLY - Use npm run generate-version
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const VERSION = '2.0.19';
|
|
13
|
+
|
|
7
14
|
/**
|
|
8
15
|
* BwServeClient — per-client connection for bwserve.
|
|
9
16
|
*
|
|
@@ -23,15 +30,19 @@ import { existsSync, statSync, readFileSync } from 'fs';
|
|
|
23
30
|
* @module bwserve/client
|
|
24
31
|
*/
|
|
25
32
|
|
|
33
|
+
|
|
26
34
|
/**
|
|
27
35
|
* BwServeClient — one connected browser tab.
|
|
28
36
|
*/
|
|
29
37
|
class BwServeClient {
|
|
38
|
+
/** bwserve version (from package.json) */
|
|
39
|
+
static version = VERSION;
|
|
30
40
|
constructor(id, res) {
|
|
31
41
|
this.id = id;
|
|
32
42
|
this._res = res; // SSE response stream (null in stub)
|
|
33
43
|
this._handlers = {}; // action name → handler
|
|
34
44
|
this._closed = false;
|
|
45
|
+
this._pending = {}; // requestId → { resolve, reject, timer }
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
/**
|
|
@@ -110,7 +121,7 @@ class BwServeClient {
|
|
|
110
121
|
/**
|
|
111
122
|
* Call a previously registered or built-in function on the client.
|
|
112
123
|
*
|
|
113
|
-
* Built-in functions (
|
|
124
|
+
* Built-in functions (registered by bwclient on connection):
|
|
114
125
|
* scrollTo, focus, download, clipboard, redirect, log
|
|
115
126
|
*
|
|
116
127
|
* @param {string} name - Function name (registered or built-in)
|
|
@@ -173,6 +184,151 @@ class BwServeClient {
|
|
|
173
184
|
}
|
|
174
185
|
}
|
|
175
186
|
|
|
187
|
+
// ── Pending promise mechanism ──
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Create a pending promise with a unique requestId and timeout.
|
|
191
|
+
*
|
|
192
|
+
* @param {number} [timeout=10000] - Timeout in ms
|
|
193
|
+
* @returns {{ requestId: string, promise: Promise }}
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
_pend(timeout) {
|
|
197
|
+
var self = this;
|
|
198
|
+
timeout = timeout || 10000;
|
|
199
|
+
var requestId = 'req_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8);
|
|
200
|
+
|
|
201
|
+
var promise = new Promise(function(resolve, reject) {
|
|
202
|
+
var timer = setTimeout(function() {
|
|
203
|
+
delete self._pending[requestId];
|
|
204
|
+
reject(new Error('Request timeout after ' + timeout + 'ms'));
|
|
205
|
+
}, timeout);
|
|
206
|
+
|
|
207
|
+
self._pending[requestId] = { resolve: resolve, reject: reject, timer: timer };
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return { requestId: requestId, promise: promise };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Resolve a pending promise by requestId.
|
|
215
|
+
* Called by the server route handler when a POST-back arrives.
|
|
216
|
+
*
|
|
217
|
+
* @param {string} requestId
|
|
218
|
+
* @param {Object} data - Response data (may contain .error)
|
|
219
|
+
* @returns {boolean} true if a pending request was found and resolved
|
|
220
|
+
* @private
|
|
221
|
+
*/
|
|
222
|
+
_resolvePending(requestId, data) {
|
|
223
|
+
var pending = this._pending[requestId];
|
|
224
|
+
if (!pending) return false;
|
|
225
|
+
|
|
226
|
+
clearTimeout(pending.timer);
|
|
227
|
+
delete this._pending[requestId];
|
|
228
|
+
|
|
229
|
+
if (data.error) {
|
|
230
|
+
pending.reject(new Error(data.error));
|
|
231
|
+
} else {
|
|
232
|
+
pending.resolve(data.result !== undefined ? data.result : data);
|
|
233
|
+
}
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── Query ──
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Execute code on the client and get the result back.
|
|
241
|
+
*
|
|
242
|
+
* @param {string} code - JavaScript code to evaluate (return value is sent back)
|
|
243
|
+
* @param {Object} [options]
|
|
244
|
+
* @param {number} [options.timeout=5000] - Timeout in ms
|
|
245
|
+
* @returns {Promise<*>} The result of evaluating the code
|
|
246
|
+
*/
|
|
247
|
+
query(code, options) {
|
|
248
|
+
var opts = options || {};
|
|
249
|
+
var pend = this._pend(opts.timeout || 5000);
|
|
250
|
+
this.call('_bw_query', { code: code, requestId: pend.requestId });
|
|
251
|
+
return pend.promise;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ── Mount ──
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Mount a BCCL component or factory function on the client.
|
|
258
|
+
*
|
|
259
|
+
* @param {string} selector - CSS selector of target element
|
|
260
|
+
* @param {string} factory - BCCL component name (e.g. 'accordion') or JS factory code
|
|
261
|
+
* @param {Object} [props] - Props to pass to the component/factory
|
|
262
|
+
* @param {Object} [options]
|
|
263
|
+
* @param {number} [options.timeout=10000] - Timeout in ms
|
|
264
|
+
* @returns {Promise<Object>} Resolves with { mounted: true } on success
|
|
265
|
+
*/
|
|
266
|
+
mount(selector, factory, props, options) {
|
|
267
|
+
var opts = options || {};
|
|
268
|
+
var pend = this._pend(opts.timeout || 10000);
|
|
269
|
+
this.call('_bw_mount', {
|
|
270
|
+
target: selector,
|
|
271
|
+
factory: factory,
|
|
272
|
+
props: props || {},
|
|
273
|
+
requestId: pend.requestId
|
|
274
|
+
});
|
|
275
|
+
return pend.promise;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── Screenshot ──
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Capture a screenshot of the client's page or a specific element.
|
|
282
|
+
*
|
|
283
|
+
* Requires the server to be created with `{ allowScreenshot: true }`.
|
|
284
|
+
* Uses html2canvas on the client side (lazy-loaded on first call).
|
|
285
|
+
*
|
|
286
|
+
* @param {string} [selector='body'] - CSS selector of element to capture
|
|
287
|
+
* @param {Object} [options]
|
|
288
|
+
* @param {string} [options.format='png'] - 'png' or 'jpeg'
|
|
289
|
+
* @param {number} [options.quality=0.85] - JPEG quality 0-1 (ignored for PNG)
|
|
290
|
+
* @param {number} [options.maxWidth] - Resize if wider (preserves aspect ratio)
|
|
291
|
+
* @param {number} [options.maxHeight] - Resize if taller (preserves aspect ratio)
|
|
292
|
+
* @param {number} [options.scale=1] - Device pixel ratio override
|
|
293
|
+
* @param {number} [options.timeout=10000] - Reject after ms
|
|
294
|
+
* @returns {Promise<Object>} { data: Buffer, width, height, format }
|
|
295
|
+
*/
|
|
296
|
+
screenshot(selector, options) {
|
|
297
|
+
var self = this;
|
|
298
|
+
var opts = options || {};
|
|
299
|
+
var timeout = opts.timeout || 10000;
|
|
300
|
+
|
|
301
|
+
if (!self._allowScreenshot) {
|
|
302
|
+
return Promise.reject(new Error('Screenshot not enabled. Set allowScreenshot: true in server options.'));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
var pend = self._pend(timeout);
|
|
306
|
+
|
|
307
|
+
// Call the bwclient-registered capture function
|
|
308
|
+
self.call('_bw_screenshot', {
|
|
309
|
+
requestId: pend.requestId,
|
|
310
|
+
selector: selector || 'body',
|
|
311
|
+
format: opts.format || 'png',
|
|
312
|
+
quality: opts.quality || 0.85,
|
|
313
|
+
maxWidth: opts.maxWidth || null,
|
|
314
|
+
maxHeight: opts.maxHeight || null,
|
|
315
|
+
scale: opts.scale || 1,
|
|
316
|
+
captureUrl: '/bw/lib/vendor/html2canvas.min.js'
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Transform the raw response into { data: Buffer, width, height, format }
|
|
320
|
+
return pend.promise.then(function(result) {
|
|
321
|
+
if (!result || !result.data) return result;
|
|
322
|
+
var base64 = result.data.split(',')[1];
|
|
323
|
+
return {
|
|
324
|
+
data: Buffer.from(base64, 'base64'),
|
|
325
|
+
width: result.width,
|
|
326
|
+
height: result.height,
|
|
327
|
+
format: result.format
|
|
328
|
+
};
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
176
332
|
/**
|
|
177
333
|
* Dispatch an incoming action from the client.
|
|
178
334
|
* @private
|
|
@@ -187,20 +343,161 @@ class BwServeClient {
|
|
|
187
343
|
}
|
|
188
344
|
}
|
|
189
345
|
|
|
346
|
+
/**
|
|
347
|
+
* bwclient.js — Browser-side protocol client for bwserve.
|
|
348
|
+
*
|
|
349
|
+
* Injected inline by bwshell. Requires window.bw (bitwrench loaded first).
|
|
350
|
+
* NOT bundled into bitwrench dist — this is a bwserve runtime asset.
|
|
351
|
+
*
|
|
352
|
+
* Responsibilities:
|
|
353
|
+
* - SSE connection lifecycle (connect, reconnect, status)
|
|
354
|
+
* - Unified POST-back via /bw/return/<route>/<clientId>
|
|
355
|
+
* - Register built-in client functions (scrollTo, focus, etc.)
|
|
356
|
+
* - data-bw-action click/key delegation
|
|
357
|
+
* - Attach mode for remote-controlling any bitwrench page
|
|
358
|
+
*
|
|
359
|
+
* @module bwserve/bwclient
|
|
360
|
+
*/
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Return the bwclient source as a string for inline injection into the shell.
|
|
365
|
+
* The version is embedded at serve-time from package.json via version.js.
|
|
366
|
+
* @returns {string} JavaScript source code
|
|
367
|
+
*/
|
|
368
|
+
function getBwClientSource() {
|
|
369
|
+
return BWCLIENT_SOURCE.replace('__BW_VERSION__', VERSION);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
var BWCLIENT_SOURCE = '(function(bw) {\n'
|
|
373
|
+
+ ' "use strict";\n'
|
|
374
|
+
+ ' if (!bw) return;\n'
|
|
375
|
+
+ '\n'
|
|
376
|
+
+ ' var _client = {\n'
|
|
377
|
+
+ ' id: null,\n'
|
|
378
|
+
+ ' version: "__BW_VERSION__",\n'
|
|
379
|
+
+ ' status: "idle",\n'
|
|
380
|
+
+ ' _es: null\n'
|
|
381
|
+
+ ' };\n'
|
|
382
|
+
+ '\n'
|
|
383
|
+
+ ' // ── Unified POST-back ──\n'
|
|
384
|
+
+ ' _client.respond = function(route, requestId, result, error) {\n'
|
|
385
|
+
+ ' fetch("/bw/return/" + route + "/" + _client.id, {\n'
|
|
386
|
+
+ ' method: "POST",\n'
|
|
387
|
+
+ ' headers: { "Content-Type": "application/json" },\n'
|
|
388
|
+
+ ' body: JSON.stringify({ requestId: requestId, route: route, result: result, error: error || null })\n'
|
|
389
|
+
+ ' }).catch(function() {});\n'
|
|
390
|
+
+ ' };\n'
|
|
391
|
+
+ '\n'
|
|
392
|
+
+ ' // ── SSE connect ──\n'
|
|
393
|
+
+ ' _client.connect = function(url, opts) {\n'
|
|
394
|
+
+ ' opts = opts || {};\n'
|
|
395
|
+
+ ' var onStatus = opts.onStatus || function() {};\n'
|
|
396
|
+
+ ' function setStatus(s) { _client.status = s; onStatus(s); }\n'
|
|
397
|
+
+ ' setStatus("connecting");\n'
|
|
398
|
+
+ ' if (typeof EventSource === "undefined") return;\n'
|
|
399
|
+
+ ' var es = new EventSource(url);\n'
|
|
400
|
+
+ ' _client._es = es;\n'
|
|
401
|
+
+ ' es.onopen = function() { setStatus("connected"); };\n'
|
|
402
|
+
+ ' es.onmessage = function(e) {\n'
|
|
403
|
+
+ ' try {\n'
|
|
404
|
+
+ ' var msg = typeof e.data === "string" ? bw.parseJSONFlex(e.data) : e.data;\n'
|
|
405
|
+
+ ' bw.apply(msg);\n'
|
|
406
|
+
+ ' } catch (err) {\n'
|
|
407
|
+
+ ' if (typeof console !== "undefined") console.error("[bwclient]", err);\n'
|
|
408
|
+
+ ' }\n'
|
|
409
|
+
+ ' };\n'
|
|
410
|
+
+ ' es.onerror = function() {\n'
|
|
411
|
+
+ ' if (_client.status === "connected") setStatus("disconnected");\n'
|
|
412
|
+
+ ' };\n'
|
|
413
|
+
+ ' };\n'
|
|
414
|
+
+ '\n'
|
|
415
|
+
+ ' // ── Attach mode ──\n'
|
|
416
|
+
+ ' _client.attach = function(url, opts) {\n'
|
|
417
|
+
+ ' opts = opts || {};\n'
|
|
418
|
+
+ ' _client.id = opts.clientId || "att_" + Math.random().toString(36).slice(2, 10);\n'
|
|
419
|
+
+ ' if (opts.allowExec) bw._allowExec = true;\n'
|
|
420
|
+
+ ' _client._registerBuiltins();\n'
|
|
421
|
+
+ ' _client._wireActions();\n'
|
|
422
|
+
+ ' _client.connect(url + "/bw/events/" + _client.id, opts);\n'
|
|
423
|
+
+ ' };\n'
|
|
424
|
+
+ '\n'
|
|
425
|
+
+ ' // ── Send action to server ──\n'
|
|
426
|
+
+ ' _client.sendAction = function(action, data) {\n'
|
|
427
|
+
+ ' _client.respond("action", null, { action: action, data: data || {} });\n'
|
|
428
|
+
+ ' };\n'
|
|
429
|
+
+ '\n'
|
|
430
|
+
+ ' // ── Register built-in functions ──\n'
|
|
431
|
+
+ ' _client._registerBuiltins = function() {\n'
|
|
432
|
+
+ ' var builtins = {\n'
|
|
433
|
+
+ ' scrollTo: "function(sel){var el=bw._el(sel);if(el)el.scrollTop=el.scrollHeight;}",\n'
|
|
434
|
+
+ ' focus: "function(sel){var el=bw._el(sel);if(el&&typeof el.focus===\\"function\\")el.focus();}",\n'
|
|
435
|
+
+ ' download: "function(fn,c,m){if(typeof document===\\"undefined\\")return;var b=new Blob([c],{type:m||\\"text/plain\\"});var a=document.createElement(\\"a\\");a.href=URL.createObjectURL(b);a.download=fn;a.click();URL.revokeObjectURL(a.href);}",\n'
|
|
436
|
+
+ ' clipboard: "function(t){if(typeof navigator!==\\"undefined\\"&&navigator.clipboard)navigator.clipboard.writeText(t);}",\n'
|
|
437
|
+
+ ' redirect: "function(u){if(typeof window!==\\"undefined\\")window.location.href=u;}",\n'
|
|
438
|
+
+ ' log: "function(){console.log.apply(console,arguments);}",\n'
|
|
439
|
+
+ ' _bw_query: "function(opts){if(!bw._bwClient)return;try{var r=new Function(opts.code)();if(r&&typeof r.then===\\"function\\"){r.then(function(v){bw._bwClient.respond(\\"query\\",opts.requestId,v);}).catch(function(e){bw._bwClient.respond(\\"query\\",opts.requestId,null,e.message);});}else{bw._bwClient.respond(\\"query\\",opts.requestId,r);}}catch(e){bw._bwClient.respond(\\"query\\",opts.requestId,null,e.message);}}",\n'
|
|
440
|
+
+ ' _bw_mount: "function(opts){if(!bw._bwClient)return;try{var taco;var f=opts.factory;var n=f.replace(/-([a-z])/g,function(_,c){return c.toUpperCase();});if(bw.BCCL&&bw.BCCL[n]){taco=bw.make(n,opts.props||{});}else if(bw._allowExec){taco=new Function(\\"props\\",f)(opts.props||{});}else{throw new Error(\\"Unknown component and allowExec disabled\\");}bw.DOM(opts.target,taco);bw._bwClient.respond(\\"mount\\",opts.requestId,{mounted:true});}catch(e){bw._bwClient.respond(\\"mount\\",opts.requestId,null,e.message);}}",\n'
|
|
441
|
+
+ ' _bw_screenshot: "function(opts){if(!bw._bwClient)return;var sel=opts.selector||\\"body\\";var el=document.querySelector(sel);if(!el){bw._bwClient.respond(\\"screenshot\\",opts.requestId,null,\\"Element not found: \\"+sel);return;}function _ls(url){return new Promise(function(res,rej){var s=document.createElement(\\"script\\");s.src=url;s.onload=function(){res(window.html2canvas);};s.onerror=function(){rej(new Error(\\"Failed to load html2canvas\\"));};document.head.appendChild(s);});}var p=window.html2canvas?Promise.resolve(window.html2canvas):_ls(opts.captureUrl||\\"/bw/lib/vendor/html2canvas.min.js\\");p.then(function(h2c){return h2c(el,{scale:opts.scale||1,useCORS:true});}).then(function(canvas){var out=canvas;var mw=opts.maxWidth;var mh=opts.maxHeight;if((mw&&canvas.width>mw)||(mh&&canvas.height>mh)){var sw=mw?mw/canvas.width:1;var sh=mh?mh/canvas.height:1;var sc=Math.min(sw,sh);out=document.createElement(\\"canvas\\");out.width=Math.round(canvas.width*sc);out.height=Math.round(canvas.height*sc);out.getContext(\\"2d\\").drawImage(canvas,0,0,out.width,out.height);}var fmt=opts.format===\\"jpeg\\"?\\"image/jpeg\\":\\"image/png\\";var q=opts.format===\\"jpeg\\"?(opts.quality||0.85):undefined;var dataUrl=out.toDataURL(fmt,q);bw._bwClient.respond(\\"screenshot\\",opts.requestId,{data:dataUrl,width:out.width,height:out.height,format:opts.format||\\"png\\"});}).catch(function(err){bw._bwClient.respond(\\"screenshot\\",opts.requestId,null,err.message||String(err));});}",\n'
|
|
442
|
+
+ ' _bw_tree: "function(opts){if(!bw._bwClient)return;var sel=opts.selector||\\"body\\";var depth=opts.depth||3;function walk(el,d){if(!el||d>depth)return null;var info={tag:el.tagName?el.tagName.toLowerCase():\\"#text\\"};if(el.id)info.id=el.id;if(el.className&&typeof el.className===\\"string\\")info.cls=el.className.split(\\" \\").slice(0,5).join(\\" \\");if(el.children&&el.children.length>0&&d<depth){info.children=[];for(var i=0;i<Math.min(el.children.length,20);i++){var c=walk(el.children[i],d+1);if(c)info.children.push(c);}}return info;}var root=document.querySelector(sel);bw._bwClient.respond(\\"query\\",opts.requestId,walk(root,0));}",\n'
|
|
443
|
+
+ ' _bw_listen: "function(opts){if(!bw._bwClient)return;if(!bw._bwClient._listeners)bw._bwClient._listeners={};var key=opts.selector+\\":::\\"+opts.event;if(bw._bwClient._listeners[key])return;var fn=function(e){var el=e.target.closest?e.target.closest(opts.selector):null;if(!el)return;bw._bwClient.respond(\\"event\\",null,{event:opts.event,selector:opts.selector,tagName:el.tagName,id:el.id||null,text:(el.textContent||\\"\\").slice(0,100)});};document.addEventListener(opts.event,fn,true);bw._bwClient._listeners[key]={fn:fn,event:opts.event};}",\n'
|
|
444
|
+
+ ' _bw_unlisten: "function(opts){if(!bw._bwClient||!bw._bwClient._listeners)return;var key=opts.selector+\\":::\\"+opts.event;var entry=bw._bwClient._listeners[key];if(!entry)return;document.removeEventListener(entry.event,entry.fn,true);delete bw._bwClient._listeners[key];}"\n'
|
|
445
|
+
+ ' };\n'
|
|
446
|
+
+ ' Object.keys(builtins).forEach(function(name) {\n'
|
|
447
|
+
+ ' bw.apply({ type: "register", name: name, body: builtins[name] });\n'
|
|
448
|
+
+ ' });\n'
|
|
449
|
+
+ ' };\n'
|
|
450
|
+
+ '\n'
|
|
451
|
+
+ ' // ── Wire up data-bw-action click delegation ──\n'
|
|
452
|
+
+ ' _client._wireActions = function() {\n'
|
|
453
|
+
+ ' document.addEventListener("click", function(e) {\n'
|
|
454
|
+
+ ' var el = e.target.closest ? e.target.closest("[data-bw-action]") : null;\n'
|
|
455
|
+
+ ' if (!el) return;\n'
|
|
456
|
+
+ ' e.preventDefault();\n'
|
|
457
|
+
+ ' var actionData = {};\n'
|
|
458
|
+
+ ' if (el.getAttribute("data-bw-id")) actionData.bwId = el.getAttribute("data-bw-id");\n'
|
|
459
|
+
+ ' var form = el.closest("div") || document;\n'
|
|
460
|
+
+ ' var inp = form.querySelector("input[type=text],input:not([type])");\n'
|
|
461
|
+
+ ' if (inp) { actionData.inputValue = inp.value; inp.value = ""; }\n'
|
|
462
|
+
+ ' _client.sendAction(el.getAttribute("data-bw-action"), actionData);\n'
|
|
463
|
+
+ ' });\n'
|
|
464
|
+
+ ' document.addEventListener("keydown", function(e) {\n'
|
|
465
|
+
+ ' if (e.key === "Enter" && e.target.tagName === "INPUT") {\n'
|
|
466
|
+
+ ' var form = e.target.closest("div") || document;\n'
|
|
467
|
+
+ ' var btn = form.querySelector("[data-bw-action]");\n'
|
|
468
|
+
+ ' if (btn) {\n'
|
|
469
|
+
+ ' _client.sendAction(btn.getAttribute("data-bw-action"), { inputValue: e.target.value });\n'
|
|
470
|
+
+ ' e.target.value = "";\n'
|
|
471
|
+
+ ' }\n'
|
|
472
|
+
+ ' }\n'
|
|
473
|
+
+ ' });\n'
|
|
474
|
+
+ ' };\n'
|
|
475
|
+
+ '\n'
|
|
476
|
+
+ ' // ── Event delegation helper ──\n'
|
|
477
|
+
+ ' _client.listen = function(selector, event, action) {\n'
|
|
478
|
+
+ ' document.addEventListener(event, function(e) {\n'
|
|
479
|
+
+ ' var el = e.target.closest ? e.target.closest(selector) : null;\n'
|
|
480
|
+
+ ' if (el) _client.sendAction(action, { selector: selector, event: event });\n'
|
|
481
|
+
+ ' });\n'
|
|
482
|
+
+ ' };\n'
|
|
483
|
+
+ '\n'
|
|
484
|
+
+ ' bw._bwClient = _client;\n'
|
|
485
|
+
+ '})(window.bw);\n';
|
|
486
|
+
|
|
190
487
|
/**
|
|
191
488
|
* bwserve shell — generates the HTML page shell served to browsers.
|
|
192
489
|
*
|
|
193
490
|
* The shell is a minimal HTML doc that:
|
|
194
|
-
* - Loads bitwrench UMD + CSS from /
|
|
195
|
-
* - Calls bw.
|
|
196
|
-
* - Optionally applies a theme
|
|
491
|
+
* - Loads bitwrench UMD + CSS from /bw/lib/ routes
|
|
492
|
+
* - Calls bw.loadStyles()
|
|
493
|
+
* - Optionally applies a custom theme
|
|
197
494
|
* - Creates a #app div
|
|
198
|
-
* -
|
|
199
|
-
* - Delegates data-bw-action clicks to the server via POST
|
|
495
|
+
* - Inlines bwclient.js for SSE, action delegation, and built-ins
|
|
200
496
|
*
|
|
201
|
-
* @module bwserve/
|
|
497
|
+
* @module bwserve/bwshell
|
|
202
498
|
*/
|
|
203
499
|
|
|
500
|
+
|
|
204
501
|
/**
|
|
205
502
|
* Generate the shell HTML page for a bwserve app.
|
|
206
503
|
*
|
|
@@ -209,6 +506,7 @@ class BwServeClient {
|
|
|
209
506
|
* @param {string} [opts.title='bwserve'] - Page title
|
|
210
507
|
* @param {string} [opts.theme] - Theme preset name or config
|
|
211
508
|
* @param {boolean} [opts.injectBitwrench=true] - Whether to inject bitwrench scripts
|
|
509
|
+
* @param {boolean} [opts.allowExec=false] - Enable exec message type
|
|
212
510
|
* @returns {string} Complete HTML document
|
|
213
511
|
*/
|
|
214
512
|
function generateShell(opts) {
|
|
@@ -223,12 +521,13 @@ function generateShell(opts) {
|
|
|
223
521
|
'<head>',
|
|
224
522
|
'<meta charset="UTF-8">',
|
|
225
523
|
'<meta name="viewport" content="width=device-width, initial-scale=1.0">',
|
|
226
|
-
'<title>' + title + '</title>'
|
|
524
|
+
'<title>' + title + '</title>',
|
|
525
|
+
'<meta name="generator" content="bwserve ' + VERSION + '">'
|
|
227
526
|
];
|
|
228
527
|
|
|
229
528
|
if (inject) {
|
|
230
|
-
head.push('<script src="/
|
|
231
|
-
head.push('<link rel="stylesheet" href="/
|
|
529
|
+
head.push('<script src="/bw/lib/bitwrench.umd.js"></script>');
|
|
530
|
+
head.push('<link rel="stylesheet" href="/bw/lib/bitwrench.css">');
|
|
232
531
|
}
|
|
233
532
|
|
|
234
533
|
head.push('</head>');
|
|
@@ -239,61 +538,109 @@ function generateShell(opts) {
|
|
|
239
538
|
'<script>',
|
|
240
539
|
'(function() {',
|
|
241
540
|
' "use strict";',
|
|
242
|
-
' bw.
|
|
541
|
+
' bw.loadStyles();'
|
|
243
542
|
];
|
|
244
543
|
|
|
245
544
|
if (opts.theme) {
|
|
246
|
-
script.push(' bw.
|
|
545
|
+
script.push(' bw.loadStyles(' + JSON.stringify(
|
|
247
546
|
typeof opts.theme === 'string'
|
|
248
547
|
? { primary: '#006666', secondary: '#333333' }
|
|
249
548
|
: opts.theme
|
|
250
549
|
) + ');');
|
|
251
550
|
}
|
|
252
551
|
|
|
552
|
+
script.push('})();');
|
|
553
|
+
script.push('</script>');
|
|
554
|
+
|
|
555
|
+
// Inline bwclient.js
|
|
556
|
+
script.push('<script>');
|
|
557
|
+
script.push(getBwClientSource());
|
|
558
|
+
script.push('</script>');
|
|
559
|
+
|
|
560
|
+
// Init script: wire up bwclient
|
|
561
|
+
script.push('<script>');
|
|
562
|
+
script.push('(function() {');
|
|
563
|
+
script.push(' "use strict";');
|
|
253
564
|
script.push(' var clientId = ' + JSON.stringify(clientId) + ';');
|
|
254
|
-
script.push(' var conn = bw.clientConnect("/__bw/events/" + clientId, {');
|
|
255
|
-
script.push(' actionUrl: "/__bw/action/" + clientId,');
|
|
256
565
|
if (opts.allowExec) {
|
|
257
|
-
script.push('
|
|
566
|
+
script.push(' bw._allowExec = true;');
|
|
258
567
|
}
|
|
568
|
+
script.push(' bw._bwClient.id = clientId;');
|
|
569
|
+
script.push(' bw._bwClient._registerBuiltins();');
|
|
570
|
+
script.push(' bw._bwClient._wireActions();');
|
|
571
|
+
script.push(' bw._bwClient.connect("/bw/events/" + clientId, {');
|
|
259
572
|
script.push(' onStatus: function(s) {');
|
|
260
573
|
script.push(' if (typeof console !== "undefined") console.log("[bwserve] " + s);');
|
|
261
574
|
script.push(' }');
|
|
262
575
|
script.push(' });');
|
|
263
|
-
|
|
264
|
-
// data-bw-action click delegation
|
|
265
|
-
script.push(' document.addEventListener("click", function(e) {');
|
|
266
|
-
script.push(' var el = e.target.closest ? e.target.closest("[data-bw-action]") : null;');
|
|
267
|
-
script.push(' if (!el) return;');
|
|
268
|
-
script.push(' e.preventDefault();');
|
|
269
|
-
script.push(' var actionData = {};');
|
|
270
|
-
script.push(' if (el.getAttribute("data-bw-id")) actionData.bwId = el.getAttribute("data-bw-id");');
|
|
271
|
-
script.push(' var form = el.closest("div") || document;');
|
|
272
|
-
script.push(' var inp = form.querySelector("input[type=text],input:not([type])");');
|
|
273
|
-
script.push(' if (inp) { actionData.inputValue = inp.value; inp.value = ""; }');
|
|
274
|
-
script.push(' conn.sendAction(el.getAttribute("data-bw-action"), actionData);');
|
|
275
|
-
script.push(' });');
|
|
276
|
-
|
|
277
|
-
// Enter key on inputs
|
|
278
|
-
script.push(' document.addEventListener("keydown", function(e) {');
|
|
279
|
-
script.push(' if (e.key === "Enter" && e.target.tagName === "INPUT") {');
|
|
280
|
-
script.push(' var form = e.target.closest("div") || document;');
|
|
281
|
-
script.push(' var btn = form.querySelector("[data-bw-action]");');
|
|
282
|
-
script.push(' if (btn) {');
|
|
283
|
-
script.push(' conn.sendAction(btn.getAttribute("data-bw-action"), { inputValue: e.target.value });');
|
|
284
|
-
script.push(' e.target.value = "";');
|
|
285
|
-
script.push(' }');
|
|
286
|
-
script.push(' }');
|
|
287
|
-
script.push(' });');
|
|
288
|
-
|
|
289
576
|
script.push('})();');
|
|
290
577
|
script.push('</script>');
|
|
578
|
+
|
|
291
579
|
script.push('</body>');
|
|
292
580
|
script.push('</html>');
|
|
293
581
|
|
|
294
582
|
return head.concat(script).join('\n');
|
|
295
583
|
}
|
|
296
584
|
|
|
585
|
+
/** bwshell version (from package.json) */
|
|
586
|
+
generateShell.version = VERSION;
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* bwserve attach — self-contained drop-in script generator.
|
|
590
|
+
*
|
|
591
|
+
* Generates JS that loads bitwrench + bwclient and auto-connects
|
|
592
|
+
* to a bwserve instance. When loaded in any browser page, it
|
|
593
|
+
* establishes an SSE connection for remote debugging.
|
|
594
|
+
*
|
|
595
|
+
* Usage:
|
|
596
|
+
* <script src="http://localhost:7902/bw/attach.js"></script>
|
|
597
|
+
*
|
|
598
|
+
* @module bwserve/attach
|
|
599
|
+
*/
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Generate the self-contained attach script.
|
|
604
|
+
*
|
|
605
|
+
* The returned JS string, when evaluated in a browser:
|
|
606
|
+
* 1. Checks if bw is already loaded; if not, injects bitwrench UMD
|
|
607
|
+
* 2. Evaluates bwclient source to set up bw._bwClient
|
|
608
|
+
* 3. Calls bw._bwClient.attach() to connect via SSE
|
|
609
|
+
*
|
|
610
|
+
* @param {Object} [opts]
|
|
611
|
+
* @param {string} [opts.origin=''] - Server origin (empty = same origin)
|
|
612
|
+
* @returns {string} JavaScript source code
|
|
613
|
+
*/
|
|
614
|
+
function generateAttachScript(opts) {
|
|
615
|
+
opts = opts || {};
|
|
616
|
+
var origin = opts.origin || '';
|
|
617
|
+
|
|
618
|
+
var clientSource = getBwClientSource();
|
|
619
|
+
|
|
620
|
+
return '(function() {\n'
|
|
621
|
+
+ ' "use strict";\n'
|
|
622
|
+
+ ' var origin = ' + JSON.stringify(origin) + ';\n'
|
|
623
|
+
+ ' function _go() {\n'
|
|
624
|
+
+ ' ' + clientSource + '\n'
|
|
625
|
+
+ ' bw._bwClient.attach(origin, {\n'
|
|
626
|
+
+ ' allowExec: true,\n'
|
|
627
|
+
+ ' onStatus: function(s) { console.log("[bw-attach] " + s); }\n'
|
|
628
|
+
+ ' });\n'
|
|
629
|
+
+ ' console.log("[bw-attach] v' + VERSION + ' connecting to " + (origin || location.origin));\n'
|
|
630
|
+
+ ' }\n'
|
|
631
|
+
+ ' if (window.bw) { _go(); return; }\n'
|
|
632
|
+
+ ' var s = document.createElement("script");\n'
|
|
633
|
+
+ ' s.src = (origin || "") + "/bw/lib/bitwrench.umd.js";\n'
|
|
634
|
+
+ ' s.onload = function() {\n'
|
|
635
|
+
+ ' if (typeof bw !== "undefined" && bw.loadStyles) bw.loadStyles();\n'
|
|
636
|
+
+ ' _go();\n'
|
|
637
|
+
+ ' };\n'
|
|
638
|
+
+ ' document.head.appendChild(s);\n'
|
|
639
|
+
+ '})();\n';
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
generateAttachScript.version = VERSION;
|
|
643
|
+
|
|
297
644
|
/**
|
|
298
645
|
* bwserve — Server-driven UI library for bitwrench
|
|
299
646
|
*
|
|
@@ -352,6 +699,7 @@ var MIME_TYPES = {
|
|
|
352
699
|
* @param {string} [opts.static] - Directory to serve static files from
|
|
353
700
|
* @param {boolean} [opts.injectBitwrench=true] - Auto-inject bitwrench client JS
|
|
354
701
|
* @param {string|Object} [opts.theme] - Theme preset name or config object
|
|
702
|
+
* @param {boolean} [opts.allowScreenshot=false] - Enable client.screenshot() capability
|
|
355
703
|
* @returns {BwServeApp} Application instance
|
|
356
704
|
*/
|
|
357
705
|
function create(opts) {
|
|
@@ -371,11 +719,12 @@ class BwServeApp {
|
|
|
371
719
|
this.injectBitwrench = opts.injectBitwrench !== false;
|
|
372
720
|
this.theme = opts.theme || null;
|
|
373
721
|
this.allowExec = opts.allowExec || false;
|
|
722
|
+
this.allowScreenshot = opts.allowScreenshot || false;
|
|
374
723
|
this.keepAliveInterval = opts.keepAliveInterval || 15000;
|
|
375
724
|
this._pages = new Map();
|
|
376
725
|
this._clients = new Map();
|
|
377
|
-
this._server = null;
|
|
378
726
|
this._clientCounter = 0;
|
|
727
|
+
this._server = null;
|
|
379
728
|
}
|
|
380
729
|
|
|
381
730
|
/**
|
|
@@ -483,31 +832,61 @@ class BwServeApp {
|
|
|
483
832
|
// Parse URL path (strip query string)
|
|
484
833
|
var path = url.split('?')[0];
|
|
485
834
|
|
|
486
|
-
// /
|
|
487
|
-
if (path === '/
|
|
835
|
+
// /bw/attach.js — self-contained attach script for remote debugging
|
|
836
|
+
if (path === '/bw/attach.js' && method === 'GET') {
|
|
837
|
+
return this._serveAttachScript(req, res);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// /bw/lib/bitwrench.umd.js — serve bitwrench client library
|
|
841
|
+
if (path === '/bw/lib/bitwrench.umd.js' && method === 'GET') {
|
|
488
842
|
return this._serveDistFile(res, 'bitwrench.umd.js');
|
|
489
843
|
}
|
|
490
844
|
|
|
491
|
-
// /
|
|
492
|
-
if (path === '/
|
|
845
|
+
// /bw/lib/bitwrench.umd.min.js — serve minified
|
|
846
|
+
if (path === '/bw/lib/bitwrench.umd.min.js' && method === 'GET') {
|
|
493
847
|
return this._serveDistFile(res, 'bitwrench.umd.min.js');
|
|
494
848
|
}
|
|
495
849
|
|
|
496
|
-
// /
|
|
497
|
-
if (path === '/
|
|
850
|
+
// /bw/lib/bitwrench.css — serve bitwrench CSS
|
|
851
|
+
if (path === '/bw/lib/bitwrench.css' && method === 'GET') {
|
|
498
852
|
return this._serveDistFile(res, 'bitwrench.css');
|
|
499
853
|
}
|
|
500
854
|
|
|
501
|
-
// /
|
|
502
|
-
if (path.startsWith('/
|
|
503
|
-
var clientId = path.slice('/
|
|
855
|
+
// /bw/events/:clientId — SSE stream
|
|
856
|
+
if (path.startsWith('/bw/events/') && method === 'GET') {
|
|
857
|
+
var clientId = path.slice('/bw/events/'.length);
|
|
504
858
|
return this._handleSSE(req, res, clientId);
|
|
505
859
|
}
|
|
506
860
|
|
|
507
|
-
// /
|
|
508
|
-
if (path.startsWith('/
|
|
509
|
-
|
|
510
|
-
|
|
861
|
+
// CORS preflight for /bw/return/ (needed for cross-origin attach)
|
|
862
|
+
if (method === 'OPTIONS' && path.startsWith('/bw/return/')) {
|
|
863
|
+
res.writeHead(204, {
|
|
864
|
+
'Access-Control-Allow-Origin': '*',
|
|
865
|
+
'Access-Control-Allow-Methods': 'POST',
|
|
866
|
+
'Access-Control-Allow-Headers': 'Content-Type'
|
|
867
|
+
});
|
|
868
|
+
res.end();
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// /bw/return/<route>/<clientId> — unified return channel
|
|
873
|
+
if (method === 'POST' && path.startsWith('/bw/return/')) {
|
|
874
|
+
var rest = path.slice('/bw/return/'.length);
|
|
875
|
+
var slash = rest.indexOf('/');
|
|
876
|
+
if (slash === -1) {
|
|
877
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
878
|
+
res.end(JSON.stringify({ error: 'Invalid return path' }));
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
var route = rest.slice(0, slash);
|
|
882
|
+
var returnClientId = rest.slice(slash + 1);
|
|
883
|
+
return this._handleReturn(req, res, route, returnClientId);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// /bw/lib/vendor/:filename — serve vendored libraries (allowlisted)
|
|
887
|
+
if (path.startsWith('/bw/lib/vendor/') && method === 'GET') {
|
|
888
|
+
var vendorFile = path.slice('/bw/lib/vendor/'.length);
|
|
889
|
+
return this._serveVendorFile(res, vendorFile);
|
|
511
890
|
}
|
|
512
891
|
|
|
513
892
|
// Registered page routes — serve shell HTML
|
|
@@ -583,6 +962,7 @@ class BwServeApp {
|
|
|
583
962
|
|
|
584
963
|
// Create client instance
|
|
585
964
|
var client = new BwServeClient(clientId, res);
|
|
965
|
+
client._allowScreenshot = this.allowScreenshot;
|
|
586
966
|
|
|
587
967
|
// Look up the pending client record (set during page serve)
|
|
588
968
|
var pending = self._clients.get(clientId);
|
|
@@ -615,38 +995,102 @@ class BwServeApp {
|
|
|
615
995
|
}
|
|
616
996
|
|
|
617
997
|
/**
|
|
618
|
-
*
|
|
998
|
+
* Unified return channel handler.
|
|
999
|
+
* Handles all client-to-server POST-backs via /bw/return/<route>/<clientId>.
|
|
1000
|
+
*
|
|
1001
|
+
* Routes:
|
|
1002
|
+
* action — fire-and-forget action dispatch (no requestId)
|
|
1003
|
+
* query — resolve pending query promise
|
|
1004
|
+
* mount — resolve pending mount promise
|
|
1005
|
+
* screenshot — resolve pending screenshot promise
|
|
1006
|
+
*
|
|
619
1007
|
* @private
|
|
620
1008
|
*/
|
|
621
|
-
|
|
1009
|
+
_handleReturn(req, res, route, clientId) {
|
|
622
1010
|
var record = this._clients.get(clientId);
|
|
623
1011
|
if (!record || !record.client) {
|
|
624
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1012
|
+
res.writeHead(404, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
625
1013
|
res.end(JSON.stringify({ error: 'Unknown client' }));
|
|
626
1014
|
return;
|
|
627
1015
|
}
|
|
628
1016
|
|
|
629
1017
|
var body = '';
|
|
630
|
-
req.on('data', function(chunk) {
|
|
631
|
-
body += chunk;
|
|
632
|
-
});
|
|
1018
|
+
req.on('data', function(chunk) { body += chunk; });
|
|
633
1019
|
req.on('end', function() {
|
|
634
1020
|
try {
|
|
635
1021
|
var data = JSON.parse(body);
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
1022
|
+
if (route === 'action' || route === 'event') {
|
|
1023
|
+
// Action/event dispatch (no requestId/pending pattern)
|
|
1024
|
+
var action = route === 'event'
|
|
1025
|
+
? '_bw_event'
|
|
1026
|
+
: (data.result ? data.result.action : data.action);
|
|
1027
|
+
var payload = route === 'event'
|
|
1028
|
+
? (data.result || data)
|
|
1029
|
+
: (data.result ? data.result.data : data.data || data);
|
|
1030
|
+
record.client._dispatch(action, payload);
|
|
1031
|
+
} else {
|
|
1032
|
+
// All other routes: resolve pending promise
|
|
1033
|
+
record.client._resolvePending(data.requestId, data);
|
|
1034
|
+
}
|
|
1035
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
640
1036
|
res.end(JSON.stringify({ ok: true }));
|
|
641
1037
|
} catch (e) {
|
|
642
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1038
|
+
res.writeHead(400, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
643
1039
|
res.end(JSON.stringify({ error: e.message }));
|
|
644
1040
|
}
|
|
645
1041
|
});
|
|
646
1042
|
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Serve the self-contained attach script at /bw/attach.js.
|
|
1046
|
+
* Loads bitwrench + bwclient and auto-connects via SSE.
|
|
1047
|
+
* @private
|
|
1048
|
+
*/
|
|
1049
|
+
_serveAttachScript(req, res) {
|
|
1050
|
+
try {
|
|
1051
|
+
var js = generateAttachScript({ origin: '' });
|
|
1052
|
+
res.writeHead(200, {
|
|
1053
|
+
'Content-Type': 'application/javascript; charset=utf-8',
|
|
1054
|
+
'Access-Control-Allow-Origin': '*',
|
|
1055
|
+
'Cache-Control': 'no-cache'
|
|
1056
|
+
});
|
|
1057
|
+
res.end(js);
|
|
1058
|
+
} catch (err) {
|
|
1059
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
1060
|
+
res.end('Error generating attach script: ' + err.message);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* Serve a vendored library file (allowlisted filenames only).
|
|
1066
|
+
* @private
|
|
1067
|
+
*/
|
|
1068
|
+
_serveVendorFile(res, filename) {
|
|
1069
|
+
var allowed = ['html2canvas.min.js'];
|
|
1070
|
+
if (allowed.indexOf(filename) === -1) {
|
|
1071
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
1072
|
+
res.end('Not found');
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
var vendorDir = resolve(__dirname$1, '..', 'vendor');
|
|
1076
|
+
var filePath = join(vendorDir, filename);
|
|
1077
|
+
if (!existsSync(filePath)) {
|
|
1078
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
1079
|
+
res.end('Vendor file not found: ' + filename);
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
var content = readFileSync(filePath);
|
|
1083
|
+
res.writeHead(200, {
|
|
1084
|
+
'Content-Type': 'application/javascript; charset=utf-8',
|
|
1085
|
+
'Cache-Control': 'public, max-age=86400'
|
|
1086
|
+
});
|
|
1087
|
+
res.end(content);
|
|
1088
|
+
}
|
|
647
1089
|
}
|
|
648
1090
|
|
|
649
|
-
var
|
|
1091
|
+
var version = VERSION;
|
|
1092
|
+
|
|
1093
|
+
var index = { create, version: VERSION, BwServeApp, BwServeClient, generateShell };
|
|
650
1094
|
|
|
651
|
-
export { BwServeApp, BwServeClient, create, index as default };
|
|
1095
|
+
export { BwServeApp, BwServeClient, create, index as default, generateShell, version };
|
|
652
1096
|
//# sourceMappingURL=bwserve.esm.js.map
|