From 70a388049c3ddf583ed220758dcbdd6261ae3b6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fran=C3=A7on?= Date: Fri, 20 Mar 2026 17:33:10 +0100 Subject: [PATCH] feat: Add wrapper chain id to vast resolution events --- spec/fetcher.spec.js | 4 ++++ spec/vast_parser.spec.js | 11 +++++++++-- src/fetcher/fetcher.js | 4 ++++ src/parser/vast_parser.js | 19 ++++++++++++++++--- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/spec/fetcher.spec.js b/spec/fetcher.spec.js index c2839d29..88f041c1 100644 --- a/spec/fetcher.spec.js +++ b/spec/fetcher.spec.js @@ -70,6 +70,7 @@ describe('Fetcher', () => { maxWrapperDepth: 4, timeout: 120000, wrapperAd: null, + wrapperChainId: 0, }); expect(mockEmit).toHaveBeenNthCalledWith(2, 'VAST-resolved', { @@ -80,6 +81,7 @@ describe('Fetcher', () => { statusCode: 200, previousUrl: null, wrapperDepth: 0, + wrapperChainId: 0, }); }); }); @@ -156,6 +158,7 @@ describe('Fetcher', () => { maxWrapperDepth: 4, timeout: 120000, wrapperAd: null, + wrapperChainId: 0, }); expect(mockEmit).toHaveBeenNthCalledWith(2, 'VAST-resolved', { @@ -165,6 +168,7 @@ describe('Fetcher', () => { statusCode: 500, previousUrl: null, wrapperDepth: 0, + wrapperChainId: 0, }); }); }); diff --git a/spec/vast_parser.spec.js b/spec/vast_parser.spec.js index 0cf3379d..9a064155 100644 --- a/spec/vast_parser.spec.js +++ b/spec/vast_parser.spec.js @@ -108,6 +108,7 @@ describe('VASTParser', () => { type: 'ERROR', url: inlineInvalidVastUrl, wrapperDepth: 0, + wrapperChainId: 0, }); } }); @@ -125,6 +126,7 @@ describe('VASTParser', () => { type: 'ERROR', url: null, wrapperDepth: 0, + wrapperChainId: 0, }); } }); @@ -171,6 +173,7 @@ describe('VASTParser', () => { url: inlineSampleVastUrl, wrapperDepth: 0, vastVersion: '4.3', + wrapperChainId: 0, }); expect(VastParser.emit).toHaveBeenCalledWith('VAST-ad-parsed', { adIndex: 1, @@ -178,6 +181,7 @@ describe('VASTParser', () => { url: inlineSampleVastUrl, wrapperDepth: 0, vastVersion: '4.3', + wrapperChainId: 0, }); }); }); @@ -198,6 +202,9 @@ describe('VASTParser', () => { isRootVAST: true, url: inlineSampleVastUrl, wrapperDepth: 0, + allowMultipleAds: undefined, + followAdditionalWrappers: undefined, + wrapperChainId: 0, }); }); }); @@ -590,8 +597,8 @@ describe('VASTParser', () => { }).then(() => { expect(VastParser.resolveWrappers).toHaveBeenCalledTimes(2); expect(VastParser.resolveWrappers.mock.calls).toEqual([ - ['ad1', 1, inlineSampleVastUrl], - ['ad2', 1, inlineSampleVastUrl], + ['ad1', 1, inlineSampleVastUrl, 0], + ['ad2', 1, inlineSampleVastUrl, 0], ]); }); }); diff --git a/src/fetcher/fetcher.js b/src/fetcher/fetcher.js index 9b7ebf50..18980495 100644 --- a/src/fetcher/fetcher.js +++ b/src/fetcher/fetcher.js @@ -66,6 +66,7 @@ export class Fetcher { * @param {(String | null)} params.previousUrl - Url of the previous VAST. * @param {Object} params.wrapperAd - Previously parsed ad node (Wrapper) related to this fetching. * @param {Number} params.maxWrapperDepth - The maximum number of Wrapper that can be fetch + * @param {Number} params.wrapperChainId - The id of the current wrapper chain. * @param {Function} params.emitter - The function used to Emit event * @emits VASTParser#VAST-resolving * @emits VASTParser#VAST-resolved @@ -78,6 +79,7 @@ export class Fetcher { wrapperDepth = 0, previousUrl = null, wrapperAd = null, + wrapperChainId = 0, }) { const timeBeforeGet = Date.now(); @@ -93,6 +95,7 @@ export class Fetcher { maxWrapperDepth, timeout: this.fetchingOptions.timeout, wrapperAd, + wrapperChainId, }); const data = await this.urlHandler.get(url, this.fetchingOptions); @@ -105,6 +108,7 @@ export class Fetcher { error: data?.error || null, duration: requestDuration, statusCode: data?.statusCode || null, + wrapperChainId, ...data?.details, }); updateEstimatedBitrate(data?.details?.byteLength, requestDuration); diff --git a/src/parser/vast_parser.js b/src/parser/vast_parser.js index 19c61952..a9de48d2 100644 --- a/src/parser/vast_parser.js +++ b/src/parser/vast_parser.js @@ -163,6 +163,7 @@ export class VASTParser extends EventEmitter { wrapperDepth = 0, allowMultipleAds, followAdditionalWrappers, + wrapperChainId = 0, } ) { // check if is a valid VAST document @@ -175,6 +176,7 @@ export class VASTParser extends EventEmitter { type: 'ERROR', url, wrapperDepth, + wrapperChainId, }); // VideoAdServingTemplate node is used for VAST 1.0 const isNonSupportedVast = @@ -230,6 +232,7 @@ export class VASTParser extends EventEmitter { wrapperDepth, adIndex: ads.length - 1, vastVersion, + wrapperChainId, }); } else { // VAST version of response not supported. @@ -263,6 +266,7 @@ export class VASTParser extends EventEmitter { isRootVAST = false, followAdditionalWrappers, allowMultipleAds, + wrapperChainId = 0, } = {} ) { let ads = []; @@ -278,6 +282,7 @@ export class VASTParser extends EventEmitter { wrapperDepth, allowMultipleAds, followAdditionalWrappers, + wrapperChainId, }); } catch (e) { return Promise.reject(e); @@ -318,6 +323,7 @@ export class VASTParser extends EventEmitter { wrapperDepth, previousUrl, url, + wrapperChainId, }); } @@ -328,17 +334,22 @@ export class VASTParser extends EventEmitter { * @param {Object} options - An options Object containing resolving parameters * @return {Promise} */ - resolveAds(ads = [], { wrapperDepth, previousUrl, url }) { + resolveAds(ads = [], { wrapperDepth, previousUrl, url, wrapperChainId = 0 } = {}) { const resolveWrappersPromises = []; previousUrl = url; ads.forEach((ad) => { const resolveWrappersPromise = this.resolveWrappers( ad, wrapperDepth, - previousUrl + previousUrl, + wrapperChainId ); resolveWrappersPromises.push(resolveWrappersPromise); + + if (wrapperDepth === 0) { + wrapperChainId++; + } }); return Promise.all(resolveWrappersPromises).then((unwrappedAds) => { @@ -354,7 +365,7 @@ export class VASTParser extends EventEmitter { * @param {String} previousUrl - The previous vast url. * @return {Promise} */ - resolveWrappers(adToUnWrap, wrapperDepth, previousUrl) { + resolveWrappers(adToUnWrap, wrapperDepth, previousUrl, wrapperChainId) { // Copy ad from parameters to prevent altering given object outside of function scope const ad = { ...adToUnWrap }; return new Promise((resolve) => { @@ -401,6 +412,7 @@ export class VASTParser extends EventEmitter { wrapperDepth, previousUrl, wrapperAd: ad, + wrapperChainId, }) .then((xml) => { return this.parse(xml, { @@ -410,6 +422,7 @@ export class VASTParser extends EventEmitter { wrapperDepth, followAdditionalWrappers: ad.followAdditionalWrappers, allowMultipleAds, + wrapperChainId, }).then((unwrappedAds) => { delete ad.nextWrapperURL; if (unwrappedAds.length === 0) {