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.
Files changed (72) hide show
  1. package/README.md +169 -75
  2. package/dist/bitwrench-bccl.cjs.js +228 -55
  3. package/dist/bitwrench-bccl.cjs.min.js +3 -3
  4. package/dist/bitwrench-bccl.esm.js +228 -55
  5. package/dist/bitwrench-bccl.esm.min.js +3 -3
  6. package/dist/bitwrench-bccl.umd.js +228 -55
  7. package/dist/bitwrench-bccl.umd.min.js +3 -3
  8. package/dist/bitwrench-code-edit.cjs.js +7 -9
  9. package/dist/bitwrench-code-edit.cjs.min.js +5 -7
  10. package/dist/bitwrench-code-edit.es5.js +6 -8
  11. package/dist/bitwrench-code-edit.es5.min.js +5 -7
  12. package/dist/bitwrench-code-edit.esm.js +7 -9
  13. package/dist/bitwrench-code-edit.esm.min.js +5 -7
  14. package/dist/bitwrench-code-edit.umd.js +7 -9
  15. package/dist/bitwrench-code-edit.umd.min.js +5 -7
  16. package/dist/bitwrench-debug.js +268 -0
  17. package/dist/bitwrench-debug.min.js +3 -0
  18. package/dist/bitwrench-lean.cjs.js +1190 -2348
  19. package/dist/bitwrench-lean.cjs.min.js +20 -20
  20. package/dist/bitwrench-lean.es5.js +1285 -2551
  21. package/dist/bitwrench-lean.es5.min.js +18 -18
  22. package/dist/bitwrench-lean.esm.js +1190 -2348
  23. package/dist/bitwrench-lean.esm.min.js +20 -20
  24. package/dist/bitwrench-lean.umd.js +1190 -2348
  25. package/dist/bitwrench-lean.umd.min.js +20 -20
  26. package/dist/bitwrench-util-css.cjs.js +236 -0
  27. package/dist/bitwrench-util-css.cjs.min.js +22 -0
  28. package/dist/bitwrench-util-css.es5.js +414 -0
  29. package/dist/bitwrench-util-css.es5.min.js +21 -0
  30. package/dist/bitwrench-util-css.esm.js +230 -0
  31. package/dist/bitwrench-util-css.esm.min.js +21 -0
  32. package/dist/bitwrench-util-css.umd.js +242 -0
  33. package/dist/bitwrench-util-css.umd.min.js +21 -0
  34. package/dist/bitwrench.cjs.js +1404 -2388
  35. package/dist/bitwrench.cjs.min.js +21 -21
  36. package/dist/bitwrench.css +503 -132
  37. package/dist/bitwrench.es5.js +1588 -2659
  38. package/dist/bitwrench.es5.min.js +19 -19
  39. package/dist/bitwrench.esm.js +1405 -2389
  40. package/dist/bitwrench.esm.min.js +21 -21
  41. package/dist/bitwrench.min.css +1 -1
  42. package/dist/bitwrench.umd.js +1404 -2388
  43. package/dist/bitwrench.umd.min.js +21 -21
  44. package/dist/builds.json +214 -104
  45. package/dist/bwserve.cjs.js +514 -68
  46. package/dist/bwserve.esm.js +513 -69
  47. package/dist/sri.json +46 -36
  48. package/package.json +6 -3
  49. package/readme.html +183 -85
  50. package/src/bitwrench-bccl-entry.js +3 -4
  51. package/src/bitwrench-bccl.js +224 -50
  52. package/src/bitwrench-code-edit.js +6 -8
  53. package/src/bitwrench-color-utils.js +31 -9
  54. package/src/bitwrench-debug.js +245 -0
  55. package/src/bitwrench-esm-entry.js +11 -0
  56. package/src/bitwrench-styles.js +474 -240
  57. package/src/bitwrench-util-css.js +229 -0
  58. package/src/bitwrench.js +689 -2042
  59. package/src/bwserve/attach.js +57 -0
  60. package/src/bwserve/bwclient.js +141 -0
  61. package/src/bwserve/bwshell.js +102 -0
  62. package/src/bwserve/client.js +151 -1
  63. package/src/bwserve/index.js +127 -28
  64. package/src/cli/attach.js +587 -0
  65. package/src/cli/convert.js +2 -5
  66. package/src/cli/index.js +7 -0
  67. package/src/cli/inject.js +1 -1
  68. package/src/cli/serve.js +185 -5
  69. package/src/generate-css.js +11 -4
  70. package/src/vendor/html2canvas.min.js +20 -0
  71. package/src/version.js +3 -3
  72. package/src/bwserve/shell.js +0 -106
@@ -1,4 +1,4 @@
1
- /*! bwserve v2.0.17 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
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 (always available, no registration needed):
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 /__bw/ routes
200
- * - Calls bw.loadDefaultStyles()
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
- * - Opens an SSE connection via bw.clientConnect()
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/shell
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="/__bw/bitwrench.umd.js"></script>');
236
- head.push('<link rel="stylesheet" href="/__bw/bitwrench.css">');
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.loadDefaultStyles();'
546
+ ' bw.loadStyles();'
248
547
  ];
249
548
 
250
549
  if (opts.theme) {
251
- script.push(' bw.generateTheme("bwserve", ' + JSON.stringify(
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(' allowExec: true,');
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
- // /__bw/bitwrench.umd.js — serve bitwrench client library
492
- if (path$1 === '/__bw/bitwrench.umd.js' && method === 'GET') {
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
- // /__bw/bitwrench.umd.min.js — serve minified
497
- if (path$1 === '/__bw/bitwrench.umd.min.js' && method === 'GET') {
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
- // /__bw/bitwrench.css — serve bitwrench CSS
502
- if (path$1 === '/__bw/bitwrench.css' && method === 'GET') {
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
- // /__bw/events/:clientId — SSE stream
507
- if (path$1.startsWith('/__bw/events/') && method === 'GET') {
508
- var clientId = path$1.slice('/__bw/events/'.length);
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
- // /__bw/action/:clientId action POST
513
- if (path$1.startsWith('/__bw/action/') && method === 'POST') {
514
- var actionClientId = path$1.slice('/__bw/action/'.length);
515
- return this._handleAction(req, res, actionClientId);
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
- * Handle an action POST from a client.
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
- _handleAction(req, res, clientId) {
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
- var action = data.action;
642
- var payload = data.data || data;
643
- record.client._dispatch(action, payload);
644
- res.writeHead(200, { 'Content-Type': 'application/json' });
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 index = { create, BwServeApp, BwServeClient };
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