@a.svetlitskiy/yandex-metrika 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,63 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## v0.3.0 (2026-08-30)
9
+
10
+ ### Summary
11
+
12
+ - Package is now published under the `@a.svetlitskiy` npm scope.
13
+ - Install and import examples updated to use `@a.svetlitskiy/yandex-metrika`.
14
+ - Fixed README table alignment for the new package name.
15
+ - Documentation for browser and server usage updated to match the new scope.
16
+ - Releasing and contributing guides now reference the correct npm scope.
17
+ - CI workflow updated to reflect the new package name and publish prerequisites.
18
+ - No changes to browser or server SDK behavior; this release is purely packaging and documentation.
19
+
20
+ ### Areas changed
21
+
22
+ - Docs: 4
23
+ - Other: 3
24
+ - CI: 1
25
+
26
+ Links: https://github.com/svetlitskiy/yandex-metrika/compare/v0.2.0...v0.3.0
27
+
28
+ ## v0.2.0 (2026-08-30)
29
+
30
+ ### Summary
31
+
32
+ - Initial public release of a framework-neutral TypeScript SDK for Yandex Metrica browser goals and server-side Measurement Protocol events.
33
+ - Ships ESM, CommonJS, and TypeScript declarations through isolated package entry points.
34
+ - Adds server helpers to read a Yandex Metrica ClientID from incoming requests, preferring the `X-Yandex-Metrika-Client-Id` header and falling back to the `_ym_uid` cookie.
35
+ - Adds a browser helper that attaches the ClientID to an existing business request, removing the need for a dedicated `/api/analytics/client-id` endpoint.
36
+ - Introduces `getPropagationHeaders` for easy server-side header propagation when a ClientID is available.
37
+ - Breaking change: browser `getClientID` is now Promise-based and resolves to `string | null`; the callback form was removed, and it resolves to `null` instead of waiting indefinitely on missing counters, invalid values, or timeouts.
38
+ - Adds complete English documentation, practical examples, security guidance, and release instructions.
39
+ - Includes tests, package artifact validation, and an automated npm publication via Trusted Publishing.
40
+
41
+ ### Areas changed
42
+
43
+ - Other: 14
44
+ - Server: 4
45
+ - Docs: 4
46
+ - Shared: 3
47
+ - Tests: 3
48
+ - CI: 3
49
+ - Browser: 1
50
+
51
+ Links: https://github.com/svetlitskiy/yandex-metrika/compare/d2b883df8cab0fe7cd05404980fe7db94e057fe0...v0.2.0
52
+
53
+ ## [0.1.0] - 2026-08-29
54
+
55
+ ### Added
56
+
57
+ - SSR-safe browser client for goals, hits, UserID, user parameters, and ClientID.
58
+ - Framework-neutral server client for Yandex Metrica Measurement Protocol goals.
59
+ - Optional pageview delivery before server-side goals.
60
+ - ESM, CommonJS, and TypeScript package exports.
61
+ - Package validation, CI, Trusted Publishing workflow, and English documentation.
62
+
63
+ [0.1.0]: https://github.com/svetlitskiy/yandex-metrika/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aleksey Svetlitskiy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,241 @@
1
+ # @a.svetlitskiy/yandex-metrika
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@a.svetlitskiy/yandex-metrika.svg)](https://www.npmjs.com/package/@a.svetlitskiy/yandex-metrika)
4
+ [![CI](https://github.com/svetlitskiy/yandex-metrika/actions/workflows/ci.yml/badge.svg)](https://github.com/svetlitskiy/yandex-metrika/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/@a.svetlitskiy/yandex-metrika.svg)](./LICENSE)
6
+
7
+ A small, framework-neutral TypeScript SDK for sending custom events to
8
+ [Yandex Metrica](https://metrica.yandex.com/) from both the browser and the
9
+ server.
10
+
11
+ Use the browser entry point for the standard `window.ym` API. Use the server
12
+ entry point for events that happen after the browser request: completed
13
+ payments, webhooks, background jobs, CRM updates, bots, or other server-side
14
+ workflows.
15
+
16
+ ## Why this package exists
17
+
18
+ The Yandex Metrica browser API is convenient for UI interactions, but many
19
+ important conversions do not finish in a browser. Yandex Metrica's Measurement
20
+ Protocol can report those conversions from a server, but it has a different
21
+ HTTP API and requires careful handling of ClientID values and secret tokens.
22
+
23
+ This package provides one typed, dependency-free transport layer for both
24
+ environments while keeping application policy in your application:
25
+
26
+ - browser calls are SSR-safe and become no-ops until `window.ym` is available;
27
+ - server calls use the official Measurement Protocol collect endpoint;
28
+ - browser and server code are exposed through separate package entry points;
29
+ - the package does not depend on React, Next.js, Express, Fastify, or a database;
30
+ - the package never invents a ClientID or decides how long your application
31
+ should store it.
32
+
33
+ Measurement Protocol supplements the regular Metrica tag; it is not a complete
34
+ replacement for it.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ npm install @a.svetlitskiy/yandex-metrika
40
+ ```
41
+
42
+ The package ships ESM, CommonJS, and TypeScript declarations. It has no runtime
43
+ dependencies and supports Node.js 18.18 or newer.
44
+
45
+ ## Package entry points
46
+
47
+ | Import | Purpose |
48
+ | --------------------------------------- | --------------------------------------- |
49
+ | `@a.svetlitskiy/yandex-metrika/browser` | The `window.ym` browser client |
50
+ | `@a.svetlitskiy/yandex-metrika/server` | Server-side Measurement Protocol events |
51
+ | `@a.svetlitskiy/yandex-metrika` | Shared helpers and types |
52
+ | `@a.svetlitskiy/yandex-metrika/types` | Explicit type-only imports |
53
+
54
+ ## Browser quick start
55
+
56
+ Loading and configuring the Yandex Metrica tag remains the host application's
57
+ responsibility. Once the tag has created `window.ym`, create a client:
58
+
59
+ ```ts
60
+ import {
61
+ createMetrikaBrowserClient,
62
+ parseMetrikaTagId,
63
+ } from "@a.svetlitskiy/yandex-metrika/browser";
64
+
65
+ const metrika = createMetrikaBrowserClient({
66
+ tagId: () => parseMetrikaTagId(process.env.NEXT_PUBLIC_YANDEX_METRIKA_ID),
67
+ });
68
+
69
+ metrika.reachGoal("signup", { plan: "pro" });
70
+ metrika.hit(window.location.href, { params: { section: "account" } });
71
+ metrika.setUserID("user-123");
72
+ metrika.userParams({ plan: "pro" });
73
+ ```
74
+
75
+ Importing this module does not access `window`. Calls are safe during SSR and
76
+ become no-ops when the counter ID or `window.ym` is unavailable.
77
+
78
+ See [Browser usage](./docs/browser.md) for ClientID capture and framework notes.
79
+
80
+ ## Server quick start
81
+
82
+ First enable Measurement Protocol in the counter's **Data security and usage**
83
+ settings and create a secret token. Never expose that token to browser code.
84
+
85
+ ```ts
86
+ import { createMetrikaServerClient } from "@a.svetlitskiy/yandex-metrika/server";
87
+
88
+ const metrika = createMetrikaServerClient({
89
+ counterId: process.env.YANDEX_METRIKA_COUNTER_ID,
90
+ measurementToken: process.env.YANDEX_METRIKA_MEASUREMENT_TOKEN,
91
+ });
92
+
93
+ const result = await metrika.reachGoal({
94
+ clientId: storedMetrikaClientId,
95
+ name: "purchase",
96
+ url: "https://example.com/checkout/success",
97
+ params: { orderId: "order-123", plan: "pro" },
98
+ });
99
+
100
+ // "sent" | "skipped_unconfigured" | "skipped_no_client_id"
101
+ ```
102
+
103
+ The method throws when a configured collect request fails. Missing credentials
104
+ and ClientID values return explicit skip results, making optional analytics easy
105
+ to integrate without masking transport failures.
106
+
107
+ See [Server usage](./docs/server.md) for new visits, event timestamps, injected
108
+ `fetch`, and error handling.
109
+
110
+ ## Connecting browser and server events
111
+
112
+ Measurement Protocol uses the Metrica ClientID to associate a server event with
113
+ a visitor. How that value reaches the backend depends on where the backend runs,
114
+ so the package supports the three realistic cases without requiring a dedicated
115
+ analytics endpoint.
116
+
117
+ ### Same domain: read it from the incoming request
118
+
119
+ When the backend shares a registrable domain with the site, the Metrica
120
+ `_ym_uid` cookie arrives with ordinary business requests:
121
+
122
+ ```ts
123
+ import { getMetrikaClientIdFromRequest } from "@a.svetlitskiy/yandex-metrika/server";
124
+
125
+ const clientId = getMetrikaClientIdFromRequest(request);
126
+ ```
127
+
128
+ The helper accepts a fetch `Request`, a Node.js `IncomingMessage`, or any object
129
+ with headers. `getMetrikaClientIdFromHeaders` and `getMetrikaClientIdFromCookie`
130
+ are available when only headers or only a cookie string are at hand.
131
+
132
+ ### Different domains: propagate an explicit header
133
+
134
+ A browser does not send `example.com` cookies to `api.other-service.io`. Attach
135
+ the ClientID to the business request you already make:
136
+
137
+ ```ts
138
+ await fetch("https://api.other-service.io/orders", {
139
+ method: "POST",
140
+ headers: {
141
+ "Content-Type": "application/json",
142
+ ...(await metrika.getPropagationHeaders()),
143
+ },
144
+ body: JSON.stringify(order),
145
+ });
146
+ ```
147
+
148
+ `getPropagationHeaders` resolves to `{ "X-Yandex-Metrika-Client-Id": "..." }`, or
149
+ to an empty object when no ClientID is available, so spreading it is always
150
+ safe. The server helpers read that header before falling back to the cookie.
151
+
152
+ The package never patches global `fetch` and never sends the ClientID anywhere
153
+ on its own. The application decides which requests carry the header.
154
+
155
+ ### Delayed events: persist the ClientID
156
+
157
+ Payment webhooks, background jobs, and CRM updates run when no browser request
158
+ exists. Store the extracted value with the entity the later event belongs to:
159
+
160
+ ```ts
161
+ order.metrikaClientId = getMetrikaClientIdFromRequest(request);
162
+
163
+ // later, from a webhook:
164
+ await metrika.reachGoal({
165
+ clientId: order.metrikaClientId,
166
+ name: "payment_completed",
167
+ url: "https://example.com/orders/payment",
168
+ });
169
+ ```
170
+
171
+ ### Optional: a dedicated synchronization endpoint
172
+
173
+ A separate endpoint is still useful when the ClientID must be attached to a
174
+ signed-in user before any business request happens:
175
+
176
+ ```ts
177
+ const clientId = await metrika.getClientID();
178
+ ```
179
+
180
+ Send that value wherever your application needs it. This is one option, not the
181
+ required integration path.
182
+
183
+ Your application decides how the ClientID maps to a user, where it is stored,
184
+ when it expires, and whether analytics consent permits storing it. A propagated
185
+ ClientID is untrusted input; the helpers validate it with `isMetrikaClientId`,
186
+ and it must never be treated as authentication or authorization data.
187
+
188
+ ## Starting a server-side visit
189
+
190
+ If an event cannot extend a recent browser visit, send a pageview immediately
191
+ before the goal:
192
+
193
+ ```ts
194
+ await metrika.reachGoal({
195
+ clientId: storedMetrikaClientId,
196
+ name: "subscription_renewed",
197
+ url: "https://example.com/_channel/background-job",
198
+ pageview: {
199
+ referrer: "https://example.com",
200
+ title: "Background job",
201
+ },
202
+ });
203
+ ```
204
+
205
+ The pageview metadata is required because Yandex Metrica requires `dr`, `dl`,
206
+ and `dt` when creating a pageview. Measurement Protocol accepts events up to 12
207
+ hours in the past; use `eventTime` only within that window.
208
+
209
+ ## What the package does not do
210
+
211
+ - It does not load or configure Yandex's browser tag.
212
+ - It does not store ClientID or UserID values.
213
+ - It does not manage consent, cookies, authentication, or user mapping.
214
+ - It does not retry failed requests or hide HTTP errors.
215
+ - It does not define goal names or application-specific event schemas.
216
+ - It does not send secret Measurement Protocol credentials from the browser.
217
+
218
+ These boundaries keep the package reusable and prevent analytics transport from
219
+ silently making product or privacy decisions.
220
+
221
+ ## Security and privacy
222
+
223
+ - Import Measurement Protocol code only from the `/server` entry point.
224
+ - Keep `measurementToken` in a server-only secret store.
225
+ - Do not put personal data or secrets into goal parameters.
226
+ - Treat ClientID as user-associated analytics data and apply your consent and
227
+ retention policy.
228
+ - Rotate the Measurement Protocol token if it is ever exposed.
229
+
230
+ Please report package vulnerabilities according to [SECURITY.md](./SECURITY.md).
231
+
232
+ ## Documentation
233
+
234
+ - [Browser usage](./docs/browser.md)
235
+ - [Server and Measurement Protocol usage](./docs/server.md)
236
+ - [Contributing](./CONTRIBUTING.md)
237
+ - [Release process](./docs/releasing.md)
238
+
239
+ ## License
240
+
241
+ [MIT](./LICENSE)
package/SECURITY.md ADDED
@@ -0,0 +1,21 @@
1
+ # Security policy
2
+
3
+ ## Supported versions
4
+
5
+ Security fixes are provided for the latest published minor version.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Please use GitHub's private vulnerability reporting feature for this repository.
10
+ Do not open a public issue containing exploit details, Measurement Protocol
11
+ tokens, ClientID values, or other sensitive data.
12
+
13
+ Reports should include the affected version, impact, reproduction steps, and any
14
+ suggested mitigation. You can expect an acknowledgement within seven days.
15
+
16
+ ## Secret handling
17
+
18
+ The `measurementToken` option is a server credential. Applications must never
19
+ place it in browser code, public environment variables, analytics parameters,
20
+ logs, or error responses. Rotate the token in Yandex Metrica if exposure is
21
+ suspected.
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ // src/shared.ts
4
+ var METRIKA_CLIENT_ID_PATTERN = /^\d{1,32}$/;
5
+ var METRIKA_CLIENT_ID_HEADER = "X-Yandex-Metrika-Client-Id";
6
+ var METRIKA_CLIENT_ID_TIMEOUT_MS = 2e3;
7
+ function isMetrikaClientId(value) {
8
+ return METRIKA_CLIENT_ID_PATTERN.test(value);
9
+ }
10
+ function parseMetrikaTagId(value) {
11
+ if (!value) return null;
12
+ const id = Number(value);
13
+ return Number.isSafeInteger(id) && id > 0 ? id : null;
14
+ }
15
+
16
+ // src/browser/index.ts
17
+ function getYm() {
18
+ if (!("window" in globalThis)) return null;
19
+ const maybeWindow = globalThis.window;
20
+ if (!maybeWindow || typeof maybeWindow !== "object") return null;
21
+ if (!("ym" in maybeWindow)) return null;
22
+ return typeof maybeWindow.ym === "function" ? maybeWindow.ym : null;
23
+ }
24
+ function resolveTagId(tagId) {
25
+ return typeof tagId === "function" ? tagId() : tagId;
26
+ }
27
+ function createMetrikaBrowserClient(options) {
28
+ function resolveTarget() {
29
+ const id = resolveTagId(options.tagId);
30
+ const ym = getYm();
31
+ if (!id || !Number.isSafeInteger(id) || id < 1 || !ym) return null;
32
+ return { id, ym };
33
+ }
34
+ function call(method, ...args) {
35
+ const target = resolveTarget();
36
+ if (!target) return;
37
+ target.ym(target.id, method, ...args);
38
+ }
39
+ function readClientId(clientIdOptions) {
40
+ const target = resolveTarget();
41
+ if (!target) return Promise.resolve(null);
42
+ return new Promise((resolve) => {
43
+ let settled = false;
44
+ const finish = (clientId) => {
45
+ if (settled) return;
46
+ settled = true;
47
+ clearTimeout(timer);
48
+ resolve(clientId);
49
+ };
50
+ const timer = setTimeout(
51
+ () => finish(null),
52
+ clientIdOptions?.timeoutMs ?? METRIKA_CLIENT_ID_TIMEOUT_MS
53
+ );
54
+ target.ym(target.id, "getClientID", (clientId) => {
55
+ finish(
56
+ typeof clientId === "string" && isMetrikaClientId(clientId) ? clientId : null
57
+ );
58
+ });
59
+ });
60
+ }
61
+ return {
62
+ reachGoal(name, params) {
63
+ params === void 0 ? call("reachGoal", name) : call("reachGoal", name, params);
64
+ },
65
+ hit(url, hitOptions) {
66
+ hitOptions === void 0 ? call("hit", url) : call("hit", url, hitOptions);
67
+ },
68
+ setUserID(userId) {
69
+ call("setUserID", userId);
70
+ },
71
+ userParams(params) {
72
+ call("userParams", params);
73
+ },
74
+ getClientID(clientIdOptions) {
75
+ return readClientId(clientIdOptions);
76
+ },
77
+ async getPropagationHeaders(clientIdOptions) {
78
+ const clientId = await readClientId(clientIdOptions);
79
+ return clientId ? { [METRIKA_CLIENT_ID_HEADER]: clientId } : {};
80
+ }
81
+ };
82
+ }
83
+
84
+ exports.METRIKA_CLIENT_ID_HEADER = METRIKA_CLIENT_ID_HEADER;
85
+ exports.METRIKA_CLIENT_ID_PATTERN = METRIKA_CLIENT_ID_PATTERN;
86
+ exports.METRIKA_CLIENT_ID_TIMEOUT_MS = METRIKA_CLIENT_ID_TIMEOUT_MS;
87
+ exports.createMetrikaBrowserClient = createMetrikaBrowserClient;
88
+ exports.isMetrikaClientId = isMetrikaClientId;
89
+ exports.parseMetrikaTagId = parseMetrikaTagId;
90
+ //# sourceMappingURL=index.cjs.map
91
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shared.ts","../../src/browser/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/** A conservative validation pattern for Yandex Metrica ClientID values. */\nexport const METRIKA_CLIENT_ID_PATTERN = /^\\d{1,32}$/;\n\n/** The public Measurement Protocol collection endpoint. */\nexport const METRIKA_COLLECT_ORIGIN = \"https://mc.yandex.ru/collect\";\n\n/** The default timeout for Measurement Protocol requests. */\nexport const METRIKA_COLLECT_TIMEOUT_MS = 5_000;\n\n/** The header used to propagate a ClientID from the browser to a backend. */\nexport const METRIKA_CLIENT_ID_HEADER = \"X-Yandex-Metrika-Client-Id\";\n\n/** The cookie the Yandex Metrica tag uses to store the ClientID. */\nexport const METRIKA_CLIENT_ID_COOKIE = \"_ym_uid\";\n\n/** The default timeout for reading a ClientID from the browser tag. */\nexport const METRIKA_CLIENT_ID_TIMEOUT_MS = 2_000;\n\n/** Returns whether a value looks like a Yandex Metrica ClientID. */\nexport function isMetrikaClientId(value: string): boolean {\n return METRIKA_CLIENT_ID_PATTERN.test(value);\n}\n\n/** Parses a counter ID from an environment variable or other string input. */\nexport function parseMetrikaTagId(\n value: string | undefined | null,\n): number | null {\n if (!value) return null;\n const id = Number(value);\n return Number.isSafeInteger(id) && id > 0 ? id : null;\n}\n","import {\n METRIKA_CLIENT_ID_HEADER,\n METRIKA_CLIENT_ID_PATTERN,\n METRIKA_CLIENT_ID_TIMEOUT_MS,\n isMetrikaClientId,\n parseMetrikaTagId,\n} from \"../shared\";\nimport type {\n MetrikaBrowserClient,\n MetrikaBrowserHitOptions,\n MetrikaClientIdOptions,\n MetrikaParams,\n MetrikaPropagationHeaders,\n} from \"../types\";\n\nexport {\n METRIKA_CLIENT_ID_HEADER,\n METRIKA_CLIENT_ID_PATTERN,\n METRIKA_CLIENT_ID_TIMEOUT_MS,\n isMetrikaClientId,\n parseMetrikaTagId,\n};\nexport type {\n MetrikaBrowserClient,\n MetrikaBrowserHitOptions,\n MetrikaClientIdOptions,\n MetrikaParams,\n MetrikaPropagationHeaders,\n};\n\nexport type YmFunction = (\n id: number,\n method: string,\n ...args: unknown[]\n) => void;\n\nexport type MetrikaTagIdInput = number | null | (() => number | null);\n\nexport type CreateMetrikaBrowserClientOptions = {\n tagId: MetrikaTagIdInput;\n};\n\nfunction getYm(): YmFunction | null {\n if (!(\"window\" in globalThis)) return null;\n const maybeWindow = globalThis.window as unknown;\n if (!maybeWindow || typeof maybeWindow !== \"object\") return null;\n if (!(\"ym\" in maybeWindow)) return null;\n return typeof maybeWindow.ym === \"function\"\n ? (maybeWindow.ym as YmFunction)\n : null;\n}\n\nfunction resolveTagId(tagId: MetrikaTagIdInput): number | null {\n return typeof tagId === \"function\" ? tagId() : tagId;\n}\n\n/**\n * Creates a browser client backed by the global `window.ym` function.\n *\n * Importing and calling the client is safe during SSR. Calls become no-ops\n * until both a valid counter ID and `window.ym` are available.\n */\nexport function createMetrikaBrowserClient(\n options: CreateMetrikaBrowserClientOptions,\n): MetrikaBrowserClient {\n function resolveTarget(): { id: number; ym: YmFunction } | null {\n const id = resolveTagId(options.tagId);\n const ym = getYm();\n if (!id || !Number.isSafeInteger(id) || id < 1 || !ym) return null;\n return { id, ym };\n }\n\n function call(method: string, ...args: unknown[]): void {\n const target = resolveTarget();\n if (!target) return;\n target.ym(target.id, method, ...args);\n }\n\n /**\n * Reads the ClientID assigned by the Metrica tag.\n *\n * Resolves with `null` instead of waiting forever when the tag is missing,\n * blocked, or slow: the `ym` stub queues calls before `tag.js` loads, so a\n * blocked tag would otherwise never invoke the callback.\n */\n function readClientId(\n clientIdOptions?: MetrikaClientIdOptions,\n ): Promise<string | null> {\n const target = resolveTarget();\n if (!target) return Promise.resolve(null);\n\n return new Promise<string | null>((resolve) => {\n let settled = false;\n const finish = (clientId: string | null) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(clientId);\n };\n\n const timer = setTimeout(\n () => finish(null),\n clientIdOptions?.timeoutMs ?? METRIKA_CLIENT_ID_TIMEOUT_MS,\n );\n\n target.ym(target.id, \"getClientID\", (clientId: unknown) => {\n finish(\n typeof clientId === \"string\" && isMetrikaClientId(clientId)\n ? clientId\n : null,\n );\n });\n });\n }\n\n return {\n reachGoal(name: string, params?: MetrikaParams) {\n params === undefined\n ? call(\"reachGoal\", name)\n : call(\"reachGoal\", name, params);\n },\n hit(url: string, hitOptions?: MetrikaBrowserHitOptions) {\n hitOptions === undefined\n ? call(\"hit\", url)\n : call(\"hit\", url, hitOptions);\n },\n setUserID(userId: string) {\n call(\"setUserID\", userId);\n },\n userParams(params: MetrikaParams) {\n call(\"userParams\", params);\n },\n getClientID(clientIdOptions?: MetrikaClientIdOptions) {\n return readClientId(clientIdOptions);\n },\n async getPropagationHeaders(\n clientIdOptions?: MetrikaClientIdOptions,\n ): Promise<MetrikaPropagationHeaders> {\n const clientId = await readClientId(clientIdOptions);\n return clientId ? { [METRIKA_CLIENT_ID_HEADER]: clientId } : {};\n },\n };\n}\n"]}
@@ -0,0 +1,18 @@
1
+ export { METRIKA_CLIENT_ID_HEADER, METRIKA_CLIENT_ID_PATTERN, METRIKA_CLIENT_ID_TIMEOUT_MS, isMetrikaClientId, parseMetrikaTagId } from '../index.cjs';
2
+ import { MetrikaBrowserClient } from '../types.cjs';
3
+ export { MetrikaBrowserHitOptions, MetrikaClientIdOptions, MetrikaParams, MetrikaPropagationHeaders } from '../types.cjs';
4
+
5
+ type YmFunction = (id: number, method: string, ...args: unknown[]) => void;
6
+ type MetrikaTagIdInput = number | null | (() => number | null);
7
+ type CreateMetrikaBrowserClientOptions = {
8
+ tagId: MetrikaTagIdInput;
9
+ };
10
+ /**
11
+ * Creates a browser client backed by the global `window.ym` function.
12
+ *
13
+ * Importing and calling the client is safe during SSR. Calls become no-ops
14
+ * until both a valid counter ID and `window.ym` are available.
15
+ */
16
+ declare function createMetrikaBrowserClient(options: CreateMetrikaBrowserClientOptions): MetrikaBrowserClient;
17
+
18
+ export { type CreateMetrikaBrowserClientOptions, MetrikaBrowserClient, type MetrikaTagIdInput, type YmFunction, createMetrikaBrowserClient };
@@ -0,0 +1,18 @@
1
+ export { METRIKA_CLIENT_ID_HEADER, METRIKA_CLIENT_ID_PATTERN, METRIKA_CLIENT_ID_TIMEOUT_MS, isMetrikaClientId, parseMetrikaTagId } from '../index.js';
2
+ import { MetrikaBrowserClient } from '../types.js';
3
+ export { MetrikaBrowserHitOptions, MetrikaClientIdOptions, MetrikaParams, MetrikaPropagationHeaders } from '../types.js';
4
+
5
+ type YmFunction = (id: number, method: string, ...args: unknown[]) => void;
6
+ type MetrikaTagIdInput = number | null | (() => number | null);
7
+ type CreateMetrikaBrowserClientOptions = {
8
+ tagId: MetrikaTagIdInput;
9
+ };
10
+ /**
11
+ * Creates a browser client backed by the global `window.ym` function.
12
+ *
13
+ * Importing and calling the client is safe during SSR. Calls become no-ops
14
+ * until both a valid counter ID and `window.ym` are available.
15
+ */
16
+ declare function createMetrikaBrowserClient(options: CreateMetrikaBrowserClientOptions): MetrikaBrowserClient;
17
+
18
+ export { type CreateMetrikaBrowserClientOptions, MetrikaBrowserClient, type MetrikaTagIdInput, type YmFunction, createMetrikaBrowserClient };
@@ -0,0 +1,84 @@
1
+ // src/shared.ts
2
+ var METRIKA_CLIENT_ID_PATTERN = /^\d{1,32}$/;
3
+ var METRIKA_CLIENT_ID_HEADER = "X-Yandex-Metrika-Client-Id";
4
+ var METRIKA_CLIENT_ID_TIMEOUT_MS = 2e3;
5
+ function isMetrikaClientId(value) {
6
+ return METRIKA_CLIENT_ID_PATTERN.test(value);
7
+ }
8
+ function parseMetrikaTagId(value) {
9
+ if (!value) return null;
10
+ const id = Number(value);
11
+ return Number.isSafeInteger(id) && id > 0 ? id : null;
12
+ }
13
+
14
+ // src/browser/index.ts
15
+ function getYm() {
16
+ if (!("window" in globalThis)) return null;
17
+ const maybeWindow = globalThis.window;
18
+ if (!maybeWindow || typeof maybeWindow !== "object") return null;
19
+ if (!("ym" in maybeWindow)) return null;
20
+ return typeof maybeWindow.ym === "function" ? maybeWindow.ym : null;
21
+ }
22
+ function resolveTagId(tagId) {
23
+ return typeof tagId === "function" ? tagId() : tagId;
24
+ }
25
+ function createMetrikaBrowserClient(options) {
26
+ function resolveTarget() {
27
+ const id = resolveTagId(options.tagId);
28
+ const ym = getYm();
29
+ if (!id || !Number.isSafeInteger(id) || id < 1 || !ym) return null;
30
+ return { id, ym };
31
+ }
32
+ function call(method, ...args) {
33
+ const target = resolveTarget();
34
+ if (!target) return;
35
+ target.ym(target.id, method, ...args);
36
+ }
37
+ function readClientId(clientIdOptions) {
38
+ const target = resolveTarget();
39
+ if (!target) return Promise.resolve(null);
40
+ return new Promise((resolve) => {
41
+ let settled = false;
42
+ const finish = (clientId) => {
43
+ if (settled) return;
44
+ settled = true;
45
+ clearTimeout(timer);
46
+ resolve(clientId);
47
+ };
48
+ const timer = setTimeout(
49
+ () => finish(null),
50
+ clientIdOptions?.timeoutMs ?? METRIKA_CLIENT_ID_TIMEOUT_MS
51
+ );
52
+ target.ym(target.id, "getClientID", (clientId) => {
53
+ finish(
54
+ typeof clientId === "string" && isMetrikaClientId(clientId) ? clientId : null
55
+ );
56
+ });
57
+ });
58
+ }
59
+ return {
60
+ reachGoal(name, params) {
61
+ params === void 0 ? call("reachGoal", name) : call("reachGoal", name, params);
62
+ },
63
+ hit(url, hitOptions) {
64
+ hitOptions === void 0 ? call("hit", url) : call("hit", url, hitOptions);
65
+ },
66
+ setUserID(userId) {
67
+ call("setUserID", userId);
68
+ },
69
+ userParams(params) {
70
+ call("userParams", params);
71
+ },
72
+ getClientID(clientIdOptions) {
73
+ return readClientId(clientIdOptions);
74
+ },
75
+ async getPropagationHeaders(clientIdOptions) {
76
+ const clientId = await readClientId(clientIdOptions);
77
+ return clientId ? { [METRIKA_CLIENT_ID_HEADER]: clientId } : {};
78
+ }
79
+ };
80
+ }
81
+
82
+ export { METRIKA_CLIENT_ID_HEADER, METRIKA_CLIENT_ID_PATTERN, METRIKA_CLIENT_ID_TIMEOUT_MS, createMetrikaBrowserClient, isMetrikaClientId, parseMetrikaTagId };
83
+ //# sourceMappingURL=index.js.map
84
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shared.ts","../../src/browser/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/** A conservative validation pattern for Yandex Metrica ClientID values. */\nexport const METRIKA_CLIENT_ID_PATTERN = /^\\d{1,32}$/;\n\n/** The public Measurement Protocol collection endpoint. */\nexport const METRIKA_COLLECT_ORIGIN = \"https://mc.yandex.ru/collect\";\n\n/** The default timeout for Measurement Protocol requests. */\nexport const METRIKA_COLLECT_TIMEOUT_MS = 5_000;\n\n/** The header used to propagate a ClientID from the browser to a backend. */\nexport const METRIKA_CLIENT_ID_HEADER = \"X-Yandex-Metrika-Client-Id\";\n\n/** The cookie the Yandex Metrica tag uses to store the ClientID. */\nexport const METRIKA_CLIENT_ID_COOKIE = \"_ym_uid\";\n\n/** The default timeout for reading a ClientID from the browser tag. */\nexport const METRIKA_CLIENT_ID_TIMEOUT_MS = 2_000;\n\n/** Returns whether a value looks like a Yandex Metrica ClientID. */\nexport function isMetrikaClientId(value: string): boolean {\n return METRIKA_CLIENT_ID_PATTERN.test(value);\n}\n\n/** Parses a counter ID from an environment variable or other string input. */\nexport function parseMetrikaTagId(\n value: string | undefined | null,\n): number | null {\n if (!value) return null;\n const id = Number(value);\n return Number.isSafeInteger(id) && id > 0 ? id : null;\n}\n","import {\n METRIKA_CLIENT_ID_HEADER,\n METRIKA_CLIENT_ID_PATTERN,\n METRIKA_CLIENT_ID_TIMEOUT_MS,\n isMetrikaClientId,\n parseMetrikaTagId,\n} from \"../shared\";\nimport type {\n MetrikaBrowserClient,\n MetrikaBrowserHitOptions,\n MetrikaClientIdOptions,\n MetrikaParams,\n MetrikaPropagationHeaders,\n} from \"../types\";\n\nexport {\n METRIKA_CLIENT_ID_HEADER,\n METRIKA_CLIENT_ID_PATTERN,\n METRIKA_CLIENT_ID_TIMEOUT_MS,\n isMetrikaClientId,\n parseMetrikaTagId,\n};\nexport type {\n MetrikaBrowserClient,\n MetrikaBrowserHitOptions,\n MetrikaClientIdOptions,\n MetrikaParams,\n MetrikaPropagationHeaders,\n};\n\nexport type YmFunction = (\n id: number,\n method: string,\n ...args: unknown[]\n) => void;\n\nexport type MetrikaTagIdInput = number | null | (() => number | null);\n\nexport type CreateMetrikaBrowserClientOptions = {\n tagId: MetrikaTagIdInput;\n};\n\nfunction getYm(): YmFunction | null {\n if (!(\"window\" in globalThis)) return null;\n const maybeWindow = globalThis.window as unknown;\n if (!maybeWindow || typeof maybeWindow !== \"object\") return null;\n if (!(\"ym\" in maybeWindow)) return null;\n return typeof maybeWindow.ym === \"function\"\n ? (maybeWindow.ym as YmFunction)\n : null;\n}\n\nfunction resolveTagId(tagId: MetrikaTagIdInput): number | null {\n return typeof tagId === \"function\" ? tagId() : tagId;\n}\n\n/**\n * Creates a browser client backed by the global `window.ym` function.\n *\n * Importing and calling the client is safe during SSR. Calls become no-ops\n * until both a valid counter ID and `window.ym` are available.\n */\nexport function createMetrikaBrowserClient(\n options: CreateMetrikaBrowserClientOptions,\n): MetrikaBrowserClient {\n function resolveTarget(): { id: number; ym: YmFunction } | null {\n const id = resolveTagId(options.tagId);\n const ym = getYm();\n if (!id || !Number.isSafeInteger(id) || id < 1 || !ym) return null;\n return { id, ym };\n }\n\n function call(method: string, ...args: unknown[]): void {\n const target = resolveTarget();\n if (!target) return;\n target.ym(target.id, method, ...args);\n }\n\n /**\n * Reads the ClientID assigned by the Metrica tag.\n *\n * Resolves with `null` instead of waiting forever when the tag is missing,\n * blocked, or slow: the `ym` stub queues calls before `tag.js` loads, so a\n * blocked tag would otherwise never invoke the callback.\n */\n function readClientId(\n clientIdOptions?: MetrikaClientIdOptions,\n ): Promise<string | null> {\n const target = resolveTarget();\n if (!target) return Promise.resolve(null);\n\n return new Promise<string | null>((resolve) => {\n let settled = false;\n const finish = (clientId: string | null) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(clientId);\n };\n\n const timer = setTimeout(\n () => finish(null),\n clientIdOptions?.timeoutMs ?? METRIKA_CLIENT_ID_TIMEOUT_MS,\n );\n\n target.ym(target.id, \"getClientID\", (clientId: unknown) => {\n finish(\n typeof clientId === \"string\" && isMetrikaClientId(clientId)\n ? clientId\n : null,\n );\n });\n });\n }\n\n return {\n reachGoal(name: string, params?: MetrikaParams) {\n params === undefined\n ? call(\"reachGoal\", name)\n : call(\"reachGoal\", name, params);\n },\n hit(url: string, hitOptions?: MetrikaBrowserHitOptions) {\n hitOptions === undefined\n ? call(\"hit\", url)\n : call(\"hit\", url, hitOptions);\n },\n setUserID(userId: string) {\n call(\"setUserID\", userId);\n },\n userParams(params: MetrikaParams) {\n call(\"userParams\", params);\n },\n getClientID(clientIdOptions?: MetrikaClientIdOptions) {\n return readClientId(clientIdOptions);\n },\n async getPropagationHeaders(\n clientIdOptions?: MetrikaClientIdOptions,\n ): Promise<MetrikaPropagationHeaders> {\n const clientId = await readClientId(clientIdOptions);\n return clientId ? { [METRIKA_CLIENT_ID_HEADER]: clientId } : {};\n },\n };\n}\n"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ // src/shared.ts
4
+ var METRIKA_CLIENT_ID_PATTERN = /^\d{1,32}$/;
5
+ var METRIKA_COLLECT_ORIGIN = "https://mc.yandex.ru/collect";
6
+ var METRIKA_COLLECT_TIMEOUT_MS = 5e3;
7
+ var METRIKA_CLIENT_ID_HEADER = "X-Yandex-Metrika-Client-Id";
8
+ var METRIKA_CLIENT_ID_COOKIE = "_ym_uid";
9
+ var METRIKA_CLIENT_ID_TIMEOUT_MS = 2e3;
10
+ function isMetrikaClientId(value) {
11
+ return METRIKA_CLIENT_ID_PATTERN.test(value);
12
+ }
13
+ function parseMetrikaTagId(value) {
14
+ if (!value) return null;
15
+ const id = Number(value);
16
+ return Number.isSafeInteger(id) && id > 0 ? id : null;
17
+ }
18
+
19
+ exports.METRIKA_CLIENT_ID_COOKIE = METRIKA_CLIENT_ID_COOKIE;
20
+ exports.METRIKA_CLIENT_ID_HEADER = METRIKA_CLIENT_ID_HEADER;
21
+ exports.METRIKA_CLIENT_ID_PATTERN = METRIKA_CLIENT_ID_PATTERN;
22
+ exports.METRIKA_CLIENT_ID_TIMEOUT_MS = METRIKA_CLIENT_ID_TIMEOUT_MS;
23
+ exports.METRIKA_COLLECT_ORIGIN = METRIKA_COLLECT_ORIGIN;
24
+ exports.METRIKA_COLLECT_TIMEOUT_MS = METRIKA_COLLECT_TIMEOUT_MS;
25
+ exports.isMetrikaClientId = isMetrikaClientId;
26
+ exports.parseMetrikaTagId = parseMetrikaTagId;
27
+ //# sourceMappingURL=index.cjs.map
28
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared.ts"],"names":[],"mappings":";;;AACO,IAAM,yBAAA,GAA4B;AAGlC,IAAM,sBAAA,GAAyB;AAG/B,IAAM,0BAAA,GAA6B;AAGnC,IAAM,wBAAA,GAA2B;AAGjC,IAAM,wBAAA,GAA2B;AAGjC,IAAM,4BAAA,GAA+B;AAGrC,SAAS,kBAAkB,KAAA,EAAwB;AACxD,EAAA,OAAO,yBAAA,CAA0B,KAAK,KAAK,CAAA;AAC7C;AAGO,SAAS,kBACd,KAAA,EACe;AACf,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,EAAA,GAAK,OAAO,KAAK,CAAA;AACvB,EAAA,OAAO,OAAO,aAAA,CAAc,EAAE,CAAA,IAAK,EAAA,GAAK,IAAI,EAAA,GAAK,IAAA;AACnD","file":"index.cjs","sourcesContent":["/** A conservative validation pattern for Yandex Metrica ClientID values. */\nexport const METRIKA_CLIENT_ID_PATTERN = /^\\d{1,32}$/;\n\n/** The public Measurement Protocol collection endpoint. */\nexport const METRIKA_COLLECT_ORIGIN = \"https://mc.yandex.ru/collect\";\n\n/** The default timeout for Measurement Protocol requests. */\nexport const METRIKA_COLLECT_TIMEOUT_MS = 5_000;\n\n/** The header used to propagate a ClientID from the browser to a backend. */\nexport const METRIKA_CLIENT_ID_HEADER = \"X-Yandex-Metrika-Client-Id\";\n\n/** The cookie the Yandex Metrica tag uses to store the ClientID. */\nexport const METRIKA_CLIENT_ID_COOKIE = \"_ym_uid\";\n\n/** The default timeout for reading a ClientID from the browser tag. */\nexport const METRIKA_CLIENT_ID_TIMEOUT_MS = 2_000;\n\n/** Returns whether a value looks like a Yandex Metrica ClientID. */\nexport function isMetrikaClientId(value: string): boolean {\n return METRIKA_CLIENT_ID_PATTERN.test(value);\n}\n\n/** Parses a counter ID from an environment variable or other string input. */\nexport function parseMetrikaTagId(\n value: string | undefined | null,\n): number | null {\n if (!value) return null;\n const id = Number(value);\n return Number.isSafeInteger(id) && id > 0 ? id : null;\n}\n"]}
@@ -0,0 +1,20 @@
1
+ export { CreateMetrikaServerClientOptions, MetrikaBrowserClient, MetrikaBrowserHitOptions, MetrikaClientIdLookupOptions, MetrikaClientIdOptions, MetrikaCollectHit, MetrikaFetch, MetrikaHeadersLike, MetrikaPageviewOptions, MetrikaParams, MetrikaPropagationHeaders, MetrikaReachGoalInput, MetrikaRequestLike, MetrikaServerClient, MetrikaServerConfig, MetrikaServerGoalResult, MetrikaTagId } from './types.cjs';
2
+
3
+ /** A conservative validation pattern for Yandex Metrica ClientID values. */
4
+ declare const METRIKA_CLIENT_ID_PATTERN: RegExp;
5
+ /** The public Measurement Protocol collection endpoint. */
6
+ declare const METRIKA_COLLECT_ORIGIN = "https://mc.yandex.ru/collect";
7
+ /** The default timeout for Measurement Protocol requests. */
8
+ declare const METRIKA_COLLECT_TIMEOUT_MS = 5000;
9
+ /** The header used to propagate a ClientID from the browser to a backend. */
10
+ declare const METRIKA_CLIENT_ID_HEADER = "X-Yandex-Metrika-Client-Id";
11
+ /** The cookie the Yandex Metrica tag uses to store the ClientID. */
12
+ declare const METRIKA_CLIENT_ID_COOKIE = "_ym_uid";
13
+ /** The default timeout for reading a ClientID from the browser tag. */
14
+ declare const METRIKA_CLIENT_ID_TIMEOUT_MS = 2000;
15
+ /** Returns whether a value looks like a Yandex Metrica ClientID. */
16
+ declare function isMetrikaClientId(value: string): boolean;
17
+ /** Parses a counter ID from an environment variable or other string input. */
18
+ declare function parseMetrikaTagId(value: string | undefined | null): number | null;
19
+
20
+ export { METRIKA_CLIENT_ID_COOKIE, METRIKA_CLIENT_ID_HEADER, METRIKA_CLIENT_ID_PATTERN, METRIKA_CLIENT_ID_TIMEOUT_MS, METRIKA_COLLECT_ORIGIN, METRIKA_COLLECT_TIMEOUT_MS, isMetrikaClientId, parseMetrikaTagId };