talkdom 0.3.0 → 0.4.0
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 +94 -0
- package/dist/talkdom-ws.min.js +1 -0
- package/dist/talkdom-ws.min.js.map +1 -0
- package/dist/talkdom.min.js +1 -1
- package/dist/talkdom.min.js.map +1 -1
- package/index.js +12 -7
- package/package.json +3 -2
- package/websocket.js +234 -0
package/README.md
CHANGED
|
@@ -223,6 +223,100 @@ talkDOM.methods["show:"] = function (el, message) {
|
|
|
223
223
|
};
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
+
## WebSocket plugin
|
|
227
|
+
|
|
228
|
+
The optional `websocket.js` plugin adds server-push via WebSocket as an alternative to polling. Load it after the core library:
|
|
229
|
+
|
|
230
|
+
```html
|
|
231
|
+
<script src="https://cdn.jsdelivr.net/npm/talkdom/dist/talkdom.min.js"></script>
|
|
232
|
+
<script src="https://cdn.jsdelivr.net/npm/talkdom/dist/talkdom-ws.min.js"></script>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Receiving
|
|
236
|
+
|
|
237
|
+
Add `ws:` as the last keyword on a receiver with a WebSocket URL as its argument. The server pushes content — no client-side method keywords needed.
|
|
238
|
+
|
|
239
|
+
```html
|
|
240
|
+
<div receiver="feed ws: ws://localhost:3000/updates"></div>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The server sends JSON messages to control what gets applied:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{"receiver": "feed", "content": "<p>New post</p>", "op": "append"}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
| Field | Required | Description |
|
|
250
|
+
|---|---|---|
|
|
251
|
+
| `receiver` | yes | Target receiver name |
|
|
252
|
+
| `content` | no | HTML or text content |
|
|
253
|
+
| `op` | no | `inner` (default), `text`, `append`, `outer` |
|
|
254
|
+
|
|
255
|
+
Omitting `receiver` broadcasts to all receivers on that connection.
|
|
256
|
+
|
|
257
|
+
The server can also send raw talkDOM message syntax instead of JSON:
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
feed apply: Updated! text
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
This dispatches through the same path as sender clicks and server triggers.
|
|
264
|
+
|
|
265
|
+
### Sending
|
|
266
|
+
|
|
267
|
+
The plugin registers a `ws:send:` method. The receiver element's value (for inputs/textareas/selects) or text content is sent over the WebSocket connection.
|
|
268
|
+
|
|
269
|
+
```html
|
|
270
|
+
<input receiver="chatbox" type="text">
|
|
271
|
+
<button sender="chatbox ws:send: ws://localhost:3000/chat">Send</button>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Shared connections
|
|
275
|
+
|
|
276
|
+
Multiple receivers pointing to the same URL share a single WebSocket connection. The server routes messages by the `receiver` field in JSON.
|
|
277
|
+
|
|
278
|
+
```html
|
|
279
|
+
<div receiver="messages ws: ws://localhost:3000/live"></div>
|
|
280
|
+
<div receiver="presence ws: ws://localhost:3000/live"></div>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Reconnection
|
|
284
|
+
|
|
285
|
+
Connections automatically reconnect with exponential backoff (1s initial, 30s max, ±25% jitter). Backoff resets on successful connection. Reconnection stops when all receivers for a URL are removed from the DOM.
|
|
286
|
+
|
|
287
|
+
### Lifecycle events
|
|
288
|
+
|
|
289
|
+
| Event | Detail |
|
|
290
|
+
|---|---|
|
|
291
|
+
| `talkdom:ws:open` | `{ url }` |
|
|
292
|
+
| `talkdom:ws:close` | `{ url, code, reason }` |
|
|
293
|
+
| `talkdom:ws:error` | `{ url }` |
|
|
294
|
+
|
|
295
|
+
Events fire on all receiver elements subscribed to the URL and bubble.
|
|
296
|
+
|
|
297
|
+
```js
|
|
298
|
+
document.addEventListener("talkdom:ws:open", function (e) {
|
|
299
|
+
console.log("connected to", e.detail.url);
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Incoming messages also fire the standard `talkdom:done` event on the target receiver after applying content.
|
|
304
|
+
|
|
305
|
+
### Programmatic API
|
|
306
|
+
|
|
307
|
+
```js
|
|
308
|
+
talkDOM.ws.connect("ws://localhost:3000/live");
|
|
309
|
+
talkDOM.ws.send("ws://localhost:3000/live", { action: "subscribe", channel: "news" });
|
|
310
|
+
talkDOM.ws.send("ws://localhost:3000/live", "plain string");
|
|
311
|
+
talkDOM.ws.disconnect("ws://localhost:3000/live");
|
|
312
|
+
|
|
313
|
+
talkDOM.ws.connections; // { "ws://...": { state: 1, receivers: 2 } }
|
|
314
|
+
talkDOM.ws.maxConnections; // default 16
|
|
315
|
+
talkDOM.ws.maxConnections = 32;
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
`talkDOM.ws.send` returns `true` if sent, `false` if the connection is not open.
|
|
319
|
+
|
|
226
320
|
## Security
|
|
227
321
|
|
|
228
322
|
talkDOM does **not** sanitize HTML. Content from `get:apply:`, `post:apply:`, server triggers, and piped `apply:` is inserted via `innerHTML` / `insertAdjacentHTML` / `outerHTML` as-is. You are responsible for ensuring that server responses do not contain untrusted markup.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(){if(window.talkDOM){var e=talkDOM.methods,n=Object.create(null),t=16,r=1e3,o=/\s+/;new MutationObserver(function(e){for(var n=0;n<e.length;n++)for(var t=e[n].addedNodes,r=0;r<t.length;r++){var o=t[r];1===o.nodeType&&(o.hasAttribute&&o.hasAttribute("receiver")&&u(o),o.querySelectorAll&&o.querySelectorAll("[receiver]").forEach(u))}}).observe(document,{childList:!0,subtree:!0}),e["ws:send:"]=function(e,t){var r=n[t];if(!r||!r.ws||r.ws.readyState!==WebSocket.OPEN)return console.error("talkdom-ws: no open connection to "+t),Promise.reject("not connected");var o="value"in e?e.value:e.textContent;r.ws.send(o)},document.querySelectorAll("[receiver]").forEach(u),talkDOM.ws={connect:function(e){if(!n[e]){if(Object.keys(n).length>=t)return void console.warn("talkdom-ws: max connections ("+t+") reached");n[e]={ws:null,receivers:new Set,backoff:r,timer:null,checkTimer:null},n[e].checkTimer=setInterval(function(){c(n[e])||s(e)},5e3)}l(e)},disconnect:function(e){s(e)},send:function(e,t){var r=n[e];return!(!r||!r.ws||r.ws.readyState!==WebSocket.OPEN)&&(r.ws.send("string"==typeof t?t:JSON.stringify(t)),!0)},get connections(){var e=Object.create(null);for(var t in n)e[t]={state:n[t].ws?n[t].ws.readyState:-1,receivers:n[t].receivers.size};return e},get maxConnections(){return t},set maxConnections(e){t=e}}}else console.error("talkdom-ws: load index.js before websocket.js");function c(e){return e.receivers.forEach(function(n){n.isConnected||e.receivers.delete(n)}),e.receivers.size>0}function i(e,n,t){e.receivers.forEach(function(e){e.isConnected&&e.dispatchEvent(new CustomEvent(n,{bubbles:!0,detail:t}))})}function a(t,r){var o=n[t];if(o){c(o);var i=r.data;if("string"==typeof i)if("{"===i.charAt(0))try{!function(n,t){var r,o=t.receiver,c=t.op||"inner",i=t.content||"";r=o?document.querySelectorAll('[receiver~="'+o+'"]'):Array.from(n.receivers);for(var a={receiver:o||"",selector:"apply:",args:[i,c]},s=0;s<r.length;s++){var l=r[s];e["apply:"](l,i,c),l.dispatchEvent(new CustomEvent("talkdom:done",{bubbles:!0,detail:a}))}}(o,JSON.parse(i))}catch(e){console.error("talkdom-ws: invalid JSON from "+t,e)}else talkDOM.send(i).catch(function(e){console.warn("talkdom-ws:",e)})}}function s(e){var t=n[e];t&&(t.timer&&clearTimeout(t.timer),t.checkTimer&&clearInterval(t.checkTimer),t.ws&&(t.ws.onclose=null,t.ws.close()),delete n[e])}function l(e){var t=n[e];if(t&&(!t.ws||t.ws.readyState!==WebSocket.OPEN&&t.ws.readyState!==WebSocket.CONNECTING)){var o=new WebSocket(e);o.onopen=function(){t.backoff=r,i(t,"talkdom:ws:open",{url:e})},o.onmessage=function(n){a(e,n)},o.onclose=function(r){i(t,"talkdom:ws:close",{url:e,code:r.code,reason:r.reason}),function(e){var t=n[e];if(t)if(c(t)){var r=Math.min(t.backoff,3e4);r*=.75+.5*Math.random(),t.timer=setTimeout(function(){t.backoff=Math.min(2*t.backoff,3e4),l(e)},r)}else s(e)}(e)},o.onerror=function(){i(t,"talkdom:ws:error",{url:e})},t.ws=o}}function u(e){var i=e.getAttribute("receiver");if(i){var a=function(e){for(var n=e.trim().split(o),t=1;t<n.length;t++)if("ws:"===n[t]&&n[t+1])return n[t+1];return null}(i);a&&function(e,o){var i=n[o];if(!i){if(Object.keys(n).length>=t)return void console.warn("talkdom-ws: max connections ("+t+") reached, ignoring "+o);i={ws:null,receivers:new Set,backoff:r,timer:null,checkTimer:null},n[o]=i,i.checkTimer=setInterval(function(){c(i)||s(o)},5e3)}i.receivers.add(e),l(o)}(e,a)}}}();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["window","talkDOM","methods","connections","Object","create","maxConnections","BASE_DELAY","WS","MutationObserver","mutations","i","length","added","addedNodes","j","node","nodeType","hasAttribute","initElement","querySelectorAll","forEach","observe","document","childList","subtree","el","url","conn","ws","readyState","WebSocket","OPEN","console","error","Promise","reject","payload","value","textContent","send","connect","keys","warn","receivers","Set","backoff","timer","checkTimer","setInterval","pruneReceivers","cleanup","connectWs","disconnect","data","JSON","stringify","out","state","size","n","isConnected","delete","fireEvent","name","detail","dispatchEvent","CustomEvent","bubbles","onMessage","event","charAt","msg","targets","receiver","op","content","Array","from","selector","args","routeJson","parse","e","catch","err","clearTimeout","clearInterval","onclose","close","CONNECTING","onopen","onmessage","code","reason","delay","Math","min","random","setTimeout","scheduleReconnect","onerror","attr","getAttribute","tokens","trim","split","parseWsUrl","add","subscribe"],"sources":["websocket.js"],"mappings":"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","ignoreList":[]}
|
package/dist/talkdom.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(){var e=/\s+/;function t(t){for(var r=t.trim(),n=r.split(e),o=n[0],i=r.substring(o.length).trim(),c=n.slice(1),u=[],s=[],a=[],l=0;l<c.length;l++){var f=c[l];f.endsWith(":")?(u.length>0&&a.length>0?(s.push(a.join(" ")),a=[]):u.length>0&&s.push(""),u.push(f)):a.push(f)}return u.length>0&&s.push(a.join(" ")),{receiver:o,selector:u.join(""),keywords:u,args:s,body:i}}function r(e){var t=e.getAttribute("receiver").trim(),r=t.indexOf(" ");return-1===r?t:t.substring(0,r)}var n=
|
|
1
|
+
!function(){var e=/\s+/;function t(t){for(var r=t.trim(),n=r.split(e),o=n[0],i=r.substring(o.length).trim(),c=n.slice(1),u=[],s=[],a=[],l=0;l<c.length;l++){var f=c[l];f.endsWith(":")?(u.length>0&&a.length>0?(s.push(a.join(" ")),a=[]):u.length>0&&s.push(""),u.push(f)):a.push(f)}return u.length>0&&s.push(a.join(" ")),{receiver:o,selector:u.join(""),keywords:u,args:s,body:i}}function r(e){var t=e.getAttribute("receiver").trim(),r=t.indexOf(" ");return-1===r?t:t.substring(0,r)}var n=Object.create(null),o=!1;function i(e){if(o||(n=Object.create(null),o=!0),n[e])return n[e];var t=document.querySelectorAll('[receiver~="'+e+'"]');return n[e]=t,t}function c(e,t,n){if(function(e,t){var r=e.getAttribute("accepts");return!r||-1!==(" "+r+" ").indexOf(" "+t+" ")}(e,t)){switch(t){case"inner":e.innerHTML=n;break;case"text":e.textContent=n;break;case"append":e.insertAdjacentHTML("beforeend",n);break;case"outer":e.outerHTML=n}return function(e,t){if(e.hasAttribute("receiver")&&e.hasAttribute("persist")){var n="talkDOM:"+r(e);"outer"===t?localStorage.setItem(n,JSON.stringify({op:t,content:e.outerHTML})):localStorage.setItem(n,JSON.stringify({op:t,content:e.innerHTML}))}}(e,t),n}console.error(r(e)+" does not accept "+t)}new MutationObserver(function(){o=!1}).observe(document,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["receiver"]});var u=null;function s(e,t,r){var n={"X-TalkDOM-Request":"true","X-TalkDOM-Current-URL":location.href};if(r&&(n["X-TalkDOM-Receiver"]=r),"GET"!==e){var o=(u&&u.isConnected||(u=document.querySelector('meta[name="csrf-token"]')),u?u.getAttribute("content"):"");o?n["X-CSRF-Token"]=o:console.warn("talkDOM: no CSRF token found for "+e+" "+t)}return fetch(t,{method:e,headers:n}).then(function(r){if(!r.ok)return console.error("talkDOM: "+e+" "+t+" "+r.status),Promise.reject(r.status);var n=r.headers.get("X-TalkDOM-Trigger");return r.text().then(function(e){return n&&m(n),e})},function(r){return console.error("talkDOM: "+e+" "+t+" failed",r),Promise.reject(r)})}function a(e){return e.hasAttribute("receiver")?r(e):""}const l={"get:":function(e,t){return s("GET",t,a(e))},"post:":function(e,t){return s("POST",t,a(e))},"put:":function(e,t){return s("PUT",t,a(e))},"delete:":function(e,t){return s("DELETE",t,a(e))},"confirm:":function(e,t){if(!confirm(t))return Promise.reject("cancelled")},"apply:":function(e,t,r){return c(e,r,t)},"get:apply:":function(e,t,r){return s("GET",t,a(e)).then(function(t){return c(e,r,t)})},"post:apply:":function(e,t,r){return s("POST",t,a(e)).then(function(t){return c(e,r,t)})},"put:apply:":function(e,t,r){return s("PUT",t,a(e)).then(function(t){return c(e,r,t)})},"delete:apply:":function(e,t,r){return s("DELETE",t,a(e)).then(function(t){return c(e,r,t)})}};var f=!1;function v(e){e&&e.sender&&(f=!0,m(e.sender),f=!1)}function d(e,t,r,n){return e.isConnected?e:(t&&t.isConnected?t.previousElementSibling:r&&r.isConnected?r.lastElementChild:null)||i(n)[0]}function h(e,t){var r=i(e.receiver);if(0!==r.length){var n=l[e.selector];if(n){var o,c=void 0!==t?[t].concat(e.args):e.args;return r.forEach(function(t){var r={receiver:e.receiver,selector:e.selector,args:e.args},i=t.parentNode,u=t.nextElementSibling;if((o=n(t,...c))&&"function"==typeof o.then)o.then(function(){var n=d(t,u,i,e.receiver);n&&n.dispatchEvent(new CustomEvent("talkdom:done",{bubbles:!0,detail:r}))},function(n){r.error=n;var o=d(t,u,i,e.receiver);o&&o.dispatchEvent(new CustomEvent("talkdom:error",{bubbles:!0,detail:r}))});else{var s=d(t,u,i,e.receiver);s&&s.dispatchEvent(new CustomEvent("talkdom:done",{bubbles:!0,detail:r}))}}),o}console.error(e.receiver+" does not understand "+e.selector)}else console.error(e.receiver+" not found")}function p(e){var r=e.trim();if(-1===r.indexOf(";")&&-1===r.indexOf("|"))return Promise.resolve(h(t(r))).then(function(e){return[e]});var n=r.split(";").map(function(e){var r=e.trim();if(!r)return Promise.resolve();var n=r.split("|").map(function(e){return e.trim()}).filter(Boolean);return 1===n.length?Promise.resolve(h(t(n[0]))):n.reduce(function(e,r){var n=t(r);return Promise.resolve(e).then(function(e){return h(n,e)})},void 0)});return Promise.all(n)}function m(e){p(e).catch(function(e){console.warn("talkDOM:",e)})}function g(e){var t=e.getAttribute("sender");m(t),f||function(e,t){if(e.hasAttribute("push-url")){var r=e.getAttribute("push-url");if(!r){var n=t.split(";")[0].split("|")[0].trim(),o=n.indexOf(":");-1!==o&&(r=n.substring(o+1).trim().split(/\s/)[0]||"")}r&&location.pathname+location.search!==r&&history.pushState({sender:t},"",r)}}(e,t)}window.addEventListener("popstate",function(e){v(e.state)});var b=0,k=64;document.addEventListener("click",function(e){const t=e.target.closest("[sender]");t&&(e.preventDefault(),g(t))}),document.querySelectorAll("[persist]").forEach(function(e){if(e.hasAttribute("receiver")){var t=r(e),n=localStorage.getItem("talkDOM:"+t);if(n){var o;try{o=JSON.parse(n)}catch(e){return void localStorage.removeItem("talkDOM:"+t)}"outer"===o.op?e.outerHTML=o.content:e.innerHTML=o.content}}}),v(history.state),document.querySelectorAll("[receiver]").forEach(function(e){var r=t(e.getAttribute("receiver"));if("poll:"===r.keywords[r.keywords.length-1])if(b>=k)console.warn("talkDOM: max pollers ("+k+") reached, ignoring "+r.receiver);else{var n=function(e){var t=e.match(/^(\d+)(s|ms)$/);if(!t)return null;var r=parseInt(t[1],10);return"s"===t[2]?1e3*r:r}(r.args[r.args.length-1]);if(n){var o=r.keywords.slice(0,-1).join(""),c=r.args.slice(0,-1),u=r.receiver,s=i(u),a=l[o];b++;var f=setInterval(function(){if(!e.isConnected)return clearInterval(f),void b--;0!==s.length&&s[0].isConnected||(s=i(u)),0!==s.length&&(a||(a=l[o]),a?s.forEach(function(e){a(e,...c)}):console.error(u+" does not understand "+o))},n)}else console.error("poll: invalid interval for "+r.receiver)}}),window.talkDOM={methods:l,send:p,get maxPollers(){return k},set maxPollers(e){k=e}}}();
|
package/dist/talkdom.min.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["WS","parseMessage","str","trimmed","trim","tokens","split","receiver","body","substring","length","rest","slice","keywords","args","currentArg","i","token","endsWith","push","join","selector","receiverName","el","attr","getAttribute","sp","indexOf","receiverCache","cacheValid","findReceivers","name","result","document","querySelectorAll","apply","op","content","accepts","innerHTML","textContent","insertAdjacentHTML","outerHTML","hasAttribute","key","localStorage","setItem","JSON","stringify","persist","console","error","MutationObserver","observe","childList","subtree","attributes","attributeFilter","csrfMeta","request","method","url","headers","location","href","isConnected","querySelector","warn","fetch","then","r","ok","status","Promise","reject","trigger","get","text","dispatchRaw","err","recName","methods","message","confirm","t","pushing","replayState","state","sender","resolveTarget","next","parent","previousElementSibling","lastElementChild","send","msg","piped","els","undefined","concat","forEach","detail","parentNode","nextElementSibling","target","dispatchEvent","CustomEvent","bubbles","run","raw","chains","map","chain","
|
|
1
|
+
{"version":3,"names":["WS","parseMessage","str","trimmed","trim","tokens","split","receiver","body","substring","length","rest","slice","keywords","args","currentArg","i","token","endsWith","push","join","selector","receiverName","el","attr","getAttribute","sp","indexOf","receiverCache","Object","create","cacheValid","findReceivers","name","result","document","querySelectorAll","apply","op","content","accepts","innerHTML","textContent","insertAdjacentHTML","outerHTML","hasAttribute","key","localStorage","setItem","JSON","stringify","persist","console","error","MutationObserver","observe","childList","subtree","attributes","attributeFilter","csrfMeta","request","method","url","headers","location","href","isConnected","querySelector","warn","fetch","then","r","ok","status","Promise","reject","trigger","get","text","dispatchRaw","err","recName","methods","message","confirm","t","pushing","replayState","state","sender","resolveTarget","next","parent","previousElementSibling","lastElementChild","send","msg","piped","els","undefined","concat","forEach","detail","parentNode","nextElementSibling","target","dispatchEvent","CustomEvent","bubbles","run","raw","resolve","chains","map","chain","step","steps","s","filter","Boolean","reduce","prev","all","catch","dispatch","senderEl","first","colonIdx","pathname","search","history","pushState","pushUrl","window","addEventListener","e","activePollers","maxPollers","closest","preventDefault","getItem","parse","removeItem","interval","match","n","parseInt","parseInterval","cachedTargets","id","setInterval","clearInterval","talkDOM"],"sources":["index.js"],"mappings":"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","ignoreList":[]}
|
package/index.js
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// Receiver cache: maps name -> NodeList, invalidated by DOM mutations.
|
|
46
|
-
var receiverCache =
|
|
46
|
+
var receiverCache = Object.create(null);
|
|
47
47
|
var cacheValid = false;
|
|
48
48
|
|
|
49
49
|
new MutationObserver(function () { cacheValid = false; })
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
|
|
52
52
|
// Find all elements whose receiver attribute contains the given name.
|
|
53
53
|
function findReceivers(name) {
|
|
54
|
-
if (!cacheValid) { receiverCache =
|
|
54
|
+
if (!cacheValid) { receiverCache = Object.create(null); cacheValid = true; }
|
|
55
55
|
if (receiverCache[name]) return receiverCache[name];
|
|
56
56
|
var result = document.querySelectorAll('[receiver~="' + name + '"]');
|
|
57
57
|
receiverCache[name] = result;
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
function accepts(el, op) {
|
|
64
64
|
var attr = el.getAttribute("accepts");
|
|
65
65
|
if (!attr) return true;
|
|
66
|
-
return attr
|
|
66
|
+
return (" " + attr + " ").indexOf(" " + op + " ") !== -1;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// Save receiver content to localStorage after apply, keyed by receiver name.
|
|
@@ -257,13 +257,18 @@
|
|
|
257
257
|
// Programmatic API: parse and execute a raw message string (supports pipes and semicolons).
|
|
258
258
|
// Returns a promise that resolves when all chains complete.
|
|
259
259
|
function run(raw) {
|
|
260
|
+
var trimmed = raw.trim();
|
|
261
|
+
// Fast path: no pipes or semicolons (most common case).
|
|
262
|
+
if (trimmed.indexOf(";") === -1 && trimmed.indexOf("|") === -1) {
|
|
263
|
+
return Promise.resolve(send(parseMessage(trimmed))).then(function (r) { return [r]; });
|
|
264
|
+
}
|
|
260
265
|
// Semicolons split into independent chains that run in parallel.
|
|
261
|
-
var chains =
|
|
262
|
-
var
|
|
263
|
-
if (!
|
|
266
|
+
var chains = trimmed.split(";").map(function (chain) {
|
|
267
|
+
var step = chain.trim();
|
|
268
|
+
if (!step) return Promise.resolve();
|
|
264
269
|
// Pipes split a chain into sequential steps where each step's return
|
|
265
270
|
// value is fed as the first argument to the next step.
|
|
266
|
-
var steps =
|
|
271
|
+
var steps = step.split("|").map(function (s) { return s.trim(); }).filter(Boolean);
|
|
267
272
|
if (steps.length === 1) {
|
|
268
273
|
return Promise.resolve(send(parseMessage(steps[0])));
|
|
269
274
|
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "talkdom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Smalltalk-inspired message passing for the DOM",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"jsdelivr": "dist/talkdom.min.js",
|
|
7
7
|
"unpkg": "dist/talkdom.min.js",
|
|
8
8
|
"files": [
|
|
9
9
|
"index.js",
|
|
10
|
+
"websocket.js",
|
|
10
11
|
"dist/"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
|
-
"build": "node -e \"require('fs').mkdirSync('dist',{recursive:true})\" && terser index.js -o dist/talkdom.min.js -c -m --source-map",
|
|
14
|
+
"build": "node -e \"require('fs').mkdirSync('dist',{recursive:true})\" && terser index.js -o dist/talkdom.min.js -c -m --source-map && terser websocket.js -o dist/talkdom-ws.min.js -c -m --source-map",
|
|
14
15
|
"test": "node test-runner.js",
|
|
15
16
|
"lint": "eslint index.js",
|
|
16
17
|
"prepublishOnly": "npm run build"
|
package/websocket.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
|
|
3
|
+
if (!window.talkDOM) {
|
|
4
|
+
console.error("talkdom-ws: load index.js before websocket.js");
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
var methods = talkDOM.methods;
|
|
9
|
+
var connections = Object.create(null);
|
|
10
|
+
var maxConnections = 16;
|
|
11
|
+
var BASE_DELAY = 1000;
|
|
12
|
+
var MAX_DELAY = 30000;
|
|
13
|
+
var CLEANUP_INTERVAL = 5000;
|
|
14
|
+
var WS = /\s+/;
|
|
15
|
+
|
|
16
|
+
// Parse receiver attribute to extract ws: URL.
|
|
17
|
+
// Returns the URL string or null if no ws: keyword found.
|
|
18
|
+
function parseWsUrl(attr) {
|
|
19
|
+
var tokens = attr.trim().split(WS);
|
|
20
|
+
for (var i = 1; i < tokens.length; i++) {
|
|
21
|
+
if (tokens[i] === "ws:" && tokens[i + 1]) return tokens[i + 1];
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Remove disconnected elements from a connection's receiver set.
|
|
27
|
+
// Returns true if any receivers remain.
|
|
28
|
+
function pruneReceivers(conn) {
|
|
29
|
+
conn.receivers.forEach(function (el) {
|
|
30
|
+
if (!el.isConnected) conn.receivers.delete(el);
|
|
31
|
+
});
|
|
32
|
+
return conn.receivers.size > 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Fire a custom event on all connected receivers for a URL.
|
|
36
|
+
function fireEvent(conn, name, detail) {
|
|
37
|
+
conn.receivers.forEach(function (el) {
|
|
38
|
+
if (el.isConnected) {
|
|
39
|
+
el.dispatchEvent(new CustomEvent(name, { bubbles: true, detail: detail }));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Route a parsed JSON message to matching receiver elements.
|
|
45
|
+
function routeJson(conn, msg) {
|
|
46
|
+
var name = msg.receiver;
|
|
47
|
+
var op = msg.op || "inner";
|
|
48
|
+
var content = msg.content || "";
|
|
49
|
+
var targets;
|
|
50
|
+
if (name) {
|
|
51
|
+
targets = document.querySelectorAll('[receiver~="' + name + '"]');
|
|
52
|
+
} else {
|
|
53
|
+
// Broadcast to all receivers on this connection.
|
|
54
|
+
targets = Array.from(conn.receivers);
|
|
55
|
+
}
|
|
56
|
+
var detail = { receiver: name || "", selector: "apply:", args: [content, op] };
|
|
57
|
+
for (var i = 0; i < targets.length; i++) {
|
|
58
|
+
var el = targets[i];
|
|
59
|
+
methods["apply:"](el, content, op);
|
|
60
|
+
el.dispatchEvent(new CustomEvent("talkdom:done", { bubbles: true, detail: detail }));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Handle an incoming WebSocket message.
|
|
65
|
+
function onMessage(url, event) {
|
|
66
|
+
var conn = connections[url];
|
|
67
|
+
if (!conn) return;
|
|
68
|
+
pruneReceivers(conn);
|
|
69
|
+
var data = event.data;
|
|
70
|
+
if (typeof data !== "string") return; // ignore binary
|
|
71
|
+
if (data.charAt(0) === "{") {
|
|
72
|
+
try {
|
|
73
|
+
var msg = JSON.parse(data);
|
|
74
|
+
routeJson(conn, msg);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
console.error("talkdom-ws: invalid JSON from " + url, e);
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
// Raw talkDOM message syntax, dispatch through core.
|
|
80
|
+
talkDOM.send(data).catch(function (err) {
|
|
81
|
+
console.warn("talkdom-ws:", err);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function scheduleReconnect(url) {
|
|
87
|
+
var conn = connections[url];
|
|
88
|
+
if (!conn) return;
|
|
89
|
+
if (!pruneReceivers(conn)) { cleanup(url); return; }
|
|
90
|
+
var delay = Math.min(conn.backoff, MAX_DELAY);
|
|
91
|
+
delay = delay * (0.75 + Math.random() * 0.5);
|
|
92
|
+
conn.timer = setTimeout(function () {
|
|
93
|
+
conn.backoff = Math.min(conn.backoff * 2, MAX_DELAY);
|
|
94
|
+
connectWs(url);
|
|
95
|
+
}, delay);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function cleanup(url) {
|
|
99
|
+
var conn = connections[url];
|
|
100
|
+
if (!conn) return;
|
|
101
|
+
if (conn.timer) clearTimeout(conn.timer);
|
|
102
|
+
if (conn.checkTimer) clearInterval(conn.checkTimer);
|
|
103
|
+
if (conn.ws) {
|
|
104
|
+
conn.ws.onclose = null; // prevent reconnect on intentional close
|
|
105
|
+
conn.ws.close();
|
|
106
|
+
}
|
|
107
|
+
delete connections[url];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function connectWs(url) {
|
|
111
|
+
var conn = connections[url];
|
|
112
|
+
if (!conn) return;
|
|
113
|
+
// Already open or connecting — skip.
|
|
114
|
+
if (conn.ws && (conn.ws.readyState === WebSocket.OPEN || conn.ws.readyState === WebSocket.CONNECTING)) return;
|
|
115
|
+
|
|
116
|
+
var ws = new WebSocket(url);
|
|
117
|
+
|
|
118
|
+
ws.onopen = function () {
|
|
119
|
+
conn.backoff = BASE_DELAY;
|
|
120
|
+
fireEvent(conn, "talkdom:ws:open", { url: url });
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
ws.onmessage = function (e) {
|
|
124
|
+
onMessage(url, e);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
ws.onclose = function (e) {
|
|
128
|
+
fireEvent(conn, "talkdom:ws:close", { url: url, code: e.code, reason: e.reason });
|
|
129
|
+
scheduleReconnect(url);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
ws.onerror = function () {
|
|
133
|
+
fireEvent(conn, "talkdom:ws:error", { url: url });
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
conn.ws = ws;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Subscribe an element to a WebSocket URL.
|
|
140
|
+
function subscribe(el, url) {
|
|
141
|
+
var conn = connections[url];
|
|
142
|
+
if (!conn) {
|
|
143
|
+
var count = Object.keys(connections).length;
|
|
144
|
+
if (count >= maxConnections) {
|
|
145
|
+
console.warn("talkdom-ws: max connections (" + maxConnections + ") reached, ignoring " + url);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
conn = { ws: null, receivers: new Set(), backoff: BASE_DELAY, timer: null, checkTimer: null };
|
|
149
|
+
connections[url] = conn;
|
|
150
|
+
// Periodic cleanup check for this connection.
|
|
151
|
+
conn.checkTimer = setInterval(function () {
|
|
152
|
+
if (!pruneReceivers(conn)) cleanup(url);
|
|
153
|
+
}, CLEANUP_INTERVAL);
|
|
154
|
+
}
|
|
155
|
+
conn.receivers.add(el);
|
|
156
|
+
connectWs(url);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Scan a single element for ws: keyword and subscribe.
|
|
160
|
+
function initElement(el) {
|
|
161
|
+
var attr = el.getAttribute("receiver");
|
|
162
|
+
if (!attr) return;
|
|
163
|
+
var url = parseWsUrl(attr);
|
|
164
|
+
if (!url) return;
|
|
165
|
+
subscribe(el, url);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Scan all existing receiver elements.
|
|
169
|
+
function initWsReceivers() {
|
|
170
|
+
document.querySelectorAll("[receiver]").forEach(initElement);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Watch for dynamically added ws: receivers.
|
|
174
|
+
new MutationObserver(function (mutations) {
|
|
175
|
+
for (var i = 0; i < mutations.length; i++) {
|
|
176
|
+
var added = mutations[i].addedNodes;
|
|
177
|
+
for (var j = 0; j < added.length; j++) {
|
|
178
|
+
var node = added[j];
|
|
179
|
+
if (node.nodeType !== 1) continue;
|
|
180
|
+
if (node.hasAttribute && node.hasAttribute("receiver")) initElement(node);
|
|
181
|
+
if (node.querySelectorAll) {
|
|
182
|
+
node.querySelectorAll("[receiver]").forEach(initElement);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}).observe(document, { childList: true, subtree: true });
|
|
187
|
+
|
|
188
|
+
// ws:send: method — send element value over an existing WebSocket connection.
|
|
189
|
+
methods["ws:send:"] = function (el, url) {
|
|
190
|
+
var conn = connections[url];
|
|
191
|
+
if (!conn || !conn.ws || conn.ws.readyState !== WebSocket.OPEN) {
|
|
192
|
+
console.error("talkdom-ws: no open connection to " + url);
|
|
193
|
+
return Promise.reject("not connected");
|
|
194
|
+
}
|
|
195
|
+
var payload = ("value" in el) ? el.value : el.textContent;
|
|
196
|
+
conn.ws.send(payload);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
initWsReceivers();
|
|
200
|
+
|
|
201
|
+
talkDOM.ws = {
|
|
202
|
+
connect: function (url) {
|
|
203
|
+
if (!connections[url]) {
|
|
204
|
+
var count = Object.keys(connections).length;
|
|
205
|
+
if (count >= maxConnections) {
|
|
206
|
+
console.warn("talkdom-ws: max connections (" + maxConnections + ") reached");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
connections[url] = { ws: null, receivers: new Set(), backoff: BASE_DELAY, timer: null, checkTimer: null };
|
|
210
|
+
connections[url].checkTimer = setInterval(function () {
|
|
211
|
+
if (!pruneReceivers(connections[url])) cleanup(url);
|
|
212
|
+
}, CLEANUP_INTERVAL);
|
|
213
|
+
}
|
|
214
|
+
connectWs(url);
|
|
215
|
+
},
|
|
216
|
+
disconnect: function (url) { cleanup(url); },
|
|
217
|
+
send: function (url, data) {
|
|
218
|
+
var conn = connections[url];
|
|
219
|
+
if (!conn || !conn.ws || conn.ws.readyState !== WebSocket.OPEN) return false;
|
|
220
|
+
conn.ws.send(typeof data === "string" ? data : JSON.stringify(data));
|
|
221
|
+
return true;
|
|
222
|
+
},
|
|
223
|
+
get connections() {
|
|
224
|
+
var out = Object.create(null);
|
|
225
|
+
for (var url in connections) {
|
|
226
|
+
out[url] = { state: connections[url].ws ? connections[url].ws.readyState : -1, receivers: connections[url].receivers.size };
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
},
|
|
230
|
+
get maxConnections() { return maxConnections; },
|
|
231
|
+
set maxConnections(n) { maxConnections = n; },
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
}());
|