@technoapple/ga4 1.0.4 → 1.1.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.
Files changed (74) hide show
  1. package/.github/workflows/node.js.yml +31 -31
  2. package/.prettierignore +1 -1
  3. package/LICENSE +21 -21
  4. package/README.md +386 -48
  5. package/REQUIREMENTS.md +548 -0
  6. package/babel.config.js +5 -5
  7. package/build/main/ga4/ga4.d.ts +13 -0
  8. package/build/main/ga4/ga4.js +24 -1
  9. package/build/main/helpers/debounce.d.ts +5 -0
  10. package/build/main/helpers/debounce.js +23 -0
  11. package/build/main/helpers/delegate.d.ts +8 -0
  12. package/build/main/helpers/delegate.js +37 -0
  13. package/build/main/helpers/dom-ready.d.ts +1 -0
  14. package/build/main/helpers/dom-ready.js +13 -0
  15. package/build/main/helpers/parse-url.d.ts +11 -0
  16. package/build/main/helpers/parse-url.js +32 -0
  17. package/build/main/helpers/session.d.ts +4 -0
  18. package/build/main/helpers/session.js +50 -0
  19. package/build/main/index.d.ts +9 -0
  20. package/build/main/index.js +19 -2
  21. package/build/main/plugins/clean-url-tracker.d.ts +17 -0
  22. package/build/main/plugins/clean-url-tracker.js +105 -0
  23. package/build/main/plugins/event-tracker.d.ts +27 -0
  24. package/build/main/plugins/event-tracker.js +76 -0
  25. package/build/main/plugins/impression-tracker.d.ts +32 -0
  26. package/build/main/plugins/impression-tracker.js +202 -0
  27. package/build/main/plugins/index.d.ts +8 -0
  28. package/build/main/plugins/index.js +20 -0
  29. package/build/main/plugins/media-query-tracker.d.ts +20 -0
  30. package/build/main/plugins/media-query-tracker.js +96 -0
  31. package/build/main/plugins/outbound-form-tracker.d.ts +17 -0
  32. package/build/main/plugins/outbound-form-tracker.js +55 -0
  33. package/build/main/plugins/outbound-link-tracker.d.ts +19 -0
  34. package/build/main/plugins/outbound-link-tracker.js +63 -0
  35. package/build/main/plugins/page-visibility-tracker.d.ts +24 -0
  36. package/build/main/plugins/page-visibility-tracker.js +93 -0
  37. package/build/main/plugins/url-change-tracker.d.ts +20 -0
  38. package/build/main/plugins/url-change-tracker.js +76 -0
  39. package/build/main/types/plugins.d.ts +78 -0
  40. package/build/main/types/plugins.js +3 -0
  41. package/build/tsconfig.tsbuildinfo +1 -1
  42. package/docs/examples/react.md +95 -0
  43. package/docs/examples/vanilla.md +65 -0
  44. package/docs/examples/vue.md +87 -0
  45. package/jest.config.ts +195 -195
  46. package/package.json +56 -56
  47. package/src/dataLayer.ts +85 -85
  48. package/src/ga4/ga4.ts +69 -40
  49. package/src/ga4/ga4option.ts +4 -4
  50. package/src/ga4/index.ts +4 -4
  51. package/src/helpers/debounce.ts +28 -0
  52. package/src/helpers/delegate.ts +51 -0
  53. package/src/helpers/dom-ready.ts +7 -0
  54. package/src/helpers/parse-url.ts +37 -0
  55. package/src/helpers/session.ts +39 -0
  56. package/src/index.ts +34 -7
  57. package/src/plugins/clean-url-tracker.ts +112 -0
  58. package/src/plugins/event-tracker.ts +90 -0
  59. package/src/plugins/impression-tracker.ts +230 -0
  60. package/src/plugins/index.ts +8 -0
  61. package/src/plugins/media-query-tracker.ts +116 -0
  62. package/src/plugins/outbound-form-tracker.ts +65 -0
  63. package/src/plugins/outbound-link-tracker.ts +72 -0
  64. package/src/plugins/page-visibility-tracker.ts +104 -0
  65. package/src/plugins/url-change-tracker.ts +84 -0
  66. package/src/types/dataLayer.ts +9 -9
  67. package/src/types/global.ts +12 -12
  68. package/src/types/gtag.ts +259 -259
  69. package/src/types/plugins.ts +98 -0
  70. package/src/util.ts +18 -18
  71. package/test/dataLayer.spec.ts +55 -55
  72. package/test/ga4.spec.ts +36 -36
  73. package/tsconfig.json +28 -28
  74. package/tsconfig.module.json +11 -11
@@ -1,31 +1,31 @@
1
- # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2
- # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3
-
4
- name: Node.js CI
5
-
6
- on:
7
- push:
8
- branches: [ "main" ]
9
- pull_request:
10
- branches: [ "main" ]
11
-
12
- jobs:
13
- build:
14
-
15
- runs-on: ubuntu-latest
16
-
17
- strategy:
18
- matrix:
19
- node-version: [14.x, 16.x, 18.x]
20
- # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21
-
22
- steps:
23
- - uses: actions/checkout@v3
24
- - name: Use Node.js ${{ matrix.node-version }}
25
- uses: actions/setup-node@v3
26
- with:
27
- node-version: ${{ matrix.node-version }}
28
- cache: 'npm'
29
- - run: npm ci
30
- - run: npm run build --if-present
31
- - run: npm test
1
+ # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3
+
4
+ name: Node.js CI
5
+
6
+ on:
7
+ push:
8
+ branches: [ "main" ]
9
+ pull_request:
10
+ branches: [ "main" ]
11
+
12
+ jobs:
13
+ build:
14
+
15
+ runs-on: ubuntu-latest
16
+
17
+ strategy:
18
+ matrix:
19
+ node-version: [14.x, 16.x, 18.x]
20
+ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21
+
22
+ steps:
23
+ - uses: actions/checkout@v3
24
+ - name: Use Node.js ${{ matrix.node-version }}
25
+ uses: actions/setup-node@v3
26
+ with:
27
+ node-version: ${{ matrix.node-version }}
28
+ cache: 'npm'
29
+ - run: npm ci
30
+ - run: npm run build --if-present
31
+ - run: npm test
package/.prettierignore CHANGED
@@ -1,2 +1,2 @@
1
- # package.json is formatted by package managers, so we ignore it here
1
+ # package.json is formatted by package managers, so we ignore it here
2
2
  package.json
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2023 keke78ui9
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.
1
+ MIT License
2
+
3
+ Copyright (c) 2023 keke78ui9
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 CHANGED
@@ -1,48 +1,386 @@
1
- # GA4
2
- GA4 TypeScript Library to provide functions to support sending GA4 events.
3
-
4
- # Features
5
- - provide functions to send GA4 events.
6
- - helper functions to get value via `window.dataLayer`.
7
-
8
-
9
- ## Get Started
10
- ```sh
11
- npm i @technoapple/ga4
12
- ```
13
-
14
- ## Basic Usage
15
-
16
- sending GA4 events.
17
- ```js
18
- import { ga4 } from '@technoapple/ga4';
19
- // init
20
- ga4.init({
21
- targetId: 'g-123'
22
- } as ga4Option);
23
-
24
- // sending event
25
- ga4.send('test_event', {
26
- 'event_key_1': 1
27
- });
28
- ```
29
-
30
- sending GA4 events by gtag.
31
-
32
- ```js
33
- ga4.gtag('event', 'test', {
34
- 'test': 1
35
- });
36
- ```
37
-
38
- get value from `dataLayer`.
39
-
40
- ```js
41
- import { dataLayerHelper } from '@technoapple/ga4';
42
- window.dataLayer.push({
43
- 'test': 11
44
- });
45
-
46
- const value = dataLayerHelper.get('test');
47
- console.info(value); // 11
48
- ```
1
+ # @technoapple/ga4
2
+
3
+ A lightweight TypeScript GA4 helper for browser apps.
4
+
5
+ It provides:
6
+ - A simple GA4 wrapper (`init`, `send`, direct `gtag` access)
7
+ - A `dataLayer` helper (`dataLayerHelper.get`)
8
+ - Opt-in automatic tracking plugins (event delegation, outbound tracking, SPA page views, impression tracking, URL cleanup, media query tracking)
9
+
10
+ ## Why this library
11
+
12
+ GA4 and `gtag.js` are flexible, but many teams still need reusable browser-side utilities:
13
+ - Keep initialization and event sending consistent
14
+ - Add automatic tracking without wiring listeners repeatedly
15
+ - Keep plugin logic tree-shakeable and removable
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @technoapple/ga4
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```ts
26
+ import { ga4 } from '@technoapple/ga4';
27
+
28
+ ga4.init({ targetId: 'G-XXXXXXX' });
29
+
30
+ ga4.send('sign_up', {
31
+ method: 'email',
32
+ plan: 'pro',
33
+ });
34
+ ```
35
+
36
+ ## Framework Examples
37
+
38
+ Use these integration guides for copy-paste setup patterns and lifecycle notes:
39
+
40
+ - [React example](docs/examples/react.md)
41
+ - [Vue example](docs/examples/vue.md)
42
+ - [Vanilla JavaScript example](docs/examples/vanilla.md)
43
+
44
+ ## API
45
+
46
+ ### `ga4.init(option)`
47
+
48
+ Initializes `window.dataLayer` and `window.gtag`, then sends:
49
+ - `gtag('js', new Date())`
50
+ - `gtag('config', option.targetId)`
51
+
52
+ ```ts
53
+ ga4.init({ targetId: 'G-XXXXXXX' });
54
+ ```
55
+
56
+ ### `ga4.send(eventName, eventParameters)`
57
+
58
+ Sends a GA4 event through `gtag('event', ...)`.
59
+
60
+ ```ts
61
+ ga4.send('purchase', {
62
+ transaction_id: 'order_123',
63
+ value: 99,
64
+ currency: 'USD',
65
+ });
66
+ ```
67
+
68
+ ### `ga4.gtag`
69
+
70
+ Direct access to the typed `gtag` function.
71
+
72
+ ```ts
73
+ ga4.gtag('event', 'login', { method: 'google' });
74
+ ```
75
+
76
+ ### `ga4.use(PluginClass, options?)`
77
+
78
+ Registers a plugin and returns its instance.
79
+
80
+ ```ts
81
+ import { ga4, OutboundLinkTracker } from '@technoapple/ga4';
82
+
83
+ const tracker = ga4.use(OutboundLinkTracker, {
84
+ eventName: 'outbound_link_click',
85
+ });
86
+
87
+ // Later
88
+ tracker.remove();
89
+ ```
90
+
91
+ ### `ga4.removeAll()`
92
+
93
+ Unregisters all plugins and calls each plugin's `remove()`.
94
+
95
+ ### `dataLayerHelper.get(key, getLast?)`
96
+
97
+ Reads values from `window.dataLayer`.
98
+
99
+ - `getLast` omitted or `false`: returns first matching value
100
+ - `getLast` set to `true`: returns last matching value
101
+
102
+ ```ts
103
+ import { dataLayerHelper } from '@technoapple/ga4';
104
+
105
+ const firstValue = dataLayerHelper.get('campaign');
106
+ const latestValue = dataLayerHelper.get('campaign', true);
107
+ ```
108
+
109
+ ## Plugin Catalog
110
+
111
+ All plugins implement:
112
+
113
+ ```ts
114
+ interface GA4Plugin {
115
+ remove(): void;
116
+ }
117
+ ```
118
+
119
+ ### `EventTracker`
120
+
121
+ Declarative DOM tracking via attributes (default prefix: `data-ga4-`).
122
+
123
+ Options matrix:
124
+
125
+ | Option | Type | Default | Notes |
126
+ |---|---|---|---|
127
+ | `events` | `string[]` | `['click']` | DOM events to listen to via delegation |
128
+ | `attributePrefix` | `string` | `'data-ga4-'` | Reads attributes like `data-ga4-on` and `data-ga4-event-name` |
129
+ | `hitFilter` | `(params, element, event) => Record<string, unknown> \| null` | `undefined` | Return `null` to skip sending |
130
+
131
+ Defaults:
132
+ - `events: ['click']`
133
+ - `attributePrefix: 'data-ga4-'`
134
+
135
+ Example:
136
+
137
+ ```html
138
+ <button
139
+ data-ga4-on="click"
140
+ data-ga4-event-name="video_play"
141
+ data-ga4-video-title="Summer Launch">
142
+ Play
143
+ </button>
144
+ ```
145
+
146
+ ```ts
147
+ import { ga4, EventTracker } from '@technoapple/ga4';
148
+
149
+ ga4.use(EventTracker);
150
+ ```
151
+
152
+ ### `OutboundLinkTracker`
153
+
154
+ Tracks clicks on links whose hostname differs from `location.hostname`.
155
+
156
+ Options matrix:
157
+
158
+ | Option | Type | Default | Notes |
159
+ |---|---|---|---|
160
+ | `events` | `string[]` | `['click']` | Event types for delegated tracking |
161
+ | `linkSelector` | `string` | `'a, area'` | CSS selector for trackable links |
162
+ | `shouldTrackOutboundLink` | `(link, parseUrl) => boolean` | Built-in external-hostname matcher | Override outbound detection logic |
163
+ | `eventName` | `string` | `'outbound_link_click'` | Custom event name |
164
+ | `hitFilter` | `(params, element, event) => Record<string, unknown> \| null` | `undefined` | Return `null` to cancel event |
165
+
166
+ Defaults:
167
+ - `events: ['click']`
168
+ - `linkSelector: 'a, area'`
169
+ - `eventName: 'outbound_link_click'`
170
+
171
+ Default params:
172
+ - `link_url`
173
+ - `link_domain`
174
+ - `outbound: true`
175
+
176
+ ### `OutboundFormTracker`
177
+
178
+ Tracks form submissions whose `form.action` points to an external hostname.
179
+
180
+ Options matrix:
181
+
182
+ | Option | Type | Default | Notes |
183
+ |---|---|---|---|
184
+ | `formSelector` | `string` | `'form'` | CSS selector for forms to observe |
185
+ | `shouldTrackOutboundForm` | `(form, parseUrl) => boolean` | Built-in external-hostname matcher | Override outbound detection logic |
186
+ | `eventName` | `string` | `'outbound_form_submit'` | Custom event name |
187
+ | `hitFilter` | `(params, element, event) => Record<string, unknown> \| null` | `undefined` | Return `null` to cancel event |
188
+
189
+ Defaults:
190
+ - `formSelector: 'form'`
191
+ - `eventName: 'outbound_form_submit'`
192
+
193
+ Default params:
194
+ - `form_action`
195
+ - `form_domain`
196
+ - `outbound: true`
197
+
198
+ ### `PageVisibilityTracker`
199
+
200
+ Tracks visible/hidden durations using `document.visibilityState`.
201
+
202
+ Options matrix:
203
+
204
+ | Option | Type | Default | Notes |
205
+ |---|---|---|---|
206
+ | `sendInitialPageview` | `boolean` | `false` | Sends initial `page_view` when page is visible |
207
+ | `sessionTimeout` | `number` | `30` | Minutes before visible return triggers new `page_view` |
208
+ | `eventName` | `string` | `'page_visibility'` | Custom visibility event name |
209
+ | `hitFilter` | `(params) => Record<string, unknown> \| null` | `undefined` | Return `null` to skip event |
210
+
211
+ Defaults:
212
+ - `sendInitialPageview: false`
213
+ - `sessionTimeout: 30` (minutes)
214
+ - `eventName: 'page_visibility'`
215
+
216
+ Default params:
217
+ - `visibility_state`
218
+ - `visibility_duration`
219
+ - `page_path`
220
+
221
+ ### `UrlChangeTracker`
222
+
223
+ Tracks SPA navigation by patching `history.pushState`, optional `replaceState`, and listening to `popstate`.
224
+
225
+ Options matrix:
226
+
227
+ | Option | Type | Default | Notes |
228
+ |---|---|---|---|
229
+ | `shouldTrackUrlChange` | `(newPath, oldPath) => boolean` | `newPath !== oldPath` | Decide when to emit `page_view` |
230
+ | `trackReplaceState` | `boolean` | `false` | Also patch `history.replaceState` |
231
+ | `hitFilter` | `(params) => Record<string, unknown> \| null` | `undefined` | Return `null` to skip event |
232
+
233
+ Defaults:
234
+ - `trackReplaceState: false`
235
+
236
+ Sends `page_view` with:
237
+ - `page_path`
238
+ - `page_title`
239
+ - `page_location`
240
+
241
+ ### `ImpressionTracker`
242
+
243
+ Tracks element impressions with `IntersectionObserver` and dynamic DOM changes via `MutationObserver`.
244
+
245
+ Options matrix:
246
+
247
+ | Option | Type | Default | Notes |
248
+ |---|---|---|---|
249
+ | `elements` | `Array<string \| ImpressionElementConfig>` | `[]` | Element IDs or config objects to observe |
250
+ | `rootMargin` | `string` | `'0px'` | Passed to `IntersectionObserver` |
251
+ | `attributePrefix` | `string` | `'data-ga4-'` | Reads matching element attributes into params |
252
+ | `eventName` | `string` | `'element_impression'` | Custom event name |
253
+ | `hitFilter` | `(params, element) => Record<string, unknown> \| null` | `undefined` | Return `null` to skip event |
254
+
255
+ Defaults:
256
+ - `rootMargin: '0px'`
257
+ - `attributePrefix: 'data-ga4-'`
258
+ - `eventName: 'element_impression'`
259
+
260
+ Can observe with element IDs or configs:
261
+
262
+ ```ts
263
+ import { ga4, ImpressionTracker } from '@technoapple/ga4';
264
+
265
+ ga4.use(ImpressionTracker, {
266
+ elements: [
267
+ 'hero-banner',
268
+ { id: 'cta-block', threshold: 0.5, trackFirstImpressionOnly: true },
269
+ ],
270
+ });
271
+ ```
272
+
273
+ ### `CleanUrlTracker`
274
+
275
+ Intercepts `gtag` calls for `config` and `page_view` payloads and normalizes:
276
+ - `page_location`
277
+ - `page_path`
278
+
279
+ Options matrix:
280
+
281
+ | Option | Type | Default | Notes |
282
+ |---|---|---|---|
283
+ | `stripQuery` | `boolean` | `false` | Remove all query params unless allowlist is set |
284
+ | `queryParamsAllowlist` | `string[]` | `undefined` | Keep only selected params when stripping query |
285
+ | `queryParamsDenylist` | `string[]` | `undefined` | Remove selected params when not stripping query |
286
+ | `trailingSlash` | `'add' \| 'remove'` | `undefined` | Normalize `page_path` slash behavior |
287
+ | `urlFilter` | `(url) => string` | `undefined` | Final custom URL transform |
288
+
289
+ Options include:
290
+ - `stripQuery`
291
+ - `queryParamsAllowlist`
292
+ - `queryParamsDenylist`
293
+ - `trailingSlash: 'add' | 'remove'`
294
+ - `urlFilter`
295
+
296
+ ### `MediaQueryTracker`
297
+
298
+ Tracks responsive breakpoint changes using `matchMedia`.
299
+
300
+ Options matrix:
301
+
302
+ | Option | Type | Default | Notes |
303
+ |---|---|---|---|
304
+ | `definitions` | `MediaQueryDefinition[]` | `[]` | Named breakpoint sets to track |
305
+ | `changeTemplate` | `(oldValue, newValue) => string` | `${oldValue} => ${newValue}` | Label formatter for change payload |
306
+ | `changeTimeout` | `number` | `1000` | Debounce delay in ms |
307
+ | `eventName` | `string` | `'media_query_change'` | Custom event name |
308
+ | `hitFilter` | `(params) => Record<string, unknown> \| null` | `undefined` | Return `null` to skip event |
309
+
310
+ Defaults:
311
+ - `changeTimeout: 1000`
312
+ - `eventName: 'media_query_change'`
313
+
314
+ Default params:
315
+ - `media_query_name`
316
+ - `media_query_value`
317
+ - `media_query_change`
318
+
319
+ ## Plugin Lifecycle
320
+
321
+ Use either approach:
322
+
323
+ ```ts
324
+ const plugin = ga4.use(EventTracker);
325
+ plugin.remove();
326
+ ```
327
+
328
+ ```ts
329
+ ga4.removeAll();
330
+ ```
331
+
332
+ ## Full Example
333
+
334
+ ```ts
335
+ import {
336
+ ga4,
337
+ EventTracker,
338
+ OutboundLinkTracker,
339
+ UrlChangeTracker,
340
+ CleanUrlTracker,
341
+ } from '@technoapple/ga4';
342
+
343
+ ga4.init({ targetId: 'G-XXXXXXX' });
344
+
345
+ ga4.use(CleanUrlTracker, {
346
+ stripQuery: true,
347
+ queryParamsAllowlist: ['utm_source', 'utm_medium', 'utm_campaign'],
348
+ trailingSlash: 'remove',
349
+ });
350
+
351
+ ga4.use(EventTracker, { events: ['click', 'submit'] });
352
+ ga4.use(OutboundLinkTracker);
353
+ ga4.use(UrlChangeTracker, { trackReplaceState: true });
354
+
355
+ ga4.send('app_initialized', { env: 'production' });
356
+ ```
357
+
358
+ ## TypeScript Support
359
+
360
+ The package ships type definitions and exports plugin option types, including:
361
+ - `EventTrackerOptions`
362
+ - `OutboundLinkTrackerOptions`
363
+ - `OutboundFormTrackerOptions`
364
+ - `PageVisibilityTrackerOptions`
365
+ - `UrlChangeTrackerOptions`
366
+ - `ImpressionTrackerOptions`
367
+ - `CleanUrlTrackerOptions`
368
+ - `MediaQueryTrackerOptions`
369
+
370
+ ## Development
371
+
372
+ ```bash
373
+ npm run build
374
+ npm test
375
+ npm run test:coverage
376
+ ```
377
+
378
+ ## Notes
379
+
380
+ - Browser-focused library: APIs rely on `window`, `document`, and browser events.
381
+ - Call `ga4.init(...)` before sending events or registering plugins.
382
+ - If you register many plugins, clean them up with `remove()` or `ga4.removeAll()` to avoid duplicate listeners in long-lived apps.
383
+
384
+ ## License
385
+
386
+ ISC