@guardian/commercial-core 0.0.0-beta-20250716123536 → 0.0.0-beta-20251023093018

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.
@@ -28,7 +28,7 @@ declare class AdSize extends Array<number> {
28
28
  get height(): number;
29
29
  }
30
30
  type SizeKeys = '160x600' | '300x1050' | '300x250' | '300x600' | '728x90' | '970x250' | 'billboard' | 'cascade' | 'empty' | 'fabric' | 'fluid' | 'googleCard' | 'halfPage' | 'leaderboard' | 'merchandising' | 'merchandisingHigh' | 'merchandisingHighAdFeature' | 'mobilesticky' | 'mpu' | 'outOfPage' | 'outstreamDesktop' | 'outstreamGoogleDesktop' | 'outstreamMobile' | 'portrait' | 'portraitInterstitial' | 'pubmaticInterscroller' | 'skyscraper' | 'sponsorLogo';
31
- type SlotName = 'article-end' | 'carrot' | 'comments-expanded' | 'comments' | 'crossword-banner-mobile' | 'exclusion' | 'external' | 'fronts-banner' | 'inline' | 'liveblog-top' | 'merchandising-high' | 'merchandising' | 'mobile-sticky' | 'football-right' | 'mostpop' | 'right' | 'sponsor-logo' | 'survey' | 'top-above-nav' | 'interactive';
31
+ type SlotName = 'article-end' | 'comments-expanded' | 'comments' | 'crossword-banner-mobile' | 'exclusion' | 'external' | 'fronts-banner' | 'inline' | 'liveblog-top' | 'merchandising-high' | 'merchandising' | 'mobile-sticky' | 'football-right' | 'mostpop' | 'right' | 'sponsor-logo' | 'survey' | 'top-above-nav' | 'interactive';
32
32
  type SizeMapping = Partial<Record<Breakpoint, readonly AdSize[]>>;
33
33
  type SlotSizeMappings = Record<SlotName, SizeMapping>;
34
34
  declare const createAdSize: (width: number, height: number) => AdSize;
@@ -139,9 +139,6 @@ declare const slotSizeMappings: {
139
139
  readonly survey: {
140
140
  readonly desktop: readonly [AdSize];
141
141
  };
142
- readonly carrot: {
143
- readonly mobile: readonly [AdSize];
144
- };
145
142
  readonly 'mobile-sticky': {
146
143
  readonly mobile: readonly [AdSize, AdSize, AdSize];
147
144
  };
@@ -311,9 +311,6 @@ const slotSizeMappings = {
311
311
  survey: {
312
312
  desktop: [adSizes.outOfPage],
313
313
  },
314
- carrot: {
315
- mobile: [adSizes.fluid],
316
- },
317
314
  'mobile-sticky': {
318
315
  mobile: [adSizes.mobilesticky, adSizes.empty, createAdSize(300, 50)],
319
316
  },
@@ -10,7 +10,6 @@ const editionToCountryCodeMap = {
10
10
  const editionToCountryCode = (editionKey = 'UK') => editionToCountryCodeMap[editionKey];
11
11
  const countryCookieName = 'GU_geo_country';
12
12
  const countryOverrideName = 'gu.geo.override';
13
- let locale;
14
13
  /*
15
14
  This method can be used as a non async way of getting the country code
16
15
  after init has been called. Returning locale should cover all/most
@@ -23,8 +22,7 @@ const getCountryCode = () => {
23
22
  const countryOverride = (0, libs_1.isString)(maybeCountryOverride)
24
23
  ? maybeCountryOverride
25
24
  : null;
26
- return (locale ??
27
- countryOverride ??
25
+ return (countryOverride ??
28
26
  (0, libs_1.getCookie)({
29
27
  name: countryCookieName,
30
28
  shouldMemoize: true,
@@ -1,5 +1,5 @@
1
1
  import type { EventTimer } from './event-timer';
2
- import type { AdBlockers, Apstag, ArticleCounts, ComscoreGlobals, Confiant, Config, DfpEnv, FetchBidResponse, GoogleTagParams, GoogleTrackConversionObject, HeaderNotification, IasPET, NetworkInformation, NSdkInstance, Ophan, OptOutAdSlot, OptOutInitializeOptions, Permutive, SafeFrameAPI, TeadsAnalytics, Trac } from './types';
2
+ import type { AdBlockers, Admiral, Apstag, ArticleCounts, ComscoreGlobals, Confiant, Config, DfpEnv, FetchBidResponse, GoogleTagParams, GoogleTrackConversionObject, HeaderNotification, IasPET, NetworkInformation, NSdkInstance, Ophan, OptOutAdSlot, OptOutInitializeOptions, Permutive, SafeFrameAPI, TeadsAnalytics, Trac } from './types';
3
3
  declare global {
4
4
  interface Navigator {
5
5
  readonly connection?: NetworkInformation;
@@ -31,6 +31,11 @@ declare global {
31
31
  sentry?: {
32
32
  reportError?: (error: Error, feature: string, tags?: Record<string, string>, extras?: Record<string, unknown>) => void;
33
33
  };
34
+ abTests?: {
35
+ getParticipations: () => Record<string, string>;
36
+ isUserInTest: (testId: string) => boolean;
37
+ isUserInTestGroup: (testId: string, variantId: string) => boolean;
38
+ };
34
39
  };
35
40
  };
36
41
  ootag: {
@@ -67,5 +72,6 @@ declare global {
67
72
  cmd: string;
68
73
  val: Record<string, unknown>;
69
74
  }>;
75
+ admiral?: Admiral;
70
76
  }
71
77
  }
package/dist/cjs/index.js CHANGED
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
36
  exports.isEligibleForTeads = exports.getPermutivePFPSegments = exports.buildImaAdTagUrl = exports.buildPageTargeting = exports.initCommercialMetrics = exports.bypassCommercialMetricsSampling = exports.constants = exports.adSizes = exports.EventTimer = exports.isAdBlockInUse = void 0;
27
37
  var detect_ad_blocker_1 = require("./detect-ad-blocker");
@@ -2,6 +2,7 @@ import type { Participations } from '@guardian/ab-core';
2
2
  import type { ConsentState, CountryCode } from '@guardian/libs';
3
3
  import type { AdManagerGroup, Frequency } from './personalised';
4
4
  import type { SharedTargeting } from './shared';
5
+ import { getLocalHour } from './shared';
5
6
  import type { TrueOrFalse } from './types';
6
7
  type PartialWithNulls<T> = {
7
8
  [P in keyof T]?: T[P] | null;
@@ -13,6 +14,7 @@ type PageTargeting = PartialWithNulls<{
13
14
  at: string;
14
15
  bp: 'mobile' | 'tablet' | 'desktop';
15
16
  cc: CountryCode;
17
+ lh: string;
16
18
  cmp_interaction: string;
17
19
  consent_tcfv2: string;
18
20
  dcre: TrueOrFalse;
@@ -43,5 +45,5 @@ type BuildPageTargetingParams = {
43
45
  youtube?: boolean;
44
46
  };
45
47
  declare const buildPageTargeting: ({ adFree, clientSideParticipations, consentState, isSignedIn, youtube, }: BuildPageTargetingParams) => Record<string, string | string[]>;
46
- export { buildPageTargeting, filterValues };
48
+ export { buildPageTargeting, filterValues, getLocalHour };
47
49
  export type { PageTargeting };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.filterValues = exports.buildPageTargeting = void 0;
3
+ exports.getLocalHour = exports.filterValues = exports.buildPageTargeting = void 0;
4
4
  const libs_1 = require("@guardian/libs");
5
5
  const event_timer_1 = require("../event-timer");
6
6
  const get_locale_1 = require("../geo/get-locale");
@@ -8,6 +8,7 @@ const content_1 = require("./content");
8
8
  const personalised_1 = require("./personalised");
9
9
  const session_1 = require("./session");
10
10
  const shared_1 = require("./shared");
11
+ Object.defineProperty(exports, "getLocalHour", { enumerable: true, get: function () { return shared_1.getLocalHour; } });
11
12
  const viewport_1 = require("./viewport");
12
13
  const filterValues = (pageTargets) => {
13
14
  const filtered = {};
@@ -70,11 +71,13 @@ const buildPageTargeting = ({ adFree, clientSideParticipations, consentState, is
70
71
  const sessionTargeting = (0, session_1.getSessionTargeting)({
71
72
  adTest: (0, libs_1.getCookie)({ name: 'adtest', shouldMemoize: true }),
72
73
  countryCode: (0, get_locale_1.getLocale)(),
74
+ localHour: (0, shared_1.getLocalHour)(),
73
75
  isSignedIn,
74
76
  pageViewId: window.guardian.config.ophan.pageViewId,
75
77
  participations: {
76
78
  clientSideParticipations,
77
79
  serverSideParticipations: window.guardian.config.tests ?? {},
80
+ betaAbTestParticipations: window.guardian.modules.abTests?.getParticipations() ?? {},
78
81
  },
79
82
  referrer,
80
83
  });
@@ -53,6 +53,14 @@ type SessionTargeting = {
53
53
  * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11703293
54
54
  */
55
55
  cc: CountryCode;
56
+ /**
57
+ * **L**ocal **H**our - [see on Ad Manager][gam]
58
+ *
59
+ * Type: _Dynamic_
60
+ *
61
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=17299564
62
+ */
63
+ lh: string;
56
64
  /**
57
65
  * Ophan **P**age **V**iew id – [see on Ad Manager][gam]
58
66
  *
@@ -96,16 +104,22 @@ type AllParticipations = {
96
104
  [key: `${string}Control`]: 'control';
97
105
  [key: `${string}Variant`]: 'variant';
98
106
  };
107
+ betaAbTestParticipations: Record<string, string>;
99
108
  };
100
- declare const experimentsTargeting: ({ clientSideParticipations, serverSideParticipations, }: AllParticipations) => SessionTargeting["ab"];
109
+ /**
110
+ * @todo drop old client/server side participations and rename to just `abTestsParticipations` once
111
+ * all tests have been migrated to the new AB testing platform
112
+ */
113
+ declare const experimentsTargeting: ({ clientSideParticipations, serverSideParticipations, betaAbTestParticipations, }: AllParticipations) => SessionTargeting["ab"];
101
114
  type Session = {
102
115
  adTest: SessionTargeting['at'];
103
116
  countryCode: CountryCode;
117
+ localHour: string;
104
118
  isSignedIn: boolean;
105
119
  pageViewId: SessionTargeting['pv'];
106
120
  participations: AllParticipations;
107
121
  referrer: string;
108
122
  };
109
- declare const getSessionTargeting: ({ adTest, countryCode, isSignedIn, pageViewId, participations, referrer, }: Session) => SessionTargeting;
123
+ declare const getSessionTargeting: ({ adTest, countryCode, localHour, isSignedIn, pageViewId, participations, referrer, }: Session) => SessionTargeting;
110
124
  export type { SessionTargeting, AllParticipations };
111
125
  export { getSessionTargeting, experimentsTargeting };
@@ -28,7 +28,11 @@ const getReferrer = (referrer) => {
28
28
  const matchedRef = referrers.find((referrerType) => referrer.includes(referrerType.match)) ?? null;
29
29
  return matchedRef ? matchedRef.id : null;
30
30
  };
31
- const experimentsTargeting = ({ clientSideParticipations, serverSideParticipations, }) => {
31
+ /**
32
+ * @todo drop old client/server side participations and rename to just `abTestsParticipations` once
33
+ * all tests have been migrated to the new AB testing platform
34
+ */
35
+ const experimentsTargeting = ({ clientSideParticipations, serverSideParticipations, betaAbTestParticipations, }) => {
32
36
  const testToParams = (testName, variant) => {
33
37
  if (variant === 'notintest')
34
38
  return null;
@@ -44,16 +48,26 @@ const experimentsTargeting = ({ clientSideParticipations, serverSideParticipatio
44
48
  const serverSideExperiments = Object.entries(serverSideParticipations)
45
49
  .map((test) => testToParams(...test))
46
50
  .filter(libs_1.isString);
47
- if (clientSideExperiment.length + serverSideExperiments.length === 0) {
51
+ const betaAbTests = Object.entries(betaAbTestParticipations)
52
+ .map((test) => {
53
+ const [name, variant] = test;
54
+ return testToParams(name, variant);
55
+ })
56
+ .filter(libs_1.isString);
57
+ if (clientSideExperiment.length +
58
+ serverSideExperiments.length +
59
+ betaAbTests.length ===
60
+ 0) {
48
61
  return null;
49
62
  }
50
- return [...clientSideExperiment, ...serverSideExperiments];
63
+ return [...clientSideExperiment, ...serverSideExperiments, ...betaAbTests];
51
64
  };
52
65
  exports.experimentsTargeting = experimentsTargeting;
53
- const getSessionTargeting = ({ adTest, countryCode, isSignedIn, pageViewId, participations, referrer, }) => ({
66
+ const getSessionTargeting = ({ adTest, countryCode, localHour, isSignedIn, pageViewId, participations, referrer, }) => ({
54
67
  ab: experimentsTargeting(participations),
55
68
  at: adTest,
56
69
  cc: countryCode,
70
+ lh: localHour,
57
71
  pv: pageViewId,
58
72
  ref: getReferrer(referrer),
59
73
  si: isSignedIn ? 't' : 'f',
@@ -145,6 +145,7 @@ type SharedTargeting = {
145
145
  */
146
146
  url: `/${string}`;
147
147
  };
148
+ declare const getLocalHour: () => string;
148
149
  /**
149
150
  * What goes in comes out
150
151
  */
@@ -153,4 +154,4 @@ export declare const _: {
153
154
  getSurgingParam: (surging: number) => SharedTargeting["su"];
154
155
  };
155
156
  export type { SharedTargeting };
156
- export { getSharedTargeting };
157
+ export { getSharedTargeting, getLocalHour };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getSharedTargeting = exports._ = void 0;
3
+ exports.getLocalHour = exports.getSharedTargeting = exports._ = void 0;
4
4
  const pick_targeting_values_1 = require("./pick-targeting-values");
5
5
  const surges = {
6
6
  0: '0',
@@ -18,6 +18,10 @@ const getSurgingParam = (surging) => {
18
18
  return thresholds.filter((n) => n <= surging).map((s) => surges[s]);
19
19
  };
20
20
  /* -- Targeting -- */
21
+ const getLocalHour = () => {
22
+ return new Date().getHours().toString();
23
+ };
24
+ exports.getLocalHour = getLocalHour;
21
25
  /**
22
26
  * What goes in comes out
23
27
  */
@@ -1,3 +1,4 @@
1
+ import type { EventPayload } from '@guardian/ophan-tracker-js';
1
2
  import type { AdSize, SizeMapping } from './ad-sizes';
2
3
  import type { PageTargeting } from './targeting/build-page-targeting';
3
4
  import 'googletag';
@@ -5,11 +6,11 @@ type HeaderBiddingSize = AdSize;
5
6
  interface Advert {
6
7
  id: string;
7
8
  node: HTMLElement;
8
- prebidAdUnit: string;
9
9
  sizes: SizeMapping;
10
10
  headerBiddingSizes: HeaderBiddingSize[] | null;
11
11
  size: AdSize | 'fluid' | null;
12
12
  slot: googletag.Slot;
13
+ gpid: string | undefined;
13
14
  isEmpty: boolean | null;
14
15
  isRendered: boolean;
15
16
  shouldRefresh: boolean;
@@ -41,7 +42,7 @@ interface NetworkInformation extends EventTarget {
41
42
  readonly downlink?: number;
42
43
  readonly effectiveType?: string;
43
44
  }
44
- type OphanRecordFunction = (event: Record<string, unknown> & {
45
+ type OphanRecordFunction = (event: EventPayload & {
45
46
  /**
46
47
  * the experiences key will override previously set values.
47
48
  * Use `recordExperiences` instead.
@@ -424,4 +425,8 @@ type GoogleTrackConversionObject = {
424
425
  google_custom_params: GoogleTagParams;
425
426
  google_remarketing_only: boolean;
426
427
  };
427
- export type { Advert, DfpEnv, ConnectionType, NetworkInformation, OphanRecordFunction, Indices, Edition, AdsConfigDisabled, AdsConfigEnabled, AdsConfigBasic, AdsConfigUSNATorAus, AdsConfigTCFV2, AdsConfig, AdTargetingBuilder, Ophan, Config, AdBlockers, ArticleCounts, FetchBidResponse, HeaderNotification, OptOutInitializeOptions, OptOutAdSlot, Confiant, Permutive, Apstag, ComscoreGlobals, IasPET, TeadsAnalytics, SafeFrameAPI, NSdkInstance, Trac, GoogleTagParams, GoogleTrackConversionObject, };
428
+ type AdmiralEvent = Record<string, unknown>;
429
+ type AdmiralCallback = (event: AdmiralEvent) => void;
430
+ type AdmiralArg = string | AdmiralCallback;
431
+ type Admiral = (...args: AdmiralArg[]) => void;
432
+ export type { Advert, DfpEnv, ConnectionType, NetworkInformation, OphanRecordFunction, Indices, Edition, AdsConfigDisabled, AdsConfigEnabled, AdsConfigBasic, AdsConfigUSNATorAus, AdsConfigTCFV2, AdsConfig, AdTargetingBuilder, Ophan, Config, AdBlockers, ArticleCounts, FetchBidResponse, HeaderNotification, OptOutInitializeOptions, OptOutAdSlot, Confiant, Permutive, Apstag, ComscoreGlobals, IasPET, TeadsAnalytics, SafeFrameAPI, NSdkInstance, Trac, GoogleTagParams, GoogleTrackConversionObject, Admiral, AdmiralEvent, };
@@ -28,7 +28,7 @@ declare class AdSize extends Array<number> {
28
28
  get height(): number;
29
29
  }
30
30
  type SizeKeys = '160x600' | '300x1050' | '300x250' | '300x600' | '728x90' | '970x250' | 'billboard' | 'cascade' | 'empty' | 'fabric' | 'fluid' | 'googleCard' | 'halfPage' | 'leaderboard' | 'merchandising' | 'merchandisingHigh' | 'merchandisingHighAdFeature' | 'mobilesticky' | 'mpu' | 'outOfPage' | 'outstreamDesktop' | 'outstreamGoogleDesktop' | 'outstreamMobile' | 'portrait' | 'portraitInterstitial' | 'pubmaticInterscroller' | 'skyscraper' | 'sponsorLogo';
31
- type SlotName = 'article-end' | 'carrot' | 'comments-expanded' | 'comments' | 'crossword-banner-mobile' | 'exclusion' | 'external' | 'fronts-banner' | 'inline' | 'liveblog-top' | 'merchandising-high' | 'merchandising' | 'mobile-sticky' | 'football-right' | 'mostpop' | 'right' | 'sponsor-logo' | 'survey' | 'top-above-nav' | 'interactive';
31
+ type SlotName = 'article-end' | 'comments-expanded' | 'comments' | 'crossword-banner-mobile' | 'exclusion' | 'external' | 'fronts-banner' | 'inline' | 'liveblog-top' | 'merchandising-high' | 'merchandising' | 'mobile-sticky' | 'football-right' | 'mostpop' | 'right' | 'sponsor-logo' | 'survey' | 'top-above-nav' | 'interactive';
32
32
  type SizeMapping = Partial<Record<Breakpoint, readonly AdSize[]>>;
33
33
  type SlotSizeMappings = Record<SlotName, SizeMapping>;
34
34
  declare const createAdSize: (width: number, height: number) => AdSize;
@@ -139,9 +139,6 @@ declare const slotSizeMappings: {
139
139
  readonly survey: {
140
140
  readonly desktop: readonly [AdSize];
141
141
  };
142
- readonly carrot: {
143
- readonly mobile: readonly [AdSize];
144
- };
145
142
  readonly 'mobile-sticky': {
146
143
  readonly mobile: readonly [AdSize, AdSize, AdSize];
147
144
  };
@@ -303,9 +303,6 @@ const slotSizeMappings = {
303
303
  survey: {
304
304
  desktop: [adSizes.outOfPage],
305
305
  },
306
- carrot: {
307
- mobile: [adSizes.fluid],
308
- },
309
306
  'mobile-sticky': {
310
307
  mobile: [adSizes.mobilesticky, adSizes.empty, createAdSize(300, 50)],
311
308
  },
@@ -7,7 +7,6 @@ const editionToCountryCodeMap = {
7
7
  const editionToCountryCode = (editionKey = 'UK') => editionToCountryCodeMap[editionKey];
8
8
  const countryCookieName = 'GU_geo_country';
9
9
  const countryOverrideName = 'gu.geo.override';
10
- let locale;
11
10
  /*
12
11
  This method can be used as a non async way of getting the country code
13
12
  after init has been called. Returning locale should cover all/most
@@ -20,8 +19,7 @@ const getCountryCode = () => {
20
19
  const countryOverride = isString(maybeCountryOverride)
21
20
  ? maybeCountryOverride
22
21
  : null;
23
- return (locale ??
24
- countryOverride ??
22
+ return (countryOverride ??
25
23
  getCookie({
26
24
  name: countryCookieName,
27
25
  shouldMemoize: true,
@@ -1,5 +1,5 @@
1
1
  import type { EventTimer } from './event-timer';
2
- import type { AdBlockers, Apstag, ArticleCounts, ComscoreGlobals, Confiant, Config, DfpEnv, FetchBidResponse, GoogleTagParams, GoogleTrackConversionObject, HeaderNotification, IasPET, NetworkInformation, NSdkInstance, Ophan, OptOutAdSlot, OptOutInitializeOptions, Permutive, SafeFrameAPI, TeadsAnalytics, Trac } from './types';
2
+ import type { AdBlockers, Admiral, Apstag, ArticleCounts, ComscoreGlobals, Confiant, Config, DfpEnv, FetchBidResponse, GoogleTagParams, GoogleTrackConversionObject, HeaderNotification, IasPET, NetworkInformation, NSdkInstance, Ophan, OptOutAdSlot, OptOutInitializeOptions, Permutive, SafeFrameAPI, TeadsAnalytics, Trac } from './types';
3
3
  declare global {
4
4
  interface Navigator {
5
5
  readonly connection?: NetworkInformation;
@@ -31,6 +31,11 @@ declare global {
31
31
  sentry?: {
32
32
  reportError?: (error: Error, feature: string, tags?: Record<string, string>, extras?: Record<string, unknown>) => void;
33
33
  };
34
+ abTests?: {
35
+ getParticipations: () => Record<string, string>;
36
+ isUserInTest: (testId: string) => boolean;
37
+ isUserInTestGroup: (testId: string, variantId: string) => boolean;
38
+ };
34
39
  };
35
40
  };
36
41
  ootag: {
@@ -67,5 +72,6 @@ declare global {
67
72
  cmd: string;
68
73
  val: Record<string, unknown>;
69
74
  }>;
75
+ admiral?: Admiral;
70
76
  }
71
77
  }
@@ -2,6 +2,7 @@ import type { Participations } from '@guardian/ab-core';
2
2
  import type { ConsentState, CountryCode } from '@guardian/libs';
3
3
  import type { AdManagerGroup, Frequency } from './personalised';
4
4
  import type { SharedTargeting } from './shared';
5
+ import { getLocalHour } from './shared';
5
6
  import type { TrueOrFalse } from './types';
6
7
  type PartialWithNulls<T> = {
7
8
  [P in keyof T]?: T[P] | null;
@@ -13,6 +14,7 @@ type PageTargeting = PartialWithNulls<{
13
14
  at: string;
14
15
  bp: 'mobile' | 'tablet' | 'desktop';
15
16
  cc: CountryCode;
17
+ lh: string;
16
18
  cmp_interaction: string;
17
19
  consent_tcfv2: string;
18
20
  dcre: TrueOrFalse;
@@ -43,5 +45,5 @@ type BuildPageTargetingParams = {
43
45
  youtube?: boolean;
44
46
  };
45
47
  declare const buildPageTargeting: ({ adFree, clientSideParticipations, consentState, isSignedIn, youtube, }: BuildPageTargetingParams) => Record<string, string | string[]>;
46
- export { buildPageTargeting, filterValues };
48
+ export { buildPageTargeting, filterValues, getLocalHour };
47
49
  export type { PageTargeting };
@@ -4,7 +4,7 @@ import { getLocale } from '../geo/get-locale';
4
4
  import { getContentTargeting } from './content';
5
5
  import { getPersonalisedTargeting } from './personalised';
6
6
  import { getSessionTargeting } from './session';
7
- import { getSharedTargeting } from './shared';
7
+ import { getLocalHour, getSharedTargeting } from './shared';
8
8
  import { getViewportTargeting } from './viewport';
9
9
  const filterValues = (pageTargets) => {
10
10
  const filtered = {};
@@ -66,11 +66,13 @@ const buildPageTargeting = ({ adFree, clientSideParticipations, consentState, is
66
66
  const sessionTargeting = getSessionTargeting({
67
67
  adTest: getCookie({ name: 'adtest', shouldMemoize: true }),
68
68
  countryCode: getLocale(),
69
+ localHour: getLocalHour(),
69
70
  isSignedIn,
70
71
  pageViewId: window.guardian.config.ophan.pageViewId,
71
72
  participations: {
72
73
  clientSideParticipations,
73
74
  serverSideParticipations: window.guardian.config.tests ?? {},
75
+ betaAbTestParticipations: window.guardian.modules.abTests?.getParticipations() ?? {},
74
76
  },
75
77
  referrer,
76
78
  });
@@ -105,4 +107,4 @@ const buildPageTargeting = ({ adFree, clientSideParticipations, consentState, is
105
107
  const pageTargeting = filterValues(pageTargets);
106
108
  return pageTargeting;
107
109
  };
108
- export { buildPageTargeting, filterValues };
110
+ export { buildPageTargeting, filterValues, getLocalHour };
@@ -53,6 +53,14 @@ type SessionTargeting = {
53
53
  * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=11703293
54
54
  */
55
55
  cc: CountryCode;
56
+ /**
57
+ * **L**ocal **H**our - [see on Ad Manager][gam]
58
+ *
59
+ * Type: _Dynamic_
60
+ *
61
+ * [gam]: https://admanager.google.com/59666047#inventory/custom_targeting/detail/custom_key_id=17299564
62
+ */
63
+ lh: string;
56
64
  /**
57
65
  * Ophan **P**age **V**iew id – [see on Ad Manager][gam]
58
66
  *
@@ -96,16 +104,22 @@ type AllParticipations = {
96
104
  [key: `${string}Control`]: 'control';
97
105
  [key: `${string}Variant`]: 'variant';
98
106
  };
107
+ betaAbTestParticipations: Record<string, string>;
99
108
  };
100
- declare const experimentsTargeting: ({ clientSideParticipations, serverSideParticipations, }: AllParticipations) => SessionTargeting["ab"];
109
+ /**
110
+ * @todo drop old client/server side participations and rename to just `abTestsParticipations` once
111
+ * all tests have been migrated to the new AB testing platform
112
+ */
113
+ declare const experimentsTargeting: ({ clientSideParticipations, serverSideParticipations, betaAbTestParticipations, }: AllParticipations) => SessionTargeting["ab"];
101
114
  type Session = {
102
115
  adTest: SessionTargeting['at'];
103
116
  countryCode: CountryCode;
117
+ localHour: string;
104
118
  isSignedIn: boolean;
105
119
  pageViewId: SessionTargeting['pv'];
106
120
  participations: AllParticipations;
107
121
  referrer: string;
108
122
  };
109
- declare const getSessionTargeting: ({ adTest, countryCode, isSignedIn, pageViewId, participations, referrer, }: Session) => SessionTargeting;
123
+ declare const getSessionTargeting: ({ adTest, countryCode, localHour, isSignedIn, pageViewId, participations, referrer, }: Session) => SessionTargeting;
110
124
  export type { SessionTargeting, AllParticipations };
111
125
  export { getSessionTargeting, experimentsTargeting };
@@ -25,7 +25,11 @@ const getReferrer = (referrer) => {
25
25
  const matchedRef = referrers.find((referrerType) => referrer.includes(referrerType.match)) ?? null;
26
26
  return matchedRef ? matchedRef.id : null;
27
27
  };
28
- const experimentsTargeting = ({ clientSideParticipations, serverSideParticipations, }) => {
28
+ /**
29
+ * @todo drop old client/server side participations and rename to just `abTestsParticipations` once
30
+ * all tests have been migrated to the new AB testing platform
31
+ */
32
+ const experimentsTargeting = ({ clientSideParticipations, serverSideParticipations, betaAbTestParticipations, }) => {
29
33
  const testToParams = (testName, variant) => {
30
34
  if (variant === 'notintest')
31
35
  return null;
@@ -41,15 +45,25 @@ const experimentsTargeting = ({ clientSideParticipations, serverSideParticipatio
41
45
  const serverSideExperiments = Object.entries(serverSideParticipations)
42
46
  .map((test) => testToParams(...test))
43
47
  .filter(isString);
44
- if (clientSideExperiment.length + serverSideExperiments.length === 0) {
48
+ const betaAbTests = Object.entries(betaAbTestParticipations)
49
+ .map((test) => {
50
+ const [name, variant] = test;
51
+ return testToParams(name, variant);
52
+ })
53
+ .filter(isString);
54
+ if (clientSideExperiment.length +
55
+ serverSideExperiments.length +
56
+ betaAbTests.length ===
57
+ 0) {
45
58
  return null;
46
59
  }
47
- return [...clientSideExperiment, ...serverSideExperiments];
60
+ return [...clientSideExperiment, ...serverSideExperiments, ...betaAbTests];
48
61
  };
49
- const getSessionTargeting = ({ adTest, countryCode, isSignedIn, pageViewId, participations, referrer, }) => ({
62
+ const getSessionTargeting = ({ adTest, countryCode, localHour, isSignedIn, pageViewId, participations, referrer, }) => ({
50
63
  ab: experimentsTargeting(participations),
51
64
  at: adTest,
52
65
  cc: countryCode,
66
+ lh: localHour,
53
67
  pv: pageViewId,
54
68
  ref: getReferrer(referrer),
55
69
  si: isSignedIn ? 't' : 'f',
@@ -145,6 +145,7 @@ type SharedTargeting = {
145
145
  */
146
146
  url: `/${string}`;
147
147
  };
148
+ declare const getLocalHour: () => string;
148
149
  /**
149
150
  * What goes in comes out
150
151
  */
@@ -153,4 +154,4 @@ export declare const _: {
153
154
  getSurgingParam: (surging: number) => SharedTargeting["su"];
154
155
  };
155
156
  export type { SharedTargeting };
156
- export { getSharedTargeting };
157
+ export { getSharedTargeting, getLocalHour };
@@ -15,6 +15,9 @@ const getSurgingParam = (surging) => {
15
15
  return thresholds.filter((n) => n <= surging).map((s) => surges[s]);
16
16
  };
17
17
  /* -- Targeting -- */
18
+ const getLocalHour = () => {
19
+ return new Date().getHours().toString();
20
+ };
18
21
  /**
19
22
  * What goes in comes out
20
23
  */
@@ -22,4 +25,4 @@ const getSharedTargeting = (shared) => pickTargetingValues(shared);
22
25
  export const _ = {
23
26
  getSurgingParam,
24
27
  };
25
- export { getSharedTargeting };
28
+ export { getSharedTargeting, getLocalHour };
@@ -1,3 +1,4 @@
1
+ import type { EventPayload } from '@guardian/ophan-tracker-js';
1
2
  import type { AdSize, SizeMapping } from './ad-sizes';
2
3
  import type { PageTargeting } from './targeting/build-page-targeting';
3
4
  import 'googletag';
@@ -5,11 +6,11 @@ type HeaderBiddingSize = AdSize;
5
6
  interface Advert {
6
7
  id: string;
7
8
  node: HTMLElement;
8
- prebidAdUnit: string;
9
9
  sizes: SizeMapping;
10
10
  headerBiddingSizes: HeaderBiddingSize[] | null;
11
11
  size: AdSize | 'fluid' | null;
12
12
  slot: googletag.Slot;
13
+ gpid: string | undefined;
13
14
  isEmpty: boolean | null;
14
15
  isRendered: boolean;
15
16
  shouldRefresh: boolean;
@@ -41,7 +42,7 @@ interface NetworkInformation extends EventTarget {
41
42
  readonly downlink?: number;
42
43
  readonly effectiveType?: string;
43
44
  }
44
- type OphanRecordFunction = (event: Record<string, unknown> & {
45
+ type OphanRecordFunction = (event: EventPayload & {
45
46
  /**
46
47
  * the experiences key will override previously set values.
47
48
  * Use `recordExperiences` instead.
@@ -424,4 +425,8 @@ type GoogleTrackConversionObject = {
424
425
  google_custom_params: GoogleTagParams;
425
426
  google_remarketing_only: boolean;
426
427
  };
427
- export type { Advert, DfpEnv, ConnectionType, NetworkInformation, OphanRecordFunction, Indices, Edition, AdsConfigDisabled, AdsConfigEnabled, AdsConfigBasic, AdsConfigUSNATorAus, AdsConfigTCFV2, AdsConfig, AdTargetingBuilder, Ophan, Config, AdBlockers, ArticleCounts, FetchBidResponse, HeaderNotification, OptOutInitializeOptions, OptOutAdSlot, Confiant, Permutive, Apstag, ComscoreGlobals, IasPET, TeadsAnalytics, SafeFrameAPI, NSdkInstance, Trac, GoogleTagParams, GoogleTrackConversionObject, };
428
+ type AdmiralEvent = Record<string, unknown>;
429
+ type AdmiralCallback = (event: AdmiralEvent) => void;
430
+ type AdmiralArg = string | AdmiralCallback;
431
+ type Admiral = (...args: AdmiralArg[]) => void;
432
+ export type { Advert, DfpEnv, ConnectionType, NetworkInformation, OphanRecordFunction, Indices, Edition, AdsConfigDisabled, AdsConfigEnabled, AdsConfigBasic, AdsConfigUSNATorAus, AdsConfigTCFV2, AdsConfig, AdTargetingBuilder, Ophan, Config, AdBlockers, ArticleCounts, FetchBidResponse, HeaderNotification, OptOutInitializeOptions, OptOutAdSlot, Confiant, Permutive, Apstag, ComscoreGlobals, IasPET, TeadsAnalytics, SafeFrameAPI, NSdkInstance, Trac, GoogleTagParams, GoogleTrackConversionObject, Admiral, AdmiralEvent, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guardian/commercial-core",
3
- "version": "0.0.0-beta-20250716123536",
3
+ "version": "0.0.0-beta-20251023093018",
4
4
  "description": "Guardian advertising business logic",
5
5
  "homepage": "https://github.com/guardian/commercial#readme",
6
6
  "bugs": {
@@ -30,23 +30,24 @@
30
30
  "./package.json": "./package.json"
31
31
  },
32
32
  "peerDependencies": {
33
- "@guardian/ab-core": "8.0.1",
34
- "@guardian/libs": "22.5.0"
33
+ "@guardian/ab-core": "^8.0.1",
34
+ "@guardian/libs": "^26.0.0"
35
35
  },
36
36
  "dependencies": {
37
37
  "@guardian/ab-core": "8.0.1",
38
- "@guardian/libs": "22.5.0",
38
+ "@guardian/libs": "26.0.0",
39
39
  "@types/googletag": "~3.3.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@types/node": "24.0.12",
43
- "typescript": "5.5.4",
42
+ "@guardian/ophan-tracker-js": "2.6.1",
44
43
  "@types/jest": "30.0.0",
45
- "jest": "^30.0.1",
46
- "jest-environment-jsdom": "~29.7.0",
44
+ "@types/node": "24.7.2",
45
+ "jest": "^30.2.0",
46
+ "jest-environment-jsdom": "^30.2.0",
47
47
  "jest-environment-jsdom-global": "~4.0.0",
48
- "ts-jest": "^29.2.5",
49
- "type-fest": "^4.31.0"
48
+ "ts-jest": "^29.4.4",
49
+ "typescript": "5.9.3",
50
+ "type-fest": "^4.41.0"
50
51
  },
51
52
  "publishConfig": {
52
53
  "access": "public"