Class: ZeroClick::Sellers::Middleware::PageViews
- Inherits:
-
Object
- Object
- ZeroClick::Sellers::Middleware::PageViews
- Defined in:
- lib/zeroclick/sellers/middleware/page_views.rb
Overview
Rack middleware that emits a fire-and-forget page-view beacon for every page-content request, without changing the response. Mount it in front of the marketing site:
use ZeroClick::Sellers::Middleware::PageViews, seller: SELLER, seller_id: "sel_public"
The contract, shared by every ZeroClick seller SDK:
- only GET requests to non-asset paths are reported (an asset is a
final path segment ending in an extension — .css, .ico, .png);
- the app runs FIRST, so the beacon carries the real status and
duration; the visitor's user-agent, accept, and connecting IP are
forwarded for server-side classification;
- ANY beacon failure — missing key, network error, non-204, timeout,
a bad resolve_url or clock — is handed to +on_error+ and swallowed.
The middleware must never break or alter the customer's site.
Compose it as the OUTER middleware around Agentify — mount PageViews before Agentify — so the agent-markdown variant's re-exposed x-zcj-id is visible on the response and stitches the view onto the same journey.
Like Agentify it never reads the request body, so no buffering ceiling applies.
Constant Summary collapse
- ASSET_PATH =
A page-content request has no asset extension on its final path segment. This skips /_astro/app.css, /favicon.ico, /logo.png and the like while still reporting /pricing, /blog/post, and / for both browsers and agents.
/\.[a-z0-9]+$/i- DEFAULT_CLOCK =
Wall clock in epoch milliseconds, for the observation time and the duration. Injectable so a test can make both deterministic.
-> { Process.clock_gettime(Process::CLOCK_REALTIME, :float_millisecond) }
Instance Method Summary collapse
- #call(env) ⇒ Object
-
#initialize(app, seller_id:, seller: nil, clock: nil, resolve_url: nil, on_error: nil) ⇒ PageViews
constructor
sellermay be a client, a callable returning one, or omitted to resolve the process-wide ZeroClick::Sellers.seller on the first request (the Rails path — see Base#initialize for why).
Constructor Details
#initialize(app, seller_id:, seller: nil, clock: nil, resolve_url: nil, on_error: nil) ⇒ PageViews
seller may be a client, a callable returning one, or omitted to
resolve the process-wide ZeroClick::Sellers.seller on the first
request (the Rails path — see Base#initialize for why).
seller_id is the seller's public id, sent in every beacon (required
by the wire contract). clock returns epoch milliseconds.
resolve_url is a callable taking the Rack env and returning the
public URL of the request, for servers behind a proxy that rewrites
the host the runtime sees. on_error observes the fire-and-forget
path; it is never re-raised.
56 57 58 59 60 61 62 63 |
# File 'lib/zeroclick/sellers/middleware/page_views.rb', line 56 def initialize(app, seller_id:, seller: nil, clock: nil, resolve_url: nil, on_error: nil) @app = app @seller_source = seller @seller_id = seller_id @clock = clock || DEFAULT_CLOCK @resolve_url = resolve_url @on_error = on_error end |
Instance Method Details
#call(env) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/zeroclick/sellers/middleware/page_views.rb', line 65 def call(env) started_at = @clock.call status, headers, body = @app.call(env) # The whole observation path is wrapped: a bad resolve_url, an # unparseable URL, a misbehaving clock, or the beacon delivery itself # must never break the page. Every failure — synchronous or from the # blocking beacon — is routed to on_error, and the app's response is # returned untouched regardless. begin observe(env, started_at: started_at, status: status, headers: headers) rescue StandardError => e @on_error&.call(e, env) end [status, headers, body] end |