Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
pkowalski-id5 committed Feb 20, 2024
2 parents ecbcdb3 + a90d5ff commit 0445107
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 62 deletions.
36 changes: 24 additions & 12 deletions integration/id5Api.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import chromePaths from 'chrome-paths';
import mockttp from 'mockttp';
import tmp from 'tmp-promise';
import path from 'path';
import { fileURLToPath } from 'url';
import chai, { expect } from 'chai';
import { version } from '../generated/version.js';
import {fileURLToPath} from 'url';
import chai, {expect} from 'chai';
import {version} from '../generated/version.js';
import chaiDateTime from 'chai-datetime';
import isDocker from 'is-docker';

Expand Down Expand Up @@ -72,7 +72,7 @@ const multiFetchResponseWithCorsAllowed = (payload, status = 200) => {
};

const multiFetchResponseWithHeaders = (payload, headers) => {
return async request => makeMultiFetchResponse(request, payload, 200 ,headers);
return async request => makeMultiFetchResponse(request, payload, 200, headers);
};

async function makeMultiFetchResponse(request, payload, status, headers) {
Expand Down Expand Up @@ -113,7 +113,7 @@ describe('The ID5 API', function () {
this.timeout((_DEBUG ? 3000 : 30) * 1000);

async function startBrowser() {
profileDir = await tmp.dir({ unsafeCleanup: true });
profileDir = await tmp.dir({unsafeCleanup: true});
const args = [
`--proxy-server=localhost:${server.port}`,
`--ignore-certificate-errors-spki-list=${caFingerprint}`,
Expand Down Expand Up @@ -222,7 +222,7 @@ describe('The ID5 API', function () {
expect(requestBody.tml).to.equal('https://my-publisher-website.net/');
expect(requestBody.cu).to.equal('https://www.id5.io/');
expect(requestBody.ref).to.equal('https://referer-page.com/');
expect(requestBody.segments).to.deep.equal([{ destination: '22', ids: ['abc'] }]);
expect(requestBody.segments).to.deep.equal([{destination: '22', ids: ['abc']}]);
expect(requestBody.ua).to.be.a('string');
expect(requestBody.extensions.lb).to.equal('LB_DATA'); // from MOCK_LB_RESPONSE
expect(requestBody.extensions.lbCDN).to.equal('%%LB_CDN%%'); // lbCDN substitution macro
Expand Down Expand Up @@ -286,8 +286,8 @@ describe('The ID5 API', function () {
const requestBody1 = (await id5FetchRequests[0].body.getJson()).requests[0];
const requestBody2 = (await id5FetchRequests[1].body.getJson()).requests[0];

expect(requestBody1.segments).to.deep.eq([{ destination: '22', ids: ['abc']}]);
expect(requestBody2.segments).to.deep.eq([{ destination: '24', ids: ['def']}]);
expect(requestBody1.segments).to.deep.eq([{destination: '22', ids: ['abc']}]);
expect(requestBody2.segments).to.deep.eq([{destination: '24', ids: ['def']}]);
});
});

Expand Down Expand Up @@ -329,7 +329,7 @@ describe('The ID5 API', function () {
expect(requestBody.metadata).to.deep.eq({eventId: 'TEST_TEST'});
});

it('does not drop local storage items', async function() {
it('does not drop local storage items', async function () {
await server.forPost(FETCH_ENDPOINT)
.thenCallback(multiFetchResponseWithCorsAllowed(MOCK_FETCH_RESPONSE));
await server.forGet('https://lb.eu-1-id5-sync.com/lb/v1')
Expand Down Expand Up @@ -467,7 +467,13 @@ describe('The ID5 API', function () {
expect(onlyRequest.metadata.trigger).is.eq('fixed-time');
expect(onlyRequest.metadata.fixed_time_msec).is.eq(3100);
expect(onlyRequest.measurements.length).is.gte(12);
const commonTags = { version: version, partner: '99', source: 'api', tml: 'https://my-publisher-website.net/' };
const commonTags = {
version: version,
partner: '99',
source: 'api',
tml: 'https://my-publisher-website.net/',
provider: 'default'
};
verifyContainsMeasurementWithTags(onlyRequest.measurements, 'id5.api.instance.load.delay', 'TIMER', commonTags);
verifyContainsMeasurementWithTags(onlyRequest.measurements, 'id5.api.invocation.count', 'SUMMARY', commonTags);
verifyContainsMeasurementWithTags(onlyRequest.measurements, 'id5.api.consent.request.time', 'TIMER', {
Expand Down Expand Up @@ -526,7 +532,13 @@ describe('The ID5 API', function () {
expect(onlyRequest.metadata.sampling).is.eq(1);
expect(onlyRequest.metadata.trigger).is.eq('beforeunload');
expect(onlyRequest.measurements.length).is.gte(12);
const commonTags = { version: version, partner: '99', source: 'api', tml: 'https://my-publisher-website.net/' };
const commonTags = {
version: version,
partner: '99',
source: 'api',
tml: 'https://my-publisher-website.net/',
provider: 'default'
};
verifyContainsMeasurementWithTags(onlyRequest.measurements, 'id5.api.instance.load.delay', 'TIMER', commonTags);
verifyContainsMeasurementWithTags(onlyRequest.measurements, 'id5.api.invocation.count', 'SUMMARY', commonTags);
verifyContainsMeasurementWithTags(onlyRequest.measurements, 'id5.api.consent.request.time', 'TIMER', {
Expand Down Expand Up @@ -914,7 +926,7 @@ describe('The ID5 API', function () {
async function fetchLocalStorage(page) {
return page.evaluate(() => {
const result = {};
for(let i = 0; i < window.localStorage.length; i++) {
for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i);
result[key] = window.localStorage.getItem(key);
}
Expand Down
24 changes: 21 additions & 3 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {NO_OP_LOGGER, StorageConfig} from '@id5io/multiplexing';
* @property {number} [att] - Indication of whether the event came from an Apple ATT event (value of 1 is yes)
* @property {Diagnostics} [diagnostics] - API diagnostics configuration
* @property {DynamicConfig} [dynamicConfig] - Dynamic configuration from prebid (not intended to be used directly)
* @property {boolean} [unregisterOnGC] - Unregister multiplexing instance when Id5Instance is reclaimed by GC
* @property {GCReclaimAllowed} [allowGCReclaim] - Determines if and/or on which stage `Id5Instance` object can be reclaimed by GC (only if there is no other external reference kept)
*/

/**
Expand Down Expand Up @@ -76,6 +76,19 @@ import {NO_OP_LOGGER, StorageConfig} from '@id5io/multiplexing';
* @property {ExtensionsCallConfig} [extensionsCall] - The configuration for making the extensions call (deprecated in mutiplexing)
*/

/**
* @enum {GCReclaimAllowed}
*/
export const GCReclaimAllowed = Object.freeze({
NEVER: 'never',
AFTER_UID_SET: 'after-uid-set',
ASAP: 'asap'
});

const ENUM_PROPERTIES = Object.freeze({
allowGCReclaim: Object.values(GCReclaimAllowed)
});

export class Config {
/** @type {number} */
invocationId;
Expand Down Expand Up @@ -111,7 +124,7 @@ export class Config {
diagnostics: 'Object',
multiplexing: 'Object',
dynamicConfig: 'Object',
unregisterOnGC: 'Boolean'
allowGCReclaim: 'String'
};

/**
Expand Down Expand Up @@ -160,7 +173,7 @@ export class Config {
multiplexing: {
_disabled: false
},
unregisterOnGC: true
allowGCReclaim: GCReclaimAllowed.AFTER_UID_SET,
};
this.providedOptions = {};

Expand Down Expand Up @@ -265,6 +278,11 @@ export class Config {
}

this.providedOptions[topic] = providedOptions[topic];
} else if (ENUM_PROPERTIES[topic] !== undefined) {
const providedValue = providedOptions[topic];
if (providedValue && ENUM_PROPERTIES[topic].includes(providedValue)) {
acceptOption(topic, providedValue);
}
} else if (topic !== 'partnerId') { // Already dealt with
const expectedType = Config.configTypes[topic];
const value = providedOptions[topic];
Expand Down
14 changes: 8 additions & 6 deletions lib/id5-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class Id5Api {

const config = new Config(passedOptions, log);
const options = config.getOptions();
const metrics = this._configureDiagnostics(options.partnerId, options.diagnostics, log);
const metrics = this._configureDiagnostics(options, log);
if (metrics) {
metrics.loadDelayTimer().recordNow(); // records time elapsed since page visit
metrics.invocationCountSummary().record(this.invocationId, {
Expand Down Expand Up @@ -115,17 +115,19 @@ class Id5Api {

/**
* @private
* @param {number} partnerId
* @param {Diagnostics} diagnosticsOptions
* @param {Id5Options} options
* @param {Logger} log
* @return {Id5CommonMetrics}
*/
_configureDiagnostics(partnerId, diagnosticsOptions, log) {
_configureDiagnostics(options, log) {
try {
let metrics = new Id5CommonMetrics(ORIGIN, currentVersion);
const partnerId = options.partnerId
const metrics = new Id5CommonMetrics(ORIGIN, currentVersion);
const diagnosticsOptions = options.diagnostics
metrics.addCommonTags({
...partnerTag(partnerId),
tml: this._referer.topmostLocation
tml: this._referer.topmostLocation,
provider: options.provider ? options.provider : 'default'
});
if (!diagnosticsOptions?.publishingDisabled) {
let publisher = createPublisher(diagnosticsOptions.publishingSampleRatio);
Expand Down
64 changes: 42 additions & 22 deletions lib/id5Instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {UaHints} from './uaHints.js';
/* eslint-disable no-unused-vars */
import {Id5CommonMetrics, startTimeMeasurement, Timer} from '@id5io/diagnostics';
import {ApiEvent, CONSTANTS, ClientStore, ConsentManagement, NamedLogger} from '@id5io/multiplexing';
import {Config} from './config.js';
import {Config, GCReclaimAllowed} from './config.js';
import {WatchdogSingletonCallback} from './callbacks.js';
/* eslint-enable no-unused-vars */

Expand Down Expand Up @@ -46,7 +46,7 @@ class UnregisterTargets {
/** @type {Id5CommonMetrics} */
_metrics;
/** @type {TimeMeasurement} */
_survivalTimer
_survivalTimer;

constructor(multiplexingInstance, metrics) {
this._targets = [multiplexingInstance, metrics];
Expand All @@ -55,9 +55,11 @@ class UnregisterTargets {
}

unregister(origin) {
this._survivalTimer.record(this._metrics.instanceSurvivalTime({
unregisterTrigger: origin // gc reclaim or api call
}));
if (this._survivalTimer && this._metrics) {
this._survivalTimer.record(this._metrics.instanceSurvivalTime({
unregisterTrigger: origin // gc reclaim or api call
}));
}
this._targets.forEach(target => {
try {
if (isDefined(target) && isFn(target.unregister)) {
Expand All @@ -73,13 +75,16 @@ class UnregisterTargets {
class Id5InstanceFinalizationRegistry {
/** @type {FinalizationRegistry} */
_finalizationRegistry;
/** @type {Set<Id5Instance>} */
_instancesHolder;

constructor() {
this._instancesHolder = new Set();
try {
this._finalizationRegistry = new FinalizationRegistry((unregisterTarget) => {
try {
if (isDefined(unregisterTarget) && isFn(unregisterTarget.unregister)) {
unregisterTarget.unregister("gc-reclaim");
unregisterTarget.unregister('gc-reclaim');
}
} catch (e) {
// ignore
Expand All @@ -96,28 +101,39 @@ class Id5InstanceFinalizationRegistry {
*/
register(instance) {
try {
if (instance.getOptions().unregisterOnGC) {
this._finalizationRegistry.register(instance, instance._unregisterTargets, instance);
if (instance.getOptions().allowGCReclaim !== GCReclaimAllowed.ASAP) {
// if not ASAP reclaimable then keep instance globally,
// it will be released on configured stage
this._instancesHolder.add(instance);
}
this._finalizationRegistry.register(instance, instance._unregisterTargets, instance);
} catch (e) {
// let continue
}
}

unregister(instance) {
try {
this._finalizationRegistry.unregister(instance);
this.releaseInstance(instance, true); // force release (unregistered by api call let GC cleanup)
this._finalizationRegistry.unregister(instance); // not needed cleanup executed by api call
} catch (e) {
// let continue
}
}

releaseInstance(instance, forceRelease = false) {
const canBeReleased = !instance.getOptions().allowGCReclaim !== GCReclaimAllowed.NEVER;
if (canBeReleased || forceRelease) {
this._instancesHolder.delete(instance);
}
}
}

if (!globalThis.__id5_finalization_registry) {
globalThis.__id5_finalization_registry = new Id5InstanceFinalizationRegistry();
}

const ID5_FINALIZATION_REGISTRY = globalThis.__id5_finalization_registry;
export const ID5_REGISTRY = globalThis.__id5_finalization_registry;

/**
* Polyfill just in case WeakRef is not supported
Expand Down Expand Up @@ -348,8 +364,8 @@ export class Id5Instance {

unregister() {
try {
this._unregisterTargets.unregister("api-call");
ID5_FINALIZATION_REGISTRY.unregister(this);
this._unregisterTargets.unregister('api-call');
ID5_REGISTRY.unregister(this);
} catch (e) {
// ignore
}
Expand Down Expand Up @@ -377,10 +393,12 @@ export class Id5Instance {
this._consentDataProvider = consentDataProvider;
this._log = new NamedLogger('Id5Instance:', logger);
this._multiplexingInstance = multiplexingInstance;
this._userIdAvailablePromise = new Promise(resolve => { this._userIdAvailablePromiseResolver = resolve; });
this._userIdAvailablePromise = new Promise(resolve => {
this._userIdAvailablePromiseResolver = resolve;
});
this._pageLevelInfo = pageLevelInfo;
this._unregisterTargets = new UnregisterTargets(this._multiplexingInstance, this._metrics);
ID5_FINALIZATION_REGISTRY.register(this);
ID5_REGISTRY.register(this);
}

bootstrap() {
Expand Down Expand Up @@ -416,7 +434,7 @@ export class Id5Instance {
} catch (e) {
id5Instance._log.error('Failed to measure provisioning metrics', e);
}
id5Instance._setUserId(userIdData.responseObj, userIdData.isFromCache);
id5Instance._setUserId(userIdData.responseObj, userIdData.isFromCache, userIdData.willBeRefreshed);
}
})
.on(ApiEvent.USER_ID_FETCH_CANCELED, details => {
Expand Down Expand Up @@ -536,11 +554,9 @@ export class Id5Instance {
* @private
*/
_ref() {
// if (typeof WeakRef !== 'undefined') {
// return new WeakRef(this);
// }
// temporarily do not allow to be reclaimable
// return always strong reference
if (typeof WeakRef !== 'undefined') {
return new WeakRef(this);
}
return new StrongRef(this);
}

Expand Down Expand Up @@ -577,8 +593,9 @@ export class Id5Instance {
* Set the user ID
* @param {Object} response
* @param {boolean} fromCache
* @param {boolean} willBeRefreshed
*/
_setUserId(response, fromCache) {
_setUserId(response, fromCache, willBeRefreshed = false) {
const userId = response.universal_uid;
this._isExposed = true;
if (isPlainObject(response.ab_testing)) {
Expand Down Expand Up @@ -609,14 +626,17 @@ export class Id5Instance {
if (this._availableCallback) {
this._availableCallback.triggerNow();
}

if (this._isRefreshing && this._refreshCallback && (fromCache === false || this._isRefreshingWithFetch === false)) {
this._refreshCallback.triggerNow();
}

if (hasChanged) {
this._fireOnUpdate();
}

if (this.getOptions().allowGCReclaim === GCReclaimAllowed.AFTER_UID_SET && (!fromCache || !willBeRefreshed)) {
ID5_REGISTRY.releaseInstance(this);
}
}

_fireOnUpdate() {
Expand Down
1 change: 1 addition & 0 deletions packages/multiplexing/src/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* @property {Date} timestamp
* @property {Object} responseObj
* @property {boolean} isFromCache
* @property {boolean} [willBeRefreshed]
*/

/**
Expand Down
Loading

0 comments on commit 0445107

Please sign in to comment.