@glomex/integration-analytics 1.1480.1 → 1.1483.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/README.md +241 -12
- package/dist/base-event-mapper.d.ts +56 -0
- package/dist/base-event-mapper.js +313 -0
- package/dist/comscore/comscore-event-mapper.d.ts +26 -0
- package/dist/comscore/comscore-event-mapper.js +218 -0
- package/dist/comscore/comscore-metadata.d.ts +15 -0
- package/dist/comscore/comscore-metadata.js +58 -0
- package/dist/comscore/comscore-sdk-loader.d.ts +12 -0
- package/dist/comscore/comscore-sdk-loader.js +35 -0
- package/dist/comscore/comscore-types.d.ts +11 -0
- package/dist/comscore/comscore-types.js +6 -0
- package/dist/comscore/index.d.ts +72 -0
- package/dist/comscore/index.js +101 -0
- package/dist/nielsen/index.d.ts +70 -0
- package/dist/nielsen/index.js +87 -0
- package/dist/nielsen/nielsen-event-mapper.d.ts +23 -0
- package/dist/nielsen/nielsen-event-mapper.js +167 -0
- package/dist/nielsen/nielsen-metadata.d.ts +35 -0
- package/dist/nielsen/nielsen-metadata.js +134 -0
- package/dist/nielsen/nielsen-sdk-loader.d.ts +13 -0
- package/dist/nielsen/nielsen-sdk-loader.js +70 -0
- package/dist/nielsen/nielsen-types.d.ts +128 -0
- package/dist/nielsen/nielsen-types.js +4 -0
- package/dist/npaw/index.d.ts +7 -291
- package/dist/npaw/index.js +32 -313
- package/dist/npaw/npaw-turbo-player-ad-adapter.d.ts +43 -0
- package/dist/npaw/npaw-turbo-player-ad-adapter.js +142 -0
- package/dist/npaw/npaw-turbo-player-adapter.d.ts +47 -0
- package/dist/npaw/npaw-turbo-player-adapter.js +136 -0
- package/dist/npaw/npaw-types.d.ts +202 -0
- package/dist/npaw/npaw-types.js +6 -0
- package/dist/npaw/package-info.d.ts +2 -0
- package/dist/npaw/package-info.js +3 -0
- package/dist/sensic/index.d.ts +89 -0
- package/dist/sensic/index.js +103 -0
- package/dist/sensic/sensic-event-mapper.d.ts +25 -0
- package/dist/sensic/sensic-event-mapper.js +147 -0
- package/dist/sensic/sensic-metadata.d.ts +34 -0
- package/dist/sensic/sensic-metadata.js +102 -0
- package/dist/sensic/sensic-sdk-loader.d.ts +16 -0
- package/dist/sensic/sensic-sdk-loader.js +102 -0
- package/dist/sensic/sensic-types.d.ts +80 -0
- package/dist/sensic/sensic-types.js +6 -0
- package/package.json +18 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @glomex/integration-analytics
|
|
2
2
|
|
|
3
|
-
A unified analytics integration package for the [turbo player](https://www.npmjs.com/package/@glomex/integration-web-component). This package provides adapters for various analytics providers including NPAW/Youbora,
|
|
3
|
+
A unified analytics integration package for the [turbo player](https://www.npmjs.com/package/@glomex/integration-web-component). This package provides adapters for various analytics providers including NPAW/Youbora, Comscore, Nielsen, and Sensic.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -21,7 +21,9 @@ import { connectToNpaw } from '@glomex/integration-analytics/npaw';
|
|
|
21
21
|
const integration = document.querySelector('glomex-integration');
|
|
22
22
|
|
|
23
23
|
// Simple setup for NPAW analytics (that also handles consent)
|
|
24
|
-
const { npawPlugin, destroy } = connectToNpaw(integration,
|
|
24
|
+
const { npawPlugin, destroy } = connectToNpaw(integration, {
|
|
25
|
+
accountCode: 'your-npaw-account-code'
|
|
26
|
+
});
|
|
25
27
|
```
|
|
26
28
|
|
|
27
29
|
#### Advanced NPAW Configuration
|
|
@@ -29,7 +31,8 @@ const { npawPlugin, destroy } = connectToNpaw(integration, 'your-npaw-account-co
|
|
|
29
31
|
```typescript
|
|
30
32
|
import { connectToNpaw } from '@glomex/integration-analytics/npaw';
|
|
31
33
|
|
|
32
|
-
const { npawPlugin, destroy } = connectToNpaw(integration,
|
|
34
|
+
const { npawPlugin, destroy } = connectToNpaw(integration, {
|
|
35
|
+
accountCode: 'your-npaw-account-code',
|
|
33
36
|
appName: 'my-app',
|
|
34
37
|
appVersion: '1.0.0'
|
|
35
38
|
});
|
|
@@ -55,22 +58,249 @@ const npawPlugin = new NpawPlugin('your-npaw-account-code');
|
|
|
55
58
|
npawPlugin.registerAdapterFromClass(integration, NpawTurboPlayerAdapter);
|
|
56
59
|
```
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
### Comscore
|
|
62
|
+
|
|
63
|
+
[Comscore Streaming Analytics](https://www.comscore.com/) integration for measuring streaming video consumption.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { connectToComscore } from '@glomex/integration-analytics/comscore';
|
|
67
|
+
|
|
68
|
+
// Get your integration element
|
|
69
|
+
const integration = document.querySelector('glomex-integration');
|
|
70
|
+
|
|
71
|
+
// Connect Comscore analytics
|
|
72
|
+
const disconnect = connectToComscore(integration, {
|
|
73
|
+
publisherId: 'your-publisher-id',
|
|
74
|
+
publisherName: 'Your Publisher Name'
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Clean up when done
|
|
78
|
+
disconnect();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### Comscore Configuration Options
|
|
82
|
+
|
|
83
|
+
| Option | Type | Required | Description |
|
|
84
|
+
|--------|------|----------|-------------|
|
|
85
|
+
| `publisherId` | `string` | Yes | Your Comscore publisher ID |
|
|
86
|
+
| `publisherName` | `string` | Yes | Your publisher name (e.g., "Joyn") |
|
|
87
|
+
| `debug` | `boolean` | No | Enable debug mode for implementation validation |
|
|
88
|
+
| `warnCallback` | `(error: Error) => void` | No | Custom callback for warning messages |
|
|
89
|
+
| `configureContentMetadata` | `(metadata) => metadata \| null` | No | Customize content metadata before tracking |
|
|
90
|
+
|
|
91
|
+
#### Advanced Comscore Configuration
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { connectToComscore } from '@glomex/integration-analytics/comscore';
|
|
95
|
+
|
|
96
|
+
const disconnect = connectToComscore(integration, {
|
|
97
|
+
publisherId: 'your-publisher-id',
|
|
98
|
+
publisherName: 'Your Publisher Name',
|
|
99
|
+
debug: true,
|
|
100
|
+
warnCallback: (error) => {
|
|
101
|
+
console.warn('Comscore warning:', error.message);
|
|
102
|
+
},
|
|
103
|
+
configureContentMetadata: (metadata) => {
|
|
104
|
+
// Customize metadata or return null to skip tracking
|
|
105
|
+
metadata.setGenreName('Sports');
|
|
106
|
+
return metadata;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### Comscore Consent Requirements
|
|
112
|
+
|
|
113
|
+
Comscore tracking only occurs when the user has given consent according to IAB TCF:
|
|
114
|
+
- **Purpose 1** consent (Store and/or access information on a device)
|
|
115
|
+
- **Vendor 77** consent (Comscore)
|
|
116
|
+
|
|
117
|
+
### Nielsen
|
|
118
|
+
|
|
119
|
+
[Nielsen Digital Content Ratings](https://www.nielsen.com/solutions/audience-measurement/streaming-measurement/) integration for streaming measurement.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { connectToNielsen } from '@glomex/integration-analytics/nielsen';
|
|
123
|
+
|
|
124
|
+
// Get your integration element
|
|
125
|
+
const integration = document.querySelector('glomex-integration');
|
|
126
|
+
|
|
127
|
+
// Connect Nielsen analytics
|
|
128
|
+
const disconnect = connectToNielsen(integration, {
|
|
129
|
+
appId: 'your-app-id',
|
|
130
|
+
country: 'de'
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Clean up when done
|
|
134
|
+
disconnect();
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### Nielsen Configuration Options
|
|
138
|
+
|
|
139
|
+
| Option | Type | Required | Description |
|
|
140
|
+
|--------|------|----------|-------------|
|
|
141
|
+
| `appId` | `string` | Yes | Nielsen App ID (starts with 'P' for production, 'T' for test) |
|
|
142
|
+
| `country` | `string` | Yes | Two-letter country code for country-specific metadata mapping (e.g., 'de' for Germany) |
|
|
143
|
+
| `debug` | `boolean` | No | Enable Nielsen SDK debug mode |
|
|
144
|
+
| `warnCallback` | `(error: Error) => void` | No | Custom callback for warning messages |
|
|
145
|
+
| `configureContentMetadata` | `(metadata) => metadata \| null` | No | Extend/modify content metadata for tracking |
|
|
146
|
+
| `configureAdMetadata` | `(metadata) => metadata` | No | Extend/modify ad metadata for tracking |
|
|
147
|
+
|
|
148
|
+
#### Supported Countries
|
|
149
|
+
|
|
150
|
+
Nielsen uses country-specific metadata mappings:
|
|
151
|
+
- `'de'` - Germany (uses AGF-specific custom variables)
|
|
152
|
+
- Other country codes - Base metadata only (`assetid`, `type`, `program`, `title`, `length` for content; `assetid`, `type` for ads)
|
|
153
|
+
|
|
154
|
+
For Germany (`country: 'de'`), the following metadata is automatically built:
|
|
155
|
+
- **Content metadata**: `assetid`, `program`, `title`, `length`, `nol_c0` (part number), `nol_c2` (web only flag), `nol_c5` (page URL), `nol_c7` (video ID), `nol_c9` (video title), `nol_c10` (publisher), `nol_c12` (video type), `nol_c15` (format ID), `nol_c18` (livestream flag)
|
|
156
|
+
- **Ad metadata**: `assetid`, `type`, `length`, `title`, `nol_c1` (universal ad ID), `nol_c2`, `nol_c4` (ad form), `nol_c10`, `nol_c11` (ad ID), `nol_c12`, `nol_c17` (placement type), `nol_c18`
|
|
157
|
+
|
|
158
|
+
For unsupported country codes, only base metadata is provided. Use `configureContentMetadata` and `configureAdMetadata` to add country-specific fields manually:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const disconnect = connectToNielsen(integration, {
|
|
162
|
+
appId: 'your-app-id',
|
|
163
|
+
country: 'fr', // No built-in mapping for France
|
|
164
|
+
configureContentMetadata: (baseMetadata) => {
|
|
165
|
+
// baseMetadata contains: assetid, type, program, title, length
|
|
166
|
+
return {
|
|
167
|
+
...baseMetadata,
|
|
168
|
+
program: integration.content?.show?.name ?? '',
|
|
169
|
+
// Add France-specific fields as needed
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
configureAdMetadata: (baseMetadata) => {
|
|
173
|
+
// baseMetadata contains: assetid, type
|
|
174
|
+
return {
|
|
175
|
+
...baseMetadata,
|
|
176
|
+
title: integration.currentAd?.title ?? ''
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### Advanced Nielsen Configuration
|
|
59
183
|
|
|
60
|
-
The
|
|
184
|
+
The `configureContentMetadata` and `configureAdMetadata` callbacks receive country-specific metadata that is automatically built from integration data. You can extend or modify this metadata:
|
|
61
185
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
186
|
+
```typescript
|
|
187
|
+
import { connectToNielsen } from '@glomex/integration-analytics/nielsen';
|
|
188
|
+
|
|
189
|
+
const disconnect = connectToNielsen(integration, {
|
|
190
|
+
appId: 'your-app-id',
|
|
191
|
+
country: 'de',
|
|
192
|
+
debug: true,
|
|
193
|
+
warnCallback: (error) => {
|
|
194
|
+
console.warn('Nielsen warning:', error.message);
|
|
195
|
+
},
|
|
196
|
+
configureContentMetadata: (metadata) => {
|
|
197
|
+
// metadata contains country-specific fields (e.g., AGF fields for 'de')
|
|
198
|
+
// Return null to skip tracking this content
|
|
199
|
+
if (!integration.content) return null;
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
...metadata,
|
|
203
|
+
// Add custom fields or override existing ones
|
|
204
|
+
subbrand: 'abc' // VCID provided by Nielsen
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
configureAdMetadata: (metadata) => {
|
|
208
|
+
// metadata contains country-specific ad fields
|
|
209
|
+
return {
|
|
210
|
+
...metadata,
|
|
211
|
+
subbrand: 'abc'
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### Nielsen Consent
|
|
218
|
+
|
|
219
|
+
Nielsen does not require consent checks and uses a synchronous stub pattern. Events are queued until the SDK loads from CDN.
|
|
220
|
+
|
|
221
|
+
### Sensic (GfK)
|
|
222
|
+
|
|
223
|
+
[Sensic](https://sensic.net/) (formerly GfK) integration for streaming measurement in Germany and Austria.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { connectToSensic } from '@glomex/integration-analytics/sensic';
|
|
227
|
+
|
|
228
|
+
// Get your integration element
|
|
229
|
+
const integration = document.querySelector('glomex-integration');
|
|
230
|
+
|
|
231
|
+
// Connect Sensic analytics
|
|
232
|
+
const disconnect = connectToSensic(integration, {
|
|
233
|
+
media: 'your-media-id',
|
|
234
|
+
platform: 'web',
|
|
235
|
+
country: 'de'
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Clean up when done
|
|
239
|
+
disconnect();
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### Sensic Configuration Options
|
|
243
|
+
|
|
244
|
+
| Option | Type | Required | Description |
|
|
245
|
+
|--------|------|----------|-------------|
|
|
246
|
+
| `media` | `string` | Yes | Sensic media identifier |
|
|
247
|
+
| `platform` | `'web' \| 'tv'` | Yes | Platform type |
|
|
248
|
+
| `country` | `string` | Yes | Two-letter country code (e.g., `'de'`, `'at'`) |
|
|
249
|
+
| `warnCallback` | `(error: Error) => void` | No | Custom callback for warning messages |
|
|
250
|
+
| `buildCustomParams` | `(customParams) => Record<string, string> \| null` | No | Extend/modify custom parameters for tracking |
|
|
251
|
+
| `buildStreamId` | `(streamId) => string` | No | Extend/modify stream ID for tracking |
|
|
252
|
+
|
|
253
|
+
#### Advanced Sensic Configuration
|
|
254
|
+
|
|
255
|
+
The `buildCustomParams` and `buildStreamId` callbacks receive country-specific values as input and are called on each stream start (VoD content, live content, and linear ads). Use `integration.content` for content data or `integration.currentAd` for ad data:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { connectToSensic } from '@glomex/integration-analytics/sensic';
|
|
259
|
+
|
|
260
|
+
const disconnect = connectToSensic(integration, {
|
|
261
|
+
media: 'your-media-id',
|
|
262
|
+
platform: 'web',
|
|
263
|
+
country: 'at',
|
|
264
|
+
warnCallback: (error) => {
|
|
265
|
+
console.warn('Sensic warning:', error.message);
|
|
266
|
+
},
|
|
267
|
+
buildCustomParams: (customParams) => {
|
|
268
|
+
// customParams contains country-specific params (e.g., AT params for Austria)
|
|
269
|
+
// Return null to skip tracking this stream
|
|
270
|
+
return {
|
|
271
|
+
...customParams,
|
|
272
|
+
genre: integration.content?.genre ?? '',
|
|
273
|
+
showId: integration.content?.show?.id ?? ''
|
|
274
|
+
};
|
|
275
|
+
},
|
|
276
|
+
buildStreamId: (streamId) => {
|
|
277
|
+
// streamId contains country-specific stream ID
|
|
278
|
+
return streamId || integration.content?.id ?? '';
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
#### Sensic Consent Requirements
|
|
284
|
+
|
|
285
|
+
Sensic tracking only occurs when the user has given consent according to IAB TCF:
|
|
286
|
+
- **Vendor 758** consent (Sensic)
|
|
287
|
+
|
|
288
|
+
#### Supported Countries
|
|
289
|
+
|
|
290
|
+
Sensic loads country-specific measurement scripts:
|
|
291
|
+
- `'de'` - Germany (`https://de-config.sensic.net/s2s-web.js`)
|
|
292
|
+
- `'at'` - Austria (`https://at-config.sensic.net/s2s-web.js`)
|
|
293
|
+
|
|
294
|
+
For TV platforms, the CTV variant is loaded automatically (e.g., `https://de-config.sensic.net/ctv/s2s-web.js`).
|
|
66
295
|
|
|
67
296
|
## Features
|
|
68
297
|
|
|
69
298
|
- 🎯 **Easy Integration**: Simple setup with turbo player
|
|
70
|
-
- 📊 **Multiple Providers**: Support for
|
|
299
|
+
- 📊 **Multiple Providers**: Support for NPAW, Comscore, Nielsen, and Sensic
|
|
71
300
|
- 🔧 **Smart Metadata Mapping**: Automatically maps player content metadata to each analytics provider's format, with full support for custom overrides
|
|
72
301
|
- 📱 **Cross-Platform**: Works with web, mobile web, and embedded players
|
|
73
|
-
- 🔒 **Consent Handling**: Built-in consent management support
|
|
302
|
+
- 🔒 **Consent Handling**: Built-in consent management support with IAB TCF compliance
|
|
303
|
+
- 🎬 **Comprehensive Tracking**: Tracks content and linear ad playback (preroll, midroll, postroll)
|
|
74
304
|
|
|
75
305
|
## License
|
|
76
306
|
|
|
@@ -79,4 +309,3 @@ MIT
|
|
|
79
309
|
## Support
|
|
80
310
|
|
|
81
311
|
For issues and questions, please visit the [glomex documentation](https://docs.glomex.com) or contact support.
|
|
82
|
-
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type IntegrationElement } from '@glomex/integration-web-component';
|
|
2
|
+
/**
|
|
3
|
+
* Base class for analytics event mappers that handles:
|
|
4
|
+
* - Event subscription/unsubscription
|
|
5
|
+
* - Event queuing until SDK is ready
|
|
6
|
+
* - Common state management (stopped, inAd, contentStarted, hasContentImpression)
|
|
7
|
+
*/
|
|
8
|
+
export declare abstract class BaseEventMapper {
|
|
9
|
+
#private;
|
|
10
|
+
protected readonly integration: IntegrationElement;
|
|
11
|
+
protected readonly onWarn: (error: Error) => void;
|
|
12
|
+
constructor(integration: IntegrationElement, onWarn: (error: Error) => void);
|
|
13
|
+
/** Whether the event mapper has been stopped/destroyed */
|
|
14
|
+
protected get isStopped(): boolean;
|
|
15
|
+
/** Whether we are currently in an ad */
|
|
16
|
+
protected get isInAd(): boolean;
|
|
17
|
+
/** Whether onContentStart has been called */
|
|
18
|
+
protected get contentStarted(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Mark SDK as ready and flush any queued events.
|
|
21
|
+
*/
|
|
22
|
+
protected markSdkReady(): void;
|
|
23
|
+
protected onContentStart(): void;
|
|
24
|
+
protected onContentImpression(): void;
|
|
25
|
+
protected onContentPlay(): void;
|
|
26
|
+
protected onContentPause(): void;
|
|
27
|
+
protected onContentSeeking(): void;
|
|
28
|
+
protected onContentSeeked(): void;
|
|
29
|
+
protected onContentBufferingStart(): void;
|
|
30
|
+
protected onContentBufferingEnd(): void;
|
|
31
|
+
protected onContentEnded(): void;
|
|
32
|
+
protected onContentTimeUpdate(): void;
|
|
33
|
+
protected onVolumeChange(): void;
|
|
34
|
+
protected onPresentationModeChange(): void;
|
|
35
|
+
protected onAdImpression(): void;
|
|
36
|
+
protected onAdPaused(): void;
|
|
37
|
+
protected onAdResumed(): void;
|
|
38
|
+
protected onAdBufferingStart(): void;
|
|
39
|
+
protected onAdBufferingEnd(): void;
|
|
40
|
+
protected onAdEnd(): void;
|
|
41
|
+
protected onAdTimeUpdate(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Override to perform SDK-specific cleanup (e.g., send end/stop signal).
|
|
44
|
+
* Called once when destroy() is invoked.
|
|
45
|
+
*/
|
|
46
|
+
protected onDestroy(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Tears down the event mapper:
|
|
49
|
+
* 1. Marks as stopped (prevents further event processing)
|
|
50
|
+
* 2. Calls onDestroy() for SDK-specific cleanup
|
|
51
|
+
* 3. Removes all event listeners
|
|
52
|
+
*
|
|
53
|
+
* Safe to call multiple times - subsequent calls are no-ops.
|
|
54
|
+
*/
|
|
55
|
+
destroy(): void;
|
|
56
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { IntegrationEvent } from '@glomex/integration-web-component';
|
|
2
|
+
/** Ad break types that are tracked (linear ads) */
|
|
3
|
+
const LINEAR_AD_BREAKS = ['preroll', 'midroll', 'postroll'];
|
|
4
|
+
/**
|
|
5
|
+
* All playback events that analytics adapters may track
|
|
6
|
+
*/
|
|
7
|
+
const PLAYBACK_EVENTS = [
|
|
8
|
+
IntegrationEvent.CONTENT_START,
|
|
9
|
+
IntegrationEvent.CONTENT_IMPRESSION,
|
|
10
|
+
IntegrationEvent.CONTENT_PLAY,
|
|
11
|
+
IntegrationEvent.CONTENT_PAUSE,
|
|
12
|
+
IntegrationEvent.CONTENT_SEEKING,
|
|
13
|
+
IntegrationEvent.CONTENT_SEEKED,
|
|
14
|
+
IntegrationEvent.CONTENT_BUFFERING_START,
|
|
15
|
+
IntegrationEvent.CONTENT_BUFFERING_END,
|
|
16
|
+
IntegrationEvent.CONTENT_ENDED,
|
|
17
|
+
IntegrationEvent.CONTENT_STOP,
|
|
18
|
+
IntegrationEvent.CONTENT_TIME_UPDATE,
|
|
19
|
+
IntegrationEvent.CONTENT_VOLUME_CHANGE,
|
|
20
|
+
IntegrationEvent.PLAYER_SET_PRESENTATION_MODE,
|
|
21
|
+
IntegrationEvent.AD_IMPRESSION,
|
|
22
|
+
IntegrationEvent.AD_PAUSED,
|
|
23
|
+
IntegrationEvent.AD_RESUMED,
|
|
24
|
+
IntegrationEvent.AD_BUFFERING_START,
|
|
25
|
+
IntegrationEvent.AD_BUFFERING_END,
|
|
26
|
+
IntegrationEvent.AD_COMPLETE,
|
|
27
|
+
IntegrationEvent.AD_SKIPPED,
|
|
28
|
+
IntegrationEvent.AD_TIME_UPDATE,
|
|
29
|
+
IntegrationEvent.AD_ERROR
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Base class for analytics event mappers that handles:
|
|
33
|
+
* - Event subscription/unsubscription
|
|
34
|
+
* - Event queuing until SDK is ready
|
|
35
|
+
* - Common state management (stopped, inAd, contentStarted, hasContentImpression)
|
|
36
|
+
*/
|
|
37
|
+
export class BaseEventMapper {
|
|
38
|
+
integration;
|
|
39
|
+
onWarn;
|
|
40
|
+
#stopped = false;
|
|
41
|
+
#inAd = false;
|
|
42
|
+
#contentStarted = false;
|
|
43
|
+
#hasContentImpression = false;
|
|
44
|
+
#eventHandler;
|
|
45
|
+
#eventQueue = [];
|
|
46
|
+
#sdkReady = false;
|
|
47
|
+
constructor(integration, onWarn) {
|
|
48
|
+
this.integration = integration;
|
|
49
|
+
this.onWarn = onWarn;
|
|
50
|
+
this.#eventHandler = this.#handleEvent.bind(this);
|
|
51
|
+
for (const eventType of PLAYBACK_EVENTS) {
|
|
52
|
+
this.integration.addEventListener(eventType, this.#eventHandler);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Whether the event mapper has been stopped/destroyed */
|
|
56
|
+
get isStopped() {
|
|
57
|
+
return this.#stopped;
|
|
58
|
+
}
|
|
59
|
+
/** Whether we are currently in an ad */
|
|
60
|
+
get isInAd() {
|
|
61
|
+
return this.#inAd;
|
|
62
|
+
}
|
|
63
|
+
/** Whether onContentStart has been called */
|
|
64
|
+
get contentStarted() {
|
|
65
|
+
return this.#contentStarted;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Mark SDK as ready and flush any queued events.
|
|
69
|
+
*/
|
|
70
|
+
markSdkReady() {
|
|
71
|
+
this.#sdkReady = true;
|
|
72
|
+
for (const fn of this.#eventQueue) {
|
|
73
|
+
fn();
|
|
74
|
+
}
|
|
75
|
+
this.#eventQueue = [];
|
|
76
|
+
}
|
|
77
|
+
#enqueue(fn) {
|
|
78
|
+
if (this.#sdkReady) {
|
|
79
|
+
fn();
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this.#eventQueue.push(fn);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
#handleEvent(event) {
|
|
86
|
+
if (this.#stopped)
|
|
87
|
+
return;
|
|
88
|
+
const handler = this.#getHandler(event.type);
|
|
89
|
+
if (!handler)
|
|
90
|
+
return;
|
|
91
|
+
this.#enqueue(() => {
|
|
92
|
+
try {
|
|
93
|
+
handler();
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
this.onWarn(error instanceof Error ? error : new Error(String(error)));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
#getHandler(type) {
|
|
101
|
+
switch (type) {
|
|
102
|
+
case IntegrationEvent.CONTENT_START:
|
|
103
|
+
return () => {
|
|
104
|
+
this.#contentStarted = true;
|
|
105
|
+
this.onContentStart();
|
|
106
|
+
};
|
|
107
|
+
case IntegrationEvent.CONTENT_IMPRESSION:
|
|
108
|
+
return () => {
|
|
109
|
+
this.#hasContentImpression = true;
|
|
110
|
+
this.onContentImpression();
|
|
111
|
+
};
|
|
112
|
+
case IntegrationEvent.CONTENT_PLAY:
|
|
113
|
+
return () => {
|
|
114
|
+
// plays before impression are not interesting
|
|
115
|
+
if (!this.#hasContentImpression)
|
|
116
|
+
return;
|
|
117
|
+
// If ad didn't end properly, end it now
|
|
118
|
+
if (this.#inAd) {
|
|
119
|
+
this.onAdEnd();
|
|
120
|
+
this.#inAd = false;
|
|
121
|
+
}
|
|
122
|
+
this.onContentPlay();
|
|
123
|
+
};
|
|
124
|
+
case IntegrationEvent.CONTENT_PAUSE:
|
|
125
|
+
return () => {
|
|
126
|
+
if (!this.#hasContentImpression)
|
|
127
|
+
return;
|
|
128
|
+
this.onContentPause();
|
|
129
|
+
};
|
|
130
|
+
case IntegrationEvent.CONTENT_SEEKING:
|
|
131
|
+
return () => {
|
|
132
|
+
if (!this.#hasContentImpression)
|
|
133
|
+
return;
|
|
134
|
+
this.onContentSeeking();
|
|
135
|
+
};
|
|
136
|
+
case IntegrationEvent.CONTENT_SEEKED:
|
|
137
|
+
return () => {
|
|
138
|
+
if (!this.#hasContentImpression)
|
|
139
|
+
return;
|
|
140
|
+
this.onContentSeeked();
|
|
141
|
+
};
|
|
142
|
+
case IntegrationEvent.CONTENT_BUFFERING_START:
|
|
143
|
+
return () => {
|
|
144
|
+
if (!this.#hasContentImpression)
|
|
145
|
+
return;
|
|
146
|
+
this.onContentBufferingStart();
|
|
147
|
+
};
|
|
148
|
+
case IntegrationEvent.CONTENT_BUFFERING_END:
|
|
149
|
+
return () => {
|
|
150
|
+
if (!this.#hasContentImpression)
|
|
151
|
+
return;
|
|
152
|
+
this.onContentBufferingEnd();
|
|
153
|
+
};
|
|
154
|
+
case IntegrationEvent.CONTENT_ENDED:
|
|
155
|
+
return () => {
|
|
156
|
+
if (!this.#hasContentImpression)
|
|
157
|
+
return;
|
|
158
|
+
this.onContentEnded();
|
|
159
|
+
};
|
|
160
|
+
case IntegrationEvent.CONTENT_STOP:
|
|
161
|
+
return () => this.destroy();
|
|
162
|
+
case IntegrationEvent.CONTENT_TIME_UPDATE:
|
|
163
|
+
return () => {
|
|
164
|
+
if (!this.#hasContentImpression)
|
|
165
|
+
return;
|
|
166
|
+
this.onContentTimeUpdate();
|
|
167
|
+
};
|
|
168
|
+
case IntegrationEvent.CONTENT_VOLUME_CHANGE:
|
|
169
|
+
return () => this.onVolumeChange();
|
|
170
|
+
case IntegrationEvent.PLAYER_SET_PRESENTATION_MODE:
|
|
171
|
+
return () => this.onPresentationModeChange();
|
|
172
|
+
case IntegrationEvent.AD_IMPRESSION:
|
|
173
|
+
return () => {
|
|
174
|
+
// only allow linear ads into #inAd mode
|
|
175
|
+
if (!this.#isLinearAd(this.integration.currentAd))
|
|
176
|
+
return;
|
|
177
|
+
this.#inAd = true;
|
|
178
|
+
this.onAdImpression();
|
|
179
|
+
};
|
|
180
|
+
case IntegrationEvent.AD_PAUSED:
|
|
181
|
+
return () => {
|
|
182
|
+
if (!this.#inAd)
|
|
183
|
+
return;
|
|
184
|
+
this.onAdPaused();
|
|
185
|
+
};
|
|
186
|
+
case IntegrationEvent.AD_RESUMED:
|
|
187
|
+
return () => {
|
|
188
|
+
if (!this.#inAd)
|
|
189
|
+
return;
|
|
190
|
+
this.onAdResumed();
|
|
191
|
+
};
|
|
192
|
+
case IntegrationEvent.AD_BUFFERING_START:
|
|
193
|
+
return () => {
|
|
194
|
+
if (!this.#inAd)
|
|
195
|
+
return;
|
|
196
|
+
this.onAdBufferingStart();
|
|
197
|
+
};
|
|
198
|
+
case IntegrationEvent.AD_BUFFERING_END:
|
|
199
|
+
return () => {
|
|
200
|
+
if (!this.#inAd)
|
|
201
|
+
return;
|
|
202
|
+
this.onAdBufferingEnd();
|
|
203
|
+
};
|
|
204
|
+
case IntegrationEvent.AD_COMPLETE:
|
|
205
|
+
case IntegrationEvent.AD_SKIPPED:
|
|
206
|
+
case IntegrationEvent.AD_ERROR:
|
|
207
|
+
return () => {
|
|
208
|
+
if (!this.#inAd)
|
|
209
|
+
return;
|
|
210
|
+
this.onAdEnd();
|
|
211
|
+
this.#inAd = false;
|
|
212
|
+
};
|
|
213
|
+
case IntegrationEvent.AD_TIME_UPDATE:
|
|
214
|
+
return () => {
|
|
215
|
+
if (!this.#inAd)
|
|
216
|
+
return;
|
|
217
|
+
this.onAdTimeUpdate();
|
|
218
|
+
};
|
|
219
|
+
default:
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Default no-op implementations - subclasses override what they need
|
|
224
|
+
onContentStart() {
|
|
225
|
+
// no-op
|
|
226
|
+
}
|
|
227
|
+
onContentImpression() {
|
|
228
|
+
// no-op
|
|
229
|
+
}
|
|
230
|
+
onContentPlay() {
|
|
231
|
+
// no-op
|
|
232
|
+
}
|
|
233
|
+
onContentPause() {
|
|
234
|
+
// no-op
|
|
235
|
+
}
|
|
236
|
+
onContentSeeking() {
|
|
237
|
+
// no-op
|
|
238
|
+
}
|
|
239
|
+
onContentSeeked() {
|
|
240
|
+
// no-op
|
|
241
|
+
}
|
|
242
|
+
onContentBufferingStart() {
|
|
243
|
+
// no-op
|
|
244
|
+
}
|
|
245
|
+
onContentBufferingEnd() {
|
|
246
|
+
// no-op
|
|
247
|
+
}
|
|
248
|
+
onContentEnded() {
|
|
249
|
+
// no-op
|
|
250
|
+
}
|
|
251
|
+
onContentTimeUpdate() {
|
|
252
|
+
// no-op
|
|
253
|
+
}
|
|
254
|
+
onVolumeChange() {
|
|
255
|
+
// no-op
|
|
256
|
+
}
|
|
257
|
+
onPresentationModeChange() {
|
|
258
|
+
// no-op
|
|
259
|
+
}
|
|
260
|
+
onAdImpression() {
|
|
261
|
+
// no-op
|
|
262
|
+
}
|
|
263
|
+
onAdPaused() {
|
|
264
|
+
// no-op
|
|
265
|
+
}
|
|
266
|
+
onAdResumed() {
|
|
267
|
+
// no-op
|
|
268
|
+
}
|
|
269
|
+
onAdBufferingStart() {
|
|
270
|
+
// no-op
|
|
271
|
+
}
|
|
272
|
+
onAdBufferingEnd() {
|
|
273
|
+
// no-op
|
|
274
|
+
}
|
|
275
|
+
onAdEnd() {
|
|
276
|
+
// no-op
|
|
277
|
+
}
|
|
278
|
+
onAdTimeUpdate() {
|
|
279
|
+
// no-op
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Override to perform SDK-specific cleanup (e.g., send end/stop signal).
|
|
283
|
+
* Called once when destroy() is invoked.
|
|
284
|
+
*/
|
|
285
|
+
onDestroy() {
|
|
286
|
+
// no-op
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Check if the ad is a linear ad (preroll, midroll, postroll).
|
|
290
|
+
*/
|
|
291
|
+
#isLinearAd(ad) {
|
|
292
|
+
if (!ad)
|
|
293
|
+
return false;
|
|
294
|
+
return LINEAR_AD_BREAKS.includes(ad.breakName);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Tears down the event mapper:
|
|
298
|
+
* 1. Marks as stopped (prevents further event processing)
|
|
299
|
+
* 2. Calls onDestroy() for SDK-specific cleanup
|
|
300
|
+
* 3. Removes all event listeners
|
|
301
|
+
*
|
|
302
|
+
* Safe to call multiple times - subsequent calls are no-ops.
|
|
303
|
+
*/
|
|
304
|
+
destroy() {
|
|
305
|
+
if (this.#stopped)
|
|
306
|
+
return;
|
|
307
|
+
this.#stopped = true;
|
|
308
|
+
this.onDestroy();
|
|
309
|
+
for (const eventType of PLAYBACK_EVENTS) {
|
|
310
|
+
this.integration.removeEventListener(eventType, this.#eventHandler);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type IntegrationElement } from '@glomex/integration-web-component';
|
|
2
|
+
import { BaseEventMapper } from '../base-event-mapper.js';
|
|
3
|
+
import type { Analytics, ContentMetadataInstance } from './comscore-types.js';
|
|
4
|
+
export declare class ComscoreEventMapper extends BaseEventMapper {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(integration: IntegrationElement, sdkReadyPromise: Promise<{
|
|
7
|
+
analytics: Analytics;
|
|
8
|
+
contentMetadata: ContentMetadataInstance;
|
|
9
|
+
} | undefined>, onWarn: (error: Error) => void);
|
|
10
|
+
protected onContentStart(): void;
|
|
11
|
+
protected onContentImpression(): void;
|
|
12
|
+
protected onContentPlay(): void;
|
|
13
|
+
protected onContentPause(): void;
|
|
14
|
+
protected onContentSeeking(): void;
|
|
15
|
+
protected onContentSeeked(): void;
|
|
16
|
+
protected onContentBufferingStart(): void;
|
|
17
|
+
protected onContentBufferingEnd(): void;
|
|
18
|
+
protected onContentEnded(): void;
|
|
19
|
+
protected onAdImpression(): void;
|
|
20
|
+
protected onAdPaused(): void;
|
|
21
|
+
protected onAdResumed(): void;
|
|
22
|
+
protected onAdBufferingStart(): void;
|
|
23
|
+
protected onAdBufferingEnd(): void;
|
|
24
|
+
protected onAdEnd(): void;
|
|
25
|
+
protected onDestroy(): void;
|
|
26
|
+
}
|