@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 +63 -0
- package/LICENSE +21 -0
- package/README.md +241 -0
- package/SECURITY.md +21 -0
- package/dist/browser/index.cjs +91 -0
- package/dist/browser/index.cjs.map +1 -0
- package/dist/browser/index.d.cts +18 -0
- package/dist/browser/index.d.ts +18 -0
- package/dist/browser/index.js +84 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/index.cjs +28 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.cjs +221 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +51 -0
- package/dist/server/index.d.ts +51 -0
- package/dist/server/index.js +204 -0
- package/dist/server/index.js.map +1 -0
- package/dist/types.cjs +4 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +92 -0
- package/dist/types.d.ts +92 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/docs/browser.md +127 -0
- package/docs/releasing.md +51 -0
- package/docs/server.md +209 -0
- package/package.json +104 -0
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
|
+
[](https://www.npmjs.com/package/@a.svetlitskiy/yandex-metrika)
|
|
4
|
+
[](https://github.com/svetlitskiy/yandex-metrika/actions/workflows/ci.yml)
|
|
5
|
+
[](./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":";;;AACO,IAAM,yBAAA,GAA4B;AASlC,IAAM,wBAAA,GAA2B;AAMjC,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;;;ACYA,SAAS,KAAA,GAA2B;AAClC,EAAA,IAAI,EAAE,QAAA,IAAY,UAAA,CAAA,EAAa,OAAO,IAAA;AACtC,EAAA,MAAM,cAAc,UAAA,CAAW,MAAA;AAC/B,EAAA,IAAI,CAAC,WAAA,IAAe,OAAO,WAAA,KAAgB,UAAU,OAAO,IAAA;AAC5D,EAAA,IAAI,EAAE,IAAA,IAAQ,WAAA,CAAA,EAAc,OAAO,IAAA;AACnC,EAAA,OAAO,OAAO,WAAA,CAAY,EAAA,KAAO,UAAA,GAC5B,YAAY,EAAA,GACb,IAAA;AACN;AAEA,SAAS,aAAa,KAAA,EAAyC;AAC7D,EAAA,OAAO,OAAO,KAAA,KAAU,UAAA,GAAa,KAAA,EAAM,GAAI,KAAA;AACjD;AAQO,SAAS,2BACd,OAAA,EACsB;AACtB,EAAA,SAAS,aAAA,GAAuD;AAC9D,IAAA,MAAM,EAAA,GAAK,YAAA,CAAa,OAAA,CAAQ,KAAK,CAAA;AACrC,IAAA,MAAM,KAAK,KAAA,EAAM;AACjB,IAAA,IAAI,CAAC,EAAA,IAAM,CAAC,MAAA,CAAO,aAAA,CAAc,EAAE,CAAA,IAAK,EAAA,GAAK,CAAA,IAAK,CAAC,EAAA,EAAI,OAAO,IAAA;AAC9D,IAAA,OAAO,EAAE,IAAI,EAAA,EAAG;AAAA,EAClB;AAEA,EAAA,SAAS,IAAA,CAAK,WAAmB,IAAA,EAAuB;AACtD,IAAA,MAAM,SAAS,aAAA,EAAc;AAC7B,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,MAAA,CAAO,EAAA,CAAG,MAAA,CAAO,EAAA,EAAI,MAAA,EAAQ,GAAG,IAAI,CAAA;AAAA,EACtC;AASA,EAAA,SAAS,aACP,eAAA,EACwB;AACxB,IAAA,MAAM,SAAS,aAAA,EAAc;AAC7B,IAAA,IAAI,CAAC,MAAA,EAAQ,OAAO,OAAA,CAAQ,QAAQ,IAAI,CAAA;AAExC,IAAA,OAAO,IAAI,OAAA,CAAuB,CAAC,OAAA,KAAY;AAC7C,MAAA,IAAI,OAAA,GAAU,KAAA;AACd,MAAA,MAAM,MAAA,GAAS,CAAC,QAAA,KAA4B;AAC1C,QAAA,IAAI,OAAA,EAAS;AACb,QAAA,OAAA,GAAU,IAAA;AACV,QAAA,YAAA,CAAa,KAAK,CAAA;AAClB,QAAA,OAAA,CAAQ,QAAQ,CAAA;AAAA,MAClB,CAAA;AAEA,MAAA,MAAM,KAAA,GAAQ,UAAA;AAAA,QACZ,MAAM,OAAO,IAAI,CAAA;AAAA,QACjB,iBAAiB,SAAA,IAAa;AAAA,OAChC;AAEA,MAAA,MAAA,CAAO,EAAA,CAAG,MAAA,CAAO,EAAA,EAAI,aAAA,EAAe,CAAC,QAAA,KAAsB;AACzD,QAAA,MAAA;AAAA,UACE,OAAO,QAAA,KAAa,QAAA,IAAY,iBAAA,CAAkB,QAAQ,IACtD,QAAA,GACA;AAAA,SACN;AAAA,MACF,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH;AAEA,EAAA,OAAO;AAAA,IACL,SAAA,CAAU,MAAc,MAAA,EAAwB;AAC9C,MAAA,MAAA,KAAW,MAAA,GACP,KAAK,WAAA,EAAa,IAAI,IACtB,IAAA,CAAK,WAAA,EAAa,MAAM,MAAM,CAAA;AAAA,IACpC,CAAA;AAAA,IACA,GAAA,CAAI,KAAa,UAAA,EAAuC;AACtD,MAAA,UAAA,KAAe,MAAA,GACX,KAAK,KAAA,EAAO,GAAG,IACf,IAAA,CAAK,KAAA,EAAO,KAAK,UAAU,CAAA;AAAA,IACjC,CAAA;AAAA,IACA,UAAU,MAAA,EAAgB;AACxB,MAAA,IAAA,CAAK,aAAa,MAAM,CAAA;AAAA,IAC1B,CAAA;AAAA,IACA,WAAW,MAAA,EAAuB;AAChC,MAAA,IAAA,CAAK,cAAc,MAAM,CAAA;AAAA,IAC3B,CAAA;AAAA,IACA,YAAY,eAAA,EAA0C;AACpD,MAAA,OAAO,aAAa,eAAe,CAAA;AAAA,IACrC,CAAA;AAAA,IACA,MAAM,sBACJ,eAAA,EACoC;AACpC,MAAA,MAAM,QAAA,GAAW,MAAM,YAAA,CAAa,eAAe,CAAA;AACnD,MAAA,OAAO,WAAW,EAAE,CAAC,wBAAwB,GAAG,QAAA,KAAa,EAAC;AAAA,IAChE;AAAA,GACF;AACF","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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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 };
|