@a2f0/skyline 0.2.36 → 0.2.38
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.
- package/NOTICE.md +5 -0
- package/docs/package.md +5 -0
- package/package.json +2 -1
- package/site/building-detail.html +8 -7
- package/site/building-study.html +1 -0
- package/site/click-tracking-page.js +23 -0
- package/site/index.html +1 -0
- package/site/skyline-3d.html +17 -16
- package/site/skyline-animated.svg +2 -0
- package/site/skyline-study.html +1 -0
- package/site/skyline-viewer.js +3 -3
- package/site/skyline-webgl.html +1 -0
- package/site/vendor/ct-client.js +213 -0
package/NOTICE.md
CHANGED
|
@@ -7,6 +7,11 @@ The self-contained viewer includes Three.js r186 under the MIT license. Its full
|
|
|
7
7
|
notice is packaged at `site/vendor/THREE-LICENSE.txt`. Imported scene and building
|
|
8
8
|
modules use the consuming application's `three` dependency instead.
|
|
9
9
|
|
|
10
|
+
The standalone site's assets also include `@a2f0/ct-client` from
|
|
11
|
+
[a2f0/ct](https://github.com/a2f0/ct), copied to `site/vendor/ct-client.js`.
|
|
12
|
+
Its package metadata uses `UNLICENSED`; this release does not grant a license
|
|
13
|
+
for that code. Importing or mounting the reusable viewer does not initialize it.
|
|
14
|
+
|
|
10
15
|
Mapped building footprints, building parts, and streets contain data from
|
|
11
16
|
© OpenStreetMap contributors, under the Open Database License (ODbL) 1.0:
|
|
12
17
|
|
package/docs/package.md
CHANGED
|
@@ -87,6 +87,11 @@ it does not install Skyline's development dependencies or run dependency
|
|
|
87
87
|
lifecycle scripts. Keep `trustedDependencies` unchanged. Run `bun install` after
|
|
88
88
|
adding the script. Both the published tarball and this GitHub build expose the
|
|
89
89
|
same entrypoints, declarations, and complete viewer assets.
|
|
90
|
+
The declared `@a2f0/ct-client` dependency supplies the standalone site's pinned
|
|
91
|
+
analytics asset during this build. Package imports and viewer mounts do not
|
|
92
|
+
initialize tracking; only the standalone production site's page bootstraps do.
|
|
93
|
+
Npm consumers also receive this dependency, although their tarball already
|
|
94
|
+
contains the built asset, so both install methods keep the same dependency graph.
|
|
90
95
|
|
|
91
96
|
## Copy the viewer assets
|
|
92
97
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a2f0/skyline",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.38",
|
|
4
4
|
"description": "Interactive Chicago skyline viewer and reusable Three.js building models",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
+
"@a2f0/ct-client": "0.1.2",
|
|
55
56
|
"@types/three": "0.186.0"
|
|
56
57
|
},
|
|
57
58
|
"scripts": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<link rel="stylesheet" href="study.css" />
|
|
11
11
|
<!-- Framed by another page, the page leaves its links to the frame's own controls. -->
|
|
12
12
|
<script>if (window.frameElement) document.documentElement.classList.add("framed");</script>
|
|
13
|
+
<script type="module" src="click-tracking-page.js"></script>
|
|
13
14
|
</head>
|
|
14
15
|
<body>
|
|
15
16
|
<!-- The viewer's panel renders .detail-root in a shadow root of its own (skyline-3d.ts). -->
|
|
@@ -34,15 +35,15 @@
|
|
|
34
35
|
</div>
|
|
35
36
|
<div class="toolbar" aria-label="Building detail controls">
|
|
36
37
|
<div class="button-group" role="group" aria-label="Camera views">
|
|
37
|
-
<button type="button" data-view="front">front</button>
|
|
38
|
-
<button type="button" data-view="quarter" aria-pressed="true">three-quarter</button>
|
|
39
|
-
<button type="button" data-view="side">side</button>
|
|
40
|
-
<button type="button" id="reset">reset view</button>
|
|
38
|
+
<button type="button" data-view="front" data-ct="detail.view.front">front</button>
|
|
39
|
+
<button type="button" data-view="quarter" data-ct="detail.view.quarter" aria-pressed="true">three-quarter</button>
|
|
40
|
+
<button type="button" data-view="side" data-ct="detail.view.side">side</button>
|
|
41
|
+
<button type="button" id="reset" data-ct="detail.reset">reset view</button>
|
|
41
42
|
</div>
|
|
42
43
|
<div class="button-group" role="group" aria-label="Model display">
|
|
43
|
-
<button type="button" id="wireframe" aria-pressed="false">wireframe</button>
|
|
44
|
-
<button type="button" id="colour" aria-pressed="false" title="Show the building in sunny-day colour">colour</button>
|
|
45
|
-
<button type="button" id="turntable" aria-pressed="false" aria-describedby="motion-status">turntable</button>
|
|
44
|
+
<button type="button" id="wireframe" data-ct="detail.wireframe" aria-pressed="false">wireframe</button>
|
|
45
|
+
<button type="button" id="colour" data-ct="detail.colour" aria-pressed="false" title="Show the building in sunny-day colour">colour</button>
|
|
46
|
+
<button type="button" id="turntable" data-ct="detail.turntable" aria-pressed="false" aria-describedby="motion-status">turntable</button>
|
|
46
47
|
</div>
|
|
47
48
|
</div>
|
|
48
49
|
</div>
|
package/site/building-study.html
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createTracker } from "./vendor/ct-client.js";
|
|
2
|
+
// Only the standalone site's pages load this entry. Package mounts never import it.
|
|
3
|
+
// Use fixed routes: building IDs, embed settings and other URL values stay private.
|
|
4
|
+
const routes = {
|
|
5
|
+
"/": "/",
|
|
6
|
+
"/index.html": "/",
|
|
7
|
+
"/skyline-3d": "/skyline-3d",
|
|
8
|
+
"/skyline-3d.html": "/skyline-3d",
|
|
9
|
+
"/building-detail": "/building-detail",
|
|
10
|
+
"/building-detail.html": "/building-detail",
|
|
11
|
+
"/skyline-study": "/skyline-study",
|
|
12
|
+
"/skyline-study.html": "/skyline-study",
|
|
13
|
+
"/building-study": "/building-study",
|
|
14
|
+
"/building-study.html": "/building-study",
|
|
15
|
+
"/skyline-webgl": "/skyline-webgl",
|
|
16
|
+
"/skyline-webgl.html": "/skyline-webgl",
|
|
17
|
+
"/skyline-animated.svg": "/skyline-animated.svg",
|
|
18
|
+
};
|
|
19
|
+
const route = routes[location.pathname];
|
|
20
|
+
if (location.origin === "https://skyline.devopsrockstars.com" && route) {
|
|
21
|
+
const tracker = createTracker({ site: "skyline", capture: "all", overrideGpc: true, page: () => route });
|
|
22
|
+
tracker.init();
|
|
23
|
+
}
|
package/site/index.html
CHANGED
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
document.querySelector('meta[name="theme-color"]').content = light ? "#f5f5f5" : "#000000";
|
|
50
50
|
}
|
|
51
51
|
</script>
|
|
52
|
+
<script type="module" src="click-tracking-page.js"></script>
|
|
52
53
|
</head>
|
|
53
54
|
<body>
|
|
54
55
|
<!-- The viewer renders into a shadow root inside this element, as in a host's page. -->
|
package/site/skyline-3d.html
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
<link rel="icon" href="data:," />
|
|
9
9
|
<link rel="stylesheet" href="study.css" />
|
|
10
10
|
<title>3D Chicago Skyline</title>
|
|
11
|
+
<script type="module" src="click-tracking-page.js"></script>
|
|
11
12
|
</head>
|
|
12
13
|
<body class="skyline-3d">
|
|
13
14
|
<main class="viewport" id="viewport">
|
|
@@ -32,35 +33,35 @@
|
|
|
32
33
|
<p class="visually-hidden" id="celebration-announcement" role="status"></p>
|
|
33
34
|
<noscript><style>#loading { display: none; }</style><p class="loading">Enable JavaScript to explore the 3D skyline.</p></noscript>
|
|
34
35
|
<section class="detail-panel" id="building-detail" role="dialog" aria-label="Building detail" hidden>
|
|
35
|
-
<button type="button" class="detail-close" id="detail-close" aria-label="Close building detail" title="Close (Esc)">×</button>
|
|
36
|
+
<button type="button" class="detail-close" id="detail-close" data-ct="skyline.detail-close" aria-label="Close building detail" title="Close (Esc)">×</button>
|
|
36
37
|
</section>
|
|
37
38
|
</main>
|
|
38
39
|
<footer class="control-bar" aria-label="3D skyline controls">
|
|
39
40
|
<!-- Chicago's six-pointed star, after devopsrockstars' white-star-only.svg, drawn in
|
|
40
41
|
the bar's muted grey: points 12 units out, the notches between them 4.66. -->
|
|
41
|
-
<button type="button" class="menu-toggle" id="menu-toggle" aria-expanded="false" aria-controls="camera-views model-display scene-notes" aria-label="Skyline controls" title="Show the controls">
|
|
42
|
+
<button type="button" class="menu-toggle" id="menu-toggle" data-ct="skyline.menu-toggle" aria-expanded="false" aria-controls="camera-views model-display scene-notes" aria-label="Skyline controls" title="Show the controls">
|
|
42
43
|
<svg viewBox="-12 -12 24 24" aria-hidden="true" focusable="false"><path d="M0-12 2.33-4.04 10.39-6 4.66 0 10.39 6 2.33 4.04 0 12-2.33 4.04-10.39 6-4.66 0-10.39-6-2.33-4.04Z" /></svg>
|
|
43
44
|
</button>
|
|
44
45
|
<div class="button-group camera-views" id="camera-views" role="group" aria-label="Camera views">
|
|
45
|
-
<button type="button" data-view="skyline" aria-pressed="true">skyline view</button>
|
|
46
|
-
<button type="button" data-view="quarter" aria-pressed="false">three-quarter</button>
|
|
47
|
-
<button type="button" data-view="side" aria-pressed="false">side</button>
|
|
48
|
-
<button type="button" data-view="top" aria-pressed="false">ground plan ↓</button>
|
|
49
|
-
<button type="button" data-view="heights" aria-pressed="false">height comparison</button>
|
|
50
|
-
<button type="button" id="reset">reset view</button>
|
|
46
|
+
<button type="button" data-view="skyline" data-ct="skyline.view.skyline" aria-pressed="true">skyline view</button>
|
|
47
|
+
<button type="button" data-view="quarter" data-ct="skyline.view.quarter" aria-pressed="false">three-quarter</button>
|
|
48
|
+
<button type="button" data-view="side" data-ct="skyline.view.side" aria-pressed="false">side</button>
|
|
49
|
+
<button type="button" data-view="top" data-ct="skyline.view.top" aria-pressed="false">ground plan ↓</button>
|
|
50
|
+
<button type="button" data-view="heights" data-ct="skyline.view.heights" aria-pressed="false">height comparison</button>
|
|
51
|
+
<button type="button" id="reset" data-ct="skyline.reset">reset view</button>
|
|
51
52
|
</div>
|
|
52
53
|
<div class="button-group model-display" id="model-display" role="group" aria-label="Model display">
|
|
53
|
-
<button type="button" id="streets" aria-pressed="true">streets</button>
|
|
54
|
-
<button type="button" id="footprints" aria-pressed="false">footprints</button>
|
|
55
|
-
<button type="button" id="wireframe" aria-pressed="false">wireframe</button>
|
|
56
|
-
<button type="button" id="colour" aria-pressed="false" title="Colour trial: a sunny day, each measured building in its photographed colours; the rest stay grey">colour</button>
|
|
57
|
-
<button type="button" id="turntable" aria-pressed="false" aria-describedby="motion-status">turntable</button>
|
|
58
|
-
<button type="button" id="show-original" title="Compare with the original skyline SVG" hidden>show original</button>
|
|
54
|
+
<button type="button" id="streets" data-ct="skyline.streets" aria-pressed="true">streets</button>
|
|
55
|
+
<button type="button" id="footprints" data-ct="skyline.footprints" aria-pressed="false">footprints</button>
|
|
56
|
+
<button type="button" id="wireframe" data-ct="skyline.wireframe" aria-pressed="false">wireframe</button>
|
|
57
|
+
<button type="button" id="colour" data-ct="skyline.colour" aria-pressed="false" title="Colour trial: a sunny day, each measured building in its photographed colours; the rest stay grey">colour</button>
|
|
58
|
+
<button type="button" id="turntable" data-ct="skyline.turntable" aria-pressed="false" aria-describedby="motion-status">turntable</button>
|
|
59
|
+
<button type="button" id="show-original" data-ct="skyline.show-original" title="Compare with the original skyline SVG" hidden>show original</button>
|
|
59
60
|
<div class="time-controls">
|
|
60
61
|
<label for="time-of-day"><span id="time-caption">Chicago time</span> <span id="time-date"></span></label>
|
|
61
62
|
<input id="time-of-day" type="range" min="0" max="1439" step="1" value="0" aria-labelledby="time-caption" />
|
|
62
63
|
<output id="time-value" for="time-of-day" aria-live="off">original night</output>
|
|
63
|
-
<button type="button" id="reset-light" disabled title="Restore the original lighting preset">reset light</button>
|
|
64
|
+
<button type="button" id="reset-light" data-ct="skyline.reset-light" disabled title="Restore the original lighting preset">reset light</button>
|
|
64
65
|
</div>
|
|
65
66
|
<div class="celebration-controls" id="celebration-controls" role="group" aria-label="Celebratory window lighting">
|
|
66
67
|
<span class="celebration-caption">lights</span>
|
|
@@ -107,7 +108,7 @@
|
|
|
107
108
|
<div class="context-menu" id="building-menu" hidden>
|
|
108
109
|
<p class="context-menu-title" id="building-menu-title"></p>
|
|
109
110
|
<div role="menu" aria-labelledby="building-menu-title">
|
|
110
|
-
<a role="menuitem" id="building-detail-link" href="building-detail.html">building detail</a>
|
|
111
|
+
<a role="menuitem" id="building-detail-link" data-ct="skyline.building-detail-link" href="building-detail.html">building detail</a>
|
|
111
112
|
<div id="building-lighting-menu" role="group" aria-label="Celebratory window lighting" hidden></div>
|
|
112
113
|
</div>
|
|
113
114
|
</div>
|
|
@@ -18684,6 +18684,8 @@
|
|
|
18684
18684
|
font-weight="600">Building name</text>
|
|
18685
18685
|
</g>
|
|
18686
18686
|
<script><![CDATA[
|
|
18687
|
+
// Analytics is optional and must not prevent the drawing from loading.
|
|
18688
|
+
import("./click-tracking-page.js").catch(() => {});
|
|
18687
18689
|
(() => {
|
|
18688
18690
|
const svg = document.documentElement;
|
|
18689
18691
|
const tooltip = document.querySelector("#building-tooltip");
|
package/site/skyline-study.html
CHANGED
package/site/skyline-viewer.js
CHANGED
|
@@ -266,16 +266,16 @@ export function mount(root, { assets, signal, controls, colour, theme, attributi
|
|
|
266
266
|
function createToolbar() {
|
|
267
267
|
const nav = element(document, "nav", { className: "controls" }, { "aria-label": "Skyline controls" });
|
|
268
268
|
const button = (id, text, attributes = {}) => element(document, "button", { type: "button", id, textContent: text }, { "aria-pressed": "false", ...attributes });
|
|
269
|
-
const fullscreenButton = button("fullscreen", "fullscreen");
|
|
269
|
+
const fullscreenButton = button("fullscreen", "fullscreen", { "data-ct": "viewer.fullscreen" });
|
|
270
270
|
fullscreenButton.hidden = !document.fullscreenEnabled;
|
|
271
271
|
fullscreenButton.addEventListener("click", () => void toggleFullscreen(), listening);
|
|
272
272
|
nav.append(fullscreenButton);
|
|
273
273
|
for (const [name, toggle] of Object.entries(toggles)) {
|
|
274
|
-
const toggleButton = button(toggle.id, toggle.label);
|
|
274
|
+
const toggleButton = button(toggle.id, toggle.label, { "data-ct": `viewer.mode.${name}` });
|
|
275
275
|
toggleButton.addEventListener("click", () => showMode(mode === name ? "3d" : name), listening);
|
|
276
276
|
nav.append(toggleButton);
|
|
277
277
|
}
|
|
278
|
-
nav.append(element(document, "a", { href: new URL("building-study.html", assets).href, textContent: "3d building" }), element(document, "a", { href: new URL("skyline-study.html", assets).href, textContent: "skyline study" }));
|
|
278
|
+
nav.append(element(document, "a", { href: new URL("building-study.html", assets).href, textContent: "3d building" }, { "data-ct": "viewer.building-study" }), element(document, "a", { href: new URL("skyline-study.html", assets).href, textContent: "skyline study" }, { "data-ct": "viewer.skyline-study" }));
|
|
279
279
|
const debugButton = button("debug-motion", "debug motion", { "aria-describedby": "stars-motion-status" });
|
|
280
280
|
const motionStatus = element(document, "p", { className: "motion-status", id: "stars-motion-status" }, { role: "status" });
|
|
281
281
|
nav.append(debugButton, motionStatus);
|
package/site/skyline-webgl.html
CHANGED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// src/privacy.ts
|
|
2
|
+
var OPT_OUT_KEY = "ct:opt-out";
|
|
3
|
+
var OPT_OUT_EVENT = "ct:opt-out-change";
|
|
4
|
+
var EXTENSION_ATTRIBUTE = "data-ct-disabled";
|
|
5
|
+
function optedOut(overrideGpc = false) {
|
|
6
|
+
try {
|
|
7
|
+
return navigator.webdriver === true || navigator.doNotTrack === "1" || !overrideGpc && navigator.globalPrivacyControl === true || document.documentElement?.hasAttribute(EXTENSION_ATTRIBUTE) === true || localStorage.getItem(OPT_OUT_KEY) === "1";
|
|
8
|
+
} catch {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function safePage(value) {
|
|
13
|
+
const path = value.split(/[?#]/, 1)[0] ?? "/";
|
|
14
|
+
return /^\/[a-zA-Z0-9/:_.-]{0,199}$/.test(path) ? path : "/";
|
|
15
|
+
}
|
|
16
|
+
function clickTarget(event, capture) {
|
|
17
|
+
const path = event.composedPath();
|
|
18
|
+
const elements = path.filter((item) => item instanceof Element);
|
|
19
|
+
if (elements.some((element) => element.matches("[data-ct-ignore], input, textarea, select, [contenteditable]:not([contenteditable='false'])")))
|
|
20
|
+
return null;
|
|
21
|
+
const selector = capture === "marked" ? "[data-ct]" : "[data-ct], a, button, [role='button'], [role='link']";
|
|
22
|
+
const target = elements.find((element) => element.matches(selector)) ?? (capture === "all" ? elements[0] : undefined);
|
|
23
|
+
if (!target)
|
|
24
|
+
return null;
|
|
25
|
+
const label = target.getAttribute("data-ct");
|
|
26
|
+
const tag = target.tagName.toLowerCase();
|
|
27
|
+
return {
|
|
28
|
+
target: /^[a-z][a-z0-9-]{0,31}$/.test(tag) ? tag : "unknown",
|
|
29
|
+
label: label && /^[a-zA-Z0-9_.:-]{1,80}$/.test(label) ? label : null
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/index.ts
|
|
34
|
+
function createTracker(options) {
|
|
35
|
+
const batchSize = bounded(options.batchSize, 20, 1, 50);
|
|
36
|
+
const interval = bounded(options.flushIntervalMs, 5000, 100, 60000);
|
|
37
|
+
const capacity = bounded(options.maxQueueSize, 500, batchSize, 1000);
|
|
38
|
+
const endpoint = options.endpoint ?? "https://ct.a2f0.net/v1/clicks";
|
|
39
|
+
const overrideGpc = options.overrideGpc === true;
|
|
40
|
+
let initialized = false;
|
|
41
|
+
let disabled = false;
|
|
42
|
+
let queue = [];
|
|
43
|
+
let timer;
|
|
44
|
+
let request;
|
|
45
|
+
let inFlight;
|
|
46
|
+
let failures = 0;
|
|
47
|
+
let generation = 0;
|
|
48
|
+
function clear() {
|
|
49
|
+
generation++;
|
|
50
|
+
failures = 0;
|
|
51
|
+
queue = [];
|
|
52
|
+
if (timer)
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
timer = undefined;
|
|
55
|
+
request?.abort();
|
|
56
|
+
}
|
|
57
|
+
function blocked() {
|
|
58
|
+
if (disabled || optedOut(overrideGpc)) {
|
|
59
|
+
clear();
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
function schedule() {
|
|
65
|
+
if (!initialized || !queue.length || timer)
|
|
66
|
+
return;
|
|
67
|
+
const delay = failures ? Math.min(60000, interval * 2 ** Math.min(failures, 5)) : interval;
|
|
68
|
+
timer = setTimeout(() => {
|
|
69
|
+
timer = undefined;
|
|
70
|
+
flush();
|
|
71
|
+
}, delay);
|
|
72
|
+
}
|
|
73
|
+
async function send() {
|
|
74
|
+
const currentGeneration = generation;
|
|
75
|
+
const batch = queue.slice(0, batchSize);
|
|
76
|
+
const controller = new AbortController;
|
|
77
|
+
request = controller;
|
|
78
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
79
|
+
try {
|
|
80
|
+
const response = await fetch(endpoint, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: { "Content-Type": "text/plain;charset=UTF-8" },
|
|
83
|
+
body: JSON.stringify({ site: options.site, events: batch }),
|
|
84
|
+
credentials: "omit",
|
|
85
|
+
referrerPolicy: "no-referrer",
|
|
86
|
+
keepalive: true,
|
|
87
|
+
signal: controller.signal
|
|
88
|
+
});
|
|
89
|
+
if (currentGeneration !== generation)
|
|
90
|
+
return;
|
|
91
|
+
if (response.ok || response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429) {
|
|
92
|
+
const ids = new Set(batch.map((event) => event.id));
|
|
93
|
+
queue = queue.filter((event) => !ids.has(event.id));
|
|
94
|
+
failures = 0;
|
|
95
|
+
} else
|
|
96
|
+
failures++;
|
|
97
|
+
} catch {
|
|
98
|
+
if (currentGeneration === generation)
|
|
99
|
+
failures++;
|
|
100
|
+
} finally {
|
|
101
|
+
clearTimeout(timeout);
|
|
102
|
+
if (request === controller)
|
|
103
|
+
request = undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function flush() {
|
|
107
|
+
if (!initialized || blocked())
|
|
108
|
+
return Promise.resolve();
|
|
109
|
+
if (inFlight)
|
|
110
|
+
return inFlight;
|
|
111
|
+
if (!queue.length)
|
|
112
|
+
return Promise.resolve();
|
|
113
|
+
if (timer)
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
timer = undefined;
|
|
116
|
+
inFlight = send().finally(() => {
|
|
117
|
+
inFlight = undefined;
|
|
118
|
+
schedule();
|
|
119
|
+
});
|
|
120
|
+
return inFlight;
|
|
121
|
+
}
|
|
122
|
+
function click(event) {
|
|
123
|
+
if (!event.isTrusted || blocked())
|
|
124
|
+
return;
|
|
125
|
+
const target = clickTarget(event, options.capture ?? "interactive");
|
|
126
|
+
if (!target || queue.length >= capacity)
|
|
127
|
+
return;
|
|
128
|
+
try {
|
|
129
|
+
queue.push({
|
|
130
|
+
id: crypto.randomUUID(),
|
|
131
|
+
timestamp: Date.now(),
|
|
132
|
+
page: safePage(options.page?.() ?? "/"),
|
|
133
|
+
...target
|
|
134
|
+
});
|
|
135
|
+
} catch {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (queue.length >= batchSize && !failures)
|
|
139
|
+
flush();
|
|
140
|
+
else
|
|
141
|
+
schedule();
|
|
142
|
+
}
|
|
143
|
+
function privacyChanged() {
|
|
144
|
+
blocked();
|
|
145
|
+
}
|
|
146
|
+
function visibilityChanged() {
|
|
147
|
+
if (document.visibilityState === "hidden")
|
|
148
|
+
flush();
|
|
149
|
+
}
|
|
150
|
+
function pageHidden() {
|
|
151
|
+
flush();
|
|
152
|
+
}
|
|
153
|
+
function online() {
|
|
154
|
+
failures = 0;
|
|
155
|
+
flush();
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
init() {
|
|
159
|
+
if (initialized)
|
|
160
|
+
return !blocked();
|
|
161
|
+
if (options.enabled === false || typeof window === "undefined" || typeof document === "undefined")
|
|
162
|
+
return false;
|
|
163
|
+
if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(options.site))
|
|
164
|
+
throw new Error("Invalid ct site identifier");
|
|
165
|
+
const url = new URL(endpoint);
|
|
166
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1"].includes(url.hostname)))
|
|
167
|
+
throw new Error("ct requires an HTTPS endpoint");
|
|
168
|
+
if (navigator.webdriver === true)
|
|
169
|
+
return false;
|
|
170
|
+
initialized = true;
|
|
171
|
+
document.addEventListener("click", click, true);
|
|
172
|
+
document.addEventListener("visibilitychange", visibilityChanged);
|
|
173
|
+
window.addEventListener("pagehide", pageHidden);
|
|
174
|
+
window.addEventListener("online", online);
|
|
175
|
+
window.addEventListener("storage", privacyChanged);
|
|
176
|
+
document.addEventListener(OPT_OUT_EVENT, privacyChanged);
|
|
177
|
+
return !blocked();
|
|
178
|
+
},
|
|
179
|
+
flush,
|
|
180
|
+
destroy() {
|
|
181
|
+
if (initialized) {
|
|
182
|
+
document.removeEventListener("click", click, true);
|
|
183
|
+
document.removeEventListener("visibilitychange", visibilityChanged);
|
|
184
|
+
window.removeEventListener("pagehide", pageHidden);
|
|
185
|
+
window.removeEventListener("online", online);
|
|
186
|
+
window.removeEventListener("storage", privacyChanged);
|
|
187
|
+
document.removeEventListener(OPT_OUT_EVENT, privacyChanged);
|
|
188
|
+
}
|
|
189
|
+
initialized = false;
|
|
190
|
+
clear();
|
|
191
|
+
},
|
|
192
|
+
setOptOut(value) {
|
|
193
|
+
disabled = value;
|
|
194
|
+
if (value)
|
|
195
|
+
clear();
|
|
196
|
+
if (typeof window === "undefined")
|
|
197
|
+
return;
|
|
198
|
+
try {
|
|
199
|
+
if (value)
|
|
200
|
+
localStorage.setItem(OPT_OUT_KEY, "1");
|
|
201
|
+
else
|
|
202
|
+
localStorage.removeItem(OPT_OUT_KEY);
|
|
203
|
+
} catch {}
|
|
204
|
+
document.dispatchEvent(new Event(OPT_OUT_EVENT));
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function bounded(value, fallback, min, max) {
|
|
209
|
+
return value !== undefined && Number.isFinite(value) ? Math.max(min, Math.min(max, Math.floor(value))) : fallback;
|
|
210
|
+
}
|
|
211
|
+
export {
|
|
212
|
+
createTracker
|
|
213
|
+
};
|