@aglyn/shared-util-first-touch 1.0.0-beta.186

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.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { FirstTouchRuntime } from './first-touch';
18
+ /**
19
+ * The bridge between a SERVED capture script and the page's own code.
20
+ *
21
+ * A surface that includes the capture as a script tag has no import to call
22
+ * it through, and its consent code — which decides whether the record may be
23
+ * written to the device — runs in a bundle that never loaded the capture. So
24
+ * the served script leaves its runtime on `window`, and this module is the
25
+ * one place that knows where. It imports nothing but a type, which is the
26
+ * point: a page pays for these few lines, not for the capture.
27
+ *
28
+ * Consent can be decided before the script has booted (the script is loaded
29
+ * `async`), so a decision is also left where the script reads it at boot.
30
+ */
31
+ /** Where a served script leaves its runtime. */
32
+ export declare const FIRST_TOUCH_PAGE_GLOBAL = "aglynFirstTouch";
33
+ /** Where a storage decision made before the script booted waits for it. */
34
+ export declare const FIRST_TOUCH_PAGE_STORAGE_GLOBAL = "__aglynFirstTouchStorage";
35
+ /** The runtime a served script booted on this page, or null. */
36
+ export declare function pageFirstTouchRuntime(): FirstTouchRuntime | null;
37
+ /**
38
+ * Tell the page's capture whether it may keep the record on the device:
39
+ * `true` to grant, `false` to refuse (and erase what it kept), `null` while
40
+ * the visitor's consent is unresolved. Reaches a runtime that is already
41
+ * running and waits for one that has not booted yet.
42
+ */
43
+ export declare function setPageFirstTouchStorage(allowed: boolean | null): void;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * The bridge between a SERVED capture script and the page's own code.
18
+ *
19
+ * A surface that includes the capture as a script tag has no import to call
20
+ * it through, and its consent code — which decides whether the record may be
21
+ * written to the device — runs in a bundle that never loaded the capture. So
22
+ * the served script leaves its runtime on `window`, and this module is the
23
+ * one place that knows where. It imports nothing but a type, which is the
24
+ * point: a page pays for these few lines, not for the capture.
25
+ *
26
+ * Consent can be decided before the script has booted (the script is loaded
27
+ * `async`), so a decision is also left where the script reads it at boot.
28
+ */ /** Where a served script leaves its runtime. */ export const FIRST_TOUCH_PAGE_GLOBAL = 'aglynFirstTouch';
29
+ /** Where a storage decision made before the script booted waits for it. */ export const FIRST_TOUCH_PAGE_STORAGE_GLOBAL = '__aglynFirstTouchStorage';
30
+ function pageWindow() {
31
+ return typeof window === 'undefined' ? null : window;
32
+ }
33
+ /** The runtime a served script booted on this page, or null. */ export function pageFirstTouchRuntime() {
34
+ var _pageWindow;
35
+ const runtime = (_pageWindow = pageWindow()) == null ? void 0 : _pageWindow[FIRST_TOUCH_PAGE_GLOBAL];
36
+ return runtime && typeof runtime.read === 'function' && typeof runtime.setStorage === 'function' ? runtime : null;
37
+ }
38
+ /**
39
+ * Tell the page's capture whether it may keep the record on the device:
40
+ * `true` to grant, `false` to refuse (and erase what it kept), `null` while
41
+ * the visitor's consent is unresolved. Reaches a runtime that is already
42
+ * running and waits for one that has not booted yet.
43
+ */ export function setPageFirstTouchStorage(allowed) {
44
+ var _pageFirstTouchRuntime;
45
+ const page = pageWindow();
46
+ if (!page) return;
47
+ const value = allowed === true ? true : allowed === false ? false : null;
48
+ page[FIRST_TOUCH_PAGE_STORAGE_GLOBAL] = value;
49
+ (_pageFirstTouchRuntime = pageFirstTouchRuntime()) == null ? void 0 : _pageFirstTouchRuntime.setStorage(value);
50
+ }
51
+
52
+ //# sourceMappingURL=first-touch-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/shared/util/first-touch/src/lib/first-touch-page.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { FirstTouchRuntime } from './first-touch'\n\n/**\n * The bridge between a SERVED capture script and the page's own code.\n *\n * A surface that includes the capture as a script tag has no import to call\n * it through, and its consent code — which decides whether the record may be\n * written to the device — runs in a bundle that never loaded the capture. So\n * the served script leaves its runtime on `window`, and this module is the\n * one place that knows where. It imports nothing but a type, which is the\n * point: a page pays for these few lines, not for the capture.\n *\n * Consent can be decided before the script has booted (the script is loaded\n * `async`), so a decision is also left where the script reads it at boot.\n */\n\n/** Where a served script leaves its runtime. */\nexport const FIRST_TOUCH_PAGE_GLOBAL = 'aglynFirstTouch'\n\n/** Where a storage decision made before the script booted waits for it. */\nexport const FIRST_TOUCH_PAGE_STORAGE_GLOBAL = '__aglynFirstTouchStorage'\n\nfunction pageWindow(): Record<string, unknown> | null {\n return typeof window === 'undefined'\n ? null\n : (window as unknown as Record<string, unknown>)\n}\n\n/** The runtime a served script booted on this page, or null. */\nexport function pageFirstTouchRuntime(): FirstTouchRuntime | null {\n const runtime = pageWindow()?.[FIRST_TOUCH_PAGE_GLOBAL] as\n | Partial<FirstTouchRuntime>\n | undefined\n return runtime &&\n typeof runtime.read === 'function' &&\n typeof runtime.setStorage === 'function'\n ? (runtime as FirstTouchRuntime)\n : null\n}\n\n/**\n * Tell the page's capture whether it may keep the record on the device:\n * `true` to grant, `false` to refuse (and erase what it kept), `null` while\n * the visitor's consent is unresolved. Reaches a runtime that is already\n * running and waits for one that has not booted yet.\n */\nexport function setPageFirstTouchStorage(allowed: boolean | null): void {\n const page = pageWindow()\n if (!page) return\n const value = allowed === true ? true : allowed === false ? false : null\n page[FIRST_TOUCH_PAGE_STORAGE_GLOBAL] = value\n pageFirstTouchRuntime()?.setStorage(value)\n}\n"],"names":["FIRST_TOUCH_PAGE_GLOBAL","FIRST_TOUCH_PAGE_STORAGE_GLOBAL","pageWindow","window","pageFirstTouchRuntime","runtime","read","setStorage","setPageFirstTouchStorage","allowed","page","value"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;;;;CAYC,GAED,8CAA8C,GAC9C,OAAO,MAAMA,0BAA0B,kBAAiB;AAExD,yEAAyE,GACzE,OAAO,MAAMC,kCAAkC,2BAA0B;AAEzE,SAASC;IACP,OAAO,OAAOC,WAAW,cACrB,OACCA;AACP;AAEA,8DAA8D,GAC9D,OAAO,SAASC;QACEF;IAAhB,MAAMG,WAAUH,cAAAA,iCAAAA,WAAc,CAACF,wBAAwB;IAGvD,OAAOK,WACL,OAAOA,QAAQC,IAAI,KAAK,cACxB,OAAOD,QAAQE,UAAU,KAAK,aAC3BF,UACD;AACN;AAEA;;;;;CAKC,GACD,OAAO,SAASG,yBAAyBC,OAAuB;QAK9DL;IAJA,MAAMM,OAAOR;IACb,IAAI,CAACQ,MAAM;IACX,MAAMC,QAAQF,YAAY,OAAO,OAAOA,YAAY,QAAQ,QAAQ;IACpEC,IAAI,CAACT,gCAAgC,GAAGU;KACxCP,yBAAAA,4CAAAA,uBAAyBG,UAAU,CAACI;AACtC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type FirstTouchConfig } from './first-touch';
18
+ /**
19
+ * The capture as a script a page runs with no bundler: the kit's own source
20
+ * text, called with this surface's configuration.
21
+ *
22
+ * Served by the platform at one URL so that including the capture on a new
23
+ * surface is one tag — `<script src="…/api/first-touch" async>` — and the
24
+ * host registry, the hand-off endpoint and the storage decision arrive with
25
+ * it rather than being configured on each surface.
26
+ *
27
+ * Two page-side overrides, both read at boot:
28
+ *
29
+ * - `data-consent="pending"` on the tag holds the record in memory until the
30
+ * page's consent code grants storage — for a surface whose own consent tool
31
+ * decides after load;
32
+ * - a decision the page's code already left through
33
+ * `setPageFirstTouchStorage` before the script arrived wins over both the
34
+ * attribute and the served default, because it is the newest answer.
35
+ *
36
+ * Everything runs inside a `try`: a capture that throws must never cost the
37
+ * page it runs on.
38
+ */
39
+ export declare function firstTouchScript(config: FirstTouchConfig): string;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { createFirstTouchKit } from "./first-touch.js";
17
+ import { FIRST_TOUCH_PAGE_GLOBAL, FIRST_TOUCH_PAGE_STORAGE_GLOBAL } from "./first-touch-page.js";
18
+ /**
19
+ * The capture as a script a page runs with no bundler: the kit's own source
20
+ * text, called with this surface's configuration.
21
+ *
22
+ * Served by the platform at one URL so that including the capture on a new
23
+ * surface is one tag — `<script src="…/api/first-touch" async>` — and the
24
+ * host registry, the hand-off endpoint and the storage decision arrive with
25
+ * it rather than being configured on each surface.
26
+ *
27
+ * Two page-side overrides, both read at boot:
28
+ *
29
+ * - `data-consent="pending"` on the tag holds the record in memory until the
30
+ * page's consent code grants storage — for a surface whose own consent tool
31
+ * decides after load;
32
+ * - a decision the page's code already left through
33
+ * `setPageFirstTouchStorage` before the script arrived wins over both the
34
+ * attribute and the served default, because it is the newest answer.
35
+ *
36
+ * Everything runs inside a `try`: a capture that throws must never cost the
37
+ * page it runs on.
38
+ */ export function firstTouchScript(config) {
39
+ var _config_hosts, _config_handoffUrl;
40
+ const served = JSON.stringify({
41
+ hosts: [
42
+ ...(_config_hosts = config.hosts) != null ? _config_hosts : []
43
+ ],
44
+ storage: config.storage === undefined ? true : config.storage,
45
+ handoffUrl: (_config_handoffUrl = config.handoffUrl) != null ? _config_handoffUrl : null
46
+ })// Inline in a `<script>` element as well as served, so nothing in the
47
+ // configuration may close the element or break a pre-2019 parser.
48
+ .replace(/</g, '\\u003c').replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
49
+ return '(function(){try{' + `var c=${served};` + 'var w=window;var s=document.currentScript;' + "if(s&&s.getAttribute('data-consent')==='pending')c.storage=null;" + `var q=w[${JSON.stringify(FIRST_TOUCH_PAGE_STORAGE_GLOBAL)}];` + 'if(q===true||q===false||q===null)c.storage=q;' + `w[${JSON.stringify(FIRST_TOUCH_PAGE_GLOBAL)}]=(${createFirstTouchKit.toString()})().boot(c);` + '}catch(e){}})();';
50
+ }
51
+
52
+ //# sourceMappingURL=first-touch-script.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/shared/util/first-touch/src/lib/first-touch-script.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createFirstTouchKit, type FirstTouchConfig } from './first-touch'\nimport {\n FIRST_TOUCH_PAGE_GLOBAL,\n FIRST_TOUCH_PAGE_STORAGE_GLOBAL,\n} from './first-touch-page'\n\n/**\n * The capture as a script a page runs with no bundler: the kit's own source\n * text, called with this surface's configuration.\n *\n * Served by the platform at one URL so that including the capture on a new\n * surface is one tag — `<script src=\"…/api/first-touch\" async>` — and the\n * host registry, the hand-off endpoint and the storage decision arrive with\n * it rather than being configured on each surface.\n *\n * Two page-side overrides, both read at boot:\n *\n * - `data-consent=\"pending\"` on the tag holds the record in memory until the\n * page's consent code grants storage — for a surface whose own consent tool\n * decides after load;\n * - a decision the page's code already left through\n * `setPageFirstTouchStorage` before the script arrived wins over both the\n * attribute and the served default, because it is the newest answer.\n *\n * Everything runs inside a `try`: a capture that throws must never cost the\n * page it runs on.\n */\nexport function firstTouchScript(config: FirstTouchConfig): string {\n const served = JSON.stringify({\n hosts: [...(config.hosts ?? [])],\n storage: config.storage === undefined ? true : config.storage,\n handoffUrl: config.handoffUrl ?? null,\n })\n // Inline in a `<script>` element as well as served, so nothing in the\n // configuration may close the element or break a pre-2019 parser.\n .replace(/</g, '\\\\u003c')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029')\n return (\n '(function(){try{' +\n `var c=${served};` +\n 'var w=window;var s=document.currentScript;' +\n \"if(s&&s.getAttribute('data-consent')==='pending')c.storage=null;\" +\n `var q=w[${JSON.stringify(FIRST_TOUCH_PAGE_STORAGE_GLOBAL)}];` +\n 'if(q===true||q===false||q===null)c.storage=q;' +\n `w[${JSON.stringify(FIRST_TOUCH_PAGE_GLOBAL)}]=(${createFirstTouchKit.toString()})().boot(c);` +\n '}catch(e){}})();'\n )\n}\n"],"names":["createFirstTouchKit","FIRST_TOUCH_PAGE_GLOBAL","FIRST_TOUCH_PAGE_STORAGE_GLOBAL","firstTouchScript","config","served","JSON","stringify","hosts","storage","undefined","handoffUrl","replace","toString"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,mBAAmB,QAA+B,mBAAe;AAC1E,SACEC,uBAAuB,EACvBC,+BAA+B,QAC1B,wBAAoB;AAE3B;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASC,iBAAiBC,MAAwB;QAEzCA,eAEAA;IAHd,MAAMC,SAASC,KAAKC,SAAS,CAAC;QAC5BC,OAAO;gBAAKJ,gBAAAA,OAAOI,KAAK,YAAZJ,gBAAgB,EAAE;SAAE;QAChCK,SAASL,OAAOK,OAAO,KAAKC,YAAY,OAAON,OAAOK,OAAO;QAC7DE,UAAU,GAAEP,qBAAAA,OAAOO,UAAU,YAAjBP,qBAAqB;IACnC,EACE,sEAAsE;IACtE,kEAAkE;KACjEQ,OAAO,CAAC,MAAM,WACdA,OAAO,CAAC,WAAW,WACnBA,OAAO,CAAC,WAAW;IACtB,OACE,qBACA,CAAC,MAAM,EAAEP,OAAO,CAAC,CAAC,GAClB,+CACA,qEACA,CAAC,QAAQ,EAAEC,KAAKC,SAAS,CAACL,iCAAiC,EAAE,CAAC,GAC9D,kDACA,CAAC,EAAE,EAAEI,KAAKC,SAAS,CAACN,yBAAyB,GAAG,EAAED,oBAAoBa,QAAQ,GAAG,YAAY,CAAC,GAC9F;AAEJ"}
@@ -0,0 +1,212 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * First-touch capture: where a visitor first arrived from, kept on their
19
+ * device until an account is created and the platform writes it down.
20
+ *
21
+ * ## The one rule everything else follows from
22
+ *
23
+ * A visitor rarely signs up on the page they landed on, and the page they
24
+ * landed on is not always the marketing site. They find a guide on the docs
25
+ * host through a search engine, read the pricing page, then sign up on the
26
+ * console. Three hosts, and only the first one saw where they came from.
27
+ *
28
+ * So every surface the platform serves includes this capture, and every one
29
+ * of them agrees on which referrers are its own. **An internal referrer is
30
+ * never a first touch.** Only an external referrer, or a landing with no
31
+ * referrer at all, starts the record; a hop between two of our own hosts
32
+ * carries the record forward and never replaces it. "docs → pricing →
33
+ * signup" therefore still reports the search engine that started it.
34
+ *
35
+ * ## Where the record lives
36
+ *
37
+ * - **A cookie on the registrable domain** of the surface (`.example.com`),
38
+ * found by asking the browser rather than by carrying a public-suffix list:
39
+ * the broadest domain that accepts a cookie is the registrable one. Every
40
+ * subdomain, and so every console door under it, reads the same value.
41
+ * - **`sessionStorage`**, when the browser refuses the cookie. It dies with
42
+ * the tab and does not cross subdomains, which is the price of a browser
43
+ * that refuses storage.
44
+ * - **Memory**, while the visitor's consent is unresolved or has been refused
45
+ * for this surface. Nothing is written to the device. A link to another of
46
+ * our hosts can still carry the record, sealed, in its URL (below).
47
+ *
48
+ * The cookie is re-written on every load with a fresh lifetime, because some
49
+ * browsers cap the life of a script-written cookie at a week of inactivity;
50
+ * the VALUE never changes once set.
51
+ *
52
+ * ## Crossing to a host the cookie cannot reach
53
+ *
54
+ * A surface on a different registrable domain, or any hop made while the
55
+ * record is held in memory, cannot read the cookie. For those links the
56
+ * capture asks the platform to SEAL the record (an HMAC over it and an
57
+ * expiry, signed with a secret no page holds) and appends the sealed token as
58
+ * `_ft` at the moment the link is followed. The receiving surface strips the
59
+ * parameter from its address bar at once, asks the platform to OPEN it, and
60
+ * adopts what comes back. Adoption is a merge, and the merge keeps the
61
+ * earlier record, so replaying a token can never move a first touch later.
62
+ *
63
+ * What the seal proves is narrow and worth stating exactly: that this install
64
+ * produced the record, recently, and nobody edited it on the way. It does not
65
+ * prove the record is TRUE — a visitor can put any `utm_*` they like on a URL
66
+ * and always could. Attribution is a label, and it grants nothing.
67
+ *
68
+ * ## What is never recorded
69
+ *
70
+ * No identifier. The record says how a visit began, and two visitors who
71
+ * arrived the same way carry the same record give or take a timestamp. Click
72
+ * ids are kept as PRESENCE only — "this arrived from a paid click" — and their
73
+ * values never leave the URL they came on. Referrers are kept as a HOST, never
74
+ * a path, because a referring path can carry a search query or an account
75
+ * page. `utm_*` values are trimmed, refused when shaped like an email
76
+ * address, and capped, the same scrub the signup campaign parser applies.
77
+ *
78
+ * ## Why the whole runtime is one function
79
+ *
80
+ * The same code must run as a module a Next app imports and as a script tag
81
+ * on a page no bundler touches (a hosted forum, a status page). So the
82
+ * runtime is ONE self-contained function, {@link createFirstTouchKit}, that
83
+ * references nothing outside itself: the served script is that function's own
84
+ * source text followed by a call to it. The constraint that keeps it working
85
+ * is the same one `next-themes` lives under — no imports, no module-level
86
+ * values, and no syntax a compiler would lower into a shared helper (object
87
+ * spread, classes, `async`). `first-touch-script.spec.ts` runs the
88
+ * stringified function in a bare context to hold that.
89
+ */
90
+ /** Click identifiers whose PRESENCE the record keeps; their values never leave the URL. */
91
+ export declare const FIRST_TOUCH_CLICK_IDS: readonly ["gclid", "fbclid", "msclkid"];
92
+ export type FirstTouchClickId = (typeof FIRST_TOUCH_CLICK_IDS)[number];
93
+ /** The `utm_*` parameters the record keeps, named without their prefix. */
94
+ export declare const FIRST_TOUCH_UTM_KEYS: readonly ["source", "medium", "campaign", "content", "term"];
95
+ export type FirstTouchUtmKey = (typeof FIRST_TOUCH_UTM_KEYS)[number];
96
+ export type FirstTouchUtm = Partial<Record<FirstTouchUtmKey, string>>;
97
+ /** The cookie the record is kept in, on the surface's registrable domain. */
98
+ export declare const FIRST_TOUCH_COOKIE = "aglyn_ft";
99
+ /** The query parameter a sealed hand-off rides on. */
100
+ export declare const FIRST_TOUCH_HANDOFF_PARAM = "_ft";
101
+ /** How a visit began, as the capture records it. */
102
+ export interface FirstTouch {
103
+ /** Record format. A reader refuses any other value rather than guess. */
104
+ v: 1;
105
+ /** When the visitor landed, epoch milliseconds. */
106
+ at: number;
107
+ /** The host of the first page they landed on. */
108
+ host: string;
109
+ /** That page's path, without its query string or fragment. */
110
+ path: string;
111
+ /** The EXTERNAL host that sent them, or null when nothing external did. */
112
+ ref: string | null;
113
+ /**
114
+ * One of our own hosts they arrived from before any surface had recorded
115
+ * them — the tell of a surface that does not include the capture yet.
116
+ */
117
+ via?: string;
118
+ /** The `utm_*` parameters the landing URL carried. */
119
+ utm?: FirstTouchUtm;
120
+ /** Which click identifiers the landing URL carried. */
121
+ click?: FirstTouchClickId[];
122
+ }
123
+ /** What {@link FirstTouchKit.buildFirstTouch} reads a landing from. */
124
+ export interface FirstTouchLanding {
125
+ /** The URL the visitor landed on. */
126
+ href: string;
127
+ /** `document.referrer` at that moment; empty when there was none. */
128
+ referrer?: string | null;
129
+ /** The hosts this install serves itself — see {@link FirstTouchConfig.hosts}. */
130
+ hosts: readonly string[];
131
+ /** The time to stamp, epoch milliseconds. */
132
+ now: number;
133
+ }
134
+ /** How a surface configures the capture. */
135
+ export interface FirstTouchConfig {
136
+ /**
137
+ * The hosts this install serves itself. An entry is an exact host
138
+ * (`example.com`) or a wildcard for every subdomain of one at any depth
139
+ * (`*.example.com`, which does not match `example.com` itself). A leading
140
+ * `!` EXCLUDES what the rest of the entry names, whatever else matches it:
141
+ * `!*.sites.example.com` keeps customer sites served under the operator's
142
+ * own domain external. A referrer on an included host is internal, and the
143
+ * capture runs only on a page whose host is included.
144
+ */
145
+ hosts: readonly string[];
146
+ /**
147
+ * Whether this surface may keep the record on the device. `null` means the
148
+ * visitor's consent is not resolved yet: the record is held in memory and
149
+ * written the moment {@link FirstTouchRuntime.setStorage} grants it.
150
+ * Omitted means true, the posture of a surface with no consent gate.
151
+ */
152
+ storage?: boolean | null;
153
+ /**
154
+ * The platform endpoint that seals and opens hand-off tokens: absolute, or
155
+ * a path on the current origin. Without it no link is decorated and no
156
+ * token is adopted, and the cookie alone carries the record.
157
+ */
158
+ handoffUrl?: string | null;
159
+ }
160
+ /** Where the record currently lives on this page. */
161
+ export type FirstTouchTier = 'cookie' | 'session' | 'memory';
162
+ /** The handle a booted capture returns. */
163
+ export interface FirstTouchRuntime {
164
+ /** The first touch as this page knows it, or null. */
165
+ read(): FirstTouch | null;
166
+ /** Grant, refuse or un-resolve device storage for this surface. */
167
+ setStorage(allowed: boolean | null): void;
168
+ /** Where the record lives right now, or null when there is none. */
169
+ tier(): FirstTouchTier | null;
170
+ }
171
+ /** Everything the capture does, as one closure — see the file comment for why. */
172
+ export interface FirstTouchKit {
173
+ /** A bare lowercase host, or '' when the value is not one. */
174
+ normalizeHost(value: unknown): string;
175
+ /** Whether `host` is one of `hosts` — see {@link FirstTouchConfig.hosts}. */
176
+ isFirstPartyHost(host: unknown, hosts: readonly string[]): boolean;
177
+ /** The touch a landing describes, or null when the URL is not a web page. */
178
+ buildFirstTouch(landing: FirstTouchLanding): FirstTouch | null;
179
+ /**
180
+ * Rebuild a record from an untrusted value, keeping only known fields, each
181
+ * re-scrubbed; null when it is not a record. `now`, when given, refuses a
182
+ * record stamped more than a day into the future.
183
+ */
184
+ sanitizeFirstTouch(value: unknown, now?: number): FirstTouch | null;
185
+ /** The first of two records: the earlier `at` wins, and a tie keeps `a`. */
186
+ mergeFirstTouch(a: FirstTouch | null | undefined, b: FirstTouch | null | undefined): FirstTouch | null;
187
+ /** The cookie-safe text form of a record. */
188
+ encodeFirstTouch(touch: FirstTouch): string;
189
+ /** A record from its cookie text, sanitized; null for anything else. */
190
+ decodeFirstTouch(value: unknown): FirstTouch | null;
191
+ /** The record a `Cookie` header carries, for a server that receives one. */
192
+ readFirstTouchCookie(cookieHeader: string | null | undefined): FirstTouch | null;
193
+ /** Start capturing on this page. Idempotent; a second call updates the config. */
194
+ boot(config: FirstTouchConfig): FirstTouchRuntime;
195
+ /** The booted runtime's record, or whatever the device holds when none booted. */
196
+ read(): FirstTouch | null;
197
+ }
198
+ /**
199
+ * Build the capture. Self-contained by construction: nothing in the body may
200
+ * reference a value declared outside it (types are erased and do not count).
201
+ */
202
+ export declare function createFirstTouchKit(): FirstTouchKit;
203
+ export declare const normalizeFirstTouchHost: (value: unknown) => string;
204
+ export declare const isFirstPartyHost: (host: unknown, hosts: readonly string[]) => boolean;
205
+ export declare const buildFirstTouch: (landing: FirstTouchLanding) => FirstTouch | null;
206
+ export declare const sanitizeFirstTouch: (value: unknown, now?: number) => FirstTouch | null;
207
+ export declare const mergeFirstTouch: (a: FirstTouch | null | undefined, b: FirstTouch | null | undefined) => FirstTouch | null;
208
+ export declare const encodeFirstTouch: (touch: FirstTouch) => string;
209
+ export declare const decodeFirstTouch: (value: unknown) => FirstTouch | null;
210
+ export declare const readFirstTouchCookie: (cookieHeader: string | null | undefined) => FirstTouch | null;
211
+ export declare const bootFirstTouch: (config: FirstTouchConfig) => FirstTouchRuntime;
212
+ export declare const readFirstTouch: () => FirstTouch | null;