Class: Dommy::FetchFn
- Inherits:
-
Object
- Object
- Dommy::FetchFn
- Defined in:
- lib/dommy/fetch.rb
Overview
fetch polyfill. No real network — instead resolves a response
entry and synthesizes a Response from it. Entries come from, in
order:
1. `window.globals["__fetch_handler__"]` — a callable
`call(url, init) -> entry-or-nil`. This is the seam host
environments use to serve real requests (e.g. dommy-rack's
NetworkBridge routes same-origin URLs to the Rack app).
Returning nil falls through to the stub maps.
2. `JS.global[:__fetchy_stub__]` (a Hash{url => entry})
installed by the test. Mirrors the same fixture protocol that
`test_fetchy.rb`'s JavaScript installer uses, so tests don't
need a JS engine to drive the stub.
Each entry supports:
"status" / "statusText" / "body" / "contentType" /
"headers" (Hash) / "url" / "redirected" / "delay" (ms)
plus AbortController signal propagation when init[:signal] is
passed.
Instance Method Summary collapse
-
#__js_call__(_method, args) ⇒ Object
JS calls
fetch(url, init)end up here via either Window-level__js_call__("fetch", ...)or as a callable handle. -
#initialize(window) ⇒ FetchFn
constructor
A new instance of FetchFn.
Constructor Details
#initialize(window) ⇒ FetchFn
Returns a new instance of FetchFn.
28 29 30 |
# File 'lib/dommy/fetch.rb', line 28 def initialize(window) @window = window end |
Instance Method Details
#__js_call__(_method, args) ⇒ Object
JS calls fetch(url, init) end up here via either Window-level
__js_call__("fetch", ...) or as a callable handle. Both routes
delegate to call(args) so behavior is identical.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/dommy/fetch.rb', line 35 def __js_call__(_method, args) # Per Fetch, the request URL is resolved against the document base URL up # front; the handler, stub maps, and response.url all see the absolute # URL (no per-handler resolution). url = @window.__internal_resolve_url__(args[0].to_s) init = normalize_init(args[1] || {}) # A cross-origin request always carries an Origin header (even for GET, # which normalize_init omits it for same-origin GETs). if cross_origin?(url) && !header?(init["headers"], "origin") init["headers"]["Origin"] = request_origin end # `js_eval`'s JS installer increments these globals; mirror so # specs that probe `__fetch_count__` / `__last_url__` / etc. # observe the same state shape they'd see from a real injector. @window.globals["__fetch_count__"] = (@window.globals["__fetch_count__"] || 0).to_i + 1 @window.globals["__last_url__"] = url @window.globals["__last_init__"] = init @window.globals["__last_body__"] = init["body"] if init.is_a?(Hash) promise = PromiseValue.new(@window) # `mode: "same-origin"` forbids a cross-origin request — it is a network # error before any fetch happens. if (init["mode"] || "cors").to_s == "same-origin" && cross_origin?(url) deliver_task { promise.reject(fetch_type_error) } return promise end # A non-simple cross-origin cors request is preceded by a CORS preflight # (OPTIONS); a failed preflight is a network error before the real request. if needs_preflight?(url, init) && !preflight_ok?(url, init) deliver_task { promise.reject(fetch_type_error) } return promise end result = resolve_entry(url, init) # A handler may answer asynchronously (live network off-thread): it returns # a deferred whose response arrives later and is applied on the page thread # (via the scheduler inbox). The sync path (stubs / cache / data:) resolves # the promise inline, exactly as before. if result.respond_to?(:on_complete) result.on_complete { |entry| fulfill_from_entry(promise, entry, url, init) } else settle_with_redirects(promise, url, init, result) end promise end |