bitwrench 2.0.32 → 2.1.1
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 +211 -125
- package/dist/bitwrench-bccl.cjs.js +349 -188
- package/dist/bitwrench-bccl.cjs.min.js +2 -39
- package/dist/bitwrench-bccl.cjs.min.js.gz +0 -0
- package/dist/bitwrench-bccl.esm.js +349 -188
- package/dist/bitwrench-bccl.esm.min.js +2 -39
- package/dist/bitwrench-bccl.esm.min.js.gz +0 -0
- package/dist/bitwrench-bccl.umd.js +349 -188
- package/dist/bitwrench-bccl.umd.min.js +2 -39
- package/dist/bitwrench-bccl.umd.min.js.gz +0 -0
- package/dist/bitwrench-code-edit.cjs.js +17 -6
- package/dist/bitwrench-code-edit.cjs.min.js +2 -20
- package/dist/bitwrench-code-edit.es5.js +8 -3
- package/dist/bitwrench-code-edit.es5.min.js +2 -19
- package/dist/bitwrench-code-edit.esm.js +17 -6
- package/dist/bitwrench-code-edit.esm.min.js +2 -19
- package/dist/bitwrench-code-edit.umd.js +17 -6
- package/dist/bitwrench-code-edit.umd.min.js +2 -19
- package/dist/bitwrench-code-edit.umd.min.js.gz +0 -0
- package/dist/bitwrench-debug.js +1 -1
- package/dist/bitwrench-debug.min.js +1 -1
- package/dist/bitwrench-lean.cjs.js +2492 -1628
- package/dist/bitwrench-lean.cjs.min.js +2 -80
- package/dist/bitwrench-lean.cjs.min.js.gz +0 -0
- package/dist/bitwrench-lean.es5.js +2740 -1838
- package/dist/bitwrench-lean.es5.min.js +2 -49
- package/dist/bitwrench-lean.es5.min.js.gz +0 -0
- package/dist/bitwrench-lean.esm.js +2492 -1628
- package/dist/bitwrench-lean.esm.min.js +2 -80
- package/dist/bitwrench-lean.esm.min.js.gz +0 -0
- package/dist/bitwrench-lean.umd.js +2492 -1628
- package/dist/bitwrench-lean.umd.min.js +2 -80
- package/dist/bitwrench-lean.umd.min.js.gz +0 -0
- package/dist/bitwrench-util-color.cjs.js +251 -0
- package/dist/bitwrench-util-color.cjs.min.js +3 -0
- package/dist/bitwrench-util-color.es5.js +256 -0
- package/dist/bitwrench-util-color.es5.min.js +3 -0
- package/dist/bitwrench-util-color.esm.js +241 -0
- package/dist/bitwrench-util-color.esm.min.js +3 -0
- package/dist/bitwrench-util-color.umd.js +257 -0
- package/dist/bitwrench-util-color.umd.min.js +3 -0
- package/dist/bitwrench-util-css.cjs.js +2 -1
- package/dist/bitwrench-util-css.cjs.min.js +2 -21
- package/dist/bitwrench-util-css.es5.js +2 -1
- package/dist/bitwrench-util-css.es5.min.js +2 -20
- package/dist/bitwrench-util-css.esm.js +2 -1
- package/dist/bitwrench-util-css.esm.min.js +1 -19
- package/dist/bitwrench-util-css.umd.js +2 -1
- package/dist/bitwrench-util-css.umd.min.js +2 -20
- package/dist/bitwrench-util-css.umd.min.js.gz +0 -0
- package/dist/bitwrench.cjs.js +2826 -1801
- package/dist/bitwrench.cjs.min.js +2 -99
- package/dist/bitwrench.cjs.min.js.gz +0 -0
- package/dist/bitwrench.css +403 -479
- package/dist/bitwrench.d.ts +70 -73
- package/dist/bitwrench.es5.js +3106 -2020
- package/dist/bitwrench.es5.min.js +2 -67
- package/dist/bitwrench.es5.min.js.gz +0 -0
- package/dist/bitwrench.esm.js +2826 -1801
- package/dist/bitwrench.esm.min.js +2 -99
- package/dist/bitwrench.esm.min.js.gz +0 -0
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +2826 -1801
- package/dist/bitwrench.umd.min.js +2 -99
- package/dist/bitwrench.umd.min.js.gz +0 -0
- package/dist/builds.json +222 -134
- package/dist/bwserve.cjs.js +289 -282
- package/dist/bwserve.d.ts +157 -0
- package/dist/bwserve.esm.js +290 -283
- package/dist/sri.json +54 -46
- package/docs/README.md +6 -3
- package/docs/app-patterns.md +7 -6
- package/docs/bitwrench-for-wasm.md +53 -54
- package/docs/bitwrench-mcp.md +2 -2
- package/docs/bitwrench-northstar-principles.md +406 -0
- package/docs/bitwrench-taco-schema-discussion.md +2 -2
- package/docs/bitwrench_api.md +191 -106
- package/docs/bitwrench_typescript_usage.md +5 -5
- package/docs/bw-attach.md +29 -75
- package/docs/bwserve.md +200 -168
- package/docs/cli.md +36 -12
- package/docs/component-cheatsheet.md +2 -2
- package/docs/component-library.md +4 -4
- package/docs/component-lifecycle.md +234 -0
- package/docs/drift-lint.md +268 -0
- package/docs/framework-translation-table.md +4 -4
- package/docs/llm-bitwrench-guide.md +60 -50
- package/docs/routing.md +11 -13
- package/docs/state-management.md +110 -109
- package/docs/taco-format.md +13 -14
- package/docs/theming.md +13 -3
- package/docs/thinking-in-bitwrench.md +858 -983
- package/docs/tutorial-bwserve.md +37 -36
- package/docs/tutorial-embedded.md +10 -21
- package/docs/tutorial-website.md +2 -2
- package/package.json +38 -7
- package/readme.html +262 -161
- package/src/bitwrench-bccl-entry.js +2 -2
- package/src/bitwrench-bccl.js +346 -185
- package/src/bitwrench-code-edit.js +16 -5
- package/src/bitwrench-color-utils.js +117 -181
- package/src/bitwrench-file-ops.js +2 -2
- package/src/bitwrench-lean.js +4 -3
- package/src/bitwrench-router.js +5 -2
- package/src/bitwrench-styles.js +420 -504
- package/src/bitwrench-util-color.js +240 -0
- package/src/bitwrench-util-css.js +1 -0
- package/src/bitwrench-utils.js +4 -0
- package/src/bitwrench.d.ts +70 -73
- package/src/bitwrench.h +5 -0
- package/src/bitwrench.js +1939 -933
- package/src/bwserve/attach.js +0 -1
- package/src/bwserve/bwclient.js +172 -32
- package/src/bwserve/bwshell.js +0 -4
- package/src/bwserve/client.js +59 -220
- package/src/bwserve/index.js +59 -26
- package/src/bwserve.d.ts +157 -0
- package/src/bwserve.h +5 -0
- package/src/cli/attach.js +12 -75
- package/src/cli/convert.js +2 -2
- package/src/cli/serve.js +37 -35
- package/src/generate-css.js +1 -1
- package/src/mcp/knowledge.js +4 -4
- package/src/mcp/live.js +21 -13
- package/src/mcp/tools.js +0 -1
- package/src/version.js +3 -7
package/src/bwserve/attach.js
CHANGED
|
@@ -38,7 +38,6 @@ export function generateAttachScript(opts) {
|
|
|
38
38
|
+ ' function _go() {\n'
|
|
39
39
|
+ ' ' + clientSource + '\n'
|
|
40
40
|
+ ' bw._bwClient.attach(origin, {\n'
|
|
41
|
-
+ ' allowExec: true,\n'
|
|
42
41
|
+ ' onStatus: function(s) { console.log("[bw-attach] " + s); }\n'
|
|
43
42
|
+ ' });\n'
|
|
44
43
|
+ ' console.log("[bw-attach] v' + VERSION + ' connecting to " + (origin || location.origin));\n'
|
package/src/bwserve/bwclient.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - SSE connection lifecycle (connect, reconnect, status)
|
|
9
9
|
* - Unified POST-back via /bw/return/<route>/<clientId>
|
|
10
10
|
* - Register built-in client functions (scrollTo, focus, etc.)
|
|
11
|
-
* -
|
|
11
|
+
* - bw_act_* class click/key delegation
|
|
12
12
|
* - Attach mode for remote-controlling any bitwrench page
|
|
13
13
|
*
|
|
14
14
|
* @module bwserve/bwclient
|
|
@@ -38,11 +38,12 @@ var BWCLIENT_SOURCE = '(function(bw) {\n'
|
|
|
38
38
|
+ '\n'
|
|
39
39
|
+ ' // ── Unified POST-back ──\n'
|
|
40
40
|
+ ' _client.respond = function(route, requestId, result, error) {\n'
|
|
41
|
-
+ '
|
|
41
|
+
+ ' var base = (typeof origin !== "undefined" ? origin : "");\n'
|
|
42
|
+
+ ' fetch(base + "/bw/return/" + route + "/" + _client.id, {\n'
|
|
42
43
|
+ ' method: "POST",\n'
|
|
43
44
|
+ ' headers: { "Content-Type": "application/json" },\n'
|
|
44
45
|
+ ' body: JSON.stringify({ requestId: requestId, route: route, result: result, error: error || null })\n'
|
|
45
|
-
+ ' }).catch(function() {});\n'
|
|
46
|
+
+ ' }).catch(function(e) { console.warn("[bwclient] respond failed:", e); });\n'
|
|
46
47
|
+ ' };\n'
|
|
47
48
|
+ '\n'
|
|
48
49
|
+ ' // ── SSE connect ──\n'
|
|
@@ -72,7 +73,6 @@ var BWCLIENT_SOURCE = '(function(bw) {\n'
|
|
|
72
73
|
+ ' _client.attach = function(url, opts) {\n'
|
|
73
74
|
+ ' opts = opts || {};\n'
|
|
74
75
|
+ ' _client.id = opts.clientId || "att_" + Math.random().toString(36).slice(2, 10);\n'
|
|
75
|
-
+ ' if (opts.allowExec) bw._allowExec = true;\n'
|
|
76
76
|
+ ' _client._registerBuiltins();\n'
|
|
77
77
|
+ ' _client._wireActions();\n'
|
|
78
78
|
+ ' _client.connect(url + "/bw/events/" + _client.id, opts);\n'
|
|
@@ -83,52 +83,192 @@ var BWCLIENT_SOURCE = '(function(bw) {\n'
|
|
|
83
83
|
+ ' _client.respond("action", null, { action: action, data: data || {} });\n'
|
|
84
84
|
+ ' };\n'
|
|
85
85
|
+ '\n'
|
|
86
|
-
+ ' // ── Register built-in functions ──\n'
|
|
86
|
+
+ ' // ── Register built-in functions (safe closures, no string eval) ──\n'
|
|
87
87
|
+ ' _client._registerBuiltins = function() {\n'
|
|
88
|
-
+ '
|
|
89
|
-
+ '
|
|
90
|
-
+ '
|
|
91
|
-
+ '
|
|
92
|
-
+ '
|
|
93
|
-
+ '
|
|
94
|
-
+ '
|
|
95
|
-
+ '
|
|
96
|
-
+ '
|
|
97
|
-
+ '
|
|
98
|
-
+ '
|
|
99
|
-
+ '
|
|
100
|
-
+ '
|
|
101
|
-
+ '
|
|
102
|
-
+ '
|
|
103
|
-
+ '
|
|
88
|
+
+ ' bw.registerRemote("scrollTo", function(sel) {\n'
|
|
89
|
+
+ ' var el = bw.el(sel); if (el) el.scrollTop = el.scrollHeight;\n'
|
|
90
|
+
+ ' });\n'
|
|
91
|
+
+ ' bw.registerRemote("focus", function(sel) {\n'
|
|
92
|
+
+ ' var el = bw.el(sel); if (el && typeof el.focus === "function") el.focus();\n'
|
|
93
|
+
+ ' });\n'
|
|
94
|
+
+ ' bw.registerRemote("download", function(fn, c, m) {\n'
|
|
95
|
+
+ ' if (typeof document === "undefined") return;\n'
|
|
96
|
+
+ ' var b = new Blob([c], { type: m || "text/plain" });\n'
|
|
97
|
+
+ ' var a = document.createElement("a");\n'
|
|
98
|
+
+ ' a.href = URL.createObjectURL(b); a.download = fn; a.click();\n'
|
|
99
|
+
+ ' URL.revokeObjectURL(a.href);\n'
|
|
100
|
+
+ ' });\n'
|
|
101
|
+
+ ' bw.registerRemote("clipboard", function(t) {\n'
|
|
102
|
+
+ ' if (typeof navigator !== "undefined" && navigator.clipboard) navigator.clipboard.writeText(t);\n'
|
|
103
|
+
+ ' });\n'
|
|
104
|
+
+ ' bw.registerRemote("redirect", function(u) {\n'
|
|
105
|
+
+ ' if (typeof window !== "undefined") window.location.href = u;\n'
|
|
106
|
+
+ ' });\n'
|
|
107
|
+
+ ' bw.registerRemote("log", function() {\n'
|
|
108
|
+
+ ' console.log.apply(console, arguments);\n'
|
|
109
|
+
+ ' });\n'
|
|
110
|
+
+ ' bw.registerRemote("_bw_mount", function(opts) {\n'
|
|
111
|
+
+ ' if (!bw._bwClient) return;\n'
|
|
112
|
+
+ ' try {\n'
|
|
113
|
+
+ ' var f = opts.factory;\n'
|
|
114
|
+
+ ' var n = f.replace(/-([a-z])/g, function(_, c) { return c.toUpperCase(); });\n'
|
|
115
|
+
+ ' if (bw.BCCL && bw.BCCL[n]) {\n'
|
|
116
|
+
+ ' var taco = bw.make(n, opts.props || {});\n'
|
|
117
|
+
+ ' bw.mount(opts.target, taco);\n'
|
|
118
|
+
+ ' bw._bwClient.respond("mount", opts.requestId, { mounted: true });\n'
|
|
119
|
+
+ ' } else {\n'
|
|
120
|
+
+ ' throw new Error("Unknown BCCL component: " + f);\n'
|
|
121
|
+
+ ' }\n'
|
|
122
|
+
+ ' } catch (e) {\n'
|
|
123
|
+
+ ' bw._bwClient.respond("mount", opts.requestId, null, e.message);\n'
|
|
124
|
+
+ ' }\n'
|
|
125
|
+
+ ' });\n'
|
|
126
|
+
+ ' bw.registerRemote("_bw_screenshot", function(opts) {\n'
|
|
127
|
+
+ ' if (!bw._bwClient) return;\n'
|
|
128
|
+
+ ' var sel = opts.selector || "body";\n'
|
|
129
|
+
+ ' var el = document.querySelector(sel);\n'
|
|
130
|
+
+ ' if (!el) { bw._bwClient.respond("screenshot", opts.requestId, null, "Element not found: " + sel); return; }\n'
|
|
131
|
+
+ ' function _ls(url) {\n'
|
|
132
|
+
+ ' return new Promise(function(res, rej) {\n'
|
|
133
|
+
+ ' var s = document.createElement("script"); s.src = url;\n'
|
|
134
|
+
+ ' s.onload = function() { res(window.html2canvas); };\n'
|
|
135
|
+
+ ' s.onerror = function() { rej(new Error("Failed to load html2canvas")); };\n'
|
|
136
|
+
+ ' document.head.appendChild(s);\n'
|
|
137
|
+
+ ' });\n'
|
|
138
|
+
+ ' }\n'
|
|
139
|
+
+ ' var p = window.html2canvas ? Promise.resolve(window.html2canvas) : _ls(opts.captureUrl || "/bw/lib/vendor/html2canvas.min.js");\n'
|
|
140
|
+
+ ' p.then(function(h2c) { return h2c(el, { scale: opts.scale || 1, useCORS: true }); })\n'
|
|
141
|
+
+ ' .then(function(canvas) {\n'
|
|
142
|
+
+ ' var out = canvas;\n'
|
|
143
|
+
+ ' var mw = opts.maxWidth; var mh = opts.maxHeight;\n'
|
|
144
|
+
+ ' if ((mw && canvas.width > mw) || (mh && canvas.height > mh)) {\n'
|
|
145
|
+
+ ' var sw = mw ? mw / canvas.width : 1; var sh = mh ? mh / canvas.height : 1;\n'
|
|
146
|
+
+ ' var sc = Math.min(sw, sh);\n'
|
|
147
|
+
+ ' out = document.createElement("canvas");\n'
|
|
148
|
+
+ ' out.width = Math.round(canvas.width * sc); out.height = Math.round(canvas.height * sc);\n'
|
|
149
|
+
+ ' out.getContext("2d").drawImage(canvas, 0, 0, out.width, out.height);\n'
|
|
150
|
+
+ ' }\n'
|
|
151
|
+
+ ' var fmt = opts.format === "jpeg" ? "image/jpeg" : "image/png";\n'
|
|
152
|
+
+ ' var q = opts.format === "jpeg" ? (opts.quality || 0.85) : undefined;\n'
|
|
153
|
+
+ ' var dataUrl = out.toDataURL(fmt, q);\n'
|
|
154
|
+
+ ' bw._bwClient.respond("screenshot", opts.requestId, { data: dataUrl, width: out.width, height: out.height, format: opts.format || "png" });\n'
|
|
155
|
+
+ ' }).catch(function(err) {\n'
|
|
156
|
+
+ ' bw._bwClient.respond("screenshot", opts.requestId, null, err.message || String(err));\n'
|
|
157
|
+
+ ' });\n'
|
|
158
|
+
+ ' });\n'
|
|
159
|
+
+ ' bw.registerRemote("_bw_tree", function(opts) {\n'
|
|
160
|
+
+ ' if (!bw._bwClient) return;\n'
|
|
161
|
+
+ ' var sel = opts.selector || "body"; var depth = opts.depth || 3;\n'
|
|
162
|
+
+ ' var root = document.querySelector(sel);\n'
|
|
163
|
+
+ ' if (typeof bw.inspect === "function" && bw.inspect.length === 2) {\n'
|
|
164
|
+
+ ' bw._bwClient.respond("query", opts.requestId, bw.inspect(root, depth)); return;\n'
|
|
165
|
+
+ ' }\n'
|
|
166
|
+
+ ' function walk(el, d) {\n'
|
|
167
|
+
+ ' if (!el || d > depth) return null;\n'
|
|
168
|
+
+ ' var info = { tag: el.tagName ? el.tagName.toLowerCase() : "#text" };\n'
|
|
169
|
+
+ ' if (el.id) info.id = el.id;\n'
|
|
170
|
+
+ ' if (el.className && typeof el.className === "string") info.cls = el.className.split(" ").slice(0, 5).join(" ");\n'
|
|
171
|
+
+ ' if (el.children && el.children.length > 0 && d < depth) {\n'
|
|
172
|
+
+ ' info.children = [];\n'
|
|
173
|
+
+ ' for (var i = 0; i < Math.min(el.children.length, 20); i++) {\n'
|
|
174
|
+
+ ' var c = walk(el.children[i], d + 1); if (c) info.children.push(c);\n'
|
|
175
|
+
+ ' }\n'
|
|
176
|
+
+ ' }\n'
|
|
177
|
+
+ ' return info;\n'
|
|
178
|
+
+ ' }\n'
|
|
179
|
+
+ ' bw._bwClient.respond("query", opts.requestId, walk(root, 0));\n'
|
|
180
|
+
+ ' });\n'
|
|
181
|
+
+ ' bw.registerRemote("_bw_query", function(opts) {\n'
|
|
182
|
+
+ ' if (!bw._bwClient) return;\n'
|
|
183
|
+
+ ' try {\n'
|
|
184
|
+
+ ' var result;\n'
|
|
185
|
+
+ ' try { result = new Function("return (" + opts.code + ")")(); }\n'
|
|
186
|
+
+ ' catch (se) { result = new Function(opts.code)(); }\n'
|
|
187
|
+
+ ' bw._bwClient.respond("query", opts.requestId, result !== undefined ? result : null);\n'
|
|
188
|
+
+ ' } catch (e) {\n'
|
|
189
|
+
+ ' bw._bwClient.respond("query", opts.requestId, null, e.message || String(e));\n'
|
|
190
|
+
+ ' }\n'
|
|
191
|
+
+ ' });\n'
|
|
192
|
+
+ ' bw.registerRemote("_bw_listen", function(opts) {\n'
|
|
193
|
+
+ ' if (!bw._bwClient) return;\n'
|
|
194
|
+
+ ' if (!bw._bwClient._listeners) bw._bwClient._listeners = {};\n'
|
|
195
|
+
+ ' var key = opts.selector + ":::" + opts.event;\n'
|
|
196
|
+
+ ' if (bw._bwClient._listeners[key]) return;\n'
|
|
197
|
+
+ ' var fn = function(e) {\n'
|
|
198
|
+
+ ' var el = e.target.closest ? e.target.closest(opts.selector) : null;\n'
|
|
199
|
+
+ ' if (!el) return;\n'
|
|
200
|
+
+ ' bw._bwClient.respond("event", null, {\n'
|
|
201
|
+
+ ' event: opts.event, selector: opts.selector, tagName: el.tagName,\n'
|
|
202
|
+
+ ' id: el.id || null, text: (el.textContent || "").slice(0, 100)\n'
|
|
203
|
+
+ ' });\n'
|
|
204
|
+
+ ' };\n'
|
|
205
|
+
+ ' document.addEventListener(opts.event, fn, true);\n'
|
|
206
|
+
+ ' bw._bwClient._listeners[key] = { fn: fn, event: opts.event };\n'
|
|
207
|
+
+ ' });\n'
|
|
208
|
+
+ ' bw.registerRemote("_bw_unlisten", function(opts) {\n'
|
|
209
|
+
+ ' if (!bw._bwClient || !bw._bwClient._listeners) return;\n'
|
|
210
|
+
+ ' var key = opts.selector + ":::" + opts.event;\n'
|
|
211
|
+
+ ' var entry = bw._bwClient._listeners[key];\n'
|
|
212
|
+
+ ' if (!entry) return;\n'
|
|
213
|
+
+ ' document.removeEventListener(entry.event, entry.fn, true);\n'
|
|
214
|
+
+ ' delete bw._bwClient._listeners[key];\n'
|
|
104
215
|
+ ' });\n'
|
|
105
216
|
+ ' };\n'
|
|
106
217
|
+ '\n'
|
|
107
|
-
+ ' // ── Wire up
|
|
218
|
+
+ ' // ── Wire up action click delegation via bw_act_* classes ──\n'
|
|
108
219
|
+ ' _client._wireActions = function() {\n'
|
|
109
220
|
+ ' document.addEventListener("click", function(e) {\n'
|
|
110
|
-
+ ' var el = e.target
|
|
111
|
-
+ '
|
|
221
|
+
+ ' var el = e.target;\n'
|
|
222
|
+
+ ' var action = null;\n'
|
|
223
|
+
+ ' while (el && el !== document) {\n'
|
|
224
|
+
+ ' if (el.classList) {\n'
|
|
225
|
+
+ ' for (var i = 0; i < el.classList.length; i++) {\n'
|
|
226
|
+
+ ' if (el.classList[i].indexOf("bw_act_") === 0) {\n'
|
|
227
|
+
+ ' action = el.classList[i].substring(7);\n'
|
|
228
|
+
+ ' break;\n'
|
|
229
|
+
+ ' }\n'
|
|
230
|
+
+ ' }\n'
|
|
231
|
+
+ ' }\n'
|
|
232
|
+
+ ' if (action) break;\n'
|
|
233
|
+
+ ' el = el.parentElement;\n'
|
|
234
|
+
+ ' }\n'
|
|
235
|
+
+ ' if (!action) return;\n'
|
|
112
236
|
+ ' e.preventDefault();\n'
|
|
113
237
|
+ ' var actionData = {};\n'
|
|
114
|
-
+ ' if (el.
|
|
115
|
-
+ ' var form = el.closest("div") || document;\n'
|
|
238
|
+
+ ' if (el.id) actionData.id = el.id;\n'
|
|
239
|
+
+ ' var form = el.closest ? (el.closest("div") || document) : document;\n'
|
|
116
240
|
+ ' var inp = form.querySelector("input[type=text],input:not([type])");\n'
|
|
117
241
|
+ ' if (inp) { actionData.inputValue = inp.value; inp.value = ""; }\n'
|
|
118
|
-
+ ' _client.sendAction(
|
|
242
|
+
+ ' _client.sendAction(action, actionData);\n'
|
|
119
243
|
+ ' });\n'
|
|
120
244
|
+ ' document.addEventListener("keydown", function(e) {\n'
|
|
121
245
|
+ ' if (e.key === "Enter" && e.target.tagName === "INPUT") {\n'
|
|
122
|
-
+ ' var form = e.target.closest("div") || document;\n'
|
|
123
|
-
+ ' var btn =
|
|
124
|
-
+ '
|
|
125
|
-
+ '
|
|
126
|
-
+ '
|
|
246
|
+
+ ' var form = e.target.closest ? (e.target.closest("div") || document) : document;\n'
|
|
247
|
+
+ ' var btn = null;\n'
|
|
248
|
+
+ ' var els = form.querySelectorAll("[class*=bw_act_]");\n'
|
|
249
|
+
+ ' if (els.length) btn = els[0];\n'
|
|
250
|
+
+ ' var action = null;\n'
|
|
251
|
+
+ ' if (btn && btn.classList) {\n'
|
|
252
|
+
+ ' for (var i = 0; i < btn.classList.length; i++) {\n'
|
|
253
|
+
+ ' if (btn.classList[i].indexOf("bw_act_") === 0) {\n'
|
|
254
|
+
+ ' action = btn.classList[i].substring(7);\n'
|
|
255
|
+
+ ' break;\n'
|
|
256
|
+
+ ' }\n'
|
|
257
|
+
+ ' }\n'
|
|
258
|
+
+ ' if (action) {\n'
|
|
259
|
+
+ ' _client.sendAction(action, { inputValue: e.target.value });\n'
|
|
260
|
+
+ ' e.target.value = "";\n'
|
|
261
|
+
+ ' }\n'
|
|
127
262
|
+ ' }\n'
|
|
128
263
|
+ ' }\n'
|
|
129
264
|
+ ' });\n'
|
|
130
265
|
+ ' };\n'
|
|
131
266
|
+ '\n'
|
|
267
|
+
+ ' // ── Wire bw.remote for server communication ──\n'
|
|
268
|
+
+ ' bw.remote = {\n'
|
|
269
|
+
+ ' send: function(msg) { _client.respond("action", null, msg); }\n'
|
|
270
|
+
+ ' };\n'
|
|
271
|
+
+ '\n'
|
|
132
272
|
+ ' // ── Event delegation helper ──\n'
|
|
133
273
|
+ ' _client.listen = function(selector, event, action) {\n'
|
|
134
274
|
+ ' document.addEventListener(event, function(e) {\n'
|
package/src/bwserve/bwshell.js
CHANGED
|
@@ -22,7 +22,6 @@ import { VERSION } from '../version.js';
|
|
|
22
22
|
* @param {string} [opts.title='bwserve'] - Page title
|
|
23
23
|
* @param {string} [opts.theme] - Theme preset name or config
|
|
24
24
|
* @param {boolean} [opts.injectBitwrench=true] - Whether to inject bitwrench scripts
|
|
25
|
-
* @param {boolean} [opts.allowExec=false] - Enable exec message type
|
|
26
25
|
* @returns {string} Complete HTML document
|
|
27
26
|
*/
|
|
28
27
|
export function generateShell(opts) {
|
|
@@ -78,9 +77,6 @@ export function generateShell(opts) {
|
|
|
78
77
|
script.push('(function() {');
|
|
79
78
|
script.push(' "use strict";');
|
|
80
79
|
script.push(' var clientId = ' + JSON.stringify(clientId) + ';');
|
|
81
|
-
if (opts.allowExec) {
|
|
82
|
-
script.push(' bw._allowExec = true;');
|
|
83
|
-
}
|
|
84
80
|
script.push(' bw._bwClient.id = clientId;');
|
|
85
81
|
script.push(' bw._bwClient._registerBuiltins();');
|
|
86
82
|
script.push(' bw._bwClient._wireActions();');
|
package/src/bwserve/client.js
CHANGED
|
@@ -4,15 +4,17 @@
|
|
|
4
4
|
* Represents one browser tab connected via SSE. The server calls methods
|
|
5
5
|
* on this object to push UI updates to the client.
|
|
6
6
|
*
|
|
7
|
-
* Protocol message types (sent as SSE data):
|
|
8
|
-
* {
|
|
9
|
-
* { type: '
|
|
10
|
-
* { type: '
|
|
11
|
-
* {
|
|
12
|
-
* { type: '
|
|
13
|
-
* {
|
|
14
|
-
* { type: 'call',
|
|
15
|
-
* { type: '
|
|
7
|
+
* Protocol message types v2.1 (sent as SSE data, all stamped v:1):
|
|
8
|
+
* { v: 1, type: 'hello' } — handshake
|
|
9
|
+
* { v: 1, type: 'mount', ref: '#app', taco: {t,a,c,o} } — mount TACO
|
|
10
|
+
* { v: 1, type: 'append', ref: '#list', taco: {t,a,c,o} }
|
|
11
|
+
* { v: 1, type: 'remove', ref: '#item-3' }
|
|
12
|
+
* { v: 1, type: 'patch', ref: '#id', text: '42' } — discriminated patch
|
|
13
|
+
* { v: 1, type: 'batch', ops: [ ...messages ] }
|
|
14
|
+
* { v: 1, type: 'call', name: 'fn', args: [...] }
|
|
15
|
+
* { v: 1, type: 'listen', topic: 'bw:lifecycle' }
|
|
16
|
+
*
|
|
17
|
+
* Removed in 2.1 (code-bearing): register, exec, query
|
|
16
18
|
*
|
|
17
19
|
* @module bwserve/client
|
|
18
20
|
*/
|
|
@@ -30,28 +32,44 @@ export class BwServeClient {
|
|
|
30
32
|
this._res = res; // SSE response stream (null in stub)
|
|
31
33
|
this._handlers = {}; // action name → handler
|
|
32
34
|
this._closed = false;
|
|
33
|
-
this._pending = {}; // requestId → { resolve, reject, timer }
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
|
-
*
|
|
38
|
+
* Mount a TACO at the given selector (2.1 verb: "mount").
|
|
39
|
+
* Replaces the content of the target element.
|
|
38
40
|
*
|
|
39
|
-
* @param {string} selector - CSS selector or UUID
|
|
40
|
-
* @param {Object} taco - TACO object to
|
|
41
|
+
* @param {string} selector - CSS selector or UUID (the "ref")
|
|
42
|
+
* @param {Object} taco - TACO object to mount
|
|
43
|
+
*/
|
|
44
|
+
mount(selector, taco) {
|
|
45
|
+
this._send({ type: 'mount', ref: selector, taco: taco });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Alias for mount() — backward compatibility with 2.0.x render().
|
|
50
|
+
* @deprecated Use mount() instead.
|
|
41
51
|
*/
|
|
42
52
|
render(selector, taco) {
|
|
43
|
-
this.
|
|
53
|
+
this.mount(selector, taco);
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
/**
|
|
47
57
|
* Patch an element's content or attributes without rebuild.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* @param {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
* 2.1 uses discriminated fields: the patch object's keys (text, attrs,
|
|
59
|
+
* content, etc.) are spread directly into the message.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} ref - CSS selector or element UUID
|
|
62
|
+
* @param {Object} fields - Discriminated patch fields (e.g. {text:'hi'}, {attrs:{class:'x'}})
|
|
63
|
+
*/
|
|
64
|
+
patch(ref, fields) {
|
|
65
|
+
var msg = { type: 'patch', ref: ref };
|
|
66
|
+
if (fields && typeof fields === 'object') {
|
|
67
|
+
var keys = Object.keys(fields);
|
|
68
|
+
for (var i = 0; i < keys.length; i++) {
|
|
69
|
+
msg[keys[i]] = fields[keys[i]];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
this._send(msg);
|
|
55
73
|
}
|
|
56
74
|
|
|
57
75
|
/**
|
|
@@ -61,7 +79,7 @@ export class BwServeClient {
|
|
|
61
79
|
* @param {Object} taco - TACO object to append
|
|
62
80
|
*/
|
|
63
81
|
append(selector, taco) {
|
|
64
|
-
this._send({ type: 'append',
|
|
82
|
+
this._send({ type: 'append', ref: selector, taco: taco });
|
|
65
83
|
}
|
|
66
84
|
|
|
67
85
|
/**
|
|
@@ -70,40 +88,27 @@ export class BwServeClient {
|
|
|
70
88
|
* @param {string} selector - CSS selector or UUID of element to remove
|
|
71
89
|
*/
|
|
72
90
|
remove(selector) {
|
|
73
|
-
this._send({ type: 'remove',
|
|
91
|
+
this._send({ type: 'remove', ref: selector });
|
|
74
92
|
}
|
|
75
93
|
|
|
76
94
|
/**
|
|
77
95
|
* Send multiple operations as a single batch.
|
|
78
96
|
*
|
|
79
|
-
* @param {Array} ops - Array of message objects
|
|
97
|
+
* @param {Array} ops - Array of message objects
|
|
80
98
|
*/
|
|
81
99
|
batch(ops) {
|
|
82
|
-
this._send({ type: 'batch', ops });
|
|
100
|
+
this._send({ type: 'batch', ops: ops });
|
|
83
101
|
}
|
|
84
102
|
|
|
85
103
|
/**
|
|
86
104
|
* Send a bw.message() dispatch to a tagged component on the client.
|
|
87
105
|
*
|
|
88
|
-
* @param {string}
|
|
106
|
+
* @param {string} ref - Component userTag or UUID
|
|
89
107
|
* @param {string} action - Method name to call
|
|
90
108
|
* @param {*} data - Data to pass to the method
|
|
91
109
|
*/
|
|
92
|
-
message(
|
|
93
|
-
this._send({ type: 'message',
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Register a named function on the client for later invocation via call().
|
|
98
|
-
*
|
|
99
|
-
* The function body is sent as a string and compiled on the client side.
|
|
100
|
-
* Registered functions persist for the lifetime of the connection.
|
|
101
|
-
*
|
|
102
|
-
* @param {string} name - Function name (used as key for later call())
|
|
103
|
-
* @param {string} body - Function source as string, e.g. "function(el) { el.scrollTop = el.scrollHeight; }"
|
|
104
|
-
*/
|
|
105
|
-
register(name, body) {
|
|
106
|
-
this._send({ type: 'register', name, body });
|
|
110
|
+
message(ref, action, data) {
|
|
111
|
+
this._send({ type: 'message', ref: ref, action: action, data: data });
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
/**
|
|
@@ -116,19 +121,21 @@ export class BwServeClient {
|
|
|
116
121
|
* @param {...*} args - Arguments to pass to the function
|
|
117
122
|
*/
|
|
118
123
|
call(name, ...args) {
|
|
119
|
-
this._send({ type: 'call', name, args });
|
|
124
|
+
this._send({ type: 'call', name: name, args: args });
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
/**
|
|
123
|
-
*
|
|
128
|
+
* Subscribe to a client-side topic. The client will forward matching
|
|
129
|
+
* events back through the return route.
|
|
124
130
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* @param {string} code - JavaScript code string to execute
|
|
131
|
+
* @param {string} topic - Topic name (e.g. 'bw:lifecycle')
|
|
132
|
+
* @param {Function} handler - Called with (data) when topic events arrive
|
|
133
|
+
* @returns {BwServeClient} this (for chaining)
|
|
129
134
|
*/
|
|
130
|
-
|
|
131
|
-
this.
|
|
135
|
+
listen(topic, handler) {
|
|
136
|
+
this._handlers['_topic:' + topic] = handler;
|
|
137
|
+
this._send({ type: 'listen', topic: topic });
|
|
138
|
+
return this;
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
/**
|
|
@@ -155,10 +162,13 @@ export class BwServeClient {
|
|
|
155
162
|
|
|
156
163
|
/**
|
|
157
164
|
* Send a protocol message to the client via SSE.
|
|
165
|
+
* All messages are stamped with v: 1 (wire protocol version).
|
|
158
166
|
* @private
|
|
159
167
|
*/
|
|
160
168
|
_send(msg) {
|
|
161
169
|
if (this._closed) return;
|
|
170
|
+
// Stamp the wire protocol version
|
|
171
|
+
msg.v = 1;
|
|
162
172
|
// Always store for testing / inspection
|
|
163
173
|
if (!this._sent) this._sent = [];
|
|
164
174
|
this._sent.push(msg);
|
|
@@ -172,177 +182,6 @@ export class BwServeClient {
|
|
|
172
182
|
}
|
|
173
183
|
}
|
|
174
184
|
|
|
175
|
-
// ── Pending promise mechanism ──
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Create a pending promise with a unique requestId and timeout.
|
|
179
|
-
*
|
|
180
|
-
* @param {number} [timeout=10000] - Timeout in ms
|
|
181
|
-
* @returns {{ requestId: string, promise: Promise }}
|
|
182
|
-
* @private
|
|
183
|
-
*/
|
|
184
|
-
_pend(timeout) {
|
|
185
|
-
var self = this;
|
|
186
|
-
timeout = timeout || 10000;
|
|
187
|
-
var requestId = 'req_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8);
|
|
188
|
-
|
|
189
|
-
var promise = new Promise(function(resolve, reject) {
|
|
190
|
-
var timer = setTimeout(function() {
|
|
191
|
-
delete self._pending[requestId];
|
|
192
|
-
reject(new Error('Request timeout after ' + timeout + 'ms'));
|
|
193
|
-
}, timeout);
|
|
194
|
-
|
|
195
|
-
self._pending[requestId] = { resolve: resolve, reject: reject, timer: timer };
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
return { requestId: requestId, promise: promise };
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Resolve a pending promise by requestId.
|
|
203
|
-
* Called by the server route handler when a POST-back arrives.
|
|
204
|
-
*
|
|
205
|
-
* @param {string} requestId
|
|
206
|
-
* @param {Object} data - Response data (may contain .error)
|
|
207
|
-
* @returns {boolean} true if a pending request was found and resolved
|
|
208
|
-
* @private
|
|
209
|
-
*/
|
|
210
|
-
_resolvePending(requestId, data) {
|
|
211
|
-
var pending = this._pending[requestId];
|
|
212
|
-
if (!pending) return false;
|
|
213
|
-
|
|
214
|
-
clearTimeout(pending.timer);
|
|
215
|
-
delete this._pending[requestId];
|
|
216
|
-
|
|
217
|
-
if (data.error) {
|
|
218
|
-
pending.reject(new Error(data.error));
|
|
219
|
-
} else {
|
|
220
|
-
pending.resolve(data.result !== undefined ? data.result : data);
|
|
221
|
-
}
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// ── Query ──
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Execute code on the client and get the result back.
|
|
229
|
-
*
|
|
230
|
-
* @param {string} code - JavaScript code to evaluate (return value is sent back)
|
|
231
|
-
* @param {Object} [options]
|
|
232
|
-
* @param {number} [options.timeout=5000] - Timeout in ms
|
|
233
|
-
* @returns {Promise<*>} The result of evaluating the code
|
|
234
|
-
*/
|
|
235
|
-
query(code, options) {
|
|
236
|
-
var opts = options || {};
|
|
237
|
-
var pend = this._pend(opts.timeout || 5000);
|
|
238
|
-
this.call('_bw_query', { code: code, requestId: pend.requestId });
|
|
239
|
-
return pend.promise;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// ── Mount ──
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Mount a BCCL component or factory function on the client.
|
|
246
|
-
*
|
|
247
|
-
* @param {string} selector - CSS selector of target element
|
|
248
|
-
* @param {string} factory - BCCL component name (e.g. 'accordion') or JS factory code
|
|
249
|
-
* @param {Object} [props] - Props to pass to the component/factory
|
|
250
|
-
* @param {Object} [options]
|
|
251
|
-
* @param {number} [options.timeout=10000] - Timeout in ms
|
|
252
|
-
* @returns {Promise<Object>} Resolves with { mounted: true } on success
|
|
253
|
-
*/
|
|
254
|
-
mount(selector, factory, props, options) {
|
|
255
|
-
var opts = options || {};
|
|
256
|
-
var pend = this._pend(opts.timeout || 10000);
|
|
257
|
-
this.call('_bw_mount', {
|
|
258
|
-
target: selector,
|
|
259
|
-
factory: factory,
|
|
260
|
-
props: props || {},
|
|
261
|
-
requestId: pend.requestId
|
|
262
|
-
});
|
|
263
|
-
return pend.promise;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// ── Inspect ──
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Inspect the DOM tree of the connected client.
|
|
270
|
-
*
|
|
271
|
-
* Calls the `_bw_tree` builtin on the client which delegates to
|
|
272
|
-
* `bw.inspect()` when available, returning a plain-object tree with
|
|
273
|
-
* bitwrench metadata (tag, uuid, type, handles, state, children).
|
|
274
|
-
*
|
|
275
|
-
* @param {string} [selector='body'] - CSS selector of root element
|
|
276
|
-
* @param {Object} [options]
|
|
277
|
-
* @param {number} [options.depth=3] - Max recursion depth
|
|
278
|
-
* @param {number} [options.timeout=10000] - Timeout in ms
|
|
279
|
-
* @returns {Promise<Object|null>} Tree object, or null if element not found
|
|
280
|
-
*/
|
|
281
|
-
inspect(selector, options) {
|
|
282
|
-
var opts = options || {};
|
|
283
|
-
var pend = this._pend(opts.timeout || 10000);
|
|
284
|
-
this.call('_bw_tree', {
|
|
285
|
-
selector: selector || 'body',
|
|
286
|
-
depth: opts.depth || 3,
|
|
287
|
-
requestId: pend.requestId
|
|
288
|
-
});
|
|
289
|
-
return pend.promise;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// ── Screenshot ──
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Capture a screenshot of the client's page or a specific element.
|
|
296
|
-
*
|
|
297
|
-
* Requires the server to be created with `{ allowScreenshot: true }`.
|
|
298
|
-
* Uses html2canvas on the client side (lazy-loaded on first call).
|
|
299
|
-
*
|
|
300
|
-
* @param {string} [selector='body'] - CSS selector of element to capture
|
|
301
|
-
* @param {Object} [options]
|
|
302
|
-
* @param {string} [options.format='png'] - 'png' or 'jpeg'
|
|
303
|
-
* @param {number} [options.quality=0.85] - JPEG quality 0-1 (ignored for PNG)
|
|
304
|
-
* @param {number} [options.maxWidth] - Resize if wider (preserves aspect ratio)
|
|
305
|
-
* @param {number} [options.maxHeight] - Resize if taller (preserves aspect ratio)
|
|
306
|
-
* @param {number} [options.scale=1] - Device pixel ratio override
|
|
307
|
-
* @param {number} [options.timeout=10000] - Reject after ms
|
|
308
|
-
* @returns {Promise<Object>} { data: Buffer, width, height, format }
|
|
309
|
-
*/
|
|
310
|
-
screenshot(selector, options) {
|
|
311
|
-
var self = this;
|
|
312
|
-
var opts = options || {};
|
|
313
|
-
var timeout = opts.timeout || 10000;
|
|
314
|
-
|
|
315
|
-
if (!self._allowScreenshot) {
|
|
316
|
-
return Promise.reject(new Error('Screenshot not enabled. Set allowScreenshot: true in server options.'));
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
var pend = self._pend(timeout);
|
|
320
|
-
|
|
321
|
-
// Call the bwclient-registered capture function
|
|
322
|
-
self.call('_bw_screenshot', {
|
|
323
|
-
requestId: pend.requestId,
|
|
324
|
-
selector: selector || 'body',
|
|
325
|
-
format: opts.format || 'png',
|
|
326
|
-
quality: opts.quality || 0.85,
|
|
327
|
-
maxWidth: opts.maxWidth || null,
|
|
328
|
-
maxHeight: opts.maxHeight || null,
|
|
329
|
-
scale: opts.scale || 1,
|
|
330
|
-
captureUrl: '/bw/lib/vendor/html2canvas.min.js'
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
// Transform the raw response into { data: Buffer, width, height, format }
|
|
334
|
-
return pend.promise.then(function(result) {
|
|
335
|
-
if (!result || !result.data) return result;
|
|
336
|
-
var base64 = result.data.split(',')[1];
|
|
337
|
-
return {
|
|
338
|
-
data: Buffer.from(base64, 'base64'),
|
|
339
|
-
width: result.width,
|
|
340
|
-
height: result.height,
|
|
341
|
-
format: result.format
|
|
342
|
-
};
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
|
|
346
185
|
/**
|
|
347
186
|
* Dispatch an incoming action from the client.
|
|
348
187
|
* @private
|