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