zeroclick-sellers 0.6.0 → 0.7.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.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 27bd4f5d8f10f026b12a89ee8681fef45ffbf58129df180f40903a55ec9743d3
|
|
4
|
+
data.tar.gz: 1ea73b94f08215d28c2974ac4937bdc423aa2f52a66c688a5746656ad837c8f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 73ef4fc05515a9d11212ca90c7df1c9f97c157bdc2628c4b1bb81738849552fbd7aca334986fc60f1c4d0d5cb6ca311b7663c453c1633a756950e82af4f06b42
|
|
7
|
+
data.tar.gz: 00dbf768601b7808759518ad6a507e0d238f4c6c65aecdd91ce5106488841a19538f5ea49ea5df93e776a049330f32f834d7d9216dd0ce58b212bf6da24e1748
|
|
@@ -191,7 +191,9 @@ module ZeroClick
|
|
|
191
191
|
# +seller+ is the seller's public id; +path+ must be an absolute path.
|
|
192
192
|
def report_page_view(seller:, path:, status: nil, duration_ms: nil, representation: nil,
|
|
193
193
|
journey_id: nil, captured_at: nil, user_agent: nil, accept: nil,
|
|
194
|
-
client_ip: nil, country: nil, referrer_host: nil
|
|
194
|
+
client_ip: nil, country: nil, referrer_host: nil, host: nil,
|
|
195
|
+
eval_tag: nil, eval_signature: nil, eval_signature_input: nil,
|
|
196
|
+
eval_signature_agent: nil, source: nil, source_signature: nil)
|
|
195
197
|
if @page_views_key.nil? || @page_views_key.empty?
|
|
196
198
|
raise Error.new("page_views_not_configured", operation: "report_page_view")
|
|
197
199
|
end
|
|
@@ -200,7 +202,10 @@ module ZeroClick
|
|
|
200
202
|
seller: seller, path: path, status: status, duration_ms: duration_ms,
|
|
201
203
|
representation: representation, journey_id: journey_id, captured_at: captured_at,
|
|
202
204
|
user_agent: user_agent, accept: accept, client_ip: client_ip,
|
|
203
|
-
country: country, referrer_host: referrer_host
|
|
205
|
+
country: country, referrer_host: referrer_host, host: host,
|
|
206
|
+
eval_tag: eval_tag, eval_signature: eval_signature,
|
|
207
|
+
eval_signature_input: eval_signature_input, eval_signature_agent: eval_signature_agent,
|
|
208
|
+
source: source, source_signature: source_signature
|
|
204
209
|
)
|
|
205
210
|
status_code, body = @http_post.call(
|
|
206
211
|
base_url: @api_base_url,
|
|
@@ -22,6 +22,11 @@ module ZeroClick
|
|
|
22
22
|
# - the app runs FIRST, so the beacon carries the real status and
|
|
23
23
|
# duration; the visitor's user-agent, accept, and connecting IP are
|
|
24
24
|
# forwarded for server-side classification;
|
|
25
|
+
# - a ZeroClick agent-eval request's tag and signature are forwarded
|
|
26
|
+
# with the page's host, so the API can keep ZeroClick's own eval
|
|
27
|
+
# traffic out of the seller's numbers; a browser-based run, which
|
|
28
|
+
# cannot sign, is told by the +src+ marker on its start URL,
|
|
29
|
+
# forwarded with the host and the signature beside it;
|
|
25
30
|
# - ANY beacon failure — missing key, network error, non-204, timeout,
|
|
26
31
|
# a bad resolve_url or clock — is handed to +on_error+ and swallowed.
|
|
27
32
|
# The middleware must never break or alter the customer's site.
|
|
@@ -43,6 +48,13 @@ module ZeroClick
|
|
|
43
48
|
# duration. Injectable so a test can make both deterministic.
|
|
44
49
|
DEFAULT_CLOCK = -> { Process.clock_gettime(Process::CLOCK_REALTIME, :float_millisecond) }
|
|
45
50
|
|
|
51
|
+
# ZeroClick's own start marker: the one query value the middleware
|
|
52
|
+
# forwards. A browser-based eval run's requests leave from the agent
|
|
53
|
+
# vendor's cloud, out of reach of the signed tag, so agent-eval marks
|
|
54
|
+
# its start URL as ?src=ze-<experiment>-<n> instead, and the API keeps
|
|
55
|
+
# the whole visit out of the seller's numbers from it.
|
|
56
|
+
EVAL_SOURCE_PREFIX = "ze-"
|
|
57
|
+
|
|
46
58
|
# +seller+ may be a client, a callable returning one, or omitted to
|
|
47
59
|
# resolve the process-wide ZeroClick::Sellers.seller on the first
|
|
48
60
|
# request (the Rails path — see Base#initialize for why).
|
|
@@ -51,7 +63,8 @@ module ZeroClick
|
|
|
51
63
|
# by the wire contract). +clock+ returns epoch milliseconds.
|
|
52
64
|
# +resolve_url+ is a callable taking the Rack env and returning the
|
|
53
65
|
# public URL of the request, for servers behind a proxy that rewrites
|
|
54
|
-
# the host the runtime sees
|
|
66
|
+
# the host the runtime sees; its path is reported and, with an eval
|
|
67
|
+
# tag, its host. +on_error+ observes the fire-and-forget
|
|
55
68
|
# path; it is never re-raised.
|
|
56
69
|
def initialize(app, seller_id:, seller: nil, clock: nil, resolve_url: nil, on_error: nil)
|
|
57
70
|
@app = app
|
|
@@ -86,7 +99,8 @@ module ZeroClick
|
|
|
86
99
|
return unless env["REQUEST_METHOD"] == "GET"
|
|
87
100
|
|
|
88
101
|
url = @resolve_url ? @resolve_url.call(env) : Agentify.public_url(env)
|
|
89
|
-
|
|
102
|
+
uri = URI.parse(url.to_s)
|
|
103
|
+
path = uri.path
|
|
90
104
|
path = "/" if path.nil? || path.empty?
|
|
91
105
|
return if ASSET_PATH.match?(path)
|
|
92
106
|
|
|
@@ -102,10 +116,67 @@ module ZeroClick
|
|
|
102
116
|
client_ip: client_ip_of(env),
|
|
103
117
|
country: env["HTTP_CF_IPCOUNTRY"],
|
|
104
118
|
referrer_host: referrer_host_of(env),
|
|
105
|
-
journey_id: journey_id_of(headers)
|
|
119
|
+
journey_id: journey_id_of(headers),
|
|
120
|
+
**start_marker_of(uri),
|
|
121
|
+
**eval_run_of(env, uri)
|
|
106
122
|
)
|
|
107
123
|
end
|
|
108
124
|
|
|
125
|
+
# ZeroClick's own agent-eval run, when the visitor carried its tag: the
|
|
126
|
+
# tag, the signature agent-eval puts on a seller's site (under
|
|
127
|
+
# ZeroClick-only header names, never Web Bot Auth's) and the public
|
|
128
|
+
# host it was signed for. Forwarded as sent; the API labels the view
|
|
129
|
+
# only when the signature verifies, so a forged tag labels nothing. A
|
|
130
|
+
# value outside what the beacon accepts (empty, or longer than
|
|
131
|
+
# agent-eval ever sends) is left out rather than sent, so a visitor's
|
|
132
|
+
# odd header can never cost the page view itself.
|
|
133
|
+
def eval_run_of(env, uri)
|
|
134
|
+
tag = capped(env["HTTP_X_ZEROCLICK_EVAL"], 512)
|
|
135
|
+
return {} if tag.nil?
|
|
136
|
+
|
|
137
|
+
{
|
|
138
|
+
host: capped(host_of(uri), 300),
|
|
139
|
+
eval_tag: tag,
|
|
140
|
+
eval_signature: capped(env["HTTP_X_ZEROCLICK_EVAL_SIGNATURE"], 1_024),
|
|
141
|
+
eval_signature_input: capped(env["HTTP_X_ZEROCLICK_EVAL_SIGNATURE_INPUT"], 1_024),
|
|
142
|
+
eval_signature_agent: capped(env["HTTP_X_ZEROCLICK_EVAL_SIGNATURE_AGENT"], 512)
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def capped(value, max)
|
|
147
|
+
value && !value.empty? && value.length <= max ? value : nil
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# The public host the page was requested on, as the JS SDK's URL.host
|
|
151
|
+
# reads it: the host and any non-default port.
|
|
152
|
+
def host_of(uri)
|
|
153
|
+
return nil if uri.host.nil? || uri.host.empty?
|
|
154
|
+
|
|
155
|
+
uri.port && uri.port != uri.default_port ? "#{uri.host}:#{uri.port}" : uri.host
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# ZeroClick's own start marker, keyed like report_page_view: empty
|
|
159
|
+
# without one. The +src+ query value when it is ours, the page's host
|
|
160
|
+
# and the +signature+ agent-eval puts beside the marker, as sent: the
|
|
161
|
+
# only query values the beacon carries. Any other +src+ stays on the
|
|
162
|
+
# site, and a marker or signature past what the beacon accepts (or a
|
|
163
|
+
# query that does not decode) is left out rather than sent.
|
|
164
|
+
def start_marker_of(uri)
|
|
165
|
+
return {} if uri.query.nil? || uri.query.empty?
|
|
166
|
+
|
|
167
|
+
values = URI.decode_www_form(uri.query)
|
|
168
|
+
src = values.assoc("src")&.last
|
|
169
|
+
return {} unless src&.start_with?(EVAL_SOURCE_PREFIX) && src.length <= 32
|
|
170
|
+
|
|
171
|
+
{
|
|
172
|
+
host: capped(host_of(uri), 300),
|
|
173
|
+
source: src,
|
|
174
|
+
source_signature: capped(values.assoc("signature")&.last, 256)
|
|
175
|
+
}
|
|
176
|
+
rescue ArgumentError
|
|
177
|
+
{}
|
|
178
|
+
end
|
|
179
|
+
|
|
109
180
|
# What the app served for this page-content request, read off the
|
|
110
181
|
# response content-type so it reflects the real representation
|
|
111
182
|
# regardless of what produced it.
|
|
@@ -146,11 +217,7 @@ module ZeroClick
|
|
|
146
217
|
rescue URI::InvalidURIError
|
|
147
218
|
nil
|
|
148
219
|
end
|
|
149
|
-
|
|
150
|
-
return nil if host.nil? || host.empty?
|
|
151
|
-
|
|
152
|
-
# Host and any non-default port only, matching the JS SDK's URL.host.
|
|
153
|
-
uri.port && uri.port != uri.default_port ? "#{host}:#{uri.port}" : host
|
|
220
|
+
uri && host_of(uri)
|
|
154
221
|
end
|
|
155
222
|
|
|
156
223
|
# Case-insensitive lookup over the app's response headers.
|
|
@@ -27,9 +27,15 @@ module ZeroClick
|
|
|
27
27
|
# Build the wire body: camelCase string keys, every absent optional key
|
|
28
28
|
# omitted. +seller+ is the seller's public id and +path+ must be an
|
|
29
29
|
# absolute path, both rejected before any request leaves the process.
|
|
30
|
+
# +host+ and the +eval_*+ values describe ZeroClick's own agent-eval run
|
|
31
|
+
# when the visitor carried its tag: the public host the run signed for
|
|
32
|
+
# and the tag with its signature, as sent. The API labels the view as
|
|
33
|
+
# eval traffic only when the signature verifies.
|
|
30
34
|
def build_payload(seller:, path:, status: nil, duration_ms: nil, representation: nil,
|
|
31
35
|
journey_id: nil, captured_at: nil, user_agent: nil, accept: nil,
|
|
32
|
-
client_ip: nil, country: nil, referrer_host: nil
|
|
36
|
+
client_ip: nil, country: nil, referrer_host: nil, host: nil,
|
|
37
|
+
eval_tag: nil, eval_signature: nil, eval_signature_input: nil,
|
|
38
|
+
eval_signature_agent: nil, source: nil, source_signature: nil)
|
|
33
39
|
Sellers.require_slug!(seller, "seller", "report_page_view")
|
|
34
40
|
unless path.is_a?(String) && path.start_with?("/")
|
|
35
41
|
raise Error.new("malformed_input", operation: "report_page_view",
|
|
@@ -51,6 +57,13 @@ module ZeroClick
|
|
|
51
57
|
payload["clientIp"] = client_ip unless client_ip.nil?
|
|
52
58
|
payload["country"] = country unless country.nil?
|
|
53
59
|
payload["referrerHost"] = referrer_host unless referrer_host.nil?
|
|
60
|
+
payload["host"] = host unless host.nil?
|
|
61
|
+
payload["evalTag"] = eval_tag unless eval_tag.nil?
|
|
62
|
+
payload["evalSignature"] = eval_signature unless eval_signature.nil?
|
|
63
|
+
payload["evalSignatureInput"] = eval_signature_input unless eval_signature_input.nil?
|
|
64
|
+
payload["evalSignatureAgent"] = eval_signature_agent unless eval_signature_agent.nil?
|
|
65
|
+
payload["source"] = source unless source.nil?
|
|
66
|
+
payload["sourceSignature"] = source_signature unless source_signature.nil?
|
|
54
67
|
payload
|
|
55
68
|
end
|
|
56
69
|
|