@glomex/integration-analytics 1.1480.0 → 1.1481.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 -11
- 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 +19 -8
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { IntegrationEvent } from '@glomex/integration-web-component';
|
|
2
|
+
import { SensicEventMapper } from './sensic-event-mapper.js';
|
|
3
|
+
import { buildDefaultCustomParams, buildDefaultStreamId, createAtCustomParamsBuilder, createAtStreamIdBuilder } from './sensic-metadata.js';
|
|
4
|
+
import { buildSensicScriptUrl, initSensicS2S } from './sensic-sdk-loader.js';
|
|
5
|
+
/** IAB TCF vendor ID for Sensic */
|
|
6
|
+
const IAB_VENDOR_SENSIC = '758';
|
|
7
|
+
/**
|
|
8
|
+
* Connects Sensic analytics integration to a turbo player integration.
|
|
9
|
+
*
|
|
10
|
+
* Sensic (formerly GfK) provides streaming measurement for video content.
|
|
11
|
+
* This integration automatically tracks playback events and reports them
|
|
12
|
+
* to Sensic's S2S (Server-to-Server) measurement system.
|
|
13
|
+
*
|
|
14
|
+
* @param integration - The integration element to connect to
|
|
15
|
+
* @param options - Sensic configuration options
|
|
16
|
+
* @returns A cleanup function to disconnect the integration
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { connectToSensic } from '@glomex/integration-analytics/sensic';
|
|
21
|
+
*
|
|
22
|
+
* const disconnect = connectToSensic(integration, {
|
|
23
|
+
* media: 'your-media-id',
|
|
24
|
+
* platform: 'web',
|
|
25
|
+
* country: 'de'
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Later, to disconnect:
|
|
29
|
+
* disconnect();
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const connectToSensic = (integration, options) => {
|
|
33
|
+
const { media, platform, country, warnCallback } = options;
|
|
34
|
+
const scriptUrl = buildSensicScriptUrl(country, platform);
|
|
35
|
+
let initializationPromise;
|
|
36
|
+
let currentMapper;
|
|
37
|
+
const onWarn = (error) => {
|
|
38
|
+
if (warnCallback) {
|
|
39
|
+
warnCallback(error);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
setTimeout(() => {
|
|
43
|
+
// biome-ignore lint/suspicious/noConsole: Fallback error logging
|
|
44
|
+
console.error(error);
|
|
45
|
+
}, 0);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const initializeSensic = () => {
|
|
49
|
+
if (initializationPromise)
|
|
50
|
+
return initializationPromise;
|
|
51
|
+
initializationPromise = initSensicS2S({
|
|
52
|
+
media,
|
|
53
|
+
url: scriptUrl,
|
|
54
|
+
type: 'WEB'
|
|
55
|
+
}).catch((error) => {
|
|
56
|
+
onWarn(error instanceof Error ? error : new Error(String(error)));
|
|
57
|
+
return undefined;
|
|
58
|
+
});
|
|
59
|
+
return initializationPromise;
|
|
60
|
+
};
|
|
61
|
+
const hasConsent = () => {
|
|
62
|
+
const hasVendorConsent = Boolean(integration.consent?.vendorConsents?.[IAB_VENDOR_SENSIC]);
|
|
63
|
+
return hasVendorConsent;
|
|
64
|
+
};
|
|
65
|
+
const onContentSelect = () => {
|
|
66
|
+
if (currentMapper) {
|
|
67
|
+
currentMapper.destroy();
|
|
68
|
+
currentMapper = undefined;
|
|
69
|
+
}
|
|
70
|
+
if (!hasConsent())
|
|
71
|
+
return;
|
|
72
|
+
const customParamsBuilder = createCustomParamsBuilder();
|
|
73
|
+
if (customParamsBuilder() == null)
|
|
74
|
+
return;
|
|
75
|
+
// Start initialization if not already done, but don't await - events will be queued
|
|
76
|
+
const sdkReadyPromise = initializeSensic();
|
|
77
|
+
currentMapper = new SensicEventMapper(integration, sdkReadyPromise, onWarn, customParamsBuilder, createStreamIdBuilder());
|
|
78
|
+
};
|
|
79
|
+
function createCustomParamsBuilder() {
|
|
80
|
+
const internalBuilder = country.toLowerCase() === 'at'
|
|
81
|
+
? createAtCustomParamsBuilder({ integration })
|
|
82
|
+
: buildDefaultCustomParams;
|
|
83
|
+
return () => {
|
|
84
|
+
const internalResult = internalBuilder();
|
|
85
|
+
return options.buildCustomParams ? options.buildCustomParams(internalResult) : internalResult;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function createStreamIdBuilder() {
|
|
89
|
+
const internalBuilder = country.toLowerCase() === 'at'
|
|
90
|
+
? createAtStreamIdBuilder({ integration })
|
|
91
|
+
: () => buildDefaultStreamId(integration, integration.currentAd !== undefined);
|
|
92
|
+
return () => {
|
|
93
|
+
const internalResult = internalBuilder();
|
|
94
|
+
return options.buildStreamId ? options.buildStreamId(internalResult) : internalResult;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
integration.addEventListener(IntegrationEvent.CONTENT_SELECT, onContentSelect);
|
|
98
|
+
return () => {
|
|
99
|
+
integration.removeEventListener(IntegrationEvent.CONTENT_SELECT, onContentSelect);
|
|
100
|
+
currentMapper?.destroy();
|
|
101
|
+
currentMapper = undefined;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type IntegrationElement } from '@glomex/integration-web-component';
|
|
2
|
+
import { BaseEventMapper } from '../base-event-mapper.js';
|
|
3
|
+
import type { SensicGlobal } from './sensic-types.js';
|
|
4
|
+
export declare class SensicEventMapper extends BaseEventMapper {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(integration: IntegrationElement, sdkReadyPromise: Promise<SensicGlobal | undefined>, onWarn: (error: Error) => void, buildCustomParams: () => Record<string, string> | null | undefined, buildStreamId?: () => string);
|
|
7
|
+
protected onContentStart(): void;
|
|
8
|
+
protected onContentImpression(): void;
|
|
9
|
+
protected onContentPlay(): void;
|
|
10
|
+
protected onContentPause(): void;
|
|
11
|
+
protected onContentSeeking(): void;
|
|
12
|
+
protected onContentSeeked(): void;
|
|
13
|
+
protected onContentBufferingStart(): void;
|
|
14
|
+
protected onContentBufferingEnd(): void;
|
|
15
|
+
protected onContentEnded(): void;
|
|
16
|
+
protected onAdImpression(): void;
|
|
17
|
+
protected onAdPaused(): void;
|
|
18
|
+
protected onAdResumed(): void;
|
|
19
|
+
protected onAdBufferingStart(): void;
|
|
20
|
+
protected onAdBufferingEnd(): void;
|
|
21
|
+
protected onAdEnd(): void;
|
|
22
|
+
protected onPresentationModeChange(): void;
|
|
23
|
+
protected onVolumeChange(): void;
|
|
24
|
+
protected onDestroy(): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { PlaybackMode, PresentationMode } from '@glomex/integration-web-component';
|
|
2
|
+
import { BaseEventMapper } from '../base-event-mapper.js';
|
|
3
|
+
export class SensicEventMapper extends BaseEventMapper {
|
|
4
|
+
#sensicSdk;
|
|
5
|
+
#buildCustomParams;
|
|
6
|
+
#buildStreamId;
|
|
7
|
+
#agent;
|
|
8
|
+
#isLive = false;
|
|
9
|
+
constructor(integration, sdkReadyPromise, onWarn, buildCustomParams, buildStreamId) {
|
|
10
|
+
super(integration, onWarn);
|
|
11
|
+
this.#buildCustomParams = buildCustomParams;
|
|
12
|
+
this.#buildStreamId = buildStreamId;
|
|
13
|
+
sdkReadyPromise.then((sensicSdk) => {
|
|
14
|
+
if (this.isStopped)
|
|
15
|
+
return;
|
|
16
|
+
if (!sensicSdk) {
|
|
17
|
+
this.onWarn(new Error('Sensic SDK not available after initialization'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
this.#sensicSdk = sensicSdk;
|
|
21
|
+
this.markSdkReady();
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
onContentStart() {
|
|
25
|
+
if (!this.#sensicSdk)
|
|
26
|
+
return;
|
|
27
|
+
this.#isLive = this.integration.source?.playbackMode === PlaybackMode.LIVE;
|
|
28
|
+
// Create agent with position callback
|
|
29
|
+
this.#agent = this.#sensicSdk.getAgent(() => this.#isLive ? undefined : this.#currentTimeInMilliseconds);
|
|
30
|
+
}
|
|
31
|
+
onContentImpression() {
|
|
32
|
+
this.#triggerPlay(this.#currentTimeInMilliseconds);
|
|
33
|
+
}
|
|
34
|
+
onContentPlay() {
|
|
35
|
+
if (this.integration.seeking)
|
|
36
|
+
return;
|
|
37
|
+
this.#triggerPlay(this.#currentTimeInMilliseconds);
|
|
38
|
+
}
|
|
39
|
+
onContentPause() {
|
|
40
|
+
if (this.integration.seeking)
|
|
41
|
+
return;
|
|
42
|
+
this.#triggerStop(this.#currentTimeInMilliseconds);
|
|
43
|
+
}
|
|
44
|
+
onContentSeeking() {
|
|
45
|
+
if (!this.integration.paused) {
|
|
46
|
+
this.#triggerStop(this.#currentTimeInMilliseconds);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
onContentSeeked() {
|
|
50
|
+
if (!this.integration.paused) {
|
|
51
|
+
this.#triggerPlay(this.#currentTimeInMilliseconds);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
onContentBufferingStart() {
|
|
55
|
+
if (this.integration.seeking)
|
|
56
|
+
return;
|
|
57
|
+
this.#triggerStop(this.#currentTimeInMilliseconds);
|
|
58
|
+
}
|
|
59
|
+
onContentBufferingEnd() {
|
|
60
|
+
if (this.integration.seeking)
|
|
61
|
+
return;
|
|
62
|
+
this.#triggerPlay(this.#currentTimeInMilliseconds);
|
|
63
|
+
}
|
|
64
|
+
onContentEnded() {
|
|
65
|
+
this.#triggerStop(this.#currentTimeInMilliseconds);
|
|
66
|
+
}
|
|
67
|
+
onAdImpression() {
|
|
68
|
+
this.#triggerPlay(0);
|
|
69
|
+
}
|
|
70
|
+
onAdPaused() {
|
|
71
|
+
this.#triggerStop(this.#adCurrentTimeInMilliseconds);
|
|
72
|
+
}
|
|
73
|
+
onAdResumed() {
|
|
74
|
+
this.#triggerPlay(this.#adCurrentTimeInMilliseconds);
|
|
75
|
+
}
|
|
76
|
+
onAdBufferingStart() {
|
|
77
|
+
this.#triggerStop(this.#adCurrentTimeInMilliseconds);
|
|
78
|
+
}
|
|
79
|
+
onAdBufferingEnd() {
|
|
80
|
+
this.#triggerPlay(this.#adCurrentTimeInMilliseconds);
|
|
81
|
+
}
|
|
82
|
+
onAdEnd() {
|
|
83
|
+
this.#triggerStop(this.#adCurrentTimeInMilliseconds);
|
|
84
|
+
}
|
|
85
|
+
onPresentationModeChange() {
|
|
86
|
+
if (!this.#agent)
|
|
87
|
+
return;
|
|
88
|
+
this.#agent.screen(this.#isFullscreen ? '1' : '0');
|
|
89
|
+
}
|
|
90
|
+
onVolumeChange() {
|
|
91
|
+
if (!this.#agent)
|
|
92
|
+
return;
|
|
93
|
+
this.#agent.volume(this.#volume.toString());
|
|
94
|
+
}
|
|
95
|
+
#triggerPlay(position) {
|
|
96
|
+
if (!this.#agent)
|
|
97
|
+
return;
|
|
98
|
+
const customParams = this.#buildCustomParams() ?? {};
|
|
99
|
+
const contentId = 'default'; // Always 'default' as per integration guide
|
|
100
|
+
const isAd = this.isInAd;
|
|
101
|
+
const streamId = this.#buildStreamId
|
|
102
|
+
? this.#buildStreamId()
|
|
103
|
+
: (() => {
|
|
104
|
+
const sourceOrContentId = this.integration.source?.id || this.integration.content?.id || '';
|
|
105
|
+
return isAd ? `${sourceOrContentId}-ad` : sourceOrContentId;
|
|
106
|
+
})();
|
|
107
|
+
const options = {
|
|
108
|
+
screen: this.#isFullscreen ? '1' : '0',
|
|
109
|
+
volume: this.#volume.toString()
|
|
110
|
+
};
|
|
111
|
+
if (this.#isLive && !isAd) {
|
|
112
|
+
const streamOffset = Math.round(Date.now() - position);
|
|
113
|
+
const contentStart = ''; // Always empty string
|
|
114
|
+
this.#agent.playStreamLive(contentId, contentStart, streamOffset, streamId, options, customParams);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
this.#agent.playStreamOnDemand(contentId, streamId, position, options, customParams);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
#triggerStop(position) {
|
|
121
|
+
if (!this.#agent)
|
|
122
|
+
return;
|
|
123
|
+
this.#agent.stop(this.#isLive ? undefined : position);
|
|
124
|
+
}
|
|
125
|
+
get #currentTimeInMilliseconds() {
|
|
126
|
+
if (this.#isLive) {
|
|
127
|
+
return this.integration.wallClockTime * 1000;
|
|
128
|
+
}
|
|
129
|
+
return this.integration.currentTime * 1000;
|
|
130
|
+
}
|
|
131
|
+
get #adCurrentTimeInMilliseconds() {
|
|
132
|
+
const adTime = this.integration.adCurrentTime;
|
|
133
|
+
return (Number.isNaN(adTime) ? 0 : adTime) * 1000;
|
|
134
|
+
}
|
|
135
|
+
get #isFullscreen() {
|
|
136
|
+
return this.integration.presentationMode === PresentationMode.FULLSCREEN;
|
|
137
|
+
}
|
|
138
|
+
get #volume() {
|
|
139
|
+
return this.integration.muted ? 0 : Math.round(this.integration.volume * 100);
|
|
140
|
+
}
|
|
141
|
+
onDestroy() {
|
|
142
|
+
// Stop any ongoing playback tracking
|
|
143
|
+
if (this.#agent) {
|
|
144
|
+
this.#triggerStop(this.#currentTimeInMilliseconds);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type IntegrationElement } from '@glomex/integration-web-component';
|
|
2
|
+
/**
|
|
3
|
+
* Build the default stream ID for Sensic tracking.
|
|
4
|
+
* Uses source ID if available, otherwise falls back to content ID.
|
|
5
|
+
*
|
|
6
|
+
* @param integration - The integration element containing source and content data
|
|
7
|
+
* @param isAd - Whether this is for an ad stream
|
|
8
|
+
* @returns The stream ID to use for tracking
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildDefaultStreamId(integration: IntegrationElement, isAd: boolean): string;
|
|
11
|
+
/**
|
|
12
|
+
* Build default custom parameters for Sensic tracking.
|
|
13
|
+
* Returns an empty object by default - consumers can provide their own buildCustomParams callback.
|
|
14
|
+
*
|
|
15
|
+
* @returns Empty custom parameters object
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildDefaultCustomParams(): Record<string, string>;
|
|
18
|
+
export interface SensicAtBuilderOptions {
|
|
19
|
+
integration: IntegrationElement;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Creates a custom params builder for Austria (GfK AT).
|
|
23
|
+
*
|
|
24
|
+
* @param options - The builder options containing integration and platform
|
|
25
|
+
* @returns A function that builds custom params for Sensic tracking, or undefined if tracking should be skipped
|
|
26
|
+
*/
|
|
27
|
+
export declare function createAtCustomParamsBuilder(options: SensicAtBuilderOptions): () => Record<string, string>;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a stream ID builder for Austria (GfK AT).
|
|
30
|
+
*
|
|
31
|
+
* @param options - The builder options containing integration
|
|
32
|
+
* @returns A function that builds the stream ID for Sensic tracking
|
|
33
|
+
*/
|
|
34
|
+
export declare function createAtStreamIdBuilder(options: SensicAtBuilderOptions): () => string;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { PlaybackMode } from '@glomex/integration-web-component';
|
|
2
|
+
/**
|
|
3
|
+
* Build the default stream ID for Sensic tracking.
|
|
4
|
+
* Uses source ID if available, otherwise falls back to content ID.
|
|
5
|
+
*
|
|
6
|
+
* @param integration - The integration element containing source and content data
|
|
7
|
+
* @param isAd - Whether this is for an ad stream
|
|
8
|
+
* @returns The stream ID to use for tracking
|
|
9
|
+
*/
|
|
10
|
+
export function buildDefaultStreamId(integration, isAd) {
|
|
11
|
+
const sourceId = integration.source?.id ?? '';
|
|
12
|
+
const contentId = integration.content?.id ?? '';
|
|
13
|
+
const baseId = sourceId || contentId;
|
|
14
|
+
return isAd ? `${baseId}-ad` : baseId;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build default custom parameters for Sensic tracking.
|
|
18
|
+
* Returns an empty object by default - consumers can provide their own buildCustomParams callback.
|
|
19
|
+
*
|
|
20
|
+
* @returns Empty custom parameters object
|
|
21
|
+
*/
|
|
22
|
+
export function buildDefaultCustomParams() {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates a custom params builder for Austria (GfK AT).
|
|
27
|
+
*
|
|
28
|
+
* @param options - The builder options containing integration and platform
|
|
29
|
+
* @returns A function that builds custom params for Sensic tracking, or undefined if tracking should be skipped
|
|
30
|
+
*/
|
|
31
|
+
export function createAtCustomParamsBuilder(options) {
|
|
32
|
+
const { integration } = options;
|
|
33
|
+
return () => {
|
|
34
|
+
const content = integration.content;
|
|
35
|
+
const source = integration.source;
|
|
36
|
+
const isLive = source?.playbackMode === PlaybackMode.LIVE;
|
|
37
|
+
const channelName = content?.channel?.name || '';
|
|
38
|
+
const tvShowId = content?.show?.id ||
|
|
39
|
+
content?.competition?.id ||
|
|
40
|
+
content?.compilation?.id ||
|
|
41
|
+
content?.additionalIds?.originId ||
|
|
42
|
+
content?.id;
|
|
43
|
+
const programName = content?.show?.name ||
|
|
44
|
+
content?.competition?.name ||
|
|
45
|
+
content?.compilation?.name ||
|
|
46
|
+
content?.title;
|
|
47
|
+
const duration = Math.floor(content?.duration === undefined ? integration.duration : content.duration) || '-1';
|
|
48
|
+
const livestreamId = isLive ? content?.id : content?.liveOnDemandChannel?.id;
|
|
49
|
+
const mediaId = isLive ? content?.id : source?.id || content?.id;
|
|
50
|
+
const mediaTitle = content?.title;
|
|
51
|
+
const externalId = content?.additionalIds?.externalId;
|
|
52
|
+
const ad = integration.currentAd;
|
|
53
|
+
const isAd = ad !== undefined;
|
|
54
|
+
const adDuration = ad?.duration?.toString() || '-1';
|
|
55
|
+
// GfK AT specific custom parameters
|
|
56
|
+
return {
|
|
57
|
+
/** Each channel gets a playerid assigned by Sensic */
|
|
58
|
+
playerid: '',
|
|
59
|
+
/** Channel/brand title */
|
|
60
|
+
channel: channelName,
|
|
61
|
+
/** Device identifier (e.g., 'WEB') */
|
|
62
|
+
deviceid: 'WEB',
|
|
63
|
+
/** Type of clip: 'Live', 'Sendung', 'preroll', 'midroll', 'postroll' */
|
|
64
|
+
cliptype: isAd ? ad.breakName || '' : isLive ? 'Live' : 'Sendung',
|
|
65
|
+
/** Video identifier */
|
|
66
|
+
videoid: isLive ? `${livestreamId}-24x7` : mediaId || '',
|
|
67
|
+
/** Episode external identifier */
|
|
68
|
+
episodeid: isLive ? '' : externalId || '',
|
|
69
|
+
/** Player duration in milliseconds, '-1' for live */
|
|
70
|
+
playerduration: isAd ? adDuration : isLive ? '-1' : duration.toString(),
|
|
71
|
+
/** Video duration in seconds, '-1' for live */
|
|
72
|
+
videoduration: isAd ? adDuration : isLive ? '-1' : duration.toString(),
|
|
73
|
+
/** Episode duration in milliseconds, '-1' for live */
|
|
74
|
+
episodeduration: isAd ? adDuration : isLive ? '-1' : duration.toString(),
|
|
75
|
+
/** Video title (asset title + media title) */
|
|
76
|
+
videotitle: isLive ? '' : [programName, mediaTitle].filter(Boolean).join(' '),
|
|
77
|
+
/** Program/show name */
|
|
78
|
+
programname: isLive ? '' : programName || '',
|
|
79
|
+
/** TV show/asset identifier */
|
|
80
|
+
tvshowid: isLive ? '' : tvShowId || ''
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Creates a stream ID builder for Austria (GfK AT).
|
|
86
|
+
*
|
|
87
|
+
* @param options - The builder options containing integration
|
|
88
|
+
* @returns A function that builds the stream ID for Sensic tracking
|
|
89
|
+
*/
|
|
90
|
+
export function createAtStreamIdBuilder(options) {
|
|
91
|
+
const { integration } = options;
|
|
92
|
+
return () => {
|
|
93
|
+
const ad = integration.currentAd;
|
|
94
|
+
if (ad) {
|
|
95
|
+
return `${ad.breakName || ''}${ad.breakIndex == null ? '' : ad.breakIndex}-${ad.positionIndex == null ? '' : ad.positionIndex}`;
|
|
96
|
+
}
|
|
97
|
+
const content = integration.content;
|
|
98
|
+
const source = integration.source;
|
|
99
|
+
const isLive = source?.playbackMode === PlaybackMode.LIVE;
|
|
100
|
+
return isLive ? content?.channel?.name || '' : content?.title || '';
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SensicConfig, SensicGlobal } from './sensic-types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds the Sensic script URL based on country and platform.
|
|
4
|
+
* Script URLs:
|
|
5
|
+
* - AT Web: https://at-config.sensic.net/s2s-web.js
|
|
6
|
+
* - AT TV: https://at-config.sensic.net/ctv/s2s-web.js
|
|
7
|
+
* - DE Web: https://de-config.sensic.net/s2s-web.js
|
|
8
|
+
* - DE TV: https://de-config.sensic.net/ctv/s2s-web.js
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildSensicScriptUrl(country: string, platform: 'web' | 'tv'): string;
|
|
11
|
+
/**
|
|
12
|
+
* Initializes the Sensic S2S library.
|
|
13
|
+
* Loads the script and sets up the global gfkS2s object.
|
|
14
|
+
* This is the original implementation from Sensic with minimal TypeScript types.
|
|
15
|
+
*/
|
|
16
|
+
export declare function initSensicS2S(gfkS2sConf: SensicConfig): Promise<SensicGlobal>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the Sensic script URL based on country and platform.
|
|
3
|
+
* Script URLs:
|
|
4
|
+
* - AT Web: https://at-config.sensic.net/s2s-web.js
|
|
5
|
+
* - AT TV: https://at-config.sensic.net/ctv/s2s-web.js
|
|
6
|
+
* - DE Web: https://de-config.sensic.net/s2s-web.js
|
|
7
|
+
* - DE TV: https://de-config.sensic.net/ctv/s2s-web.js
|
|
8
|
+
*/
|
|
9
|
+
export function buildSensicScriptUrl(country, platform) {
|
|
10
|
+
const path = platform === 'web' ? 's2s-web.js' : 'ctv/s2s-web.js';
|
|
11
|
+
return `https://${country}-config.sensic.net/${path}`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Initializes the Sensic S2S library.
|
|
15
|
+
* Loads the script and sets up the global gfkS2s object.
|
|
16
|
+
* This is the original implementation from Sensic with minimal TypeScript types.
|
|
17
|
+
*/
|
|
18
|
+
export function initSensicS2S(gfkS2sConf) {
|
|
19
|
+
return ((w, d, c, s, id) => {
|
|
20
|
+
if (w[id]) {
|
|
21
|
+
return Promise.resolve(w[id]);
|
|
22
|
+
}
|
|
23
|
+
w.gfkS2sConf = c;
|
|
24
|
+
if (d.getElementById(id)) {
|
|
25
|
+
return Promise.resolve(w[id]);
|
|
26
|
+
}
|
|
27
|
+
w[id] = {};
|
|
28
|
+
w[id].agents = [];
|
|
29
|
+
const api = [
|
|
30
|
+
'playStreamLive',
|
|
31
|
+
'playLive',
|
|
32
|
+
'playStreamOnDemand',
|
|
33
|
+
'playVOD',
|
|
34
|
+
'stop',
|
|
35
|
+
'skip',
|
|
36
|
+
'screen',
|
|
37
|
+
'volume',
|
|
38
|
+
'impression'
|
|
39
|
+
];
|
|
40
|
+
w.gfks = (() => {
|
|
41
|
+
function f(sA, e, cb) {
|
|
42
|
+
return (...args) => {
|
|
43
|
+
sA.p = cb();
|
|
44
|
+
sA.queue.push({ f: e, a: args });
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function s(c, pId, cb) {
|
|
48
|
+
const sA = { queue: [], config: c, cb: cb, pId: pId };
|
|
49
|
+
for (let i = 0; i < api.length; i++) {
|
|
50
|
+
const e = api[i];
|
|
51
|
+
sA[e] = f(sA, e, cb);
|
|
52
|
+
}
|
|
53
|
+
return sA;
|
|
54
|
+
}
|
|
55
|
+
return s;
|
|
56
|
+
})();
|
|
57
|
+
w[id].getAgent = (cb, pId) => {
|
|
58
|
+
// Use 'new' to call gfks as a constructor - the Sensic script replaces gfks
|
|
59
|
+
// with a constructor function that expects to be called with 'new'
|
|
60
|
+
const GfksConstructor = w.gfks;
|
|
61
|
+
const innerAgent = new GfksConstructor(c, pId || '', cb || (() => 0));
|
|
62
|
+
// Use Proxy to pass through all method calls to the inner agent
|
|
63
|
+
// This ensures that when gfks is replaced by the real Sensic implementation,
|
|
64
|
+
// all methods (including setStreamPositionCallback) are accessible
|
|
65
|
+
const agentProxy = new Proxy({ a: innerAgent }, {
|
|
66
|
+
get(target, prop) {
|
|
67
|
+
if (prop === 'a')
|
|
68
|
+
return target.a;
|
|
69
|
+
// Pass through all other property access to the inner agent
|
|
70
|
+
const value = target.a[prop];
|
|
71
|
+
if (typeof value === 'function') {
|
|
72
|
+
return (...args) => value.apply(target.a, args);
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
w[id].agents.push(agentProxy);
|
|
78
|
+
return agentProxy;
|
|
79
|
+
};
|
|
80
|
+
const lJS = (eId, url) => new Promise((resolve, reject) => {
|
|
81
|
+
const existingTag = d.querySelector(`script[src="${url}"]`);
|
|
82
|
+
let tag = existingTag;
|
|
83
|
+
if (!existingTag) {
|
|
84
|
+
tag = d.createElement(s);
|
|
85
|
+
tag.async = true;
|
|
86
|
+
tag.type = 'text/javascript';
|
|
87
|
+
tag.src = url;
|
|
88
|
+
tag.id = eId;
|
|
89
|
+
}
|
|
90
|
+
const el = d.getElementsByTagName(s)[0];
|
|
91
|
+
if (tag) {
|
|
92
|
+
tag.onload = () => resolve(w[id]);
|
|
93
|
+
tag.onerror = () => reject(new Error('Failed to load Sensic S2S script'));
|
|
94
|
+
tag.onabort = () => reject(new Error('Sensic S2S script load aborted'));
|
|
95
|
+
}
|
|
96
|
+
if (!existingTag && tag && el?.parentNode) {
|
|
97
|
+
el.parentNode.insertBefore(tag, el);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return lJS(id, c.url);
|
|
101
|
+
})(window, document, gfkS2sConf, 'script', 'gfkS2s');
|
|
102
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sensic S2S (Server-to-Server) TypeScript Definitions
|
|
3
|
+
* Types for the GfK Sensic streaming measurement SDK
|
|
4
|
+
* @see https://at-config.sensic.net/s2s-web.js
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Options passed to play methods for screen and volume state
|
|
8
|
+
*/
|
|
9
|
+
export interface SensicOptions {
|
|
10
|
+
/** Fullscreen state: '1' for fullscreen, '0' for not fullscreen */
|
|
11
|
+
screen: string;
|
|
12
|
+
/** Volume level as string (0-100) */
|
|
13
|
+
volume: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Custom parameters sent with each play event.
|
|
17
|
+
* The specific keys and values depend on the integration.
|
|
18
|
+
*/
|
|
19
|
+
export type SensicCustomParams = Record<string, string>;
|
|
20
|
+
/**
|
|
21
|
+
* Sensic streaming agent instance for tracking playback
|
|
22
|
+
*/
|
|
23
|
+
export interface SensicAgent {
|
|
24
|
+
/**
|
|
25
|
+
* Update the volume state
|
|
26
|
+
* @param volume - Volume level as string (0-100)
|
|
27
|
+
*/
|
|
28
|
+
volume: (volume: string) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Update the fullscreen state
|
|
31
|
+
* @param isFullscreen - '1' for fullscreen, '0' for not fullscreen
|
|
32
|
+
*/
|
|
33
|
+
screen: (isFullscreen: string) => void;
|
|
34
|
+
/**
|
|
35
|
+
* Start tracking a live stream
|
|
36
|
+
* @param contentId - Content identifier (usually 'default')
|
|
37
|
+
* @param contentStart - Content start time (usually empty string)
|
|
38
|
+
* @param streamOffset - Offset from live edge in milliseconds
|
|
39
|
+
* @param streamId - Stream/content title
|
|
40
|
+
* @param options - Screen and volume options
|
|
41
|
+
* @param customParams - Custom tracking parameters
|
|
42
|
+
*/
|
|
43
|
+
playStreamLive: (contentId: string, contentStart: string, streamOffset: number, streamId: string, options: SensicOptions, customParams: Record<string, string>) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Start tracking an on-demand stream
|
|
46
|
+
* @param contentId - Content identifier (usually 'default')
|
|
47
|
+
* @param streamId - Stream/content title
|
|
48
|
+
* @param position - Current playback position in milliseconds
|
|
49
|
+
* @param options - Screen and volume options
|
|
50
|
+
* @param customParams - Custom tracking parameters
|
|
51
|
+
*/
|
|
52
|
+
playStreamOnDemand: (contentId: string, streamId: string, position: number, options: SensicOptions, customParams: Record<string, string>) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Stop tracking playback
|
|
55
|
+
* @param position - Current playback position in milliseconds (undefined for live)
|
|
56
|
+
*/
|
|
57
|
+
stop: (position?: number) => void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Configuration for initializing the Sensic SDK
|
|
61
|
+
*/
|
|
62
|
+
export interface SensicConfig {
|
|
63
|
+
/** Media identifier provided by Sensic */
|
|
64
|
+
media: string;
|
|
65
|
+
/** URL to the Sensic S2S script */
|
|
66
|
+
url: string;
|
|
67
|
+
/** Device type (e.g., 'WEB') */
|
|
68
|
+
type: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Global Sensic SDK interface
|
|
72
|
+
*/
|
|
73
|
+
export interface SensicGlobal {
|
|
74
|
+
/**
|
|
75
|
+
* Get a streaming agent for tracking playback
|
|
76
|
+
* @param agentArg - Callback that returns current position (undefined for live)
|
|
77
|
+
* @returns A SensicAgent instance for tracking
|
|
78
|
+
*/
|
|
79
|
+
getAgent: (agentArg: () => number | undefined) => SensicAgent;
|
|
80
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glomex/integration-analytics",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1481.0",
|
|
4
4
|
"description": "Analytics integrations for the turbo player",
|
|
5
5
|
"documentation": "https://docs.glomex.com",
|
|
6
6
|
"homepage": "https://glomex.com",
|
|
@@ -25,6 +25,18 @@
|
|
|
25
25
|
"./npaw": {
|
|
26
26
|
"types": "./dist/npaw/index.d.ts",
|
|
27
27
|
"import": "./dist/npaw/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./comscore": {
|
|
30
|
+
"types": "./dist/comscore/index.d.ts",
|
|
31
|
+
"import": "./dist/comscore/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./sensic": {
|
|
34
|
+
"types": "./dist/sensic/index.d.ts",
|
|
35
|
+
"import": "./dist/sensic/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./nielsen": {
|
|
38
|
+
"types": "./dist/nielsen/index.d.ts",
|
|
39
|
+
"import": "./dist/nielsen/index.js"
|
|
28
40
|
}
|
|
29
41
|
},
|
|
30
42
|
"files": [
|
|
@@ -34,23 +46,22 @@
|
|
|
34
46
|
],
|
|
35
47
|
"scripts": {
|
|
36
48
|
"prepack": "npm run build",
|
|
37
|
-
"build": "rm -rf dist &&
|
|
38
|
-
"build:ts": "tsc --build",
|
|
39
|
-
"build:tsup": "tsup",
|
|
49
|
+
"build": "rm -rf dist && tsc --build --force",
|
|
40
50
|
"lint": "tsc --noEmit && biome ci",
|
|
41
|
-
"watch": "
|
|
51
|
+
"watch": "tsc --build --watch"
|
|
42
52
|
},
|
|
43
53
|
"dependencies": {
|
|
44
|
-
"@
|
|
54
|
+
"@comscore/analytics": "^7.13.2",
|
|
55
|
+
"@glomex/integration-web-component": "1.1481.0",
|
|
45
56
|
"npaw-plugin": "^7.3.18"
|
|
46
57
|
},
|
|
47
58
|
"devDependencies": {
|
|
48
59
|
"@biomejs/biome": "catalog:",
|
|
49
|
-
"tsup": "catalog:",
|
|
50
60
|
"typescript": "catalog:"
|
|
51
61
|
},
|
|
52
62
|
"publishConfig": {
|
|
53
63
|
"access": "public"
|
|
54
64
|
},
|
|
55
|
-
"license": "MIT"
|
|
65
|
+
"license": "MIT",
|
|
66
|
+
"gitHead": "a25482ec869f700c6735db24bda8c69eb3ba7c88"
|
|
56
67
|
}
|