bitwrench 2.0.16 → 2.0.18
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 +127 -38
- package/dist/bitwrench-bccl.cjs.js +13 -9
- package/dist/bitwrench-bccl.cjs.min.js +2 -2
- package/dist/bitwrench-bccl.esm.js +13 -9
- package/dist/bitwrench-bccl.esm.min.js +2 -2
- package/dist/bitwrench-bccl.umd.js +13 -9
- package/dist/bitwrench-bccl.umd.min.js +2 -2
- package/dist/bitwrench-code-edit.cjs.js +1 -1
- package/dist/bitwrench-code-edit.cjs.min.js +1 -1
- package/dist/bitwrench-code-edit.es5.js +1 -1
- package/dist/bitwrench-code-edit.es5.min.js +1 -1
- package/dist/bitwrench-code-edit.esm.js +1 -1
- package/dist/bitwrench-code-edit.esm.min.js +1 -1
- package/dist/bitwrench-code-edit.umd.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js +1 -1
- package/dist/bitwrench-lean.cjs.js +1438 -920
- package/dist/bitwrench-lean.cjs.min.js +20 -20
- package/dist/bitwrench-lean.es5.js +1518 -1105
- package/dist/bitwrench-lean.es5.min.js +18 -18
- package/dist/bitwrench-lean.esm.js +1437 -920
- package/dist/bitwrench-lean.esm.min.js +20 -20
- package/dist/bitwrench-lean.umd.js +1438 -920
- 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 +1450 -928
- package/dist/bitwrench.cjs.min.js +21 -21
- package/dist/bitwrench.css +456 -132
- package/dist/bitwrench.es5.js +1563 -1140
- package/dist/bitwrench.es5.min.js +19 -19
- package/dist/bitwrench.esm.js +1450 -929
- package/dist/bitwrench.esm.min.js +21 -21
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +1450 -928
- package/dist/bitwrench.umd.min.js +21 -21
- package/dist/builds.json +178 -90
- package/dist/bwserve.cjs.js +528 -68
- package/dist/bwserve.esm.js +527 -69
- package/dist/sri.json +44 -36
- package/package.json +5 -2
- package/readme.html +136 -49
- package/src/bitwrench-bccl.js +12 -8
- package/src/bitwrench-color-utils.js +31 -9
- package/src/bitwrench-esm-entry.js +11 -0
- package/src/bitwrench-styles.js +439 -232
- package/src/bitwrench-util-css.js +229 -0
- package/src/bitwrench.js +979 -630
- 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 +139 -29
- package/src/cli/attach.js +555 -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/layout-default.js +47 -32
- package/src/cli/serve.js +6 -2
- 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 -103
package/dist/bwserve.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! bwserve v2.0.
|
|
1
|
+
/*! bwserve v2.0.18 | 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.18';
|
|
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,58 +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
|
-
|
|
260
|
-
|
|
570
|
+
if (opts.allowExec) {
|
|
571
|
+
script.push(' bw._allowExec = true;');
|
|
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, {');
|
|
261
577
|
script.push(' onStatus: function(s) {');
|
|
262
578
|
script.push(' if (typeof console !== "undefined") console.log("[bwserve] " + s);');
|
|
263
579
|
script.push(' }');
|
|
264
580
|
script.push(' });');
|
|
265
|
-
|
|
266
|
-
// data-bw-action click delegation
|
|
267
|
-
script.push(' document.addEventListener("click", function(e) {');
|
|
268
|
-
script.push(' var el = e.target.closest ? e.target.closest("[data-bw-action]") : null;');
|
|
269
|
-
script.push(' if (!el) return;');
|
|
270
|
-
script.push(' e.preventDefault();');
|
|
271
|
-
script.push(' var actionData = {};');
|
|
272
|
-
script.push(' if (el.getAttribute("data-bw-id")) actionData.bwId = el.getAttribute("data-bw-id");');
|
|
273
|
-
script.push(' var form = el.closest("div") || document;');
|
|
274
|
-
script.push(' var inp = form.querySelector("input[type=text],input:not([type])");');
|
|
275
|
-
script.push(' if (inp) { actionData.inputValue = inp.value; inp.value = ""; }');
|
|
276
|
-
script.push(' conn.sendAction(el.getAttribute("data-bw-action"), actionData);');
|
|
277
|
-
script.push(' });');
|
|
278
|
-
|
|
279
|
-
// Enter key on inputs
|
|
280
|
-
script.push(' document.addEventListener("keydown", function(e) {');
|
|
281
|
-
script.push(' if (e.key === "Enter" && e.target.tagName === "INPUT") {');
|
|
282
|
-
script.push(' var form = e.target.closest("div") || document;');
|
|
283
|
-
script.push(' var btn = form.querySelector("[data-bw-action]");');
|
|
284
|
-
script.push(' if (btn) {');
|
|
285
|
-
script.push(' conn.sendAction(btn.getAttribute("data-bw-action"), { inputValue: e.target.value });');
|
|
286
|
-
script.push(' e.target.value = "";');
|
|
287
|
-
script.push(' }');
|
|
288
|
-
script.push(' }');
|
|
289
|
-
script.push(' });');
|
|
290
|
-
|
|
291
581
|
script.push('})();');
|
|
292
582
|
script.push('</script>');
|
|
583
|
+
|
|
293
584
|
script.push('</body>');
|
|
294
585
|
script.push('</html>');
|
|
295
586
|
|
|
296
587
|
return head.concat(script).join('\n');
|
|
297
588
|
}
|
|
298
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
|
+
|
|
299
649
|
/**
|
|
300
650
|
* bwserve — Server-driven UI library for bitwrench
|
|
301
651
|
*
|
|
@@ -316,7 +666,16 @@ function generateShell(opts) {
|
|
|
316
666
|
|
|
317
667
|
|
|
318
668
|
var __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bwserve.cjs.js', document.baseURI).href))));
|
|
669
|
+
|
|
670
|
+
// Resolve dist/ — try source layout (src/bwserve/), then npm install layout,
|
|
671
|
+
// then dist/ itself (when running from dist/bwserve.esm.js)
|
|
319
672
|
var DIST_DIR = path.resolve(__dirname$1, '..', '..', 'dist');
|
|
673
|
+
if (!fs.existsSync(DIST_DIR)) {
|
|
674
|
+
DIST_DIR = path.resolve(__dirname$1, '..', 'dist');
|
|
675
|
+
}
|
|
676
|
+
if (!fs.existsSync(DIST_DIR)) {
|
|
677
|
+
DIST_DIR = __dirname$1;
|
|
678
|
+
}
|
|
320
679
|
|
|
321
680
|
// MIME type lookup for static file serving
|
|
322
681
|
var MIME_TYPES = {
|
|
@@ -345,6 +704,7 @@ var MIME_TYPES = {
|
|
|
345
704
|
* @param {string} [opts.static] - Directory to serve static files from
|
|
346
705
|
* @param {boolean} [opts.injectBitwrench=true] - Auto-inject bitwrench client JS
|
|
347
706
|
* @param {string|Object} [opts.theme] - Theme preset name or config object
|
|
707
|
+
* @param {boolean} [opts.allowScreenshot=false] - Enable client.screenshot() capability
|
|
348
708
|
* @returns {BwServeApp} Application instance
|
|
349
709
|
*/
|
|
350
710
|
function create(opts) {
|
|
@@ -363,11 +723,13 @@ class BwServeApp {
|
|
|
363
723
|
this.staticDir = opts.static || null;
|
|
364
724
|
this.injectBitwrench = opts.injectBitwrench !== false;
|
|
365
725
|
this.theme = opts.theme || null;
|
|
726
|
+
this.allowExec = opts.allowExec || false;
|
|
727
|
+
this.allowScreenshot = opts.allowScreenshot || false;
|
|
366
728
|
this.keepAliveInterval = opts.keepAliveInterval || 15000;
|
|
367
729
|
this._pages = new Map();
|
|
368
730
|
this._clients = new Map();
|
|
369
|
-
this._server = null;
|
|
370
731
|
this._clientCounter = 0;
|
|
732
|
+
this._server = null;
|
|
371
733
|
}
|
|
372
734
|
|
|
373
735
|
/**
|
|
@@ -475,31 +837,61 @@ class BwServeApp {
|
|
|
475
837
|
// Parse URL path (strip query string)
|
|
476
838
|
var path$1 = url.split('?')[0];
|
|
477
839
|
|
|
478
|
-
// /
|
|
479
|
-
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') {
|
|
480
847
|
return this._serveDistFile(res, 'bitwrench.umd.js');
|
|
481
848
|
}
|
|
482
849
|
|
|
483
|
-
// /
|
|
484
|
-
if (path$1 === '/
|
|
850
|
+
// /bw/lib/bitwrench.umd.min.js — serve minified
|
|
851
|
+
if (path$1 === '/bw/lib/bitwrench.umd.min.js' && method === 'GET') {
|
|
485
852
|
return this._serveDistFile(res, 'bitwrench.umd.min.js');
|
|
486
853
|
}
|
|
487
854
|
|
|
488
|
-
// /
|
|
489
|
-
if (path$1 === '/
|
|
855
|
+
// /bw/lib/bitwrench.css — serve bitwrench CSS
|
|
856
|
+
if (path$1 === '/bw/lib/bitwrench.css' && method === 'GET') {
|
|
490
857
|
return this._serveDistFile(res, 'bitwrench.css');
|
|
491
858
|
}
|
|
492
859
|
|
|
493
|
-
// /
|
|
494
|
-
if (path$1.startsWith('/
|
|
495
|
-
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);
|
|
496
863
|
return this._handleSSE(req, res, clientId);
|
|
497
864
|
}
|
|
498
865
|
|
|
499
|
-
// /
|
|
500
|
-
if (path$1.startsWith('/
|
|
501
|
-
|
|
502
|
-
|
|
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);
|
|
503
895
|
}
|
|
504
896
|
|
|
505
897
|
// Registered page routes — serve shell HTML
|
|
@@ -509,7 +901,8 @@ class BwServeApp {
|
|
|
509
901
|
clientId: clientId2,
|
|
510
902
|
title: this.title,
|
|
511
903
|
theme: this.theme,
|
|
512
|
-
injectBitwrench: this.injectBitwrench
|
|
904
|
+
injectBitwrench: this.injectBitwrench,
|
|
905
|
+
allowExec: this.allowExec
|
|
513
906
|
});
|
|
514
907
|
// Store the page path for this client so SSE knows which handler to call
|
|
515
908
|
this._clients.set(clientId2, { pagePath: path$1, client: null });
|
|
@@ -574,6 +967,7 @@ class BwServeApp {
|
|
|
574
967
|
|
|
575
968
|
// Create client instance
|
|
576
969
|
var client = new BwServeClient(clientId, res);
|
|
970
|
+
client._allowScreenshot = this.allowScreenshot;
|
|
577
971
|
|
|
578
972
|
// Look up the pending client record (set during page serve)
|
|
579
973
|
var pending = self._clients.get(clientId);
|
|
@@ -606,41 +1000,107 @@ class BwServeApp {
|
|
|
606
1000
|
}
|
|
607
1001
|
|
|
608
1002
|
/**
|
|
609
|
-
*
|
|
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
|
+
*
|
|
610
1012
|
* @private
|
|
611
1013
|
*/
|
|
612
|
-
|
|
1014
|
+
_handleReturn(req, res, route, clientId) {
|
|
613
1015
|
var record = this._clients.get(clientId);
|
|
614
1016
|
if (!record || !record.client) {
|
|
615
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1017
|
+
res.writeHead(404, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
616
1018
|
res.end(JSON.stringify({ error: 'Unknown client' }));
|
|
617
1019
|
return;
|
|
618
1020
|
}
|
|
619
1021
|
|
|
620
1022
|
var body = '';
|
|
621
|
-
req.on('data', function(chunk) {
|
|
622
|
-
body += chunk;
|
|
623
|
-
});
|
|
1023
|
+
req.on('data', function(chunk) { body += chunk; });
|
|
624
1024
|
req.on('end', function() {
|
|
625
1025
|
try {
|
|
626
1026
|
var data = JSON.parse(body);
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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': '*' });
|
|
631
1041
|
res.end(JSON.stringify({ ok: true }));
|
|
632
1042
|
} catch (e) {
|
|
633
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1043
|
+
res.writeHead(400, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
634
1044
|
res.end(JSON.stringify({ error: e.message }));
|
|
635
1045
|
}
|
|
636
1046
|
});
|
|
637
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
|
+
}
|
|
638
1094
|
}
|
|
639
1095
|
|
|
640
|
-
var
|
|
1096
|
+
var version = VERSION;
|
|
1097
|
+
|
|
1098
|
+
var index = { create, version: VERSION, BwServeApp, BwServeClient, generateShell };
|
|
641
1099
|
|
|
642
1100
|
exports.BwServeApp = BwServeApp;
|
|
643
1101
|
exports.BwServeClient = BwServeClient;
|
|
644
1102
|
exports.create = create;
|
|
645
1103
|
exports.default = index;
|
|
1104
|
+
exports.generateShell = generateShell;
|
|
1105
|
+
exports.version = version;
|
|
646
1106
|
//# sourceMappingURL=bwserve.cjs.js.map
|