sentiero 1.0.0.alpha1 → 1.0.0.beta3
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 +4 -4
- data/README.md +15 -13
- data/lib/sentiero/analytics/analyzer.rb +16 -4
- data/lib/sentiero/analytics/browser_event_discovery.rb +2 -2
- data/lib/sentiero/analytics/conversion_analyzer.rb +1 -1
- data/lib/sentiero/analytics/engagement_analyzer.rb +1 -1
- data/lib/sentiero/analytics/error_discovery.rb +9 -2
- data/lib/sentiero/analytics/exporter.rb +1 -1
- data/lib/sentiero/analytics/frustration_analyzer.rb +1 -1
- data/lib/sentiero/analytics/funnel_analyzer.rb +2 -2
- data/lib/sentiero/analytics/heatmap_analyzer.rb +3 -3
- data/lib/sentiero/analytics/page_report_analyzer.rb +2 -2
- data/lib/sentiero/analytics/scroll_depth_analyzer.rb +1 -1
- data/lib/sentiero/analytics/segmenter.rb +16 -2
- data/lib/sentiero/analytics/stats_aggregator/result_builder.rb +2 -0
- data/lib/sentiero/analytics/stats_aggregator.rb +45 -6
- data/lib/sentiero/analytics/web_vitals_analyzer.rb +2 -2
- data/lib/sentiero/configuration.rb +29 -0
- data/lib/sentiero/fingerprint/config.rb +40 -0
- data/lib/sentiero/fingerprint.rb +45 -11
- data/lib/sentiero/geo.rb +86 -0
- data/lib/sentiero/reporter.rb +2 -1
- data/lib/sentiero/store/session_store.rb +62 -3
- data/lib/sentiero/store.rb +7 -0
- data/lib/sentiero/stores/file.rb +5 -1
- data/lib/sentiero/stores/memory.rb +15 -3
- data/lib/sentiero/stores/redis.rb +17 -5
- data/lib/sentiero/stores/sqlite/payload_codec.rb +42 -0
- data/lib/sentiero/stores/sqlite/schema.rb +72 -6
- data/lib/sentiero/stores/sqlite.rb +216 -101
- data/lib/sentiero/version.rb +1 -1
- data/lib/sentiero/web/analytics_app.rb +10 -0
- data/lib/sentiero/web/assets/dashboard-YINQE5ZH.js +3 -0
- data/lib/sentiero/web/assets/manifest.json +4 -4
- data/lib/sentiero/web/assets/recorder-W2RCU5VM.js +78 -0
- data/lib/sentiero/web/assets/rrweb-player-d522d74c.js +201 -0
- data/lib/sentiero/web/assets/{style-d71e72fd.css → style-80e536e6.css} +1 -1
- data/lib/sentiero/web/body_reader.rb +21 -10
- data/lib/sentiero/web/dashboard_app.rb +77 -5
- data/lib/sentiero/web/errors_app.rb +14 -1
- data/lib/sentiero/web/events_app.rb +6 -1
- data/lib/sentiero/web/ingest_app.rb +1 -1
- data/lib/sentiero/web/templates/client_error_show.html.erb +6 -1
- data/lib/sentiero/web/templates/dashboard.html.erb +7 -0
- data/lib/sentiero/web/templates/maintenance.html.erb +50 -0
- data/lib/sentiero/web/templates/segments.html.erb +9 -0
- data/lib/sentiero/web/templates/sessions_index.html.erb +12 -4
- data/lib/sentiero/web/views/analytics_index_view.rb +7 -1
- data/lib/sentiero/web/views/client_error_show_view.rb +1 -1
- data/lib/sentiero/web/views/maintenance_view.rb +27 -0
- data/lib/sentiero/web/views/segments_view.rb +3 -2
- data/lib/sentiero/web/views/sessions_index_view.rb +6 -2
- data/lib/sentiero/web/views.rb +1 -0
- data/lib/sentiero.rb +1 -0
- metadata +10 -5
- data/lib/sentiero/web/assets/dashboard-JFYNHZZV.js +0 -3
- data/lib/sentiero/web/assets/recorder-SLLXSUUX.js +0 -71
- data/lib/sentiero/web/assets/rrweb-player-cd435a95.js +0 -126
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<form method="get" action="<%= view.h(view.base_path) %>/">
|
|
4
4
|
<div class="card mb-4">
|
|
5
|
-
<div class="flex items-end gap-3 px-4 py-3">
|
|
5
|
+
<div class="flex flex-wrap items-end gap-3 px-4 py-3">
|
|
6
6
|
<div class="shrink-0">
|
|
7
7
|
<label for="search" class="label">Search</label>
|
|
8
8
|
<input type="text" name="search" id="search" class="input" style="width:11rem" value="<%= view.h(view.search) %>" placeholder="Session ID or metadata...">
|
|
@@ -15,6 +15,14 @@
|
|
|
15
15
|
<label for="until" class="label">To</label>
|
|
16
16
|
<input type="date" name="until" id="until" class="input" style="width:8.5rem" value="<%= view.h(view.until_param) %>">
|
|
17
17
|
</div>
|
|
18
|
+
<div class="shrink-0">
|
|
19
|
+
<label for="min_duration" class="label">Min duration (s)</label>
|
|
20
|
+
<input type="number" name="min_duration" id="min_duration" class="input" style="width:7rem" min="1" value="<%= view.h(view.min_duration) %>">
|
|
21
|
+
</div>
|
|
22
|
+
<div class="shrink-0">
|
|
23
|
+
<label for="min_events" class="label">Min events</label>
|
|
24
|
+
<input type="number" name="min_events" id="min_events" class="input" style="width:6.5rem" min="1" value="<%= view.h(view.min_events) %>">
|
|
25
|
+
</div>
|
|
18
26
|
<div class="shrink-0">
|
|
19
27
|
<label for="sort_by" class="label">Sort by</label>
|
|
20
28
|
<select name="sort_by" id="sort_by" class="select" style="width:9rem">
|
|
@@ -39,7 +47,7 @@
|
|
|
39
47
|
<% if view.sessions.empty? -%>
|
|
40
48
|
<div class="card">
|
|
41
49
|
<div class="text-center py-10 px-6 text-gray-400 text-xs">
|
|
42
|
-
<% if !view.search.empty? || !view.since.empty? || !view.until_param.empty? || view.has_errors -%>
|
|
50
|
+
<% if !view.search.empty? || !view.since.empty? || !view.until_param.empty? || view.has_errors || !view.min_duration.empty? || !view.min_events.empty? -%>
|
|
43
51
|
No results match your filters. <a href="<%= view.h(view.base_path) %>/" class="text-blue-500 hover:text-blue-700">Clear filters</a>
|
|
44
52
|
<% else -%>
|
|
45
53
|
No sessions recorded yet.
|
|
@@ -79,10 +87,10 @@
|
|
|
79
87
|
<nav aria-label="Session pagination">
|
|
80
88
|
<ul class="pagination">
|
|
81
89
|
<li class="<%= 'page-disabled' if view.page <= 1 %>">
|
|
82
|
-
<a class="page-link" href="<%= view.h(view.base_path) %>/?page=<%= view.page - 1 %>&per_page=<%= view.per_page %>&search=<%= view.h(view.search) %>&sort_by=<%= view.h(view.sort_by) %>&since=<%= view.h(view.since) %>&until=<%= view.h(view.until_param) %><%= view.has_errors ? "&has_errors=true" : "" %>">Previous</a>
|
|
90
|
+
<a class="page-link" href="<%= view.h(view.base_path) %>/?page=<%= view.page - 1 %>&per_page=<%= view.per_page %>&search=<%= view.h(view.search) %>&sort_by=<%= view.h(view.sort_by) %>&since=<%= view.h(view.since) %>&until=<%= view.h(view.until_param) %><%= view.has_errors ? "&has_errors=true" : "" %><%= view.min_duration.empty? ? "" : "&min_duration=#{view.h(view.min_duration)}" %><%= view.min_events.empty? ? "" : "&min_events=#{view.h(view.min_events)}" %>">Previous</a>
|
|
83
91
|
</li>
|
|
84
92
|
<li class="<%= 'page-disabled' unless view.has_next %>">
|
|
85
|
-
<a class="page-link" href="<%= view.h(view.base_path) %>/?page=<%= view.page + 1 %>&per_page=<%= view.per_page %>&search=<%= view.h(view.search) %>&sort_by=<%= view.h(view.sort_by) %>&since=<%= view.h(view.since) %>&until=<%= view.h(view.until_param) %><%= view.has_errors ? "&has_errors=true" : "" %>">Next</a>
|
|
93
|
+
<a class="page-link" href="<%= view.h(view.base_path) %>/?page=<%= view.page + 1 %>&per_page=<%= view.per_page %>&search=<%= view.h(view.search) %>&sort_by=<%= view.h(view.sort_by) %>&since=<%= view.h(view.since) %>&until=<%= view.h(view.until_param) %><%= view.has_errors ? "&has_errors=true" : "" %><%= view.min_duration.empty? ? "" : "&min_duration=#{view.h(view.min_duration)}" %><%= view.min_events.empty? ? "" : "&min_events=#{view.h(view.min_events)}" %>">Next</a>
|
|
86
94
|
</li>
|
|
87
95
|
</ul>
|
|
88
96
|
</nav>
|
|
@@ -46,10 +46,16 @@ module Sentiero
|
|
|
46
46
|
def max_sessions = series.map { |d| d[:session_count] }.max || 0
|
|
47
47
|
|
|
48
48
|
def distributions
|
|
49
|
-
[
|
|
49
|
+
base = [
|
|
50
50
|
["Browsers", stats[:browser_distribution], "No browser data."],
|
|
51
51
|
["Devices", stats[:device_distribution], "No device data."]
|
|
52
52
|
]
|
|
53
|
+
# Omitted when empty, so feature-off deployments don't see two permanently empty cards.
|
|
54
|
+
{"Countries" => :country_distribution, "Cities" => :city_distribution}.each do |label, key|
|
|
55
|
+
dist = stats[key] || {}
|
|
56
|
+
base << [label, dist, ""] unless dist.empty?
|
|
57
|
+
end
|
|
58
|
+
base
|
|
53
59
|
end
|
|
54
60
|
|
|
55
61
|
def buckets = stats[:session_duration_buckets] || {}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_view"
|
|
4
|
+
|
|
5
|
+
module Sentiero
|
|
6
|
+
module Web
|
|
7
|
+
module Views
|
|
8
|
+
class MaintenanceView < BaseView
|
|
9
|
+
def initialize(retention_period:, purged: nil, error: nil)
|
|
10
|
+
super()
|
|
11
|
+
@retention_period = retention_period
|
|
12
|
+
@purged = purged
|
|
13
|
+
@error = error
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :retention_period, :purged, :error
|
|
17
|
+
|
|
18
|
+
def retention_days
|
|
19
|
+
days = (retention_period / 86_400.0).round(1)
|
|
20
|
+
(days % 1 == 0) ? days.to_i : days
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def template = "maintenance.html.erb"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -6,11 +6,12 @@ module Sentiero
|
|
|
6
6
|
module Web
|
|
7
7
|
module Views
|
|
8
8
|
class SegmentsView < BaseView
|
|
9
|
-
def initialize(filters:, browser_options:, device_options:, sessions:, page:, per_page:, has_next:, was_truncated:, filter_query:)
|
|
9
|
+
def initialize(filters:, browser_options:, device_options:, country_options:, sessions:, page:, per_page:, has_next:, was_truncated:, filter_query:)
|
|
10
10
|
super()
|
|
11
11
|
@filters = filters
|
|
12
12
|
@browser_options = browser_options
|
|
13
13
|
@device_options = device_options
|
|
14
|
+
@country_options = country_options
|
|
14
15
|
@sessions = sessions
|
|
15
16
|
@page = page
|
|
16
17
|
@per_page = per_page
|
|
@@ -19,7 +20,7 @@ module Sentiero
|
|
|
19
20
|
@filter_query = filter_query
|
|
20
21
|
end
|
|
21
22
|
|
|
22
|
-
attr_reader :filters, :browser_options, :device_options, :sessions, :page, :per_page, :has_next, :was_truncated, :filter_query
|
|
23
|
+
attr_reader :filters, :browser_options, :device_options, :country_options, :sessions, :page, :per_page, :has_next, :was_truncated, :filter_query
|
|
23
24
|
|
|
24
25
|
def template = "segments.html.erb"
|
|
25
26
|
end
|
|
@@ -6,7 +6,8 @@ module Sentiero
|
|
|
6
6
|
module Web
|
|
7
7
|
module Views
|
|
8
8
|
class SessionsIndexView < BaseView
|
|
9
|
-
def initialize(sessions:, page:, per_page:, has_next:, search:, sort_by:, since:, until_param:, has_errors
|
|
9
|
+
def initialize(sessions:, page:, per_page:, has_next:, search:, sort_by:, since:, until_param:, has_errors:,
|
|
10
|
+
min_duration: "", min_events: "")
|
|
10
11
|
super()
|
|
11
12
|
@sessions = sessions
|
|
12
13
|
@page = page
|
|
@@ -17,9 +18,12 @@ module Sentiero
|
|
|
17
18
|
@since = since
|
|
18
19
|
@until_param = until_param
|
|
19
20
|
@has_errors = has_errors
|
|
21
|
+
@min_duration = min_duration
|
|
22
|
+
@min_events = min_events
|
|
20
23
|
end
|
|
21
24
|
|
|
22
|
-
attr_reader :sessions, :page, :per_page, :has_next, :search, :sort_by, :since, :until_param, :has_errors
|
|
25
|
+
attr_reader :sessions, :page, :per_page, :has_next, :search, :sort_by, :since, :until_param, :has_errors,
|
|
26
|
+
:min_duration, :min_events
|
|
23
27
|
|
|
24
28
|
def template = "sessions_index.html.erb"
|
|
25
29
|
end
|
data/lib/sentiero/web/views.rb
CHANGED
|
@@ -11,6 +11,7 @@ require_relative "views/forms_view"
|
|
|
11
11
|
require_relative "views/heatmap_view"
|
|
12
12
|
require_relative "views/export_view"
|
|
13
13
|
require_relative "views/import_view"
|
|
14
|
+
require_relative "views/maintenance_view"
|
|
14
15
|
require_relative "views/analyzer_view"
|
|
15
16
|
require_relative "views/scroll_view"
|
|
16
17
|
require_relative "views/vitals_view"
|
data/lib/sentiero.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "sentiero/version"
|
|
4
4
|
require_relative "sentiero/redaction"
|
|
5
5
|
require_relative "sentiero/configuration"
|
|
6
|
+
require_relative "sentiero/geo"
|
|
6
7
|
require_relative "sentiero/ip_anonymizer"
|
|
7
8
|
require_relative "sentiero/store"
|
|
8
9
|
require_relative "sentiero/erasure"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sentiero
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.0.
|
|
4
|
+
version: 1.0.0.beta3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stephen Ierodiaconou
|
|
@@ -88,6 +88,8 @@ files:
|
|
|
88
88
|
- lib/sentiero/configuration.rb
|
|
89
89
|
- lib/sentiero/erasure.rb
|
|
90
90
|
- lib/sentiero/fingerprint.rb
|
|
91
|
+
- lib/sentiero/fingerprint/config.rb
|
|
92
|
+
- lib/sentiero/geo.rb
|
|
91
93
|
- lib/sentiero/ip_anonymizer.rb
|
|
92
94
|
- lib/sentiero/redaction.rb
|
|
93
95
|
- lib/sentiero/redaction/config.rb
|
|
@@ -115,20 +117,21 @@ files:
|
|
|
115
117
|
- lib/sentiero/stores/redis/keys.rb
|
|
116
118
|
- lib/sentiero/stores/redis/lua.rb
|
|
117
119
|
- lib/sentiero/stores/sqlite.rb
|
|
120
|
+
- lib/sentiero/stores/sqlite/payload_codec.rb
|
|
118
121
|
- lib/sentiero/stores/sqlite/schema.rb
|
|
119
122
|
- lib/sentiero/user_agent.rb
|
|
120
123
|
- lib/sentiero/version.rb
|
|
121
124
|
- lib/sentiero/web/analytics_app.rb
|
|
122
125
|
- lib/sentiero/web/assets/analytics-RH24EOLD.js
|
|
123
|
-
- lib/sentiero/web/assets/dashboard-
|
|
126
|
+
- lib/sentiero/web/assets/dashboard-YINQE5ZH.js
|
|
124
127
|
- lib/sentiero/web/assets/heatmap-EBKFWSKN.js
|
|
125
128
|
- lib/sentiero/web/assets/import-HIMBJJ4S.js
|
|
126
129
|
- lib/sentiero/web/assets/manifest.json
|
|
127
|
-
- lib/sentiero/web/assets/recorder-
|
|
128
|
-
- lib/sentiero/web/assets/rrweb-player-cd435a95.js
|
|
130
|
+
- lib/sentiero/web/assets/recorder-W2RCU5VM.js
|
|
129
131
|
- lib/sentiero/web/assets/rrweb-player-css-ce5e9629.css
|
|
132
|
+
- lib/sentiero/web/assets/rrweb-player-d522d74c.js
|
|
130
133
|
- lib/sentiero/web/assets/sessions_index-2RAGTEZM.js
|
|
131
|
-
- lib/sentiero/web/assets/style-
|
|
134
|
+
- lib/sentiero/web/assets/style-80e536e6.css
|
|
132
135
|
- lib/sentiero/web/assets_app.rb
|
|
133
136
|
- lib/sentiero/web/base_app.rb
|
|
134
137
|
- lib/sentiero/web/basic_auth.rb
|
|
@@ -176,6 +179,7 @@ files:
|
|
|
176
179
|
- lib/sentiero/web/templates/forms.html.erb
|
|
177
180
|
- lib/sentiero/web/templates/heatmap.html.erb
|
|
178
181
|
- lib/sentiero/web/templates/import.html.erb
|
|
182
|
+
- lib/sentiero/web/templates/maintenance.html.erb
|
|
179
183
|
- lib/sentiero/web/templates/problem_show.html.erb
|
|
180
184
|
- lib/sentiero/web/templates/segments.html.erb
|
|
181
185
|
- lib/sentiero/web/templates/session_show.html.erb
|
|
@@ -197,6 +201,7 @@ files:
|
|
|
197
201
|
- lib/sentiero/web/views/funnel_view.rb
|
|
198
202
|
- lib/sentiero/web/views/heatmap_view.rb
|
|
199
203
|
- lib/sentiero/web/views/import_view.rb
|
|
204
|
+
- lib/sentiero/web/views/maintenance_view.rb
|
|
200
205
|
- lib/sentiero/web/views/page_report_view.rb
|
|
201
206
|
- lib/sentiero/web/views/problem_show_view.rb
|
|
202
207
|
- lib/sentiero/web/views/scroll_view.rb
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
(()=>{var C={navigation:{label:"Navigation",color:"#198754"},click:{label:"Click",color:"#dc3545"},input:{label:"Input",color:"#0d6efd"},error:{label:"Error",color:"#ff0000"},custom:{label:"Custom",color:"#6f42c1"},frustration:{label:"Frustration",color:"#ff6b6b"},server_exception:{label:"Server Exception",color:"#b91c1c"},server_event:{label:"Server Event",color:"#7c3aed"}},it=1e3,S=5e3,lt=3;function ct(t){if(t.length<2)return 0;let e=0,r=t[0].timestamp,n=t[0].timestamp;for(let a=1;a<t.length;a++){let o=t[a].timestamp;o-n>S&&(e+=n-r,r=o),n=o}return e+=n-r,e}function k(t){let e=Math.round(t/1e3);if(e<60)return`${e}s`;if(e<3600){let a=Math.floor(e/60),o=e%60;return o>0?`${a}m ${o}s`:`${a}m`}let r=Math.floor(e/3600),n=Math.floor(e%3600/60);return n>0?`${r}h ${n}m`:`${r}h`}function M(t){let e=Math.floor(t/1e3),r=Math.floor(e/3600),n=Math.floor(e%3600/60),a=e%60,o=i=>(i<10?"0":"")+i;return r>0?`${r}:${o(n)}:${o(a)}`:`${n}:${o(a)}`}function _(t,e){return t.length<=e?t:t.substring(0,e-3)+"..."}function st(t){for(let e=0;e<t.length;e++)if(t[e].type===4&&t[e].data){let r=t[e].data.width,n=t[e].data.height;if(r&&n)return`${r}x${n}`}return null}function Bt(t){return!!(t&&t.type===3&&t.data&&t.data.source===5&&typeof t.data.id=="number")}function Gt(t){return!!t&&typeof t.isChecked=="boolean"}function W(t){if(!Array.isArray(t))return[];let e=new Map;for(let n=0;n<t.length;n++){let a=t[n];if(!Bt(a))continue;let o=a.data.id,i=e.get(o);i||(i={nodeId:o,fillCount:0,isToggle:!1,firstTimestamp:a.timestamp,lastTimestamp:a.timestamp},e.set(o,i)),i.fillCount+=1,i.lastTimestamp=a.timestamp,Gt(a.data)&&(i.isToggle=!0)}let r=Array.from(e.values()).sort((n,a)=>n.firstTimestamp-a.firstTimestamp);return r.forEach((n,a)=>{n.order=a+1,n.label=`Field ${a+1}`}),r}function U(t){let e=W(t),r={},n=[];for(let a=0;a<e.length;a++){let o=e[a],i=e[a+1];o.nextFieldOffset=i?i.firstTimestamp-o.firstTimestamp:null,r[o.nodeId]=o,n.push(o.label)}return{totalFields:e.length,sequence:n,byNodeId:r}}function Ht(t){return t<1e3?`${Math.round(t/100)*100}ms`:`${(Math.round(t/100)/10).toFixed(1)}s`}function dt(t,e){if(!t||t.category!=="input")return[];if(!e||!e.byNodeId)return[];let r=t.event&&t.event.data;if(!r||typeof r.id!="number")return[];let n=e.byNodeId[r.id];if(!n)return[];let a=[`Field ${n.order} of ${e.totalFields}`];return n.isToggle?n.fillCount>1&&a.push(`Toggled: ${n.fillCount} times`):n.fillCount>1&&a.push(`Re-filled: ${n.fillCount} times`),n.nextFieldOffset!=null&&n.nextFieldOffset>0&&n.nextFieldOffset<=S&&a.push(`Time to next field: ${Ht(n.nextFieldOffset)}`),a}function F(t,e){let r=[],n=t;function a(){fetch(n).then(o=>{if(!o.ok)throw new Error(`Failed to fetch events: ${o.status}`);return o.json()}).then(o=>{if(!Array.isArray(o)||o.length===0){e(null,r);return}if(r=r.concat(o),o.length<it){e(null,r);return}let i=o[o.length-1];if(i&&i.timestamp){let s=t.indexOf("?")===-1?"?":"&";n=`${t}${s}after=${i.timestamp}`,a()}else e(null,r)}).catch(o=>{r.length>0?e(null,r):e(o,[])})}a()}var j=null;function w(){return j}function Vt(t){if(t.type===4&&t.data&&t.data.href)return"navigation";if(t.type===3&&t.data){if(t.data.source===2){let e=t.data.type;return e===2||e===4||e===3||e===7?"click":null}if(t.data.source===5)return"input"}return t.type===5?t.data&&t.data.tag==="error"?"error":t.data&&t.data.tag==="navigation"?"navigation":"custom":null}function Yt(t){if(!t||t.length<2)return{map:()=>0,gaps:[]};let e=t[0].timestamp,r=t[t.length-1].timestamp,n=r-e;if(n<=0)return{map:()=>0,gaps:[]};let a=[],o=[],i=e;for(let l=1;l<t.length;l++){let c=t[l].timestamp-t[l-1].timestamp;c>S&&(o.push({start:i,end:t[l-1].timestamp}),a.push({start:t[l-1].timestamp,end:t[l].timestamp,duration:c}),i=t[l].timestamp)}if(o.push({start:i,end:r}),a.length===0)return{map:l=>(l-e)/n,gaps:[]};let s=0;for(let l=0;l<o.length;l++)s+=o[l].end-o[l].start;let f=a.length*lt;f>40&&(f=40);let u=f/a.length,m=100-f,d=[],p=0;for(let l=0;l<o.length;l++){let c=o[l].end-o[l].start,h=s>0?c/s*m:0;d.push({type:"active",start:o[l].start,end:o[l].end,posStart:p,posEnd:p+h}),p+=h,l<a.length&&(d.push({type:"gap",start:a[l].start,end:a[l].end,duration:a[l].duration,posStart:p,posEnd:p+u}),p+=u)}function g(l){for(let c=0;c<d.length;c++){let h=d[c];if(h.type==="active"&&l>=h.start&&l<=h.end){let v=h.end-h.start,V=v>0?(l-h.start)/v:0;return(h.posStart+V*(h.posEnd-h.posStart))/100}}for(let c=0;c<d.length;c++)if(d[c].type==="gap"&&l>=d[c].start&&l<=d[c].end)return d[c].posStart/100;return l<=e?0:1}let y=d.filter(l=>l.type==="gap");return{map:g,gaps:y}}function mt(t){if(!t||t.length===0)return[];let e=t[0].timestamp;j=Yt(t);let r=U(t),n=0,a=[];for(let o=0;o<t.length;o++){let i=Vt(t[o]);if(!i)continue;let s={index:o,timestamp:t[o].timestamp,offset:t[o].timestamp-e,position:j.map(t[o].timestamp),category:i,event:t[o]};i==="navigation"&&t[o].type===4&&(n++,s.metaIndex=n),i==="input"&&(s.formContext=r),a.push(s)}return a}function yt(t){return Array.isArray(t)?t.map(e=>({offset:e.offset_ms||0,category:e.kind==="exception"?"server_exception":"server_event",kind:e.kind,label:e.label||"",level:e.level||"",href:e.href||null,isServerMarker:!0})):[]}function q(t){return typeof t.isChecked=="boolean"}function R(t){if(t.isServerMarker)return t.label||(t.category==="server_exception"?"Server exception":"Server event");if(t.category==="navigation"&&t.event.data&&t.event.data.href){let r=t.event.data.href;try{let n=_(new URL(r).pathname,40);return t.metaIndex===1?`Page loaded: ${n}`:`Navigated to: ${n}`}catch{return t.metaIndex===1?"Page loaded":"Page navigation"}}if(t.category==="navigation"&&t.event.data&&t.event.data.payload){let r=t.event.data.payload,n=r.url||"",a=r.external?"Leaving to: ":"Navigating to: ";try{let o=new URL(n),i=r.external?o.hostname+o.pathname:o.pathname;return a+_(i,40)}catch{return a+_(n,40)}}if(t.category==="click"&&t.event.data){let r=t.event.data.type,n="Click";return r===4?n="Double Click":r===3?n="Right Click":r===7&&(n="Touch"),t.event.data.x!=null&&t.event.data.y!=null&&(n+=` (${Math.round(t.event.data.x)}, ${Math.round(t.event.data.y)})`),n}if(t.category==="input"&&t.event.data){let r=t.event.data;return typeof r.text=="string"&&r.text.length>0&&!q(r)?`Input: ${_(r.text,20)}`:q(r)?r.isChecked?"Checkbox checked":"Checkbox unchecked":"Input cleared"}if(t.category==="frustration"){let r=t.x!=null&&t.y!=null?` (${Math.round(t.x)}, ${Math.round(t.y)})`:"";return t.subtype==="rage_click"?`Rage click${r}`:`Dead click${r}`}if(t.category==="error"&&t.event.data&&t.event.data.payload){let r=t.event.data.payload.message||"Error";return _(r,50)}if(t.category==="custom"&&t.event.data&&t.event.data.tag)return t.event.data.tag;let e=C[t.category];return e?e.label:"Event"}function ht(t){if(t.isServerMarker){let n=[];return t.level&&n.push(`Level: ${t.level}`),t.href&&n.push(`Details: ${t.href}`),n}let e=[],r=t.event.data||{};if(t.category==="frustration")return t.x!=null&&t.y!=null&&e.push(`Position: (${Math.round(t.x)}, ${Math.round(t.y)})`),t.subtype==="rage_click"?e.push(`Clicks: ${t.count}`):e.push(`No response within ${t.elapsed||500}ms`),e;if(t.category==="navigation")return r.href?(e.push(`URL: ${r.href}`),r.width&&r.height&&e.push(`Viewport: ${r.width}x${r.height}`)):r.payload&&(r.payload.url&&e.push(`URL: ${r.payload.url}`),r.payload.text&&e.push(`Link text: ${r.payload.text}`),r.payload.external&&e.push("External link")),e;if(t.category==="click")r.x!=null&&r.y!=null&&e.push(`Position: (${Math.round(r.x)}, ${Math.round(r.y)})`);else if(t.category==="input")typeof r.text=="string"&&r.text.length>0&&e.push(`Value: ${r.text}`),q(r)&&e.push(`Checked: ${r.isChecked}`),dt(t,t.formContext).forEach(n=>e.push(n));else if(t.category==="error"&&r.payload){let n=r.payload;if(n.message&&e.push(`Message: ${n.message}`),n.source){let a=n.source;n.lineno&&(a+=`:${n.lineno}`),n.colno&&(a+=`:${n.colno}`),e.push(`Source: ${a}`)}n.type&&e.push(`Type: ${n.type}`),n.stack&&e.push(`Stack:
|
|
2
|
-
${n.stack}`)}else if(t.category==="custom"&&r.payload)try{e.push(`Payload: ${JSON.stringify(r.payload,null,2)}`)}catch{e.push("Payload: [object]")}return e}var Wt=1.5,gt=!1,X=null,Et=0;function jt(t){if(t.length===0)return[];let e=[],r={events:[t[0]],position:t[0].position*100};for(let n=1;n<t.length;n++){let a=t[n].position*100;Math.abs(a-r.position)<=Wt?r.events.push(t[n]):(e.push(r),r={events:[t[n]],position:a})}return e.push(r),e}function J(){let t=document.querySelectorAll(".marker-dropdown");for(let e=0;e<t.length;e++)t[e].parentNode.removeChild(t[e])}function xt(t){if(!X)return;let e=w();if(!e)return;let r=e.map(Et+t)*100;X.style.left=`${Math.max(0,Math.min(100,r))}%`}function Ct(t,e){let r=document.getElementById("event-markers");if(!r)return;if(t.length===0){r.style.display="none";return}e&&e.length>0&&(Et=e[0].timestamp),r.innerHTML="";let n=document.createElement("div");n.className="marker-legend",Object.keys(C).forEach(m=>{let d=C[m],p=document.createElement("span");p.className="marker-legend-item";let g=document.createElement("span");g.className="marker-legend-dot",g.style.backgroundColor=d.color,p.appendChild(g),p.appendChild(document.createTextNode(d.label)),n.appendChild(p)}),r.appendChild(n);let a=document.createElement("div");a.className="marker-bar";let o=document.createElement("div");o.className="marker-track",a.appendChild(o);let i=document.createElement("div");i.className="marker-playhead",i.style.left="0%",a.appendChild(i),X=i;let s=t;s.length>500&&(s=t.filter(m=>m.category!=="input")),jt(s).forEach(m=>{let d=`${Math.max(1,Math.min(99,m.position))}%`;if(m.events.length===1){let p=m.events[0],g=C[p.category];if(!g)return;let y=document.createElement("div");y.className=`event-marker${p.category==="error"?" error-marker":""}`,y.style.left=d,y.style.backgroundColor=g.color,y.title=`${R(p)} at ${M(p.offset)}`,y.addEventListener("click",()=>P(p.offset)),a.appendChild(y)}else{let p=m.events.some(c=>c.category==="error"),g=document.createElement("div");g.className="marker-group",g.style.left=d;let y=document.createElement("div");y.className=`event-marker marker-group-dot${p?" error-marker":""}`,y.style.backgroundColor=p?"#ff0000":"#6c757d",y.title=`${m.events.length} events at ${M(m.events[0].offset)}`;let l=document.createElement("span");l.className="marker-group-count",l.textContent=m.events.length,g.appendChild(y),g.appendChild(l),g.addEventListener("click",c=>{c.stopPropagation(),J();let h=document.createElement("div");h.className="marker-dropdown",m.events.forEach(v=>{let V=C[v.category]||{},O=document.createElement("div");O.className=`marker-dropdown-item${v.category==="error"?" error-entry":""}`;let Y=document.createElement("span");Y.className="activity-dot",Y.style.backgroundColor=V.color||"#6c757d";let ot=document.createElement("span");ot.textContent=`${M(v.offset)} ${R(v)}`,O.appendChild(Y),O.appendChild(ot),O.addEventListener("click",Ft=>{Ft.stopPropagation(),J(),P(v.offset)}),h.appendChild(O)}),g.appendChild(h)}),a.appendChild(g)}});let u=w();u&&u.gaps.length>0&&u.gaps.forEach(m=>{let d=document.createElement("div");d.className="marker-gap",d.style.left=`${m.posStart}%`,d.style.width=`${m.posEnd-m.posStart}%`,d.title=`Inactive: ${k(m.duration)}`,a.appendChild(d)}),gt||(gt=!0,document.addEventListener("click",J)),r.appendChild(a),r.style.display=""}var B=null,z=[];function vt(t){let e=document.getElementById("activity-sidebar"),r=document.getElementById("activity-list"),n=document.getElementById("activity-count");if(!(!e||!r)){if(t.length===0){e.style.display="none";return}z=t,e.style.display="",n&&(n.textContent=t.length),r.innerHTML="",t.forEach((a,o)=>{let i=C[a.category];if(!i)return;if(o>0){let y=t[o-1].offset,l=a.offset-y;if(l>S){let c=document.createElement("div");c.className="activity-gap",c.textContent=`${k(l)} inactive`,r.appendChild(c)}}let s=document.createElement("div");s.className="activity-wrapper";let f=document.createElement("div"),u="activity-entry";a.category==="error"?u+=" error-entry":a.category==="navigation"?u+=" navigation-entry":a.category==="frustration"?u+=" frustration-entry":a.category==="server_exception"?u+=" server-exception-entry":a.category==="server_event"&&(u+=" server-event-entry"),f.className=u,f.setAttribute("data-offset",a.offset),f.setAttribute("data-index",o);let m=document.createElement("span");m.className="activity-time",m.textContent=M(a.offset);let d=document.createElement("span");d.className="activity-dot",d.style.backgroundColor=i.color;let p;a.isServerMarker&&a.href?(p=document.createElement("a"),p.href=a.href,p.className="activity-label activity-label-link",p.addEventListener("click",y=>y.stopPropagation())):(p=document.createElement("span"),p.className="activity-label"),p.textContent=R(a),f.appendChild(m),f.appendChild(d),f.appendChild(p),f.addEventListener("click",()=>P(a.offset)),s.appendChild(f);let g=ht(a);if(g.length>0){let y=document.createElement("div");y.className="activity-detail",g.forEach(l=>{let c=document.createElement("div");if(c.className="activity-detail-line",l.indexOf(`
|
|
3
|
-
`)!==-1){let h=document.createElement("pre");h.className="activity-detail-pre",h.textContent=l,c.appendChild(h)}else c.textContent=l;y.appendChild(c)}),s.appendChild(y)}r.appendChild(s)}),qt()}}function Z(){B&&(clearInterval(B),B=null)}function qt(){Z(),B=setInterval(()=>{let t=A();if(t)try{let r=t.getReplayer().getCurrentTime();Kt(r),xt(r)}catch{}},250)}function Kt(t){let e=document.querySelectorAll(".activity-wrapper");if(e.length===0)return;let r=-1;for(let n=z.length-1;n>=0;n--)if(z[n].offset<=t){r=n;break}for(let n=0;n<e.length;n++){let a=e[n].querySelector(".activity-entry");a&&(n===r?a.classList.contains("active")||(a.classList.add("active"),e[n].scrollIntoView({block:"nearest",behavior:"smooth"})):a.classList.remove("active"))}}var Jt=["LCP","CLS","INP"];function Xt(t){let e=Object.create(null);if(!Array.isArray(t))return e;for(let r of t)if(r&&r.type===5&&r.data&&r.data.tag==="__perf"&&r.data.payload){let n=r.data.payload;typeof n.metric=="string"&&typeof n.value=="number"&&(e[n.metric]={value:n.value,rating:n.rating})}return e}function zt(t,e){return t==="CLS"?e.toFixed(3):`${Math.round(e)} ms`}function Zt(t){return t==="good"?"badge-success":t==="needs-improvement"?"badge-warning":t==="poor"?"badge-danger":"badge-neutral"}function Tt(t,e){if(!e)return;let r=Xt(t);e.innerHTML="";let n=!1;for(let a of Jt){let o=r[a];if(!o)continue;n=!0;let i=document.createElement("span");i.className=`badge ${Zt(o.rating)} shrink-0`,i.textContent=`${a} ${zt(a,o.value)}`,o.rating&&(i.title=o.rating),e.appendChild(i)}e.style.display=n?"inline-flex":"none"}function te(t){let e=0;if(!Array.isArray(t))return e;for(let r=0;r<t.length;r++){let n=t[r];n&&n.type===3&&n.data&&n.data.source===3&&typeof n.data.y=="number"&&n.data.y>e&&(e=n.data.y)}return e}function ee(t){if(!Array.isArray(t))return null;for(let e=0;e<t.length;e++){let r=t[e];if(r&&r.type===4&&r.data&&r.data.height)return r.data.height}return null}function Q(t){let e=te(t);if(e<=0)return null;let r=ee(t),n=r&&r>0?Math.round((e+r)/r*10)/10:null;return{y:e,viewports:n}}function bt(t,e){if(!e)return;let r=Q(t);if(!r){e.style.display="none",e.textContent="";return}let n=r.viewports!=null?` (~${r.viewports}\xD7 viewport)`:"";e.textContent=`Max Scroll: ${r.y}px${n}`,e.style.display="inline"}var St=6,kt=1e3,b=!1,I=null;function re(t){let e=[];if(!Array.isArray(t))return e;for(let r=0;r<t.length;r++){let n=t[r];n&&n.type===3&&n.data&&n.data.source===2&&n.data.type===2&&typeof n.data.x=="number"&&typeof n.data.y=="number"&&e.push({x:n.data.x,y:n.data.y})}return e}function ne(t){if(!Array.isArray(t))return null;for(let e=0;e<t.length;e++){let r=t[e];if(r&&r.type===4&&r.data&&r.data.width&&r.data.height)return{width:r.data.width,height:r.data.height}}return null}function ae(t,e,r){let n=e&&e.width>0?r.width/e.width:1,a=e&&e.height>0?r.height/e.height:1,o=Math.max(0,Math.min(r.width,t.x*n)),i=Math.max(0,Math.min(r.height,t.y*a));return{x:o,y:i}}function oe(){let t=document.getElementById("replayer");return t?t.querySelector(".replayer-wrapper")||t.querySelector(".rr-player__frame")||t:null}function tt(){I&&I.parentNode&&I.parentNode.removeChild(I),I=null}function ie(){tt();let t=oe();if(!t)return;let e=D(),r=ne(e),n=re(e);if(n.length>kt){let i=Math.ceil(n.length/kt);n=n.filter((s,f)=>f%i===0)}let a={width:t.clientWidth||(r?r.width:0),height:t.clientHeight||(r?r.height:0)},o=document.createElement("div");o.className="click-overlay-container",n.forEach(i=>{let{x:s,y:f}=ae(i,r,a),u=document.createElement("div");u.className="click-dot",u.style.left=`${s-St}px`,u.style.top=`${f-St}px`,o.appendChild(u)}),getComputedStyle(t).position==="static"&&(t.style.position="relative"),t.appendChild(o),I=o}function Mt(){b=!1,tt();let t=document.getElementById("toggle-clicks-btn");t&&(t.setAttribute("aria-pressed","false"),t.classList.remove("btn-active"))}function G(){if(!A())return b;b=!b,b?ie():tt();let t=document.getElementById("toggle-clicks-btn");return t&&(t.setAttribute("aria-pressed",b?"true":"false"),t.classList.toggle("btn-active",b)),b}var _t=500,At=10,It=3,Ot=500;function Nt(t){return!!(t&&t.type===3&&t.data&&t.data.source===2&&t.data.type===2&&typeof t.data.x=="number"&&typeof t.data.y=="number")}function ce(t){return!t||!t.data?!1:t.type===4?!0:t.type===3?t.data.source===0||t.data.source===5:!1}function se(t){if(!Array.isArray(t))return[];let e=[];for(let a=0;a<t.length;a++)Nt(t[a])&&e.push(t[a]);if(e.length<It)return[];let r=[],n=0;for(let a=1;a<=e.length;a++){let o=e[a-1],i=e[a],s=e[n];if(!(i&&i.timestamp-o.timestamp<=_t&&i.timestamp-s.timestamp<=_t&&Math.abs(i.data.x-s.data.x)<=At&&Math.abs(i.data.y-s.data.y)<=At)){let u=a-n;if(u>=It){let m=[];for(let d=n;d<a;d++)m.push(e[d].timestamp);r.push({subtype:"rage_click",timestamp:s.timestamp,count:u,x:s.data.x,y:s.data.y,memberTimestamps:m,event:s})}n=a}}return r}function de(t){if(!Array.isArray(t))return[];let e=[];for(let r=0;r<t.length;r++){if(!Nt(t[r]))continue;let n=t[r].timestamp,a=n+Ot,o=!1;for(let i=r+1;i<t.length;i++){let s=t[i].timestamp;if(s>a)break;if(s>n&&ce(t[i])){o=!0;break}}o||e.push({subtype:"dead_click",timestamp:n,x:t[r].data.x,y:t[r].data.y,elapsed:Ot,event:t[r]})}return e}function H(t){if(!Array.isArray(t)||t.length===0)return[];let e=t[0].timestamp,r=se(t),n=de(t),a=new Set;r.forEach(i=>(i.memberTimestamps||[i.timestamp]).forEach(s=>a.add(s)));let o=[];return r.forEach(i=>o.push(i)),n.forEach(i=>{a.has(i.timestamp)||o.push(i)}),o.map(i=>({category:"frustration",subtype:i.subtype,timestamp:i.timestamp,offset:i.timestamp-e,count:i.count,elapsed:i.elapsed,x:i.x,y:i.y,event:i.event})).sort((i,s)=>i.offset-s.offset)}var x=null,Pt=[],Lt=[1,2,4,8,16],et=0,$t=!1;function A(){return x}function D(){return Pt}function pe(t){let e=document.getElementById("active-time");if(!e)return;let r=ct(t);if(r>0){let n=t[t.length-1].timestamp-t[0].timestamp,a=n>0?Math.round(r/n*100):100;e.textContent=`(active: ${k(r)} / ${a}%)`,e.style.display="inline"}}function fe(t){let e=document.getElementById("viewport-info");if(!e)return;let r=st(t);r&&(e.textContent=`Viewport: ${r}`,e.style.display="inline")}function ue(t){if(t.length===0)return null;let r=new URLSearchParams(window.location.search).get("t");if(r!==null&&r!==""){let n=parseInt(r,10);if(!isNaN(n)){let a=n-t[0].timestamp;return a>=0?t[0].timestamp+a:null}}return null}function P(t){if(x)try{let e=x.getReplayer();e&&(e.play(t),e.pause())}catch(e){console.warn("Sentiero: seek failed",e)}}function wt(t,e){try{let r=t.getMetaData(),n=t.getCurrentTime(),a=Math.max(0,Math.min(n+e,r.totalTime));t.play(a),t.pause()}catch(r){console.warn("Sentiero: seek failed",r)}}function Rt(t){et=Math.max(0,Math.min(Lt.length-1,et+t));try{let e=x.getReplayer();e&&e.setConfig&&e.setConfig({speed:Lt[et]})}catch(e){console.warn("Sentiero: speed change failed",e)}}function me(){$t||($t=!0,document.addEventListener("keydown",t=>{if(!x||t.target.tagName==="INPUT"||t.target.tagName==="TEXTAREA")return;let e;try{e=x.getReplayer()}catch{return}if(e)switch(t.key){case" ":t.preventDefault();try{x.$$?.ctx?.[0]?x.toggle():e.play()}catch{try{e.play()}catch{}}break;case"ArrowLeft":t.preventDefault(),wt(e,-5e3);break;case"ArrowRight":t.preventDefault(),wt(e,5e3);break;case"ArrowUp":t.preventDefault(),Rt(1);break;case"ArrowDown":t.preventDefault(),Rt(-1);break}}))}function rt(t,e,r=[]){if(!e){console.error("Sentiero: player container not found");return}F(t,(n,a)=>{if(n){console.error("Sentiero: failed to load events",n),e.innerHTML="";let c=document.createElement("div");c.className="alert alert-danger",c.textContent=`Failed to load session events: ${n.message}`,e.appendChild(c);return}if(a.length===0){e.innerHTML='<p class="text-muted">No events recorded for this window.</p>';return}Pt=a,pe(a),fe(a),bt(a,document.getElementById("scroll-depth-info")),Tt(a,document.getElementById("web-vitals-badges"));let o=mt(a),i=w(),s=a[0].timestamp,f=H(a);f.forEach(c=>{c.position=i?i.map(c.timestamp):0});let u=Array.isArray(r)?r:[];u.forEach(c=>{c.position=i?i.map(s+c.offset):0});let m=o.concat(f).concat(u).sort((c,h)=>c.offset-h.offset);Ct(m,a),vt(m);let d=e.closest(".player-frame")||e.parentElement,p=d?d.clientWidth:1024,g=Math.min(2048,p),y=rrwebPlayer.default||rrwebPlayer;x=new y({target:e,props:{events:a,showController:!0,autoPlay:!1,skipInactive:!0,width:g}});let l=ue(a);if(l)try{let c=x.getReplayer();c&&setTimeout(()=>{c.play(l-a[0].timestamp),c.pause()},100)}catch(c){console.warn("Sentiero: could not seek to timestamp",c)}Mt(),me()})}function nt(){let t=A(),e=D();if(!(!t||e.length===0))try{let n=t.getReplayer().getCurrentTime(),a=new URL(window.location.href);a.searchParams.set("t",Math.round(n)),navigator.clipboard.writeText(a.toString()).then(()=>{let o=document.getElementById("copy-link-btn");if(o){let i=o.textContent;o.textContent="Copied!",setTimeout(()=>{o.textContent=i},2e3)}})}catch(r){console.warn("Sentiero: copy link failed",r)}}function at(){let t=D();if(t.length!==0)try{let e=JSON.stringify(t,null,2),r=new Blob([e],{type:"application/json"}),n=URL.createObjectURL(r),a=document.createElement("a");a.href=n,a.download="sentiero-session-events.json",document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(n)}catch(e){console.warn("Sentiero: download failed",e)}}function Dt(){document.addEventListener("click",t=>{let e=t.target.closest("[data-action]");if(!e)return;let r=e.getAttribute("data-action");if(r==="copy-link")nt();else if(r==="toggle-clicks")G();else if(r==="download-json")at();else if(r==="confirm-delete"){let n=e.getAttribute("data-confirm")||"Are you sure?";confirm(n)||t.preventDefault()}})}function ye(t,e,r){return!(e!=="all"&&t.dataset.activityKind!==e||r&&t.dataset.activityLevel!==r)}function Ut(){let t=document.getElementById("server-activity");if(!t)return;let e=Array.from(t.querySelectorAll("[data-activity-row]"));if(e.length===0)return;let r=t.querySelector("[data-activity-filter-type]"),n=t.querySelector("[data-activity-filter-level]");if(!r&&!n)return;let a="all",o="";function i(){e.forEach(s=>{let f=ye(s,a,o);s.style.display=f?"":"none"})}r&&r.addEventListener("click",s=>{let f=s.target.closest("[data-filter-type]");f&&(a=f.dataset.filterType,Array.from(r.querySelectorAll("[data-filter-type]")).forEach(u=>{u.classList.toggle("btn-active",u===f),u.classList.toggle("btn-secondary",u!==f)}),i())}),n&&n.addEventListener("change",()=>{o=n.value,i()})}window.SentieroDashboard={initPlayer:rt,fetchAllEvents:F,copyLink:nt,downloadJSON:at,toggleClickOverlay:G,computeScrollDepth:Q,detectFrustrationEvents:H,analyzeFormInteractions:W,buildFormContext:U};function he(){let t=document.getElementById("server-activity-markers");if(!t)return[];try{return yt(JSON.parse(t.textContent))}catch(e){return console.warn("Sentiero: failed to parse server-activity markers",e),[]}}document.addEventListener("DOMContentLoaded",()=>{let t=document.getElementById("sentiero-player-config");if(t)try{let e=JSON.parse(t.textContent),r=document.getElementById("replayer");if(e.eventsUrl&&r){let n=he();rt(e.eventsUrl,r,n)}}catch(e){console.error("Sentiero: failed to parse player config",e)}Dt(),Ut(),window.addEventListener("pagehide",Z)});})();
|