turbo_reflex 0.0.5 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -3
- data/README.md +1 -6
- data/app/assets/builds/turbo_reflex.js +1 -1
- data/app/assets/builds/turbo_reflex.js.map +4 -4
- data/app/controllers/concerns/turbo_reflex/controller.rb +12 -108
- data/app/javascript/activity.js +20 -0
- data/app/javascript/delegates.js +27 -0
- data/app/javascript/drivers/form.js +12 -0
- data/app/javascript/drivers/frame.js +11 -0
- data/app/javascript/drivers/index.js +63 -0
- data/app/javascript/drivers/window.js +70 -0
- data/app/javascript/elements.js +29 -50
- data/app/javascript/index.js +72 -0
- data/app/javascript/lifecycle.js +40 -0
- data/app/javascript/logger.js +34 -0
- data/app/javascript/schema.js +6 -0
- data/app/javascript/turbo.js +24 -0
- data/app/javascript/urls.js +9 -0
- data/app/javascript/uuids.js +10 -0
- data/lib/turbo_reflex/base.rb +59 -9
- data/lib/turbo_reflex/engine.rb +2 -10
- data/lib/turbo_reflex/runner.rb +199 -0
- data/lib/turbo_reflex/version.rb +1 -1
- data/package.json +5 -4
- data/turbo_reflex.gemspec +1 -0
- data/yarn.lock +146 -155
- metadata +30 -11
- data/app/controllers/turbo_reflex/turbo_reflexes_controller.rb +0 -12
- data/app/helpers/turbo_reflex/turbo_reflex_helper.rb +0 -9
- data/app/javascript/event_registry.js +0 -34
- data/app/javascript/frame_sources.js +0 -28
- data/app/javascript/lifecycle_events.js +0 -24
- data/app/javascript/security.js +0 -7
- data/app/javascript/turbo_reflex.js +0 -111
- data/config/routes.rb +0 -5
- data/lib/turbo_reflex/errors.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d777dc777c4797542c2a285da1ba2ee9ff75e3a329b90f148abee3691da210f
|
4
|
+
data.tar.gz: cda1b302d539fe5f3081ce6722336eb663d2fa2c2881cc5b48ae65fe24e400b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 584be627cda098a3aca5471e841c3c3da3b0b4eb51891e69570b6ab330b82f80977b604f288f4955895a6dbd21ae2c4661c814982e8f4a05168bbc9a7220625f
|
7
|
+
data.tar.gz: 42820cbb510fb61ef22c07a550831d69cae13f08867d15c626ed0f6dce045440d3ca10e0668d5e75671e8d5db04a58d75cecf091e9d11370d219f89d10b39fed
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
turbo_reflex (0.0.
|
4
|
+
turbo_reflex (0.0.7)
|
5
5
|
rails (>= 6.1)
|
6
6
|
turbo-rails (>= 1.1)
|
7
|
+
turbo_ready (>= 0.1)
|
7
8
|
|
8
9
|
GEM
|
9
10
|
remote: https://rubygems.org/
|
@@ -192,7 +193,7 @@ GEM
|
|
192
193
|
zeitwerk (~> 2.5)
|
193
194
|
rainbow (3.1.1)
|
194
195
|
rake (13.0.6)
|
195
|
-
regexp_parser (2.
|
196
|
+
regexp_parser (2.6.0)
|
196
197
|
rexml (3.2.5)
|
197
198
|
rouge (4.0.0)
|
198
199
|
rubocop (1.35.1)
|
@@ -238,7 +239,7 @@ GEM
|
|
238
239
|
railties (>= 6.0.0)
|
239
240
|
thor (1.2.1)
|
240
241
|
timeout (0.3.0)
|
241
|
-
turbo-rails (1.
|
242
|
+
turbo-rails (1.3.0)
|
242
243
|
actionpack (>= 6.0.0)
|
243
244
|
activejob (>= 6.0.0)
|
244
245
|
railties (>= 6.0.0)
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
</h1>
|
9
9
|
<p align="center">
|
10
10
|
<a href="http://blog.codinghorror.com/the-best-code-is-no-code-at-all/">
|
11
|
-
<img alt="Lines of Code" src="https://img.shields.io/badge/loc-
|
11
|
+
<img alt="Lines of Code" src="https://img.shields.io/badge/loc-672-47d299.svg" />
|
12
12
|
</a>
|
13
13
|
<a href="https://codeclimate.com/github/hopsoft/turbo_reflex/maintainability">
|
14
14
|
<img src="https://api.codeclimate.com/v1/badges/fe1162a742fe83a4fdfd/maintainability" />
|
@@ -145,7 +145,6 @@ TurboReflex is a lightweight Turbo Frame extension... which means that reactivit
|
|
145
145
|
+ <%= turbo_reflex_meta_tag %>
|
146
146
|
</head>
|
147
147
|
<body>
|
148
|
-
+ <%= turbo_reflex_frame_tag %>
|
149
148
|
</body>
|
150
149
|
</html>
|
151
150
|
```
|
@@ -207,12 +206,8 @@ TurboReflex.registerEvent('sl-change', ['sl-switch'])
|
|
207
206
|
|
208
207
|
TurboReflex supports the following lifecycle events.
|
209
208
|
|
210
|
-
- `turbo-reflex:before-start` - fires before reflex processing starts
|
211
209
|
- `turbo-reflex:start` - fires before the reflex is sent to the server
|
212
210
|
- `turbo-reflex:finish` - fires after the server has processed the reflex and responded
|
213
|
-
- `turbo-reflex:missing-frame-id` - fires if the reflex cannot determine the target frame id
|
214
|
-
- `turbo-reflex:missing-frame` - fires if the the reflex cannot locate the frame element
|
215
|
-
- `turbo-reflex:missing-frame-src` - fires if the reflex cannot determine the frame's `src`
|
216
211
|
- `turbo-reflex:error` - fires if an unexpected error occurs
|
217
212
|
|
218
213
|
### Targeting Frames
|
@@ -1,2 +1,2 @@
|
|
1
|
-
var
|
1
|
+
var S={frameAttribute:"data-turbo-frame",reflexAttribute:"data-turbo-reflex"},u={...S};var v={};function D(e){v[e.id]=e}function H(e){delete v[e]}var b={add:D,remove:H,get reflexes(){return[...Object.values(v)]},get length(){return Object.keys(v).length}};var f={start:"turbo-reflex:start",success:"turbo-reflex:success",finish:"turbo-reflex:finish",abort:"turbo-reflex:abort",clientError:"turbo-reflex:client-error",serverError:"turbo-reflex:server-error"};function p(e,t=document,r={},n=!1){try{t=t||document;let s=new CustomEvent(e,{detail:r,cancelable:!1,bubbles:!0});t.dispatchEvent(s)}catch(s){if(n)throw s;p(f.clientError,t,{error:s,...r},!0)}}function w(e){e.detail.endedAt=new Date().getTime(),e.detail.milliseconds=e.detail.endedAt-e.detail.startedAt,setTimeout(()=>p(f.finish,e.target,e.detail),10)}addEventListener(f.serverError,w);addEventListener(f.success,w);addEventListener(f.finish,e=>b.remove(e.detail.id),!0);var o={dispatch:p,events:f};function _(e){return e.closest(`[${u.reflexAttribute}]`)}function q(e){return e.closest("turbo-frame")}function P(e,t={}){if(e.tagName.toLowerCase()!=="select")return t.value=e.value;if(!e.multiple)return t.value=e.options[e.selectedIndex].value;t.values=Array.from(e.options).reduce((r,n)=>(n.selected&&r.push(n.value),r),[])}function V(e){let n=Array.from(e.attributes).reduce((s,c)=>{let i=c.value;return typeof i=="string"&&i.length>100&&(i=i.slice(0,100)+"..."),s[c.name]=i,s},{});return n.tag=e.tagName,n.checked=e.checked,n.disabled=e.disabled,P(e,n),typeof n.value=="string"&&n.value.length>500&&(n.value=n.value.slice(0,500)+"..."),delete n.class,delete n[u.reflexAttribute],delete n[u.frameAttribute],n}var a={buildAttributePayload:V,findClosestReflex:_,findClosestFrame:q,get metaElement(){return document.getElementById("turbo-reflex")},get metaElementToken(){return document.getElementById("turbo-reflex").getAttribute("content")}};var E={};addEventListener("turbo:before-fetch-request",e=>{let t=e.target.closest("turbo-frame"),{fetchOptions:r}=e.detail;r.headers["TurboReflex-Token"]=a.metaElementToken});addEventListener("turbo:before-fetch-response",e=>{let t=e.target.closest("turbo-frame");t&&(E[t.id]=t.src)});addEventListener("turbo:frame-load",e=>{let t=e.target.closest("turbo-frame");t.dataset.turboReflexSrc=E[t.id]||t.src||t.dataset.turboReflexSrc,delete E[t.id]});var m={},T;function $(e,t){m[e]=t,document.addEventListener(e,T,!0)}function N(e,t){return t=t.toLowerCase(),m[e].includes(t)||!Object.values(m).flat().includes(t)&&m[e].includes("*")}var l={events:m,register:$,isRegistered:N,set handler(e){T=e}};function B(e,t={}){t.token=a.metaElementToken;let r=document.createElement("input");r.type="hidden",r.name="turbo_reflex",r.value=JSON.stringify(t),e.appendChild(r)}var y={invokeReflex:B};function F(e,t={}){let r=document.createElement("a");r.href=e;let n=new URL(r);return n.searchParams.set("turbo_reflex",JSON.stringify(t)),n}var g={build:F};function M(e,t){let r=t.src;t={...t},delete t.src,e.src=g.build(r,t)}var k={invokeReflex:M};function U(e){let t=e.target;o.dispatch(o.events.abort,window,{xhr:t,...e.detail})}function R(e){let t=e.target;o.dispatch(o.events.clientError,window,{xhr:t,...e.detail,error:`Server returned a ${t.status} status code! TurboReflex requires 2XX status codes. Server message: ${t.statusText}`},!0)}function X(e){let t=e.target,r=t.responseText,n=t.getResponseHeader("TurboReflex-Hijacked")==="true";if((t.status<200||t.status>299)&&R(e),n){let s="<turbo-stream",c="</turbo-stream>",i=r.indexOf(s),d=r.lastIndexOf(c);if(i>=0&&d>=0){let h=r.slice(i,d+c.length);document.body.insertAdjacentHTML("beforeend",h)}}else{let s="<html",c="</html",i=r.indexOf(s),d=r.lastIndexOf(c);if(i>=0&&d>=0){let h=r.slice(r.indexOf(">",i)+1,d);document.documentElement.innerHTML=h}}}function J(e){let t=e.src;e={...e},delete e.src;try{let r=new XMLHttpRequest;r.open("GET",g.build(t,e),!0),r.setRequestHeader("TurboReflex-Token",a.metaElementToken),r.addEventListener("abort",U),r.addEventListener("error",R),r.addEventListener("load",X),r.send()}catch(r){let n=`Unexpected error sending HTTP request! ${r.message}`;R(r,{detail:{message:n}})}}var A={invokeReflex:J};function L(e,t){return t=t||{dataset:{}},e.href||t.src||t.dataset.turboReflexSrc||location.href}function G(e){let t=a.findClosestFrame(e),r=e.dataset.turboFrame;return e.tagName.toLowerCase()==="form"?{name:"form",reason:"Element is a form.",frame:t,src:e.action,invokeReflex:y.invokeReflex}:r&&r!=="_self"?(t=document.getElementById(r),{name:"frame",reason:"element targets a frame that is not _self",frame:t,src:L(e,t),invokeReflex:k.invokeReflex}):(!r||r==="_self")&&t?{name:"frame",reason:"element does NOT target a frame or targets _self and is contained by a frame",frame:t,src:L(e,t),invokeReflex:k.invokeReflex}:{name:"window",reason:"element matches one or more of the following conditions (targets _top, does NOT target a frame, is NOT contained by a frame)",frame:null,src:L(e),invokeReflex:A.invokeReflex}}var O={find:G};var x="unknown",I={debug:Object.values(o.events),info:Object.values(o.events),warn:[o.events.abort,o.events.clientError,o.events.serverError],error:[o.events.clientError,o.events.serverError],unknown:[]};Object.values(o.events).forEach(e=>{addEventListener(e,t=>{I[x].includes(t.type)&&console[x==="debug"?"log":x](t.type,t.detail)})});var j={get level(){return x},set level(e){return Object.keys(I).includes(e)||(e="unknown"),x=e}};function z(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16))}var C={v4:z};function K(e){let t,r={};try{if(t=a.findClosestReflex(e.target),!t||!l.isRegistered(e.type,t.tagName))return;let n=O.find(t);switch(r={id:`reflex-${C.v4()}`,name:t.dataset.turboReflex,driver:n.name,src:n.src,frameId:n.frame?n.frame.id:null,elementId:t.id.length>0?t.id:null,elementAttributes:a.buildAttributePayload(t),startedAt:new Date().getTime()},b.add(r),o.dispatch(o.events.start,t,r),n.name!=="form"&&e.preventDefault(),n.name){case"form":return n.invokeReflex(t,r);case"frame":return n.invokeReflex(n.frame,r);case"window":return n.invokeReflex(r)}}catch(n){o.dispatch(o.events.clientError,t,{error:n,...r})}}l.handler=K;l.register("change",["input","select","textarea"]);l.register("submit",["form"]);l.register("click",["*"]);var qe={schema:u,logger:j,registerEventDelegate:l.register,get eventDelegates(){return{...l.events}},get lifecycleEvents(){return[...Object.values(o.events)]}};export{qe as default};
|
2
2
|
//# sourceMappingURL=turbo_reflex.js.map
|
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"version": 3,
|
3
|
-
"sources": ["../../javascript/
|
4
|
-
"sourcesContent": ["const events = {\n beforeStart: 'turbo-reflex:before-start',\n start: 'turbo-reflex:start',\n finish: 'turbo-reflex:finish',\n error: 'turbo-reflex:error',\n missingFrameId: 'turbo-reflex:missing-frame-id',\n missingFrame: 'turbo-reflex:missing-frame',\n missingFrameSrc: 'turbo-reflex:missing-frame-src'\n}\n\nfunction dispatch (name, target = document, detail = {}) {\n const event = new CustomEvent(name, {\n detail,\n cancelable: true,\n bubbles: true\n })\n target.dispatchEvent(event)\n}\n\nfunction logEventNames () {\n Object.values(events).forEach(name => console.log(name))\n}\n\nexport default { ...events, dispatch, logEventNames }\n", "import LifecycleEvents from './lifecycle_events'\nconst frameSources = {}\n\n// fires after receiving a turbo HTTP response\naddEventListener('turbo:before-fetch-response', event => {\n const frame = event.target\n frameSources[frame.id] = frame.src\n\n const { turboReflexActive, turboReflexElementId } = frame.dataset\n if (!turboReflexActive) return\n\n const element = document.getElementById(turboReflexElementId)\n delete frame.dataset.turboReflexActive\n delete frame.dataset.turboReflexElementId\n\n LifecycleEvents.dispatch(LifecycleEvents.finish, element || document, {\n frame,\n element: element || 'Unknown! Missing id attribute.'\n })\n})\n\n// fires when a frame element is navigated and finishes loading\naddEventListener('turbo:frame-load', event => {\n const frame = event.target\n frame.dataset.turboReflexSrc =\n frameSources[frame.id] || frame.src || frame.dataset.turboReflexSrc\n delete frameSources[frame.id]\n})\n", "const Security = {\n get token () {\n return document.getElementById('turbo-reflex-token').getAttribute('content')\n }\n}\n\nexport default Security\n", "import LifecycleEvents from './lifecycle_events'\n\nfunction findClosestReflex (element) {\n return element.closest('[data-turbo-reflex]')\n}\n\nfunction findClosestFrame (element) {\n return element.closest('turbo-frame')\n}\n\nfunction findFrameId (element) {\n let id = element.dataset.turboFrame\n if (!id) {\n const frame = findClosestFrame(element)\n if (frame) id = frame.id\n }\n if (!id) {\n console.error(\n `The reflex element does not specify a frame!`,\n `Please move the reflex element inside a <turbo-frame> or set the 'data-turbo-frame' attribute.`,\n element\n )\n LifecycleEvents.dispatch(LifecycleEvents.missingFrameId, element, {\n element\n })\n }\n return id\n}\n\nfunction findFrame (id) {\n const frame = document.getElementById(id)\n if (!frame) {\n console.error(`The frame '${id}' does not exist!`)\n LifecycleEvents.dispatch(LifecycleEvents.missingFrame, document, { id })\n }\n return frame\n}\n\nfunction findFrameSrc (frame) {\n const frameSrc = frame.dataset.turboReflexSrc || frame.src\n if (!frameSrc) {\n console.error(\n `The the 'src' for <turbo-frame id='${frame.id}'> is unknown!`,\n `TurboReflex uses 'src' to (re)render frame content after the reflex is invoked.`,\n `Please set the 'src' or 'data-turbo-reflex-src' attribute on the <turbo-frame> element.`,\n frame\n )\n LifecycleEvents.dispatch(LifecycleEvents.missingFrameSrc, frame, { frame })\n }\n return frameSrc\n}\n\nfunction assignElementValueToPayload (element, payload = {}) {\n if (element.tagName.toLowerCase() !== 'select')\n return (payload.value = element.value)\n\n if (!element.multiple)\n return (payload.value = element.options[element.selectedIndex].value)\n\n payload.values = Array.from(element.options).reduce((memo, option) => {\n if (option.selected) memo.push(option.value)\n return memo\n }, [])\n}\n\nfunction buildAttributePayload (element) {\n const payload = Array.from(element.attributes).reduce((memo, attr) => {\n memo[attr.name] = attr.value\n return memo\n }, {})\n\n payload.tag = element.tagName\n payload.checked = element.checked\n payload.disabled = element.disabled\n assignElementValueToPayload(element, payload)\n\n return payload\n}\n\nexport {\n findClosestReflex,\n findClosestFrame,\n findFrameId,\n findFrame,\n findFrameSrc,\n buildAttributePayload\n}\n", "const registeredEvents = {}\nlet eventListener\n\nfunction registerEventListener (fn) {\n eventListener = fn\n}\n\nfunction registerEvent (eventName, tagNames) {\n registeredEvents[eventName] = tagNames\n document.addEventListener(eventName, eventListener, true)\n}\n\nfunction isRegisteredEvent (eventName, tagName) {\n tagName = tagName.toLowerCase()\n return (\n registeredEvents[eventName].includes(tagName) ||\n (!Object.values(registeredEvents)\n .flat()\n .includes(tagName) &&\n registeredEvents[eventName].includes('*'))\n )\n}\n\nfunction logRegisteredEvents () {\n console.log(registeredEvents)\n}\n\nexport {\n registerEventListener,\n registerEvent,\n registeredEvents,\n isRegisteredEvent,\n logRegisteredEvents\n}\n", "import './frame_sources'\nimport Security from './security'\nimport LifecycleEvents from './lifecycle_events'\nimport {\n findClosestReflex,\n findClosestFrame,\n findFrameId,\n findFrame,\n findFrameSrc,\n buildAttributePayload\n} from './elements'\nimport {\n registerEventListener,\n registerEvent,\n registeredEvents,\n isRegisteredEvent,\n logRegisteredEvents\n} from './event_registry'\n\n// fires before making a turbo HTTP request\naddEventListener('turbo:before-fetch-request', event => {\n const frame = event.target\n const { turboReflexActive } = frame.dataset\n if (!turboReflexActive) return\n const { fetchOptions } = event.detail\n fetchOptions.headers['Turbo-Reflex'] = Security.token\n})\n\nfunction buildURL (urlString) {\n const a = document.createElement('a')\n a.href = urlString\n return new URL(a)\n}\n\nfunction invokeFormReflex (form, payload = {}) {\n payload.token = Security.token\n const input = document.createElement('input')\n input.type = 'hidden'\n input.name = 'turbo_reflex'\n input.value = JSON.stringify(payload)\n form.appendChild(input)\n}\n\nfunction invokeReflex (event) {\n let element, frameId, frame, frameSrc\n try {\n element = findClosestReflex(event.target)\n if (!element) return\n\n if (!isRegisteredEvent(event.type, element.tagName)) return\n\n LifecycleEvents.dispatch(LifecycleEvents.beforeStart, element, { element })\n\n frameId = findFrameId(element)\n if (!frameId) return\n\n frame = findFrame(frameId)\n if (!frame) return\n\n frameSrc = findFrameSrc(frame)\n if (!frameSrc) return\n\n const payload = {\n frameId: frameId,\n element: buildAttributePayload(element)\n }\n\n LifecycleEvents.dispatch(LifecycleEvents.start, element, {\n element,\n frameId,\n frame,\n frameSrc,\n payload\n })\n frame.dataset.turboReflexActive = true\n frame.dataset.turboReflexElementId = element.id\n\n if (element.tagName.toLowerCase() === 'form')\n return invokeFormReflex(element, payload)\n\n event.preventDefault()\n const frameURL = buildURL(frameSrc)\n frameURL.searchParams.set('turbo_reflex', JSON.stringify(payload))\n frame.src = frameURL.toString()\n } catch (error) {\n console.error(\n `TurboReflex encountered an unexpected error!`,\n { element, frameId, frame, frameSrc, target: event.target },\n error\n )\n LifecycleEvents.dispatch(LifecycleEvents.error, element || document, {\n element,\n frameId,\n frame,\n frameSrc,\n error\n })\n }\n}\n\n// wire things up and setup default events\nregisterEventListener(invokeReflex)\nregisterEvent('change', ['input', 'select', 'textarea'])\nregisterEvent('submit', ['form'])\nregisterEvent('click', ['*'])\n\nexport default {\n registerEvent,\n logRegisteredEvents,\n logLifecycleEventNames: LifecycleEvents.logEventNames\n}\n"],
|
5
|
-
"mappings": "AAAA,IAAMA,EAAS,CACb,
|
6
|
-
"names": ["events", "dispatch", "name", "target", "detail", "event", "
|
3
|
+
"sources": ["../../javascript/schema.js", "../../javascript/activity.js", "../../javascript/lifecycle.js", "../../javascript/elements.js", "../../javascript/turbo.js", "../../javascript/delegates.js", "../../javascript/drivers/form.js", "../../javascript/urls.js", "../../javascript/drivers/frame.js", "../../javascript/drivers/window.js", "../../javascript/drivers/index.js", "../../javascript/logger.js", "../../javascript/uuids.js", "../../javascript/index.js"],
|
4
|
+
"sourcesContent": ["const schema = {\n frameAttribute: 'data-turbo-frame',\n reflexAttribute: 'data-turbo-reflex'\n}\n\nexport default { ...schema }\n", "const active = {}\n\nfunction add (payload) {\n active[payload.id] = payload\n}\n\nfunction remove (id) {\n delete active[id]\n}\n\nexport default {\n add,\n remove,\n get reflexes () {\n return [...Object.values(active)]\n },\n get length () {\n return Object.keys(active).length\n }\n}\n", "import activity from './activity'\n\nconst events = {\n start: 'turbo-reflex:start',\n success: 'turbo-reflex:success',\n finish: 'turbo-reflex:finish',\n abort: 'turbo-reflex:abort',\n clientError: 'turbo-reflex:client-error',\n serverError: 'turbo-reflex:server-error'\n}\n\nfunction dispatch (name, target = document, detail = {}, raise = false) {\n try {\n target = target || document\n const event = new CustomEvent(name, {\n detail,\n cancelable: false,\n bubbles: true\n })\n target.dispatchEvent(event)\n } catch (error) {\n if (raise) throw error\n dispatch(events.clientError, target, { error, ...detail }, true)\n }\n}\n\nfunction finish (event) {\n event.detail.endedAt = new Date().getTime()\n event.detail.milliseconds = event.detail.endedAt - event.detail.startedAt\n setTimeout(() => dispatch(events.finish, event.target, event.detail), 10)\n}\n\naddEventListener(events.serverError, finish)\naddEventListener(events.success, finish)\naddEventListener(events.finish, event => activity.remove(event.detail.id), true)\n\nexport default {\n dispatch,\n events\n}\n", "import schema from './schema.js'\nimport lifecycle from './lifecycle'\n\nfunction findClosestReflex (element) {\n return element.closest(`[${schema.reflexAttribute}]`)\n}\n\nfunction findClosestFrame (element) {\n return element.closest('turbo-frame')\n}\n\nfunction assignElementValueToPayload (element, payload = {}) {\n if (element.tagName.toLowerCase() !== 'select')\n return (payload.value = element.value)\n\n if (!element.multiple)\n return (payload.value = element.options[element.selectedIndex].value)\n\n payload.values = Array.from(element.options).reduce((memo, option) => {\n if (option.selected) memo.push(option.value)\n return memo\n }, [])\n}\n\nfunction buildAttributePayload (element) {\n // truncate long values to optimize payload size\n // TODO: revisit this decision\n const maxAttributeLength = 100\n const maxValueLength = 500\n\n const payload = Array.from(element.attributes).reduce((memo, attr) => {\n let value = attr.value\n if (typeof value === 'string' && value.length > maxAttributeLength)\n value = value.slice(0, maxAttributeLength) + '...'\n memo[attr.name] = value\n return memo\n }, {})\n\n payload.tag = element.tagName\n payload.checked = element.checked\n payload.disabled = element.disabled\n assignElementValueToPayload(element, payload)\n\n if (\n typeof payload.value === 'string' &&\n payload.value.length > maxValueLength\n )\n payload.value = payload.value.slice(0, maxValueLength) + '...'\n\n delete payload.class\n delete payload[schema.reflexAttribute]\n delete payload[schema.frameAttribute]\n return payload\n}\n\nexport default {\n buildAttributePayload,\n findClosestReflex,\n findClosestFrame,\n get metaElement () {\n return document.getElementById('turbo-reflex')\n },\n get metaElementToken () {\n return document.getElementById('turbo-reflex').getAttribute('content')\n }\n}\n", "import elements from './elements'\n\nconst frameSources = {}\n\n// fires before making a turbo HTTP request\naddEventListener('turbo:before-fetch-request', event => {\n const frame = event.target.closest('turbo-frame')\n const { fetchOptions } = event.detail\n fetchOptions.headers['TurboReflex-Token'] = elements.metaElementToken\n})\n\n// fires after receiving a turbo HTTP response\naddEventListener('turbo:before-fetch-response', event => {\n const frame = event.target.closest('turbo-frame')\n if (frame) frameSources[frame.id] = frame.src\n})\n\n// fires when a frame element is navigated and finishes loading\naddEventListener('turbo:frame-load', event => {\n const frame = event.target.closest('turbo-frame')\n frame.dataset.turboReflexSrc =\n frameSources[frame.id] || frame.src || frame.dataset.turboReflexSrc\n delete frameSources[frame.id]\n})\n", "const events = {}\nlet eventListener\n\nfunction register (eventName, tagNames) {\n events[eventName] = tagNames\n document.addEventListener(eventName, eventListener, true)\n}\n\nfunction isRegistered (eventName, tagName) {\n tagName = tagName.toLowerCase()\n return (\n events[eventName].includes(tagName) ||\n (!Object.values(events)\n .flat()\n .includes(tagName) &&\n events[eventName].includes('*'))\n )\n}\n\nexport default {\n events,\n register,\n isRegistered,\n set handler (fn) {\n eventListener = fn\n }\n}\n", "import elements from '../elements'\n\nfunction invokeReflex (form, payload = {}) {\n payload.token = elements.metaElementToken\n const input = document.createElement('input')\n input.type = 'hidden'\n input.name = 'turbo_reflex'\n input.value = JSON.stringify(payload)\n form.appendChild(input)\n}\n\nexport default { invokeReflex }\n", "function build (urlString, payload = {}) {\n const a = document.createElement('a')\n a.href = urlString\n const url = new URL(a)\n url.searchParams.set('turbo_reflex', JSON.stringify(payload))\n return url\n}\n\nexport default { build }\n", "import urls from '../urls'\nimport elements from '../elements'\n\nfunction invokeReflex (frame, payload) {\n const src = payload.src\n payload = { ...payload }\n delete payload.src\n frame.src = urls.build(src, payload)\n}\n\nexport default { invokeReflex }\n", "import elements from '../elements'\nimport lifecycle from '../lifecycle'\nimport urls from '../urls'\n\nfunction aborted (event) {\n const xhr = event.target\n lifecycle.dispatch(lifecycle.events.abort, window, { xhr, ...event.detail })\n}\n\nfunction errored (event) {\n const xhr = event.target\n lifecycle.dispatch(\n lifecycle.events.clientError,\n window,\n {\n xhr,\n ...event.detail,\n error: `Server returned a ${xhr.status} status code! TurboReflex requires 2XX status codes. Server message: ${xhr.statusText}`\n },\n true\n )\n}\n\nfunction loaded (event) {\n const xhr = event.target\n const content = xhr.responseText\n const hijacked = xhr.getResponseHeader('TurboReflex-Hijacked') === 'true'\n if (xhr.status < 200 || xhr.status > 299) errored(event)\n\n if (hijacked) {\n const head = '<turbo-stream'\n const tail = '</turbo-stream>'\n const headIndex = content.indexOf(head)\n const tailIndex = content.lastIndexOf(tail)\n if (headIndex >= 0 && tailIndex >= 0) {\n const streams = content.slice(headIndex, tailIndex + tail.length)\n document.body.insertAdjacentHTML('beforeend', streams)\n }\n } else {\n const head = '<html'\n const tail = '</html'\n const headIndex = content.indexOf(head)\n const tailIndex = content.lastIndexOf(tail)\n if (headIndex >= 0 && tailIndex >= 0) {\n const html = content.slice(content.indexOf('>', headIndex) + 1, tailIndex)\n document.documentElement.innerHTML = html\n }\n }\n}\n\nfunction invokeReflex (payload) {\n const src = payload.src\n payload = { ...payload }\n delete payload.src\n\n try {\n const xhr = new XMLHttpRequest()\n xhr.open('GET', urls.build(src, payload), true)\n xhr.setRequestHeader('TurboReflex-Token', elements.metaElementToken)\n xhr.addEventListener('abort', aborted)\n xhr.addEventListener('error', errored)\n xhr.addEventListener('load', loaded)\n xhr.send()\n } catch (ex) {\n const message = `Unexpected error sending HTTP request! ${ex.message}`\n errored(ex, { detail: { message } })\n }\n}\n\nexport default { invokeReflex }\n", "import elements from '../elements'\nimport formDriver from './form'\nimport frameDriver from './frame'\nimport windowDriver from './window'\n\nfunction src (element, frame) {\n frame = frame || { dataset: {} }\n return (\n element.href || frame.src || frame.dataset.turboReflexSrc || location.href\n )\n}\n\nfunction find (element) {\n let frame = elements.findClosestFrame(element)\n const targetId = element.dataset.turboFrame\n\n if (element.tagName.toLowerCase() === 'form')\n return {\n name: 'form',\n reason: 'Element is a form.',\n frame,\n src: element.action,\n invokeReflex: formDriver.invokeReflex\n }\n\n // element targets a frame that is not _self\n if (targetId && targetId !== '_self') {\n frame = document.getElementById(targetId)\n return {\n name: 'frame',\n reason: 'element targets a frame that is not _self',\n frame,\n src: src(element, frame),\n invokeReflex: frameDriver.invokeReflex\n }\n }\n\n // element does NOT target a frame or targets _self and is contained by a frame\n if ((!targetId || targetId === '_self') && frame)\n return {\n name: 'frame',\n reason:\n 'element does NOT target a frame or targets _self and is contained by a frame',\n frame,\n src: src(element, frame),\n invokeReflex: frameDriver.invokeReflex\n }\n\n // element matches one or more of the following conditions\n // - targets _top\n // - does NOT target a frame\n // - is NOT contained by a frame\n return {\n name: 'window',\n reason:\n 'element matches one or more of the following conditions (targets _top, does NOT target a frame, is NOT contained by a frame)',\n frame: null,\n src: src(element),\n invokeReflex: windowDriver.invokeReflex\n }\n}\n\nexport default { find }\n", "import lifecycle from './lifecycle'\n\nlet currentLevel = 'unknown'\n\nconst logLevels = {\n debug: Object.values(lifecycle.events),\n info: Object.values(lifecycle.events),\n warn: [\n lifecycle.events.abort,\n lifecycle.events.clientError,\n lifecycle.events.serverError\n ],\n error: [lifecycle.events.clientError, lifecycle.events.serverError],\n unknown: []\n}\n\nObject.values(lifecycle.events).forEach(name => {\n addEventListener(name, event => {\n if (logLevels[currentLevel].includes(event.type)) {\n const level = currentLevel === 'debug' ? 'log' : currentLevel\n console[level](event.type, event.detail)\n }\n })\n})\n\nexport default {\n get level () {\n return currentLevel\n },\n set level (value) {\n if (!Object.keys(logLevels).includes(value)) value = 'unknown'\n return (currentLevel = value)\n }\n}\n", "function v4 () {\n return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>\n (\n c ^\n (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))\n ).toString(16)\n )\n}\n\nexport default { v4 }\n", "import './turbo'\nimport schema from './schema.js'\nimport activity from './activity'\nimport delegates from './delegates'\nimport drivers from './drivers'\nimport elements from './elements'\nimport lifecycle from './lifecycle'\nimport logger from './logger'\nimport urls from './urls'\nimport uuids from './uuids'\n\nfunction invokeReflex (event) {\n let element\n let payload = {}\n\n try {\n element = elements.findClosestReflex(event.target)\n if (!element) return\n if (!delegates.isRegistered(event.type, element.tagName)) return\n\n const driver = drivers.find(element)\n\n // payload sent to server (also used for lifecycle event.detail)\n payload = {\n id: `reflex-${uuids.v4()}`,\n name: element.dataset.turboReflex,\n driver: driver.name,\n src: driver.src,\n frameId: driver.frame ? driver.frame.id : null,\n elementId: element.id.length > 0 ? element.id : null,\n elementAttributes: elements.buildAttributePayload(element),\n startedAt: new Date().getTime()\n }\n\n activity.add(payload)\n lifecycle.dispatch(lifecycle.events.start, element, payload)\n\n if (driver.name !== 'form') event.preventDefault()\n\n switch (driver.name) {\n case 'form':\n return driver.invokeReflex(element, payload)\n case 'frame':\n return driver.invokeReflex(driver.frame, payload)\n case 'window':\n return driver.invokeReflex(payload)\n }\n } catch (error) {\n lifecycle.dispatch(lifecycle.events.clientError, element, {\n error,\n ...payload\n })\n }\n}\n\n// wire things up and setup defaults for event delegation\ndelegates.handler = invokeReflex\ndelegates.register('change', ['input', 'select', 'textarea'])\ndelegates.register('submit', ['form'])\ndelegates.register('click', ['*'])\n\nexport default {\n schema,\n logger,\n registerEventDelegate: delegates.register,\n get eventDelegates () {\n return { ...delegates.events }\n },\n get lifecycleEvents () {\n return [...Object.values(lifecycle.events)]\n }\n}\n"],
|
5
|
+
"mappings": "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",
|
6
|
+
"names": ["schema", "schema_default", "active", "add", "payload", "remove", "id", "activity_default", "events", "dispatch", "name", "target", "detail", "raise", "event", "error", "finish", "activity_default", "lifecycle_default", "findClosestReflex", "element", "schema_default", "findClosestFrame", "assignElementValueToPayload", "payload", "memo", "option", "buildAttributePayload", "attr", "value", "elements_default", "frameSources", "event", "frame", "fetchOptions", "elements_default", "events", "eventListener", "register", "eventName", "tagNames", "isRegistered", "tagName", "delegates_default", "fn", "invokeReflex", "form", "payload", "elements_default", "input", "form_default", "build", "urlString", "payload", "a", "url", "urls_default", "invokeReflex", "frame", "payload", "src", "urls_default", "frame_default", "aborted", "event", "xhr", "lifecycle_default", "errored", "loaded", "content", "hijacked", "head", "tail", "headIndex", "tailIndex", "streams", "html", "invokeReflex", "payload", "src", "urls_default", "elements_default", "ex", "message", "window_default", "src", "element", "frame", "find", "elements_default", "targetId", "form_default", "frame_default", "window_default", "drivers_default", "currentLevel", "logLevels", "lifecycle_default", "name", "event", "logger_default", "value", "v4", "c", "uuids_default", "invokeReflex", "event", "element", "payload", "elements_default", "delegates_default", "driver", "drivers_default", "uuids_default", "activity_default", "lifecycle_default", "error", "javascript_default", "schema_default", "logger_default"]
|
7
7
|
}
|
@@ -4,130 +4,34 @@ module TurboReflex::Controller
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
included do
|
7
|
-
before_action :
|
8
|
-
after_action :
|
9
|
-
after_action :assign_turbo_reflex_token
|
7
|
+
before_action :run_turbo_reflex
|
8
|
+
after_action :append_turbo_reflex_to_response
|
10
9
|
helper_method :turbo_reflex_meta_tag, :turbo_reflex_performed?, :turbo_reflex_requested?
|
11
|
-
# helper TurboReflex::TurboReflexHelper # only required if we isolate_namespace
|
12
10
|
end
|
13
11
|
|
14
|
-
def
|
15
|
-
|
16
|
-
options = {id: "turbo-reflex-token", name: "turbo-reflex-token", content: masked_token}
|
17
|
-
view_context.tag("meta", options).html_safe
|
12
|
+
def turbo_reflex_runner
|
13
|
+
@turbo_reflex_runner ||= TurboReflex::Runner.new(self)
|
18
14
|
end
|
19
15
|
|
20
|
-
def
|
21
|
-
|
22
|
-
@turbo_reflex_params ||= begin
|
23
|
-
payload = JSON.parse(params[:turbo_reflex]).deep_transform_keys(&:underscore)
|
24
|
-
ActionController::Parameters.new(payload).permit!
|
25
|
-
end
|
16
|
+
def turbo_reflex_meta_tag
|
17
|
+
turbo_reflex_runner.meta_tag
|
26
18
|
end
|
27
19
|
|
28
20
|
def turbo_reflex_requested?
|
29
|
-
|
30
|
-
return false unless turbo_reflex_params.present?
|
31
|
-
true
|
32
|
-
end
|
33
|
-
|
34
|
-
def turbo_reflex_element
|
35
|
-
return nil if turbo_reflex_params.blank?
|
36
|
-
@turbo_reflex_element ||= Struct
|
37
|
-
.new(*turbo_reflex_params[:element].keys.map { |key| key.to_s.parameterize.underscore.to_sym })
|
38
|
-
.new(*turbo_reflex_params[:element].values)
|
39
|
-
end
|
40
|
-
|
41
|
-
def turbo_reflex_name
|
42
|
-
return nil unless turbo_reflex_requested?
|
43
|
-
turbo_reflex_element.data_turbo_reflex
|
44
|
-
end
|
45
|
-
|
46
|
-
def turbo_reflex_class_name
|
47
|
-
return nil unless turbo_reflex_requested?
|
48
|
-
turbo_reflex_name.split("#").first
|
49
|
-
end
|
50
|
-
|
51
|
-
def turbo_reflex_method_name
|
52
|
-
return nil unless turbo_reflex_requested?
|
53
|
-
turbo_reflex_name.split("#").last
|
54
|
-
end
|
55
|
-
|
56
|
-
def turbo_reflex_class
|
57
|
-
@turbo_reflex_class ||= turbo_reflex_class_name&.safe_constantize
|
58
|
-
end
|
59
|
-
|
60
|
-
def turbo_reflex_instance
|
61
|
-
@turbo_reflex_instance ||= turbo_reflex_class&.new(self)
|
62
|
-
end
|
63
|
-
|
64
|
-
def turbo_reflex_valid?
|
65
|
-
return false if request.get? && client_turbo_reflex_token.blank?
|
66
|
-
return false unless valid_turbo_reflex_token?
|
67
|
-
return false unless turbo_reflex_instance.is_a?(TurboReflex::Base)
|
68
|
-
turbo_reflex_instance.respond_to? turbo_reflex_method_name
|
21
|
+
turbo_reflex_runner.reflex_requested?
|
69
22
|
end
|
70
23
|
|
71
24
|
def turbo_reflex_performed?
|
72
|
-
|
25
|
+
turbo_reflex_runner.reflex_performed?
|
73
26
|
end
|
74
27
|
|
75
28
|
protected
|
76
29
|
|
77
|
-
def
|
78
|
-
|
79
|
-
turbo_reflex_instance.public_send turbo_reflex_method_name
|
80
|
-
end
|
81
|
-
|
82
|
-
def append_turbo_reflex_turbo_streams
|
83
|
-
return unless turbo_reflex_performed?
|
84
|
-
return unless turbo_reflex_instance&.turbo_streams.present?
|
85
|
-
append_turbo_reflex_content turbo_reflex_instance.turbo_streams.map(&:to_s).join
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
def turbo_reflex_message_verifier
|
91
|
-
ActiveSupport::MessageVerifier.new session.id.to_s, digest: "SHA256"
|
92
|
-
end
|
93
|
-
|
94
|
-
def client_turbo_reflex_token
|
95
|
-
(request.headers["Turbo-Reflex"] || turbo_reflex_params[:token]).to_s
|
96
|
-
end
|
97
|
-
|
98
|
-
def new_turbo_reflex_token
|
99
|
-
@new_turbo_reflex_token ||= SecureRandom.urlsafe_base64(32)
|
100
|
-
end
|
101
|
-
|
102
|
-
def current_turbo_reflex_token
|
103
|
-
session[:turbo_reflex_token]
|
104
|
-
end
|
105
|
-
|
106
|
-
def valid_turbo_reflex_token?
|
107
|
-
return false unless turbo_reflex_message_verifier.valid_message?(client_turbo_reflex_token)
|
108
|
-
unmasked_token = turbo_reflex_message_verifier.verify(client_turbo_reflex_token)
|
109
|
-
unmasked_token == current_turbo_reflex_token
|
110
|
-
end
|
111
|
-
|
112
|
-
def assign_turbo_reflex_token
|
113
|
-
return unless turbo_reflex_requested? || client_turbo_reflex_token.blank?
|
114
|
-
session[:turbo_reflex_token] = new_turbo_reflex_token
|
115
|
-
append_turbo_reflex_content turbo_stream.replace("turbo-reflex-token", turbo_reflex_meta_tag)
|
116
|
-
end
|
117
|
-
|
118
|
-
def turbo_reflex_response_type
|
119
|
-
body = response.body.to_s.strip
|
120
|
-
return :stream if body.ends_with?("</turbo-stream>")
|
121
|
-
return :frame if body.ends_with?("</turbo-frame>")
|
122
|
-
:default
|
30
|
+
def run_turbo_reflex
|
31
|
+
turbo_reflex_runner.run
|
123
32
|
end
|
124
33
|
|
125
|
-
def
|
126
|
-
|
127
|
-
case turbo_reflex_response_type
|
128
|
-
when :stream then response.body << sanitized_content
|
129
|
-
when :frame then response.body.sub!("</turbo-frame>", "#{sanitized_content}</turbo-frame>")
|
130
|
-
when :default then response.body.sub!("</body>", "#{sanitized_content}</body>")
|
131
|
-
end
|
34
|
+
def append_turbo_reflex_to_response
|
35
|
+
turbo_reflex_runner.append_to_response
|
132
36
|
end
|
133
37
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
const active = {}
|
2
|
+
|
3
|
+
function add (payload) {
|
4
|
+
active[payload.id] = payload
|
5
|
+
}
|
6
|
+
|
7
|
+
function remove (id) {
|
8
|
+
delete active[id]
|
9
|
+
}
|
10
|
+
|
11
|
+
export default {
|
12
|
+
add,
|
13
|
+
remove,
|
14
|
+
get reflexes () {
|
15
|
+
return [...Object.values(active)]
|
16
|
+
},
|
17
|
+
get length () {
|
18
|
+
return Object.keys(active).length
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
const events = {}
|
2
|
+
let eventListener
|
3
|
+
|
4
|
+
function register (eventName, tagNames) {
|
5
|
+
events[eventName] = tagNames
|
6
|
+
document.addEventListener(eventName, eventListener, true)
|
7
|
+
}
|
8
|
+
|
9
|
+
function isRegistered (eventName, tagName) {
|
10
|
+
tagName = tagName.toLowerCase()
|
11
|
+
return (
|
12
|
+
events[eventName].includes(tagName) ||
|
13
|
+
(!Object.values(events)
|
14
|
+
.flat()
|
15
|
+
.includes(tagName) &&
|
16
|
+
events[eventName].includes('*'))
|
17
|
+
)
|
18
|
+
}
|
19
|
+
|
20
|
+
export default {
|
21
|
+
events,
|
22
|
+
register,
|
23
|
+
isRegistered,
|
24
|
+
set handler (fn) {
|
25
|
+
eventListener = fn
|
26
|
+
}
|
27
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import elements from '../elements'
|
2
|
+
|
3
|
+
function invokeReflex (form, payload = {}) {
|
4
|
+
payload.token = elements.metaElementToken
|
5
|
+
const input = document.createElement('input')
|
6
|
+
input.type = 'hidden'
|
7
|
+
input.name = 'turbo_reflex'
|
8
|
+
input.value = JSON.stringify(payload)
|
9
|
+
form.appendChild(input)
|
10
|
+
}
|
11
|
+
|
12
|
+
export default { invokeReflex }
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import urls from '../urls'
|
2
|
+
import elements from '../elements'
|
3
|
+
|
4
|
+
function invokeReflex (frame, payload) {
|
5
|
+
const src = payload.src
|
6
|
+
payload = { ...payload }
|
7
|
+
delete payload.src
|
8
|
+
frame.src = urls.build(src, payload)
|
9
|
+
}
|
10
|
+
|
11
|
+
export default { invokeReflex }
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import elements from '../elements'
|
2
|
+
import formDriver from './form'
|
3
|
+
import frameDriver from './frame'
|
4
|
+
import windowDriver from './window'
|
5
|
+
|
6
|
+
function src (element, frame) {
|
7
|
+
frame = frame || { dataset: {} }
|
8
|
+
return (
|
9
|
+
element.href || frame.src || frame.dataset.turboReflexSrc || location.href
|
10
|
+
)
|
11
|
+
}
|
12
|
+
|
13
|
+
function find (element) {
|
14
|
+
let frame = elements.findClosestFrame(element)
|
15
|
+
const targetId = element.dataset.turboFrame
|
16
|
+
|
17
|
+
if (element.tagName.toLowerCase() === 'form')
|
18
|
+
return {
|
19
|
+
name: 'form',
|
20
|
+
reason: 'Element is a form.',
|
21
|
+
frame,
|
22
|
+
src: element.action,
|
23
|
+
invokeReflex: formDriver.invokeReflex
|
24
|
+
}
|
25
|
+
|
26
|
+
// element targets a frame that is not _self
|
27
|
+
if (targetId && targetId !== '_self') {
|
28
|
+
frame = document.getElementById(targetId)
|
29
|
+
return {
|
30
|
+
name: 'frame',
|
31
|
+
reason: 'element targets a frame that is not _self',
|
32
|
+
frame,
|
33
|
+
src: src(element, frame),
|
34
|
+
invokeReflex: frameDriver.invokeReflex
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
// element does NOT target a frame or targets _self and is contained by a frame
|
39
|
+
if ((!targetId || targetId === '_self') && frame)
|
40
|
+
return {
|
41
|
+
name: 'frame',
|
42
|
+
reason:
|
43
|
+
'element does NOT target a frame or targets _self and is contained by a frame',
|
44
|
+
frame,
|
45
|
+
src: src(element, frame),
|
46
|
+
invokeReflex: frameDriver.invokeReflex
|
47
|
+
}
|
48
|
+
|
49
|
+
// element matches one or more of the following conditions
|
50
|
+
// - targets _top
|
51
|
+
// - does NOT target a frame
|
52
|
+
// - is NOT contained by a frame
|
53
|
+
return {
|
54
|
+
name: 'window',
|
55
|
+
reason:
|
56
|
+
'element matches one or more of the following conditions (targets _top, does NOT target a frame, is NOT contained by a frame)',
|
57
|
+
frame: null,
|
58
|
+
src: src(element),
|
59
|
+
invokeReflex: windowDriver.invokeReflex
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
export default { find }
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import elements from '../elements'
|
2
|
+
import lifecycle from '../lifecycle'
|
3
|
+
import urls from '../urls'
|
4
|
+
|
5
|
+
function aborted (event) {
|
6
|
+
const xhr = event.target
|
7
|
+
lifecycle.dispatch(lifecycle.events.abort, window, { xhr, ...event.detail })
|
8
|
+
}
|
9
|
+
|
10
|
+
function errored (event) {
|
11
|
+
const xhr = event.target
|
12
|
+
lifecycle.dispatch(
|
13
|
+
lifecycle.events.clientError,
|
14
|
+
window,
|
15
|
+
{
|
16
|
+
xhr,
|
17
|
+
...event.detail,
|
18
|
+
error: `Server returned a ${xhr.status} status code! TurboReflex requires 2XX status codes. Server message: ${xhr.statusText}`
|
19
|
+
},
|
20
|
+
true
|
21
|
+
)
|
22
|
+
}
|
23
|
+
|
24
|
+
function loaded (event) {
|
25
|
+
const xhr = event.target
|
26
|
+
const content = xhr.responseText
|
27
|
+
const hijacked = xhr.getResponseHeader('TurboReflex-Hijacked') === 'true'
|
28
|
+
if (xhr.status < 200 || xhr.status > 299) errored(event)
|
29
|
+
|
30
|
+
if (hijacked) {
|
31
|
+
const head = '<turbo-stream'
|
32
|
+
const tail = '</turbo-stream>'
|
33
|
+
const headIndex = content.indexOf(head)
|
34
|
+
const tailIndex = content.lastIndexOf(tail)
|
35
|
+
if (headIndex >= 0 && tailIndex >= 0) {
|
36
|
+
const streams = content.slice(headIndex, tailIndex + tail.length)
|
37
|
+
document.body.insertAdjacentHTML('beforeend', streams)
|
38
|
+
}
|
39
|
+
} else {
|
40
|
+
const head = '<html'
|
41
|
+
const tail = '</html'
|
42
|
+
const headIndex = content.indexOf(head)
|
43
|
+
const tailIndex = content.lastIndexOf(tail)
|
44
|
+
if (headIndex >= 0 && tailIndex >= 0) {
|
45
|
+
const html = content.slice(content.indexOf('>', headIndex) + 1, tailIndex)
|
46
|
+
document.documentElement.innerHTML = html
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
function invokeReflex (payload) {
|
52
|
+
const src = payload.src
|
53
|
+
payload = { ...payload }
|
54
|
+
delete payload.src
|
55
|
+
|
56
|
+
try {
|
57
|
+
const xhr = new XMLHttpRequest()
|
58
|
+
xhr.open('GET', urls.build(src, payload), true)
|
59
|
+
xhr.setRequestHeader('TurboReflex-Token', elements.metaElementToken)
|
60
|
+
xhr.addEventListener('abort', aborted)
|
61
|
+
xhr.addEventListener('error', errored)
|
62
|
+
xhr.addEventListener('load', loaded)
|
63
|
+
xhr.send()
|
64
|
+
} catch (ex) {
|
65
|
+
const message = `Unexpected error sending HTTP request! ${ex.message}`
|
66
|
+
errored(ex, { detail: { message } })
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
export default { invokeReflex }
|