-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Async vast loading #45
base: master
Are you sure you want to change the base?
Changes from 10 commits
6cf5198
13cc834
919cece
49de389
c8dca54
2475bef
4e66ea6
c5f62c9
fb70aa2
d1e771c
390bc68
544207c
8e7f8cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[ignore] | ||
.*/node_modules/documentation/.* | ||
.*/test/.* | ||
|
||
[include] | ||
|
||
[libs] | ||
|
||
[options] | ||
unsafe.enable_getters_and_setters=true |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,137 +2,195 @@ | |
|
||
[![npm](https://img.shields.io/npm/v/iab-vast-loader.svg)](https://www.npmjs.com/package/iab-vast-loader) [![Dependencies](https://img.shields.io/david/zentrick/iab-vast-loader.svg)](https://david-dm.org/zentrick/iab-vast-loader) [![Build Status](https://img.shields.io/circleci/project/github/zentrick/iab-vast-loader/master.svg)](https://circleci.com/gh/zentrick/iab-vast-loader) [![Coverage Status](https://img.shields.io/coveralls/zentrick/iab-vast-loader/master.svg)](https://coveralls.io/r/zentrick/iab-vast-loader) [![JavaScript Standard Style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) | ||
|
||
Loads and parses IAB VAST tags, resolving wrapped tags along the way. | ||
Loads IAB VAST tag trees using a preorder depth first strategy. The package is statically typed using [Flow](https://flow.org). [Observable streams](http://npmjs.com/package/rxjs) are used to update the consumer in time with new VAST documents. | ||
|
||
This is a major rewrite from the [earlier version](https://github.com/zentrick/iab-vast-loader/tree/v0.8.0) of this package with the main benefit that it asynchronously fetches the complete VAST document tree. The previous version of this package used [promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) and waited for the complete VAST tree to be fetched. One failing VAST document within the tree, made it fail completely. Cancellation semantics were also absent, by using Observables, we get them for free. | ||
|
||
The new implementation gives you a `loadVast()` function that returns an Observable which allows you to react on failures and offers you the choice to continue listening for subsequent VAST documents in the tree. It also delivers you a newly VAST document right away (preserving preorder depth first traversal semantics), instead of waiting for the whole tree to be fetched. | ||
|
||
It also gives you a `vastToAd()` function to map the a stream of VAST objects to a stream of Ad objects (both Wrapper and InLine), also in preorder depth first order. This gives you the right abstraction on which you can easily build further upon, using the RxJS `filter()` operator to for example only return `InLine` elements. | ||
|
||
## Usage | ||
|
||
```js | ||
import VASTLoader from 'iab-vast-loader' | ||
import { loadVast } from 'iab-vast-loader' | ||
|
||
const tagUrl = 'https://example.com/vast.xml' | ||
|
||
// Create the loader | ||
const loader = new VASTLoader(tagUrl) | ||
const vast$ = loadVast({ | ||
url: 'https://example.com/vast.xml' | ||
}) | ||
|
||
// Load the tag chain and await the resulting Promise | ||
loader.load() | ||
.then((chain) => { | ||
console.info('Loaded VAST tags:', chain) | ||
// Load the VAST tree and log all the VAST tags in the tree. | ||
vast$ | ||
.subscribe({ | ||
next: action => { | ||
switch (action.type) { | ||
case 'VAST_LOADED': | ||
console.info('Loaded next VAST tag: ', action.vast) | ||
break; | ||
case 'VAST_LOADING_FAILED': | ||
console.info('Loading next VAST tag failed: ', action.error, action.wrapper) | ||
break; | ||
} | ||
}, | ||
complete: () => { | ||
console.info('Finished loading the complete VAST tree') | ||
} | ||
}) | ||
.catch((err) => { | ||
console.error('Error loading tag:', err) | ||
|
||
// When interested in Ad events | ||
const ad$ = vastToAd(vast$) | ||
|
||
ad$ | ||
.subscribe({ | ||
next: action => { | ||
switch (action.type) { | ||
case 'AD_LOADED': | ||
console.info('Loaded next Ad: ', action.ad) | ||
break; | ||
case 'AD_LOADING_FAILED' | ||
console.info('Loading next Ad failed: ', action.error, action.wrapper) | ||
break; | ||
} | ||
}, | ||
complete: () => { | ||
console.info('Finished loading the complete VAST tree') | ||
} | ||
}) | ||
``` | ||
|
||
## API | ||
|
||
### `#loadVast()` | ||
|
||
```js | ||
new VASTLoader(tagUrl[, options]) | ||
type LoadVast = (config: Config) => Observable<VastLoadAction> | ||
``` | ||
|
||
Creates a VAST loader. | ||
`loadVast` creates a stream of `VastLoadAction` objects. In a fully reactive codebase, this stream will be composed within another stream. If this library is used at the boundary, then you need to subscribe yourself like this: | ||
|
||
```js | ||
loader.load() | ||
import { loadVast } from 'iab-vast-loader' | ||
const vast$ = loadVast(config) | ||
|
||
loadVast$.subscribe({ | ||
next: value => { }, | ||
complete: () => { } | ||
}) | ||
``` | ||
|
||
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). | ||
#### Configuration | ||
|
||
## Error Handling | ||
|
||
In addition to the default export `VASTLoader`, the main module also exports | ||
the `VASTLoaderError` class, which maps errors to the VAST specification: | ||
`loadVast` accepts the following `Config` object: | ||
|
||
```js | ||
import { default as VASTLoader, VASTLoaderError } from 'iab-vast-loader' | ||
|
||
const loader = new VASTLoader(tagUrl) | ||
type Config = { | ||
url: string, | ||
maxDepth?: number, | ||
timeout?: number, | ||
retryCount?: number, | ||
credentials: Credentials | ||
} | ||
``` | ||
|
||
loader.load() | ||
.catch((err) => { | ||
if (err instanceof VASTLoaderError) { | ||
console.error('VAST error: ' + err.code + ' ' + err.message) | ||
} else { | ||
console.error('Unknown error: ' + err) | ||
} | ||
An overview of its properties: | ||
|
||
- `url`: The url that points to the root VAST document of the VAST document tree that we need to fetch. | ||
- `maxDepth`: The maximum number of VAST documents to load within one chain. The default is | ||
`10`. | ||
- `timeout`: The maximum number of milliseconds to spend per HTTP request. The default is | ||
`10000`. | ||
- `retryCount`: The amount of times it will retry fetching a VAST document in case of failure. The default is `0`. | ||
- `credentials: Credentials`: Controls [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) | ||
behavior. You should pass an array of CredentialsType or functions that return a [CredentialsType value]((https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)), which is either `'omit'`, `'same-origin'` or `'include'`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if typing (or the flow types below) is the best way to convey the API for credentials. Something like "either an array containing any of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, maybe we want to support a single string as well? |
||
|
||
```js | ||
type Credentials = (CredentialsType | (url: string) => CredentialsType)[] | ||
type CredentialsType = 'omit' | 'same-origin' | 'include' | ||
``` | ||
|
||
You can use this option to control the behavior on a per-request basis. For example: | ||
|
||
```js | ||
const loadVast$ = loadVast({ | ||
url: 'https://example.com/vast.xml', | ||
credentials: [ | ||
url => uri.indexOf('.doubleclick.net/') !== 0 ? 'include' : 'omit' | ||
] | ||
}) | ||
``` | ||
``` | ||
|
||
As with [iab-vast-model](https://www.npmjs.com/package/iab-vast-model), if | ||
`instanceof` doesn't work for you, you may want to inspect `error.$type` | ||
instead. This issue can occur if you load multiple versions of iab-vast-loader, | ||
each with their own `VASTLoaderError` class. | ||
You can also pass multiple CORS strategies with the array. The implementation will race the different strategies in parallel, and will use the first request that succeeds. If none of the CORS strategies succeed, it will result in a `VAST_LOADING_FAILED` action. Notice that passing an empty array doesn't make sense, because it will make your request to fail always. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "because ..." at the end is kind of weird. |
||
|
||
## Options | ||
The default is `['omit']`. | ||
|
||
### `maxDepth` | ||
#### Output | ||
|
||
The maximum number of VAST documents to load within one chain. The default is | ||
10. | ||
The output of loadVast is a stream of VastLoadAction objects: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe put identifiers in backticks. |
||
|
||
### `timeout` | ||
```js | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again not sure if I'd put flow types in the readme, but I'm not sure how else to do it. I don't want to scare people away just because they don't know about static typing though, because that shouldn't prevent them from using this library. |
||
type VastLoadedAction = { | ||
type: 'VAST_LOADED', | ||
vast: VAST | ||
} | ||
|
||
type VastLoadingFailedAction = { | ||
type: 'VAST_LOADING_FAILED', | ||
error: VASTLoaderError, | ||
wrapper: ?Wrapper | ||
} | ||
|
||
type VastLoadAction = VastLoadedAction | VastLoadingFailedAction | ||
``` | ||
|
||
The `VAST` and `Wrapper` types are provided by [iab-vast-model](https://www.npmjs.com/package/iab-vast-model). | ||
|
||
The maximum number of milliseconds to spend per HTTP request. The default is | ||
10,000. | ||
### `#vastToAd()` | ||
|
||
### `credentials` | ||
```js | ||
import { loadVast, vastToAd } from 'iab-vast-loader' | ||
const vast$ = loadVast(config) | ||
const ad$ = vastToAd(vast$) | ||
``` | ||
|
||
Controls [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) | ||
behavior. You can either pass a string or a function. | ||
`vastToAd` maps a stream of `VastLoadAction` objects to `AdLoadAction` objects. You can subscribe on this stream directly, or indirectly using RxJS operators, just like `loadVast`. | ||
|
||
If you pass a string, it will be used as the value for the `credentials` option | ||
to every request. | ||
[Valid values](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) | ||
are `'omit'` (the default), `'same-origin'` and `'include'`. | ||
#### Output | ||
|
||
To control the behavior on a per-request basis, pass a function receiving the | ||
request URL and returning one of the accepted values. For example: | ||
The output of `vastToAd` is a stream of `AdLoadAction` objects: | ||
|
||
```js | ||
const loader = new VASTLoader(wrapperUrl, { | ||
credentials (uri) { | ||
if (uri.indexOf('.doubleclick.net/') >= 0) { | ||
return 'include' | ||
} else { | ||
return 'omit' | ||
} | ||
} | ||
}) | ||
type AdLoadedAction = { | ||
type: 'AD_LOADED', | ||
ad: Ad | ||
} | ||
|
||
type AdLoadingFailedAction = { | ||
type: 'AD_LOADING_FAILED', | ||
error: VASTLoaderError, | ||
wrapper: ?Wrapper | ||
} | ||
|
||
type AdLoadAction = AdLoadedAction | AdLoadingFailedAction | ||
``` | ||
|
||
## Events | ||
The `Ad` and `Wrapper` types are provided by [iab-vast-model](https://www.npmjs.com/package/iab-vast-model). | ||
|
||
A `VASTLoader` is an `EventEmitter`. To be notified about progress, you can | ||
subscribe to the events `willFetch`, `didFetch`, `willParse`, and `didParse` | ||
as follows: | ||
## Error Handling | ||
|
||
```js | ||
loader | ||
.on('willFetch', ({ uri }) => { | ||
console.info('Fetching', uri) | ||
}) | ||
.on('didFetch', ({ uri, body }) => { | ||
console.info('Fetched', body.length, 'bytes from', uri) | ||
}) | ||
.on('willParse', ({ uri, body }) => { | ||
console.info('Parsing', uri) | ||
}) | ||
.on('didParse', ({ uri, body, vast }) => { | ||
console.info('Parsed', uri) | ||
}) | ||
.load() | ||
.then((chain) => { | ||
console.info('Loaded VAST tags:', chain) | ||
}) | ||
.catch((err) => { | ||
console.error('Error loading tag:', err) | ||
}) | ||
``` | ||
In case the libary fails to load the next VAST document, it will emit a `VAST_LOADING_FAILED` action. You can react to this, by unsubscribing using the `takeUntil` operator, or you can continue listening for other values of another subtree of the VAST document tree. We don't push the stream into error state, because we want to enable the consumer to use subsequent VAST documents from the tree, after one subtree failed to fetch. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The further I read, the more I think we need some diagrams. That might also alleviate some of the typing overhead. :-) |
||
|
||
In addition to the default export `VASTLoader`, the main module also exports | ||
the `VASTLoaderError` class, which maps errors to the VAST specification. You can get the VAST error code using its `code` property, and the cause using its `cause` property. | ||
|
||
As with [iab-vast-model](https://www.npmjs.com/package/iab-vast-model), if | ||
`instanceof` doesn't work for you, you may want to inspect `error.$type` | ||
instead. This issue can occur if you load multiple versions of iab-vast-loader, | ||
each with their own `VASTLoaderError` class. | ||
|
||
## Maintainer | ||
## Maintainers | ||
|
||
[Tim De Pauw](https://github.com/timdp) | ||
- [Tim De Pauw](https://github.com/timdp) | ||
- [Laurent De Smet](https://github.com/laurentdesmet) | ||
|
||
## License | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not have the readme assume that people are familiar with previous versions, or at least not by default. We should move most of this to a section on upgrading.