diff --git a/README.md b/README.md index 90ca18b..fb27929 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ const loader = new VASTLoader(tagUrl) // Load the tag chain and await the resulting Promise loader.load() - .then((chain) => { - console.info('Loaded VAST tags:', chain) + .then((tree) => { + console.info('Loaded VAST tags:', tree) }) .catch((err) => { console.error('Error loading tag:', err) @@ -36,8 +36,19 @@ Creates a VAST loader. loader.load() ``` -Returns a `Promise` for an array of `VAST` instances. The `VAST` class is -provided by [iab-vast-model](https://www.npmjs.com/package/iab-vast-model). +Returns a `Promise` for a `VASTTreeNode` instance. +A `VASTTreeNode` has a property called `vast` which contains the loaded VAST document, parsed into a `VAST` class. +This `VAST` class is provided by [iab-vast-model](https://www.npmjs.com/package/iab-vast-model). +The `VASTTreeNode` also has a property called `childNodes` that contains an array of "sub ads", again instances of `VASTTreeNode`. + +## VASTTreeNode + +```js +node.vast // the VAST document +node.childNodes // array of VASTTreeNode +node.firstChild // first element in the childNodes array +node.hasChildNodes() // function that tells you if any sub ads where present +``` ## Error Handling diff --git a/package.json b/package.json index 4a5ac41..f05d25d 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "globals": [ "describe", "it", + "xit", "before", "after", "beforeEach", diff --git a/src/index.js b/src/index.js index a00fd97..8b6a524 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ import EventEmitter from 'eventemitter3' import fetch from 'isomorphic-fetch' import parse from 'iab-vast-parser' import { Wrapper } from 'iab-vast-model' +import VASTTreeNode from './vast-tree-node' import VASTLoaderError from './error' import atob from './atob' @@ -44,9 +45,13 @@ export default class Loader extends EventEmitter { .then(() => { this._emit('willFetch', { uri }) const match = RE_DATA_URI.exec(uri) - return (match == null) ? this._fetchUri() - : (match[1] != null) ? atob(match[2]) - : decodeURIComponent(match[2]) + if (match == null) { + return this._fetchUri() + } else if (match[1] != null) { + return atob(match[2]) + } else { + return decodeURIComponent(match[2]) + } }) .then((body) => { this._emit('didFetch', { uri, body }) @@ -54,14 +59,20 @@ export default class Loader extends EventEmitter { const vast = parse(body) this._emit('didParse', { uri, body, vast }) if (vast.ads.length > 0) { - const ad = vast.ads.get(0) - if (ad instanceof Wrapper || ad.$type === 'Wrapper') { - return this._loadWrapped(ad.vastAdTagURI, vast) + const adsToLoad = [] + for (var i = 0; i < vast.ads.length; i++) { + var ad = vast.ads.get(i) + if (ad instanceof Wrapper || ad.$type === 'Wrapper') { + adsToLoad.push(this._loadWrapped(ad.vastAdTagURI, vast)) + } } + return Promise.all(adsToLoad).then((ads) => { + return new VASTTreeNode(vast, ads) + }) } else if (this._depth > 1) { throw new VASTLoaderError(303) } - return [vast] + return new VASTTreeNode(vast) }) } @@ -75,9 +86,6 @@ export default class Loader extends EventEmitter { const childLoader = new Loader(vastAdTagURI, null, this) return childLoader.load() }) - .then((children) => { - return [vast, ...children] - }) } _fetchUri () { diff --git a/src/vast-tree-node.js b/src/vast-tree-node.js new file mode 100644 index 0000000..3987707 --- /dev/null +++ b/src/vast-tree-node.js @@ -0,0 +1,22 @@ +export default class VASTTreeNode { + constructor (vast, children = []) { + this._vast = vast + this._children = children + } + + get vast () { + return this._vast + } + + get childNodes () { + return this._children + } + + get firstChild () { + return this._children[0] + } + + hasChildNodes () { + return this._children.length > 0 + } +} diff --git a/test/fixtures/ads-wrapper-multi.xml b/test/fixtures/ads-wrapper-multi.xml new file mode 100644 index 0000000..2f2a9bc --- /dev/null +++ b/test/fixtures/ads-wrapper-multi.xml @@ -0,0 +1,13 @@ + + + + + http://demo.tremormedia.com/proddev/vast/vast_inline_linear.xml + + + + + http://demo.tremormedia.com/proddev/vast/vast_inline_linear.xml + + + diff --git a/test/fixtures/no-ads-alt.xml b/test/fixtures/no-ads-alt.xml new file mode 100644 index 0000000..2134bff --- /dev/null +++ b/test/fixtures/no-ads-alt.xml @@ -0,0 +1,2 @@ + + diff --git a/test/unit/vast-loader-error.js b/test/unit/vast-loader-error.js new file mode 100644 index 0000000..17f0601 --- /dev/null +++ b/test/unit/vast-loader-error.js @@ -0,0 +1,32 @@ +import VASTLoaderError from '../../src/error' + +describe('VASTLoaderError', function () { + describe('#code', function () { + it('gets set from the constructor', function () { + const error = new VASTLoaderError(301) + expect(error.code).to.equal(301) + }) + }) + + describe('#message', function () { + it('resolves from the code', function () { + const error = new VASTLoaderError(301) + expect(error.message).to.equal('Timeout.') + }) + }) + + describe('#cause', function () { + it('gets set from the constructor', function () { + const cause = new Error('Foo') + const error = new VASTLoaderError(301, cause) + expect(error.cause).to.equal(cause) + }) + }) + + describe('#$type', function () { + it('is VASTLoaderError', function () { + const error = new VASTLoaderError(900) + expect(error.$type).to.equal('VASTLoaderError') + }) + }) +}) diff --git a/test/unit/node.js b/test/unit/vast-loader.js similarity index 83% rename from test/unit/node.js rename to test/unit/vast-loader.js index 067aabf..1313d49 100644 --- a/test/unit/node.js +++ b/test/unit/vast-loader.js @@ -2,6 +2,7 @@ import express from 'express' import fsp from 'fs-promise' import path from 'path' import { default as VASTLoader, VASTLoaderError } from '../../src/' +import VASTTreeNode from '../../src/vast-tree-node' const expectLoaderError = (error, code, message, cause) => { expect(error).to.be.an.instanceof(VASTLoaderError) @@ -12,42 +13,12 @@ const expectLoaderError = (error, code, message, cause) => { } } -describe('VASTLoaderError', function () { - describe('#code', function () { - it('gets set from the constructor', function () { - const error = new VASTLoaderError(301) - expect(error.code).to.equal(301) - }) - }) - - describe('#message', function () { - it('resolves from the code', function () { - const error = new VASTLoaderError(301) - expect(error.message).to.equal('Timeout.') - }) - }) - - describe('#cause', function () { - it('gets set from the constructor', function () { - const cause = new Error('Foo') - const error = new VASTLoaderError(301, cause) - expect(error.cause).to.equal(cause) - }) - }) - - describe('#$type', function () { - it('is VASTLoaderError', function () { - const error = new VASTLoaderError(900) - expect(error.$type).to.equal('VASTLoaderError') - }) - }) -}) - describe('VASTLoader', function () { const fixturesPath = path.resolve(__dirname, '../fixtures') const proxyPaths = { 'http://demo.tremormedia.com/proddev/vast/vast_inline_linear.xml': 'tremor-video/vast_inline_linear.xml', 'http://example.com/no-ads.xml': 'no-ads.xml', + 'http://example.com/no-ads-alt.xml': 'no-ads-alt.xml', 'http://example.com/invalid-ads.xml': 'invalid-ads.xml' } @@ -104,16 +75,27 @@ describe('VASTLoader', function () { describe('#load()', function () { it('loads the InLine', async function () { const loader = createLoader('tremor-video/vast_inline_linear.xml') - const chain = await loader.load() - expect(chain).to.be.an.instanceof(Array) - expect(chain.length).to.equal(1) + const tree = await loader.load() + expect(tree).to.be.an.instanceof(VASTTreeNode) + expect(tree.hasChildNodes()).to.be.false }) it('loads the Wrapper', async function () { const loader = createLoader('tremor-video/vast_wrapper_linear_1.xml') - const chain = await loader.load() - expect(chain).to.be.an.instanceof(Array) - expect(chain.length).to.equal(2) + const tree = await loader.load() + expect(tree).to.be.an.instanceof(VASTTreeNode) + expect(tree.hasChildNodes()).to.be.true + expect(tree.childNodes.length).to.equal(1) + }) + + xit('loads alls ads in a Wrapper', async function () { + // iab-vast-parser first needs to support ad buffets + const loader = createLoader('ads-wrapper-multi.xml') + const tree = await loader.load() + expect(tree).to.be.an.instanceof(VASTTreeNode) + expect(tree.hasChildNodes()).to.be.true + expect(tree.childNodes.length).to.equal(2) + expect(tree.childNodes[0]).to.equal(tree.firstChild) }) it('loads the InLine as Base64', async function () { @@ -121,9 +103,9 @@ describe('VASTLoader', function () { const base64 = (await fsp.readFile(file)).toString('base64') const dataUri = 'data:text/xml;base64,' + base64 const loader = new VASTLoader(dataUri) - const chain = await loader.load() - expect(chain).to.be.an.instanceof(Array) - expect(chain.length).to.equal(1) + const tree = await loader.load() + expect(tree).to.be.an.instanceof(VASTTreeNode) + expect(tree.hasChildNodes()).to.be.false }) it('loads the InLine as XML', async function () { @@ -131,16 +113,16 @@ describe('VASTLoader', function () { const xml = (await fsp.readFile(file, 'utf8')).replace(/\r?\n/g, '') const dataUri = 'data:text/xml,' + xml const loader = new VASTLoader(dataUri) - const chain = await loader.load() - expect(chain).to.be.an.instanceof(Array) - expect(chain.length).to.equal(1) + const tree = await loader.load() + expect(tree).to.be.an.instanceof(VASTTreeNode) + expect(tree.hasChildNodes()).to.be.false }) it('loads the empty tag', async function () { const loader = createLoader('no-ads.xml') - const chain = await loader.load() - expect(chain.length).to.equal(1) - expect(chain[0].ads.length).to.equal(0) + const tree = await loader.load() + expect(tree.hasChildNodes()).to.be.false + expect(tree.vast.ads.length).to.equal(0) }) it('throws VAST 303 on empty InLine inside Wrapper', async function () {