@guardian/commercial-core 30.2.0 → 32.0.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.
@@ -1,4 +1,4 @@
1
- type HashClient = 'id5' | 'uid2' | 'euid';
1
+ type HashClient = 'euid' | 'id5' | 'liveramp' | 'uid2' | 'permutive';
2
2
  type Email = `${string}@${string}`;
3
3
  declare function normaliseEmail(email: string): Email;
4
4
  declare function hashEmailForClient(email: string, client: HashClient): Promise<string>;
@@ -2,18 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.hashEmailForClient = hashEmailForClient;
4
4
  exports.normaliseEmail = normaliseEmail;
5
- function toHex(hashBuffer) {
6
- const hashArray = Array.from(new Uint8Array(hashBuffer));
5
+ function toBase64(content) {
6
+ const hashBytes = new Uint8Array(content);
7
+ const base64Hash = btoa(String.fromCharCode(...hashBytes));
8
+ return base64Hash;
9
+ }
10
+ function toHex(content) {
11
+ const hashArray = Array.from(new Uint8Array(content));
7
12
  const hashHex = hashArray
8
13
  .map((bytes) => bytes.toString(16).padStart(2, '0'))
9
14
  .join('');
10
15
  return hashHex;
11
16
  }
12
- function toBase64(hashBuffer) {
13
- const hashBytes = new Uint8Array(hashBuffer);
14
- const base64Hash = btoa(String.fromCharCode(...hashBytes));
15
- return base64Hash;
16
- }
17
17
  function normaliseEmail(email) {
18
18
  const normalisedEmail = email.trim().toLowerCase();
19
19
  const [name, domain] = normalisedEmail.split('@');
@@ -25,13 +25,15 @@ function normaliseEmail(email) {
25
25
  }
26
26
  async function hashEmailForClient(email, client) {
27
27
  const normalisedEmail = normaliseEmail(email);
28
- const utf8 = new TextEncoder().encode(normalisedEmail);
29
- const hashBuffer = await crypto.subtle.digest('SHA-256', utf8);
28
+ const textAsBuffer = new TextEncoder().encode(normalisedEmail);
29
+ const hashBuffer = await crypto.subtle.digest('SHA-256', textAsBuffer);
30
30
  switch (client) {
31
- case 'id5':
32
- return toHex(hashBuffer);
33
- case 'uid2':
34
31
  case 'euid':
32
+ case 'uid2':
35
33
  return toBase64(hashBuffer);
34
+ case 'id5':
35
+ case 'liveramp':
36
+ case 'permutive':
37
+ return toHex(hashBuffer);
36
38
  }
37
39
  }
@@ -1,4 +1,9 @@
1
- import type { ConnectionType } from './types.js';
1
+ import type { ConnectionType, NetworkInformation } from './types.js';
2
+ declare global {
3
+ interface Navigator {
4
+ readonly connection?: NetworkInformation;
5
+ }
6
+ }
2
7
  declare const supportsPerformanceAPI: () => boolean;
3
8
  interface EventTimerProperties {
4
9
  type?: ConnectionType;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `globalAdEvents` acts as an event bus that broadcasts ad lifecycle
3
+ * events, allowing consumers to react to ad status changes without
4
+ * needing direct access to `Advert` instances.
5
+ * @see /docs/global-ad-events.md
6
+ */
7
+ declare const globalAdEvents: EventTarget;
8
+ export { globalAdEvents };
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.globalAdEvents = void 0;
4
+ /**
5
+ * `globalAdEvents` acts as an event bus that broadcasts ad lifecycle
6
+ * events, allowing consumers to react to ad status changes without
7
+ * needing direct access to `Advert` instances.
8
+ * @see /docs/global-ad-events.md
9
+ */
10
+ const globalAdEvents = new EventTarget();
11
+ exports.globalAdEvents = globalAdEvents;
@@ -1,77 +1,13 @@
1
- import type { EventTimer } from './event-timer.js';
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.js';
1
+ import type { CoreGuardian } from './types.js';
3
2
  declare global {
4
- interface Navigator {
5
- readonly connection?: NetworkInformation;
6
- readonly cookieDeprecationLabel?: {
7
- getValue: () => Promise<string>;
8
- };
9
- }
10
3
  interface Window {
11
- guardian: {
12
- ophan?: Ophan;
13
- config: Config;
14
- queue: Array<() => Promise<void>>;
15
- mustardCut?: boolean;
16
- polyfilled?: boolean;
17
- adBlockers: AdBlockers;
18
- css: {
19
- onLoad: () => void;
20
- loaded: boolean;
21
- };
22
- articleCounts?: ArticleCounts;
23
- commercial?: {
24
- dfpEnv?: DfpEnv;
25
- a9WinningBids?: FetchBidResponse[];
26
- };
27
- notificationEventHistory?: HeaderNotification[][];
28
- commercialTimer?: EventTimer;
29
- offlineCount?: number;
30
- modules: {
31
- sentry?: {
32
- reportError?: (error: Error, feature: string, tags?: Record<string, string>, extras?: Record<string, unknown>) => void;
33
- };
34
- abTests?: {
35
- getParticipations: () => Record<string, string>;
36
- isUserInTest: (testId: string) => boolean;
37
- isUserInTestGroup: (testId: string, variantId: string) => boolean;
38
- };
39
- };
40
- };
41
- ootag: {
42
- queue: Array<() => void>;
43
- initializeOo: (o: OptOutInitializeOptions) => void;
44
- addParameter: (key: string, value: string | string[]) => void;
45
- addParameterForSlot: (slotId: string, key: string, value: string | string[]) => void;
46
- defineSlot: (o: OptOutAdSlot) => void;
47
- makeRequests: () => void;
48
- refreshSlot: (slotId: string) => void;
49
- refreshAllSlots: () => void;
50
- logger: (...args: unknown[]) => void;
51
- };
52
- readonly navigator: Navigator;
53
- confiant?: Confiant;
54
- apstag?: Apstag;
55
- permutive?: Permutive;
56
- _comscore?: ComscoreGlobals[];
57
- __iasPET?: IasPET;
58
- teads_analytics?: TeadsAnalytics;
59
- $sf: SafeFrameAPI;
60
- conf: unknown;
61
- NOLCMB: {
62
- getInstance: (apid: string) => NSdkInstance;
4
+ guardian: CoreGuardian;
5
+ googletag: {
6
+ getConfig?: <Key extends string>(key: Key) => Key extends 'targeting' ? {
7
+ targeting: Record<string, string | string[]>;
8
+ } : unknown;
9
+ pubads: () => unknown;
63
10
  };
64
- nol_t: (pvar: {
65
- cid: string;
66
- content: string;
67
- server: string;
68
- }) => Trac;
69
- google_trackConversion?: (arg0: GoogleTrackConversionObject) => void;
70
- google_tag_params?: GoogleTagParams;
71
- _brandmetrics?: Array<{
72
- cmd: string;
73
- val: Record<string, unknown>;
74
- }>;
75
- admiral?: Admiral;
76
11
  }
77
12
  }
13
+ export type { CoreGuardian };
@@ -9,6 +9,7 @@ export { buildImaAdTagUrl } from './targeting/youtube-ima.js';
9
9
  export { getPermutivePFPSegments } from './permutive.js';
10
10
  export { isEligibleForTeads } from './targeting/teads-eligibility.js';
11
11
  export { hashEmailForClient } from './email-hash.js';
12
+ export { globalAdEvents } from './global-ad-events.js';
12
13
  export type { AdSize, SizeMapping, SlotName } from './ad-sizes.js';
13
14
  export type { PageTargeting } from './targeting/build-page-targeting.js';
14
15
  export type { AdsConfigDisabled, AdsConfigUSNATorAus, AdsConfigTCFV2, } from './types.js';
package/dist/cjs/index.js CHANGED
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.hashEmailForClient = exports.isEligibleForTeads = exports.getPermutivePFPSegments = exports.buildImaAdTagUrl = exports.buildPageTargeting = exports.initCommercialMetrics = exports.bypassCommercialMetricsSampling = exports.constants = exports.adSizes = exports.EventTimer = exports.isAdBlockInUse = void 0;
36
+ exports.globalAdEvents = exports.hashEmailForClient = exports.isEligibleForTeads = exports.getPermutivePFPSegments = exports.buildImaAdTagUrl = exports.buildPageTargeting = exports.initCommercialMetrics = exports.bypassCommercialMetricsSampling = exports.constants = exports.adSizes = exports.EventTimer = exports.isAdBlockInUse = void 0;
37
37
  var detect_ad_blocker_1 = require("./detect-ad-blocker.js");
38
38
  Object.defineProperty(exports, "isAdBlockInUse", { enumerable: true, get: function () { return detect_ad_blocker_1.isAdBlockInUse; } });
39
39
  var event_timer_1 = require("./event-timer.js");
@@ -56,3 +56,5 @@ var teads_eligibility_1 = require("./targeting/teads-eligibility.js");
56
56
  Object.defineProperty(exports, "isEligibleForTeads", { enumerable: true, get: function () { return teads_eligibility_1.isEligibleForTeads; } });
57
57
  var email_hash_1 = require("./email-hash.js");
58
58
  Object.defineProperty(exports, "hashEmailForClient", { enumerable: true, get: function () { return email_hash_1.hashEmailForClient; } });
59
+ var global_ad_events_1 = require("./global-ad-events.js");
60
+ Object.defineProperty(exports, "globalAdEvents", { enumerable: true, get: function () { return global_ad_events_1.globalAdEvents; } });
@@ -1,4 +1,3 @@
1
- import type { Participations } from '@guardian/ab-core';
2
1
  import type { ConsentState, CountryCode } from '@guardian/libs';
3
2
  import type { AdManagerGroup, Frequency } from './personalised.js';
4
3
  import type { SharedTargeting } from './shared.js';
@@ -40,7 +39,7 @@ type PageTargeting = PartialWithNulls<{
40
39
  declare const filterValues: (pageTargets: Record<string, unknown>) => Record<string, string | string[]>;
41
40
  type UserId = {
42
41
  name: string;
43
- params?: Record<string, string | number>;
42
+ params?: Record<string, string | number | boolean | Record<string, unknown>>;
44
43
  storage?: {
45
44
  type: 'cookie' | 'html5';
46
45
  name: string;
@@ -50,12 +49,12 @@ type UserId = {
50
49
  };
51
50
  type BuildPageTargetingParams = {
52
51
  adFree: boolean;
53
- clientSideParticipations: Participations;
52
+ abTestParticipations: Record<string, string>;
54
53
  consentState: ConsentState;
55
54
  isSignedIn?: boolean;
56
55
  youtube?: boolean;
57
56
  idProviders?: UserId[];
58
57
  };
59
- declare const buildPageTargeting: ({ adFree, clientSideParticipations, consentState, isSignedIn, youtube, idProviders, }: BuildPageTargetingParams) => Record<string, string | string[]>;
58
+ declare const buildPageTargeting: ({ adFree, abTestParticipations, consentState, isSignedIn, youtube, idProviders, }: BuildPageTargetingParams) => Record<string, string | string[]>;
60
59
  export { buildPageTargeting, filterValues, getLocalHour };
61
60
  export type { UserId, PageTargeting };
@@ -49,7 +49,7 @@ const isFirstVisit = (referrer) => {
49
49
  }
50
50
  return !referrerMatchesHost(referrer);
51
51
  };
52
- const buildPageTargeting = ({ adFree, clientSideParticipations, consentState, isSignedIn = false, youtube = false, idProviders = [], }) => {
52
+ const buildPageTargeting = ({ adFree, abTestParticipations, consentState, isSignedIn = false, youtube = false, idProviders = [], }) => {
53
53
  const { page, isDotcomRendering } = window.guardian.config;
54
54
  const adFreeTargeting = adFree ? { af: 't' } : {};
55
55
  const sharedAdTargeting = page.sharedAdTargeting
@@ -74,11 +74,7 @@ const buildPageTargeting = ({ adFree, clientSideParticipations, consentState, is
74
74
  localHour: (0, shared_1.getLocalHour)(),
75
75
  isSignedIn,
76
76
  pageViewId: window.guardian.config.ophan.pageViewId,
77
- participations: {
78
- clientSideParticipations,
79
- serverSideParticipations: window.guardian.config.tests ?? {},
80
- betaAbTestParticipations: window.guardian.modules.abTests?.getParticipations() ?? {},
81
- },
77
+ participations: abTestParticipations,
82
78
  referrer,
83
79
  idProviders,
84
80
  });
@@ -1,4 +1,3 @@
1
- import type { Participations } from '@guardian/ab-core';
2
1
  import type { CountryCode } from '@guardian/libs';
3
2
  import type { UserId } from '../targeting/build-page-targeting.js';
4
3
  import type { False, True } from './types.js';
@@ -107,29 +106,17 @@ type SessionTargeting = {
107
106
  */
108
107
  idp: string[] | null;
109
108
  };
110
- type AllParticipations = {
111
- clientSideParticipations: Participations;
112
- serverSideParticipations: {
113
- [key: `${string}Control`]: 'control';
114
- [key: `${string}Variant`]: 'variant';
115
- };
116
- betaAbTestParticipations: Record<string, string>;
117
- };
118
- /**
119
- * @todo drop old client/server side participations and rename to just `abTestsParticipations` once
120
- * all tests have been migrated to the new AB testing platform
121
- */
122
- declare const experimentsTargeting: ({ clientSideParticipations, serverSideParticipations, betaAbTestParticipations, }: AllParticipations) => SessionTargeting["ab"];
109
+ declare const abTestingTargeting: (abTestParticipations: Record<string, string>) => SessionTargeting["ab"];
123
110
  type Session = {
124
111
  adTest: SessionTargeting['at'];
125
112
  countryCode: CountryCode;
126
113
  localHour: string;
127
114
  isSignedIn: boolean;
128
115
  pageViewId: SessionTargeting['pv'];
129
- participations: AllParticipations;
116
+ participations: Record<string, string>;
130
117
  referrer: string;
131
118
  idProviders: UserId[];
132
119
  };
133
120
  declare const getSessionTargeting: ({ adTest, countryCode, localHour, isSignedIn, pageViewId, participations, referrer, idProviders, }: Session) => SessionTargeting;
134
- export type { SessionTargeting, AllParticipations };
135
- export { getSessionTargeting, experimentsTargeting };
121
+ export type { SessionTargeting };
122
+ export { getSessionTargeting, abTestingTargeting as experimentsTargeting };
@@ -28,46 +28,30 @@ 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
- /**
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, }) => {
31
+ const abTestingTargeting = (abTestParticipations) => {
36
32
  const testToParams = (testName, variant) => {
37
33
  if (variant === 'notintest')
38
34
  return null;
39
35
  // GAM key-value pairs accept value strings up to 40 characters long
40
36
  return `${testName}-${variant}`.substring(0, 40);
41
37
  };
42
- const clientSideExperiment = Object.entries(clientSideParticipations)
43
- .map((test) => {
44
- const [name, variant] = test;
45
- return testToParams(name, variant.variant);
46
- })
47
- .filter(libs_1.isString);
48
- const serverSideExperiments = Object.entries(serverSideParticipations)
49
- .map((test) => testToParams(...test))
50
- .filter(libs_1.isString);
51
- const betaAbTests = Object.entries(betaAbTestParticipations)
38
+ const abTests = Object.entries(abTestParticipations)
52
39
  .map((test) => {
53
40
  const [name, variant] = test;
54
41
  return testToParams(name, variant);
55
42
  })
56
43
  .filter(libs_1.isString);
57
- if (clientSideExperiment.length +
58
- serverSideExperiments.length +
59
- betaAbTests.length ===
60
- 0) {
44
+ if (abTests.length === 0) {
61
45
  return null;
62
46
  }
63
- return [...clientSideExperiment, ...serverSideExperiments, ...betaAbTests];
47
+ return abTests;
64
48
  };
65
- exports.experimentsTargeting = experimentsTargeting;
49
+ exports.experimentsTargeting = abTestingTargeting;
66
50
  const getIdProviders = (userIds) => {
67
51
  return userIds.map((id) => id.name);
68
52
  };
69
53
  const getSessionTargeting = ({ adTest, countryCode, localHour, isSignedIn, pageViewId, participations, referrer, idProviders, }) => ({
70
- ab: experimentsTargeting(participations),
54
+ ab: abTestingTargeting(participations),
71
55
  at: adTest,
72
56
  cc: countryCode,
73
57
  lh: localHour,
@@ -1,12 +1,11 @@
1
- import type { Participations } from '@guardian/ab-core';
2
1
  import type { ConsentState } from '@guardian/libs';
3
2
  import type { CustomParams } from './types.js';
4
3
  type BuildImaAdTagUrl = {
5
4
  adUnit: string;
6
5
  customParams: CustomParams;
7
6
  consentState: ConsentState;
8
- clientSideParticipations: Participations;
7
+ abTestParticipations: Record<string, string>;
9
8
  isSignedIn: boolean;
10
9
  };
11
- declare const buildImaAdTagUrl: ({ adUnit, clientSideParticipations, consentState, customParams, isSignedIn, }: BuildImaAdTagUrl) => string;
10
+ declare const buildImaAdTagUrl: ({ adUnit, abTestParticipations, consentState, customParams, isSignedIn, }: BuildImaAdTagUrl) => string;
12
11
  export { buildImaAdTagUrl };
@@ -18,12 +18,12 @@ const encodeCustomParams = (params) => {
18
18
  .join('&');
19
19
  return encodedParams;
20
20
  };
21
- const mergeCustomParamsWithTargeting = (customParams, consentState, clientSideParticipations, isSignedIn) => {
21
+ const mergeCustomParamsWithTargeting = (customParams, consentState, abTestParticipations, isSignedIn) => {
22
22
  let pageTargeting = {};
23
23
  try {
24
24
  pageTargeting = (0, build_page_targeting_1.buildPageTargeting)({
25
25
  adFree: false,
26
- clientSideParticipations,
26
+ abTestParticipations,
27
27
  consentState: consentState,
28
28
  isSignedIn: isSignedIn,
29
29
  });
@@ -43,8 +43,8 @@ const mergeCustomParamsWithTargeting = (customParams, consentState, clientSidePa
43
43
  };
44
44
  return mergedCustomParams;
45
45
  };
46
- const buildImaAdTagUrl = ({ adUnit, clientSideParticipations, consentState, customParams, isSignedIn, }) => {
47
- const mergedCustomParams = mergeCustomParamsWithTargeting(customParams, consentState, clientSideParticipations, isSignedIn);
46
+ const buildImaAdTagUrl = ({ adUnit, abTestParticipations, consentState, customParams, isSignedIn, }) => {
47
+ const mergedCustomParams = mergeCustomParamsWithTargeting(customParams, consentState, abTestParticipations, isSignedIn);
48
48
  const queryParams = {
49
49
  iu: adUnit,
50
50
  tfcd: '0',
@@ -1,41 +1,6 @@
1
1
  import type { EventPayload } from '@guardian/ophan-tracker-js';
2
- import type { AdSize, SizeMapping } from './ad-sizes.js';
2
+ import type { EventTimer } from './event-timer.js';
3
3
  import type { PageTargeting } from './targeting/build-page-targeting.js';
4
- import '@types/google-publisher-tag';
5
- type HeaderBiddingSize = AdSize;
6
- interface Advert {
7
- id: string;
8
- node: HTMLElement;
9
- sizes: SizeMapping;
10
- headerBiddingSizes: HeaderBiddingSize[] | null;
11
- size: AdSize | 'fluid' | null;
12
- slot: googletag.Slot;
13
- gpid: string | undefined;
14
- isEmpty: boolean | null;
15
- isRendered: boolean;
16
- shouldRefresh: boolean;
17
- whenSlotReady: Promise<void>;
18
- extraNodeClasses: string[];
19
- hasPrebidSize: boolean;
20
- headerBiddingBidRequest: Promise<unknown> | null;
21
- lineItemId: number | null;
22
- creativeId: number | null;
23
- creativeTemplateId: number | null;
24
- testgroup: string | undefined;
25
- finishedRendering(isRendered: boolean): void;
26
- updateExtraSlotClasses(...newClasses: string[]): Promise<void>;
27
- generateSizeMapping(additionalSizeMapping: SizeMapping): SizeMapping;
28
- updateSizeMapping(additionalSizeMapping: SizeMapping): void;
29
- }
30
- interface DfpEnv {
31
- renderStartTime: number;
32
- adSlotSelector: string;
33
- lazyLoadEnabled: boolean;
34
- lazyLoadObserve: boolean;
35
- advertsToLoad: Advert[];
36
- adverts: Map<Advert['id'], Advert>;
37
- shouldLazyLoad: () => boolean;
38
- }
39
4
  type ConnectionType = 'bluetooth' | 'cellular' | 'ethernet' | 'mixed' | 'none' | 'other' | 'unknown' | 'wifi';
40
5
  interface NetworkInformation extends EventTarget {
41
6
  readonly type?: ConnectionType;
@@ -430,4 +395,19 @@ type AdmiralEvent = Record<string, unknown>;
430
395
  type AdmiralCallback = (event: AdmiralEvent) => void;
431
396
  type AdmiralArg = string | AdmiralCallback;
432
397
  type Admiral = (...args: AdmiralArg[]) => void;
433
- 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, };
398
+ interface CoreGuardian {
399
+ config: Config;
400
+ commercialTimer?: EventTimer;
401
+ offlineCount?: number;
402
+ modules: {
403
+ sentry?: {
404
+ reportError?: (error: Error, feature: string, tags?: Record<string, string>, extras?: Record<string, unknown>) => void;
405
+ };
406
+ abTests?: {
407
+ getParticipations: () => Record<string, string>;
408
+ isUserInTest: (testId: string) => boolean;
409
+ isUserInTestGroup: (testId: string, variantId: string) => boolean;
410
+ };
411
+ };
412
+ }
413
+ export type { 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, CoreGuardian, };
package/dist/cjs/types.js CHANGED
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- require("@types/google-publisher-tag");
4
3
  var BlockingType;
5
4
  (function (BlockingType) {
6
5
  BlockingType[BlockingType["Manual"] = 1] = "Manual";
@@ -1,4 +1,4 @@
1
- type HashClient = 'id5' | 'uid2' | 'euid';
1
+ type HashClient = 'euid' | 'id5' | 'liveramp' | 'uid2' | 'permutive';
2
2
  type Email = `${string}@${string}`;
3
3
  declare function normaliseEmail(email: string): Email;
4
4
  declare function hashEmailForClient(email: string, client: HashClient): Promise<string>;
@@ -1,15 +1,15 @@
1
- function toHex(hashBuffer) {
2
- const hashArray = Array.from(new Uint8Array(hashBuffer));
1
+ function toBase64(content) {
2
+ const hashBytes = new Uint8Array(content);
3
+ const base64Hash = btoa(String.fromCharCode(...hashBytes));
4
+ return base64Hash;
5
+ }
6
+ function toHex(content) {
7
+ const hashArray = Array.from(new Uint8Array(content));
3
8
  const hashHex = hashArray
4
9
  .map((bytes) => bytes.toString(16).padStart(2, '0'))
5
10
  .join('');
6
11
  return hashHex;
7
12
  }
8
- function toBase64(hashBuffer) {
9
- const hashBytes = new Uint8Array(hashBuffer);
10
- const base64Hash = btoa(String.fromCharCode(...hashBytes));
11
- return base64Hash;
12
- }
13
13
  function normaliseEmail(email) {
14
14
  const normalisedEmail = email.trim().toLowerCase();
15
15
  const [name, domain] = normalisedEmail.split('@');
@@ -21,14 +21,16 @@ function normaliseEmail(email) {
21
21
  }
22
22
  async function hashEmailForClient(email, client) {
23
23
  const normalisedEmail = normaliseEmail(email);
24
- const utf8 = new TextEncoder().encode(normalisedEmail);
25
- const hashBuffer = await crypto.subtle.digest('SHA-256', utf8);
24
+ const textAsBuffer = new TextEncoder().encode(normalisedEmail);
25
+ const hashBuffer = await crypto.subtle.digest('SHA-256', textAsBuffer);
26
26
  switch (client) {
27
- case 'id5':
28
- return toHex(hashBuffer);
29
- case 'uid2':
30
27
  case 'euid':
28
+ case 'uid2':
31
29
  return toBase64(hashBuffer);
30
+ case 'id5':
31
+ case 'liveramp':
32
+ case 'permutive':
33
+ return toHex(hashBuffer);
32
34
  }
33
35
  }
34
36
  export { hashEmailForClient, normaliseEmail };
@@ -1,4 +1,9 @@
1
- import type { ConnectionType } from './types.js';
1
+ import type { ConnectionType, NetworkInformation } from './types.js';
2
+ declare global {
3
+ interface Navigator {
4
+ readonly connection?: NetworkInformation;
5
+ }
6
+ }
2
7
  declare const supportsPerformanceAPI: () => boolean;
3
8
  interface EventTimerProperties {
4
9
  type?: ConnectionType;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `globalAdEvents` acts as an event bus that broadcasts ad lifecycle
3
+ * events, allowing consumers to react to ad status changes without
4
+ * needing direct access to `Advert` instances.
5
+ * @see /docs/global-ad-events.md
6
+ */
7
+ declare const globalAdEvents: EventTarget;
8
+ export { globalAdEvents };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `globalAdEvents` acts as an event bus that broadcasts ad lifecycle
3
+ * events, allowing consumers to react to ad status changes without
4
+ * needing direct access to `Advert` instances.
5
+ * @see /docs/global-ad-events.md
6
+ */
7
+ const globalAdEvents = new EventTarget();
8
+ export { globalAdEvents };
@@ -1,77 +1,13 @@
1
- import type { EventTimer } from './event-timer.js';
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.js';
1
+ import type { CoreGuardian } from './types.js';
3
2
  declare global {
4
- interface Navigator {
5
- readonly connection?: NetworkInformation;
6
- readonly cookieDeprecationLabel?: {
7
- getValue: () => Promise<string>;
8
- };
9
- }
10
3
  interface Window {
11
- guardian: {
12
- ophan?: Ophan;
13
- config: Config;
14
- queue: Array<() => Promise<void>>;
15
- mustardCut?: boolean;
16
- polyfilled?: boolean;
17
- adBlockers: AdBlockers;
18
- css: {
19
- onLoad: () => void;
20
- loaded: boolean;
21
- };
22
- articleCounts?: ArticleCounts;
23
- commercial?: {
24
- dfpEnv?: DfpEnv;
25
- a9WinningBids?: FetchBidResponse[];
26
- };
27
- notificationEventHistory?: HeaderNotification[][];
28
- commercialTimer?: EventTimer;
29
- offlineCount?: number;
30
- modules: {
31
- sentry?: {
32
- reportError?: (error: Error, feature: string, tags?: Record<string, string>, extras?: Record<string, unknown>) => void;
33
- };
34
- abTests?: {
35
- getParticipations: () => Record<string, string>;
36
- isUserInTest: (testId: string) => boolean;
37
- isUserInTestGroup: (testId: string, variantId: string) => boolean;
38
- };
39
- };
40
- };
41
- ootag: {
42
- queue: Array<() => void>;
43
- initializeOo: (o: OptOutInitializeOptions) => void;
44
- addParameter: (key: string, value: string | string[]) => void;
45
- addParameterForSlot: (slotId: string, key: string, value: string | string[]) => void;
46
- defineSlot: (o: OptOutAdSlot) => void;
47
- makeRequests: () => void;
48
- refreshSlot: (slotId: string) => void;
49
- refreshAllSlots: () => void;
50
- logger: (...args: unknown[]) => void;
51
- };
52
- readonly navigator: Navigator;
53
- confiant?: Confiant;
54
- apstag?: Apstag;
55
- permutive?: Permutive;
56
- _comscore?: ComscoreGlobals[];
57
- __iasPET?: IasPET;
58
- teads_analytics?: TeadsAnalytics;
59
- $sf: SafeFrameAPI;
60
- conf: unknown;
61
- NOLCMB: {
62
- getInstance: (apid: string) => NSdkInstance;
4
+ guardian: CoreGuardian;
5
+ googletag: {
6
+ getConfig?: <Key extends string>(key: Key) => Key extends 'targeting' ? {
7
+ targeting: Record<string, string | string[]>;
8
+ } : unknown;
9
+ pubads: () => unknown;
63
10
  };
64
- nol_t: (pvar: {
65
- cid: string;
66
- content: string;
67
- server: string;
68
- }) => Trac;
69
- google_trackConversion?: (arg0: GoogleTrackConversionObject) => void;
70
- google_tag_params?: GoogleTagParams;
71
- _brandmetrics?: Array<{
72
- cmd: string;
73
- val: Record<string, unknown>;
74
- }>;
75
- admiral?: Admiral;
76
11
  }
77
12
  }
13
+ export type { CoreGuardian };
@@ -9,6 +9,7 @@ export { buildImaAdTagUrl } from './targeting/youtube-ima.js';
9
9
  export { getPermutivePFPSegments } from './permutive.js';
10
10
  export { isEligibleForTeads } from './targeting/teads-eligibility.js';
11
11
  export { hashEmailForClient } from './email-hash.js';
12
+ export { globalAdEvents } from './global-ad-events.js';
12
13
  export type { AdSize, SizeMapping, SlotName } from './ad-sizes.js';
13
14
  export type { PageTargeting } from './targeting/build-page-targeting.js';
14
15
  export type { AdsConfigDisabled, AdsConfigUSNATorAus, AdsConfigTCFV2, } from './types.js';
package/dist/esm/index.js CHANGED
@@ -9,3 +9,4 @@ export { buildImaAdTagUrl } from './targeting/youtube-ima.js';
9
9
  export { getPermutivePFPSegments } from './permutive.js';
10
10
  export { isEligibleForTeads } from './targeting/teads-eligibility.js';
11
11
  export { hashEmailForClient } from './email-hash.js';
12
+ export { globalAdEvents } from './global-ad-events.js';
@@ -1,4 +1,3 @@
1
- import type { Participations } from '@guardian/ab-core';
2
1
  import type { ConsentState, CountryCode } from '@guardian/libs';
3
2
  import type { AdManagerGroup, Frequency } from './personalised.js';
4
3
  import type { SharedTargeting } from './shared.js';
@@ -40,7 +39,7 @@ type PageTargeting = PartialWithNulls<{
40
39
  declare const filterValues: (pageTargets: Record<string, unknown>) => Record<string, string | string[]>;
41
40
  type UserId = {
42
41
  name: string;
43
- params?: Record<string, string | number>;
42
+ params?: Record<string, string | number | boolean | Record<string, unknown>>;
44
43
  storage?: {
45
44
  type: 'cookie' | 'html5';
46
45
  name: string;
@@ -50,12 +49,12 @@ type UserId = {
50
49
  };
51
50
  type BuildPageTargetingParams = {
52
51
  adFree: boolean;
53
- clientSideParticipations: Participations;
52
+ abTestParticipations: Record<string, string>;
54
53
  consentState: ConsentState;
55
54
  isSignedIn?: boolean;
56
55
  youtube?: boolean;
57
56
  idProviders?: UserId[];
58
57
  };
59
- declare const buildPageTargeting: ({ adFree, clientSideParticipations, consentState, isSignedIn, youtube, idProviders, }: BuildPageTargetingParams) => Record<string, string | string[]>;
58
+ declare const buildPageTargeting: ({ adFree, abTestParticipations, consentState, isSignedIn, youtube, idProviders, }: BuildPageTargetingParams) => Record<string, string | string[]>;
60
59
  export { buildPageTargeting, filterValues, getLocalHour };
61
60
  export type { UserId, PageTargeting };
@@ -44,7 +44,7 @@ const isFirstVisit = (referrer) => {
44
44
  }
45
45
  return !referrerMatchesHost(referrer);
46
46
  };
47
- const buildPageTargeting = ({ adFree, clientSideParticipations, consentState, isSignedIn = false, youtube = false, idProviders = [], }) => {
47
+ const buildPageTargeting = ({ adFree, abTestParticipations, consentState, isSignedIn = false, youtube = false, idProviders = [], }) => {
48
48
  const { page, isDotcomRendering } = window.guardian.config;
49
49
  const adFreeTargeting = adFree ? { af: 't' } : {};
50
50
  const sharedAdTargeting = page.sharedAdTargeting
@@ -69,11 +69,7 @@ const buildPageTargeting = ({ adFree, clientSideParticipations, consentState, is
69
69
  localHour: getLocalHour(),
70
70
  isSignedIn,
71
71
  pageViewId: window.guardian.config.ophan.pageViewId,
72
- participations: {
73
- clientSideParticipations,
74
- serverSideParticipations: window.guardian.config.tests ?? {},
75
- betaAbTestParticipations: window.guardian.modules.abTests?.getParticipations() ?? {},
76
- },
72
+ participations: abTestParticipations,
77
73
  referrer,
78
74
  idProviders,
79
75
  });
@@ -1,4 +1,3 @@
1
- import type { Participations } from '@guardian/ab-core';
2
1
  import type { CountryCode } from '@guardian/libs';
3
2
  import type { UserId } from '../targeting/build-page-targeting.js';
4
3
  import type { False, True } from './types.js';
@@ -107,29 +106,17 @@ type SessionTargeting = {
107
106
  */
108
107
  idp: string[] | null;
109
108
  };
110
- type AllParticipations = {
111
- clientSideParticipations: Participations;
112
- serverSideParticipations: {
113
- [key: `${string}Control`]: 'control';
114
- [key: `${string}Variant`]: 'variant';
115
- };
116
- betaAbTestParticipations: Record<string, string>;
117
- };
118
- /**
119
- * @todo drop old client/server side participations and rename to just `abTestsParticipations` once
120
- * all tests have been migrated to the new AB testing platform
121
- */
122
- declare const experimentsTargeting: ({ clientSideParticipations, serverSideParticipations, betaAbTestParticipations, }: AllParticipations) => SessionTargeting["ab"];
109
+ declare const abTestingTargeting: (abTestParticipations: Record<string, string>) => SessionTargeting["ab"];
123
110
  type Session = {
124
111
  adTest: SessionTargeting['at'];
125
112
  countryCode: CountryCode;
126
113
  localHour: string;
127
114
  isSignedIn: boolean;
128
115
  pageViewId: SessionTargeting['pv'];
129
- participations: AllParticipations;
116
+ participations: Record<string, string>;
130
117
  referrer: string;
131
118
  idProviders: UserId[];
132
119
  };
133
120
  declare const getSessionTargeting: ({ adTest, countryCode, localHour, isSignedIn, pageViewId, participations, referrer, idProviders, }: Session) => SessionTargeting;
134
- export type { SessionTargeting, AllParticipations };
135
- export { getSessionTargeting, experimentsTargeting };
121
+ export type { SessionTargeting };
122
+ export { getSessionTargeting, abTestingTargeting as experimentsTargeting };
@@ -25,45 +25,29 @@ 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
- /**
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, }) => {
28
+ const abTestingTargeting = (abTestParticipations) => {
33
29
  const testToParams = (testName, variant) => {
34
30
  if (variant === 'notintest')
35
31
  return null;
36
32
  // GAM key-value pairs accept value strings up to 40 characters long
37
33
  return `${testName}-${variant}`.substring(0, 40);
38
34
  };
39
- const clientSideExperiment = Object.entries(clientSideParticipations)
40
- .map((test) => {
41
- const [name, variant] = test;
42
- return testToParams(name, variant.variant);
43
- })
44
- .filter(isString);
45
- const serverSideExperiments = Object.entries(serverSideParticipations)
46
- .map((test) => testToParams(...test))
47
- .filter(isString);
48
- const betaAbTests = Object.entries(betaAbTestParticipations)
35
+ const abTests = Object.entries(abTestParticipations)
49
36
  .map((test) => {
50
37
  const [name, variant] = test;
51
38
  return testToParams(name, variant);
52
39
  })
53
40
  .filter(isString);
54
- if (clientSideExperiment.length +
55
- serverSideExperiments.length +
56
- betaAbTests.length ===
57
- 0) {
41
+ if (abTests.length === 0) {
58
42
  return null;
59
43
  }
60
- return [...clientSideExperiment, ...serverSideExperiments, ...betaAbTests];
44
+ return abTests;
61
45
  };
62
46
  const getIdProviders = (userIds) => {
63
47
  return userIds.map((id) => id.name);
64
48
  };
65
49
  const getSessionTargeting = ({ adTest, countryCode, localHour, isSignedIn, pageViewId, participations, referrer, idProviders, }) => ({
66
- ab: experimentsTargeting(participations),
50
+ ab: abTestingTargeting(participations),
67
51
  at: adTest,
68
52
  cc: countryCode,
69
53
  lh: localHour,
@@ -72,4 +56,4 @@ const getSessionTargeting = ({ adTest, countryCode, localHour, isSignedIn, pageV
72
56
  si: isSignedIn ? 't' : 'f',
73
57
  idp: getIdProviders(idProviders),
74
58
  });
75
- export { getSessionTargeting, experimentsTargeting };
59
+ export { getSessionTargeting, abTestingTargeting as experimentsTargeting };
@@ -1,12 +1,11 @@
1
- import type { Participations } from '@guardian/ab-core';
2
1
  import type { ConsentState } from '@guardian/libs';
3
2
  import type { CustomParams } from './types.js';
4
3
  type BuildImaAdTagUrl = {
5
4
  adUnit: string;
6
5
  customParams: CustomParams;
7
6
  consentState: ConsentState;
8
- clientSideParticipations: Participations;
7
+ abTestParticipations: Record<string, string>;
9
8
  isSignedIn: boolean;
10
9
  };
11
- declare const buildImaAdTagUrl: ({ adUnit, clientSideParticipations, consentState, customParams, isSignedIn, }: BuildImaAdTagUrl) => string;
10
+ declare const buildImaAdTagUrl: ({ adUnit, abTestParticipations, consentState, customParams, isSignedIn, }: BuildImaAdTagUrl) => string;
12
11
  export { buildImaAdTagUrl };
@@ -15,12 +15,12 @@ const encodeCustomParams = (params) => {
15
15
  .join('&');
16
16
  return encodedParams;
17
17
  };
18
- const mergeCustomParamsWithTargeting = (customParams, consentState, clientSideParticipations, isSignedIn) => {
18
+ const mergeCustomParamsWithTargeting = (customParams, consentState, abTestParticipations, isSignedIn) => {
19
19
  let pageTargeting = {};
20
20
  try {
21
21
  pageTargeting = buildPageTargeting({
22
22
  adFree: false,
23
- clientSideParticipations,
23
+ abTestParticipations,
24
24
  consentState: consentState,
25
25
  isSignedIn: isSignedIn,
26
26
  });
@@ -40,8 +40,8 @@ const mergeCustomParamsWithTargeting = (customParams, consentState, clientSidePa
40
40
  };
41
41
  return mergedCustomParams;
42
42
  };
43
- const buildImaAdTagUrl = ({ adUnit, clientSideParticipations, consentState, customParams, isSignedIn, }) => {
44
- const mergedCustomParams = mergeCustomParamsWithTargeting(customParams, consentState, clientSideParticipations, isSignedIn);
43
+ const buildImaAdTagUrl = ({ adUnit, abTestParticipations, consentState, customParams, isSignedIn, }) => {
44
+ const mergedCustomParams = mergeCustomParamsWithTargeting(customParams, consentState, abTestParticipations, isSignedIn);
45
45
  const queryParams = {
46
46
  iu: adUnit,
47
47
  tfcd: '0',
@@ -1,41 +1,6 @@
1
1
  import type { EventPayload } from '@guardian/ophan-tracker-js';
2
- import type { AdSize, SizeMapping } from './ad-sizes.js';
2
+ import type { EventTimer } from './event-timer.js';
3
3
  import type { PageTargeting } from './targeting/build-page-targeting.js';
4
- import '@types/google-publisher-tag';
5
- type HeaderBiddingSize = AdSize;
6
- interface Advert {
7
- id: string;
8
- node: HTMLElement;
9
- sizes: SizeMapping;
10
- headerBiddingSizes: HeaderBiddingSize[] | null;
11
- size: AdSize | 'fluid' | null;
12
- slot: googletag.Slot;
13
- gpid: string | undefined;
14
- isEmpty: boolean | null;
15
- isRendered: boolean;
16
- shouldRefresh: boolean;
17
- whenSlotReady: Promise<void>;
18
- extraNodeClasses: string[];
19
- hasPrebidSize: boolean;
20
- headerBiddingBidRequest: Promise<unknown> | null;
21
- lineItemId: number | null;
22
- creativeId: number | null;
23
- creativeTemplateId: number | null;
24
- testgroup: string | undefined;
25
- finishedRendering(isRendered: boolean): void;
26
- updateExtraSlotClasses(...newClasses: string[]): Promise<void>;
27
- generateSizeMapping(additionalSizeMapping: SizeMapping): SizeMapping;
28
- updateSizeMapping(additionalSizeMapping: SizeMapping): void;
29
- }
30
- interface DfpEnv {
31
- renderStartTime: number;
32
- adSlotSelector: string;
33
- lazyLoadEnabled: boolean;
34
- lazyLoadObserve: boolean;
35
- advertsToLoad: Advert[];
36
- adverts: Map<Advert['id'], Advert>;
37
- shouldLazyLoad: () => boolean;
38
- }
39
4
  type ConnectionType = 'bluetooth' | 'cellular' | 'ethernet' | 'mixed' | 'none' | 'other' | 'unknown' | 'wifi';
40
5
  interface NetworkInformation extends EventTarget {
41
6
  readonly type?: ConnectionType;
@@ -430,4 +395,19 @@ type AdmiralEvent = Record<string, unknown>;
430
395
  type AdmiralCallback = (event: AdmiralEvent) => void;
431
396
  type AdmiralArg = string | AdmiralCallback;
432
397
  type Admiral = (...args: AdmiralArg[]) => void;
433
- 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, };
398
+ interface CoreGuardian {
399
+ config: Config;
400
+ commercialTimer?: EventTimer;
401
+ offlineCount?: number;
402
+ modules: {
403
+ sentry?: {
404
+ reportError?: (error: Error, feature: string, tags?: Record<string, string>, extras?: Record<string, unknown>) => void;
405
+ };
406
+ abTests?: {
407
+ getParticipations: () => Record<string, string>;
408
+ isUserInTest: (testId: string) => boolean;
409
+ isUserInTestGroup: (testId: string, variantId: string) => boolean;
410
+ };
411
+ };
412
+ }
413
+ export type { 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, CoreGuardian, };
package/dist/esm/types.js CHANGED
@@ -1,4 +1,3 @@
1
- import '@types/google-publisher-tag';
2
1
  var BlockingType;
3
2
  (function (BlockingType) {
4
3
  BlockingType[BlockingType["Manual"] = 1] = "Manual";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guardian/commercial-core",
3
- "version": "30.2.0",
3
+ "version": "32.0.0",
4
4
  "description": "Guardian advertising business logic",
5
5
  "homepage": "https://github.com/guardian/commercial#readme",
6
6
  "bugs": {
@@ -32,24 +32,21 @@
32
32
  "./package.json": "./package.json"
33
33
  },
34
34
  "peerDependencies": {
35
- "@guardian/ab-core": "^9.0.0",
36
- "@guardian/libs": "^27.0.0"
35
+ "@guardian/libs": "^30.1.0"
37
36
  },
38
37
  "dependencies": {
39
- "@guardian/ab-core": "9.0.0",
40
- "@guardian/libs": "27.0.0",
41
- "@types/google-publisher-tag": "~1.20251117.0"
38
+ "@guardian/libs": "30.1.0"
42
39
  },
43
40
  "devDependencies": {
44
- "@guardian/ophan-tracker-js": "2.6.3",
41
+ "@guardian/ophan-tracker-js": "2.8.0",
45
42
  "@types/jest": "30.0.0",
46
- "@types/node": "24.10.1",
47
- "jest": "^30.2.0",
48
- "jest-environment-jsdom": "^30.2.0",
43
+ "@types/node": "25.4.0",
44
+ "jest": "^30.3.0",
45
+ "jest-environment-jsdom": "^30.3.0",
49
46
  "jest-environment-jsdom-global": "~4.0.0",
50
47
  "ts-jest": "^29.4.6",
51
48
  "tsc-alias": "1.8.16",
52
- "type-fest": "^4.41.0",
49
+ "type-fest": "^5.4.3",
53
50
  "typescript": "5.9.3"
54
51
  },
55
52
  "publishConfig": {