From 6cf5198709ade379172a743aa55046a1a587fa53 Mon Sep 17 00:00:00 2001 From: Laurent De Smet Date: Mon, 29 May 2017 16:22:49 +0200 Subject: [PATCH 01/13] add flow definitions --- flow-typed/npm/chai_v3.5.x.js | 216 +++++++++ flow-typed/npm/rxjs_v5.0.x.js | 825 ++++++++++++++++++++++++++++++++++ 2 files changed, 1041 insertions(+) create mode 100644 flow-typed/npm/chai_v3.5.x.js create mode 100644 flow-typed/npm/rxjs_v5.0.x.js diff --git a/flow-typed/npm/chai_v3.5.x.js b/flow-typed/npm/chai_v3.5.x.js new file mode 100644 index 0000000..b920857 --- /dev/null +++ b/flow-typed/npm/chai_v3.5.x.js @@ -0,0 +1,216 @@ +// flow-typed signature: bad3c03afed474d4fb576a7afe069701 +// flow-typed version: 731a6ee6c3/chai_v3.5.x/flow_>=v0.24.0 + +declare module "chai" { + + declare type ExpectChain = { + and: ExpectChain, + at: ExpectChain, + be: ExpectChain, + been: ExpectChain, + have: ExpectChain, + has: ExpectChain, + is: ExpectChain, + of: ExpectChain, + same: ExpectChain, + that: ExpectChain, + to: ExpectChain, + which: ExpectChain, + with: ExpectChain, + + not: ExpectChain, + deep: ExpectChain, + any: ExpectChain, + all: ExpectChain, + + a: ExpectChain & (type: string) => ExpectChain, + an: ExpectChain & (type: string) => ExpectChain, + + include: ExpectChain & (value: mixed) => ExpectChain, + includes: ExpectChain & (value: mixed) => ExpectChain, + contain: ExpectChain & (value: mixed) => ExpectChain, + contains: ExpectChain & (value: mixed) => ExpectChain, + + eql: (value: T) => ExpectChain, + equal: (value: T) => ExpectChain, + equals: (value: T) => ExpectChain, + + above: (value: T & number) => ExpectChain, + least: (value: T & number) => ExpectChain, + below: (value: T & number) => ExpectChain, + most: (value: T & number) => ExpectChain, + within: (start: T & number, finish: T & number) => ExpectChain, + + instanceof: (constructor: mixed) => ExpectChain, + property: ( +

(name: string, value?: P) => ExpectChain

+ & (name: string) => ExpectChain + ), + + length: (value: number) => ExpectChain | ExpectChain, + lengthOf: (value: number) => ExpectChain, + + match: (regex: RegExp) => ExpectChain, + string: (string: string) => ExpectChain, + + key: (key: string) => ExpectChain, + keys: (key: string | Array, ...keys: Array) => ExpectChain, + + throw: ( + err: Class | Error | RegExp | string, + errMsgMatcher?: RegExp | string, + msg?: string) => ExpectChain, + + respondTo: (method: string) => ExpectChain, + itself: ExpectChain, + + satisfy: (method: (value: T) => bool) => ExpectChain, + + closeTo: (expected: T & number, delta: number) => ExpectChain, + + members: (set: mixed) => ExpectChain, + oneOf: (list: Array) => ExpectChain, + + change: (obj: mixed, key: string) => ExpectChain, + increase: (obj: mixed, key: string) => ExpectChain, + decrease: (obj: mixed, key: string) => ExpectChain, + + // dirty-chai + ok: () => ExpectChain, + true: () => ExpectChain, + false: () => ExpectChain, + null: () => ExpectChain, + undefined: () => ExpectChain, + exist: () => ExpectChain, + empty: () => ExpectChain, + + // chai-immutable + size: (n: number) => ExpectChain, + + // sinon-chai + called: () => ExpectChain, + callCount: (n: number) => ExpectChain, + calledOnce: () => ExpectChain, + calledBefore: (spy: mixed) => ExpectChain, + calledAfter: (spy: mixed) => ExpectChain, + calledWith: (...args: Array) => ExpectChain, + calledWithMatch: (...args: Array) => ExpectChain, + calledWithExactly: (...args: Array) => ExpectChain, + + // chai-as-promised + eventually: ExpectChain, + resolvedWith: (value: mixed) => Promise & ExpectChain, + resolved: () => Promise & ExpectChain, + rejectedWith: (value: mixed) => Promise & ExpectChain, + rejected: () => Promise & ExpectChain, + notify: (callback: () => mixed) => ExpectChain, + + // chai-subset + containSubset: (obj: Object | Object[]) => ExpectChain + }; + + declare function expect(actual: T): ExpectChain; + + declare function use(plugin: (chai: Object, utils: Object) => void): void; + + declare class assert { + static(expression: mixed, message?: string): void; + static fail(actual: mixed, expected: mixed, message?: string, operator?: string): void; + + static isOk(object: mixed, message?: string): void; + static isNotOk(object: mixed, message?: string): void; + + static equal(actual: mixed, expected: mixed, message?: string): void; + static notEqual(actual: mixed, expected: mixed, message?: string): void; + + static strictEqual(act: mixed, exp: mixed, msg?: string): void; + static notStrictEqual(act: mixed, exp: mixed, msg?: string): void; + + static deepEqual(act: mixed, exp: mixed, msg?: string): void; + static notDeepEqual(act: mixed, exp: mixed, msg?: string): void; + + static ok(val: mixed, msg?: string): void; + static isTrue(val: mixed, msg?: string): void; + static isNotTrue(val: mixed, msg?: string): void; + static isFalse(val: mixed, msg?: string): void; + static isNotFalse(val: mixed, msg?: string): void; + + static isNull(val: mixed, msg?: string): void; + static isNotNull(val: mixed, msg?: string): void; + + static isUndefined(val: mixed, msg?: string): void; + static isDefined(val: mixed, msg?: string): void; + + static isNaN(val: mixed, msg?: string): void; + static isNotNaN(val: mixed, msg?: string): void; + + static isAbove(val: number, abv: number, msg?: string): void; + static isBelow(val: number, blw: number, msg?: string): void; + + static isAtMost(val: number, atmst: number, msg?: string): void; + static isAtLeast(val: number, atlst: number, msg?: string): void; + + static isFunction(val: mixed, msg?: string): void; + static isNotFunction(val: mixed, msg?: string): void; + + static isObject(val: mixed, msg?: string): void; + static isNotObject(val: mixed, msg?: string): void; + + static isArray(val: mixed, msg?: string): void; + static isNotArray(val: mixed, msg?: string): void; + + static isString(val: mixed, msg?: string): void; + static isNotString(val: mixed, msg?: string): void; + + static isNumber(val: mixed, msg?: string): void; + static isNotNumber(val: mixed, msg?: string): void; + + static isBoolean(val: mixed, msg?: string): void; + static isNotBoolean(val: mixed, msg?: string): void; + + static typeOf(val: mixed, type: string, msg?: string): void; + static notTypeOf(val: mixed, type: string, msg?: string): void; + + static instanceOf(val: mixed, constructor: Function, msg?: string): void; + static notInstanceOf(val: mixed, constructor: Function, msg?: string): void; + + static include(exp: string, inc: mixed, msg?: string): void; + static include(exp: Array, inc: T, msg?: string): void; + + static notInclude(exp: string, inc: mixed, msg?: string): void; + static notInclude(exp: Array, inc: T, msg?: string): void; + + static match(exp: mixed, re: RegExp, msg?: string): void; + static notMatch(exp: mixed, re: RegExp, msg?: string): void; + + static property(obj: Object, prop: string, msg?: string): void; + static notProperty(obj: Object, prop: string, msg?: string): void; + static deepProperty(obj: Object, prop: string, msg?: string): void; + static notDeepProperty(obj: Object, prop: string, msg?: string): void; + + static propertyVal(obj: Object, prop: string, val: mixed, msg?: string): void; + static propertyNotVal(obj: Object, prop: string, val: mixed, msg?: string): void; + + static deepPropertyVal(obj: Object, prop: string, val: mixed, msg?: string): void; + static deepPropertyNotVal(obj: Object, prop: string, val: mixed, msg?: string): void; + + static lengthOf(exp: mixed, len: number, msg?: string): void; + + static throws( + func: () => any, + err?: Class | Error | RegExp | string, + errorMsgMatcher?: string | RegExp, + msg?: string): void; + static doesNotThrow( + func: () => any, + err?: Class | Error | RegExp | string, + errorMsgMatcher?: string | RegExp, + msg?: string): void; + } + + declare var config: { + includeStack: boolean, + showDiff: boolean, + truncateThreshold: number + }; +} diff --git a/flow-typed/npm/rxjs_v5.0.x.js b/flow-typed/npm/rxjs_v5.0.x.js new file mode 100644 index 0000000..53d374a --- /dev/null +++ b/flow-typed/npm/rxjs_v5.0.x.js @@ -0,0 +1,825 @@ +// flow-typed signature: 8ecb01da08c708bfc429a63dd22d3cdd +// flow-typed version: 201cfddff4/rxjs_v5.0.x/flow_>=v0.34.x + +// FIXME(samgoldman) Remove top-level interface once Babel supports +// `declare interface` syntax. +// FIXME(samgoldman) Remove this once rxjs$Subject can mixin rxjs$Observer +interface rxjs$IObserver<-T> { + next(value: T): mixed; + error(error: any): mixed; + complete(): mixed; +} + +type rxjs$PartialObserver<-T> = + | { + +next: (value: T) => mixed; + +error?: (error: any) => mixed; + +complete?: () => mixed; + } + | { + +next?: (value: T) => mixed; + +error: (error: any) => mixed; + +complete?: () => mixed; + } + | { + +next?: (value: T) => mixed; + +error?: (error: any) => mixed; + +complete: () => mixed; + } + +interface rxjs$ISubscription { + unsubscribe(): void; +} + +type rxjs$TeardownLogic = rxjs$ISubscription | () => void; + +type rxjs$EventListenerOptions = { + capture?: boolean; + passive?: boolean; + once?: boolean; +} | boolean; + +type AjaxRequest = {| + url?: string; + body?: any; + user?: string; + async?: boolean; + method?: string; + headers?: Object; + timeout?: number; + password?: string; + hasContent?: boolean; + crossDomain?: boolean; + withCredentials?: boolean; + createXHR?: () => XMLHttpRequest; + // progressSubscriber?: Subscriber; + responseType?: string; +|} + +type AjaxResponse = { + status: number, + response: any, + responseText: string, + responseType: string +} + +declare class rxjs$Observable<+T> { + static bindCallback(callbackFunc: (callback: (_: void) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): () => rxjs$Observable; + static bindCallback(callbackFunc: (callback: (result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): () => rxjs$Observable; + static bindCallback(callbackFunc: (v1: T, callback: (result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): (v1: T) => rxjs$Observable; + static bindCallback(callbackFunc: (v1: T, v2: T2, callback: (result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2) => rxjs$Observable; + static bindCallback(callbackFunc: (v1: T, v2: T2, v3: T3, callback: (result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2, v3: T3) => rxjs$Observable; + static bindCallback(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, callback: (result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2, v3: T3, v4: T4) => rxjs$Observable; + static bindCallback(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, callback: (result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2, v3: T3, v4: T4, v5: T5) => rxjs$Observable; + static bindCallback(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, callback: (result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6) => rxjs$Observable; + static bindCallback(callbackFunc: (callback: (...args: Array) => any) => any, selector: (...args: Array) => U, scheduler?: rxjs$SchedulerClass): () => rxjs$Observable; + static bindCallback(callbackFunc: (v1: T, callback: (...args: Array) => any) => any, selector: (...args: Array) => U, scheduler?: rxjs$SchedulerClass): (v1: T) => rxjs$Observable; + static bindCallback(callbackFunc: (v1: T, v2: T2, callback: (...args: Array) => any) => any, selector: (...args: Array) => U, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2) => rxjs$Observable; + static bindCallback(callbackFunc: (v1: T, v2: T2, v3: T3, callback: (...args: Array) => any) => any, selector: (...args: Array) => U, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2, v3: T3) => rxjs$Observable; + static bindCallback(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, callback: (...args: Array) => any) => any, selector: (...args: Array) => U, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2, v3: T3, v4: T4) => rxjs$Observable; + static bindCallback(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, callback: (...args: Array) => any) => any, selector: (...args: Array) => U, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2, v3: T3, v4: T4, v5: T5) => rxjs$Observable; + static bindCallback(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, callback: (...args: Array) => any) => any, selector: (...args: Array) => U, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6) => rxjs$Observable; + static bindCallback(callbackFunc: Function, selector?: void, scheduler?: rxjs$SchedulerClass): (...args: Array) => rxjs$Observable; + static bindCallback(callbackFunc: Function, selector?: (...args: Array) => T, scheduler?: rxjs$SchedulerClass): (...args: Array) => rxjs$Observable; + + static bindNodeCallback(callbackFunc: (callback: (err: any, result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): () => rxjs$Observable; + static bindNodeCallback(callbackFunc: (v1: T, callback: (err: any, result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): (v1: T) => rxjs$Observable; + static bindNodeCallback(callbackFunc: (v1: T, v2: T2, callback: (err: any, result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2) => rxjs$Observable; + static bindNodeCallback(callbackFunc: (v1: T, v2: T2, v3: T3, callback: (err: any, result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2, v3: T3) => rxjs$Observable; + static bindNodeCallback(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, callback: (err: any, result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2, v3: T3, v4: T4) => rxjs$Observable; + static bindNodeCallback(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, callback: (err: any, result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2, v3: T3, v4: T4, v5: T5) => rxjs$Observable; + static bindNodeCallback(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, callback: (err: any, result: U) => any) => any, selector?: void, scheduler?: rxjs$SchedulerClass): (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6) => rxjs$Observable; + static bindNodeCallback(callbackFunc: Function, selector?: void, scheduler?: rxjs$SchedulerClass): (...args: Array) => rxjs$Observable; + static bindNodeCallback(callbackFunc: Function, selector?: (...args: Array) => T, scheduler?: rxjs$SchedulerClass): (...args: Array) => rxjs$Observable; + + static concat(...sources: rxjs$Observable[]): rxjs$Observable; + + static create( + subscribe: (observer: rxjs$Observer) => rxjs$ISubscription | Function | void + ): rxjs$Observable; + + static defer(observableFactory: () => rxjs$Observable | Promise): rxjs$Observable; + + static from(iterable: Iterable): rxjs$Observable; + + static fromEvent(element: any, eventName: string, ...none: Array): rxjs$Observable; + static fromEvent( + element: any, + eventName: string, + options: rxjs$EventListenerOptions, + ...none: Array + ): rxjs$Observable; + static fromEvent( + element: any, + eventName: string, + selector: () => T, + ...none: Array + ): rxjs$Observable; + static fromEvent( + element: any, + eventName: string, + options: rxjs$EventListenerOptions, + selector: () => T, + ): rxjs$Observable; + + static fromEventPattern( + addHandler: (handler: () => void) => void, + removeHandler: (handler: () => void) => void, + selector?: () => T, + ): rxjs$Observable; + + static fromPromise(promise: Promise): rxjs$Observable; + + static empty(): rxjs$Observable; + + static interval(period: number): rxjs$Observable; + + static timer(initialDelay: (number | Date), period?: number, scheduler?: rxjs$SchedulerClass): rxjs$Observable; + + static merge( + source0: rxjs$Observable, + source1: rxjs$Observable, + ): rxjs$Observable; + static merge( + source0: rxjs$Observable, + source1: rxjs$Observable, + source2: rxjs$Observable, + ): rxjs$Observable; + static merge(...sources: rxjs$Observable[]): rxjs$Observable; + + static never(): rxjs$Observable; + + static of(...values: T[]): rxjs$Observable; + + static throw(error: any): rxjs$Observable; + + audit(durationSelector: (value: T) => rxjs$Observable | Promise): rxjs$Observable; + + race(other: rxjs$Observable): rxjs$Observable; + + repeat(): rxjs$Observable; + + buffer(bufferBoundaries: rxjs$Observable): rxjs$Observable>; + + catch(selector: (err: any, caught: rxjs$Observable) => rxjs$Observable): rxjs$Observable; + + concat(...sources: rxjs$Observable[]): rxjs$Observable; + + concatAll(): rxjs$Observable; + + concatMap( + f: (value: T) => rxjs$Observable | Promise | Iterable + ): rxjs$Observable; + + debounceTime(dueTime: number, scheduler?: rxjs$SchedulerClass): rxjs$Observable; + + delay(dueTime: number, scheduler?: rxjs$SchedulerClass): rxjs$Observable; + + distinctUntilChanged(compare?: (x: T, y: T) => boolean): rxjs$Observable; + + distinct(keySelector?: (value: T) => U, flushes?: rxjs$Observable): rxjs$Observable; + + distinctUntilKeyChanged(key: string, compare?: (x: mixed, y: mixed) => boolean): rxjs$Observable; + + elementAt(index: number, defaultValue?: T): rxjs$Observable; + + filter(predicate: (value: T) => boolean): rxjs$Observable; + + finally(f: () => mixed): rxjs$Observable; + + first( + predicate?: (value: T, index: number, source: rxjs$Observable) => boolean, + ): rxjs$Observable; + first( + predicate: ?(value: T, index: number, source: rxjs$Observable) => boolean, + resultSelector: (value: T, index: number) => U, + ): rxjs$Observable; + first( + predicate: ?(value: T, index: number, source: rxjs$Observable) => boolean, + resultSelector: ?(value: T, index: number) => U, + defaultValue: U, + ): rxjs$Observable; + + groupBy( + keySelector: (value: T) => mixed, + elementSelector?: (value: T) => T, + compare?: (x: T, y: T) => boolean, + ): rxjs$Observable>; + + ignoreElements(): rxjs$Observable; + + let(project: (self: rxjs$Observable) => rxjs$Observable): rxjs$Observable; + + // Alias for `let` + letBind(project: (self: rxjs$Observable) => rxjs$Observable): rxjs$Observable; + + switch(): T; // assumption: T is Observable + + // Alias for `mergeMap` + flatMap( + project: (value: T) => rxjs$Observable | Promise | Iterable, + index?: number, + ): rxjs$Observable; + + flatMapTo( + innerObservable: rxjs$Observable + ): rxjs$Observable; + + flatMapTo( + innerObservable: rxjs$Observable < U >, + resultSelector: (outerValue: T, innerValue: U, outerIndex: number, innerIndex: number) => V, + concurrent ?: number + ): rxjs$Observable; + + switchMap( + project: (value: T) => rxjs$Observable | Promise | Iterable, + index?: number, + ): rxjs$Observable; + + switchMapTo( + innerObservable: rxjs$Observable, + ): rxjs$Observable; + + map(f: (value: T) => U): rxjs$Observable; + + mapTo(value: U): rxjs$Observable; + + merge(other: rxjs$Observable): rxjs$Observable; + + mergeAll(): rxjs$Observable; + + mergeMap( + project: (value: T, index?: number) => rxjs$Observable | Promise | Iterable, + index?: number, + ): rxjs$Observable; + + mergeMapTo( + innerObservable: rxjs$Observable + ): rxjs$Observable; + + mergeMapTo( + innerObservable: rxjs$Observable < U >, + resultSelector: (outerValue: T, innerValue: U, outerIndex: number, innerIndex: number) => V, + concurrent ?: number + ): rxjs$Observable; + + multicast( + subjectOrSubjectFactory: rxjs$Subject | () => rxjs$Subject, + ): rxjs$ConnectableObservable; + + observeOn(scheduler: rxjs$SchedulerClass): rxjs$Observable; + + pairwise(): rxjs$Observable<[T, T]>; + + publish(): rxjs$ConnectableObservable; + + publishLast(): rxjs$ConnectableObservable; + + reduce( + accumulator: ( + acc: U, + currentValue: T, + index: number, + source: rxjs$Observable, + ) => U, + seed: U, + ): rxjs$Observable; + + sample(notifier: rxjs$Observable): rxjs$Observable; + + sampleTime(delay: number): rxjs$Observable; + + publishReplay(bufferSize?: number, windowTime?: number, scheduler?: rxjs$SchedulerClass): rxjs$ConnectableObservable; + + retry(retryCount: number): rxjs$Observable; + + retryWhen(notifier: (errors: rxjs$Observable) => rxjs$Observable): rxjs$Observable; + + scan( + f: (acc: U, value: T) => U, + initialValue: U, + ): rxjs$Observable; + + share(): rxjs$Observable; + + skip(count: number): rxjs$Observable; + + skipUntil(other: rxjs$Observable | Promise): rxjs$Observable; + + skipWhile(predicate: (value: T) => boolean): rxjs$Observable; + + startWith(...values: Array): rxjs$Observable; + + subscribeOn(scheduler: rxjs$SchedulerClass): rxjs$Observable; + + take(count: number): rxjs$Observable; + + takeUntil(other: rxjs$Observable): rxjs$Observable; + + takeWhile(f: (value: T) => boolean): rxjs$Observable; + + do( + onNext?: (value: T) => mixed, + onError?: (error: any) => mixed, + onCompleted?: () => mixed, + ): rxjs$Observable; + do(observer: { + next?: (value: T) => mixed; + error?: (error: any) => mixed; + complete?: () => mixed; + }): rxjs$Observable; + + throttleTime(duration: number): rxjs$Observable; + + timeout(due: number | Date, _: void): rxjs$Observable; + + toArray(): rxjs$Observable; + + toPromise(): Promise; + + subscribe(observer: rxjs$PartialObserver): rxjs$Subscription; + subscribe( + onNext: ?(value: T) => mixed, + onError: ?(error: any) => mixed, + onCompleted: ?() => mixed, + ): rxjs$Subscription; + + static combineLatest( + a: rxjs$Observable, + resultSelector: (a: A) => B, + ): rxjs$Observable; + + static combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + resultSelector: (a: A, b: B) => C, + ): rxjs$Observable; + + static combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + resultSelector: (a: A, b: B, c: C) => D, + ): rxjs$Observable; + + static combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D) => E, + ): rxjs$Observable; + + static combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D, e: E) => F, + ): rxjs$Observable; + + static combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D, e: E, f: F) => G, + ): rxjs$Observable; + + static combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + g: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => H, + ): rxjs$Observable; + + static combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B]>; + + static combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B, C]>; + + static combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B, C, D]>; + + static combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B, C, D, E]>; + + static combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B, C, D, E, F]>; + + static combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + g: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B, C, D, E, F, G]>; + + static combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + g: rxjs$Observable, + h: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B, C, D, E, F, G, H]>; + + combineLatest( + a: rxjs$Observable, + _: void, + ): rxjs$Observable<[T, A]>; + + combineLatest( + a: rxjs$Observable, + resultSelector: (a: A) => B, + ): rxjs$Observable; + + combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + resultSelector: (a: A, b: B) => C, + ): rxjs$Observable; + + combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + resultSelector: (a: A, b: B, c: C) => D, + ): rxjs$Observable; + + combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D) => E, + ): rxjs$Observable; + + combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D, e: E) => F, + ): rxjs$Observable; + + combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D, e: E, f: F) => G, + ): rxjs$Observable; + + combineLatest( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + g: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => H, + ): rxjs$Observable; + + static forkJoin( + a: rxjs$Observable[] + ): rxjs$Observable; + + static forkJoin( + a: rxjs$Observable, + resultSelector: (a: A) => B, + ): rxjs$Observable; + + static forkJoin( + a: rxjs$Observable, + b: rxjs$Observable, + resultSelector: (a: A, b: B) => C, + ): rxjs$Observable; + + static forkJoin( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + resultSelector: (a: A, b: B, c: C) => D, + ): rxjs$Observable; + + static forkJoin( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D) => E, + ): rxjs$Observable; + + static forkJoin( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D, e: E) => F, + ): rxjs$Observable; + + static forkJoin( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D, e: E, f: F) => G, + ): rxjs$Observable; + + static forkJoin( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + g: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => H, + ): rxjs$Observable; + + static forkJoin( + a: rxjs$Observable, + b: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B]>; + + static forkJoin( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B, C]>; + + static forkJoin( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B, C, D]>; + + static forkJoin( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B, C, D, E]>; + + static forkJoin( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B, C, D, E, F]>; + + static forkJoin( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + g: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B, C, D, E, F, G]>; + + static forkJoin( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + g: rxjs$Observable, + h: rxjs$Observable, + _: void, + ): rxjs$Observable<[A, B, C, D, E, F, G, H]>; + + static ajax(urlOrRequest: string | AjaxRequest): rxjs$Observable; + + withLatestFrom( + a: rxjs$Observable, + _: void, + ): rxjs$Observable<[T, A]>; + + withLatestFrom( + a: rxjs$Observable, + resultSelector: (a: A) => B, + ): rxjs$Observable; + + withLatestFrom( + a: rxjs$Observable, + b: rxjs$Observable, + resultSelector: (a: A, b: B) => C, + ): rxjs$Observable; + + withLatestFrom( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + resultSelector: (a: A, b: B, c: C) => D, + ): rxjs$Observable; + + withLatestFrom( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D) => E, + ): rxjs$Observable; + + withLatestFrom( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D, e: E) => F, + ): rxjs$Observable; + + withLatestFrom( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D, e: E, f: F) => G, + ): rxjs$Observable; + + withLatestFrom( + a: rxjs$Observable, + b: rxjs$Observable, + c: rxjs$Observable, + d: rxjs$Observable, + e: rxjs$Observable, + f: rxjs$Observable, + g: rxjs$Observable, + resultSelector: (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => H, + ): rxjs$Observable; + + static using( + resourceFactory: () => ?R, + observableFactory: (resource: R) => rxjs$Observable | Promise | void, + ): rxjs$Observable; +} + +declare class rxjs$ConnectableObservable extends rxjs$Observable { + connect(): rxjs$Subscription; + refCount(): rxjs$Observable; +} + +declare class rxjs$Observer { + next(value: T): mixed; + + error(error: any): mixed; + + complete(): mixed; +} + +// FIXME(samgoldman) should be `mixins rxjs$Observable, rxjs$Observer` +// once Babel parsing support exists: https://phabricator.babeljs.io/T6821 +declare class rxjs$Subject extends rxjs$Observable { + asObservable(): rxjs$Observable; + + observers: Array>; + + unsubscribe(): void; + + // Copied from rxjs$Observer + next(value: T): mixed; + error(error: any): mixed; + complete(): mixed; + + // For use in subclasses only: + _next(value: T): void; + _subscribe(observer: rxjs$PartialObserver): rxjs$Subscription; +} + +declare class rxjs$BehaviorSubject extends rxjs$Subject { + constructor(initialValue: T): void; + + getValue(): T; +} + +declare class rxjs$ReplaySubject extends rxjs$Subject { + constructor(bufferSize?: number, windowTime?: number, scheduler?: rxjs$SchedulerClass): void; +} + +declare class rxjs$Subscription { + unsubscribe(): void; + add(teardown: rxjs$TeardownLogic): rxjs$Subscription; +} + +declare class rxjs$SchedulerClass { + schedule(work: (state?: T) => void, delay?: number, state?: T): rxjs$Subscription; +} + +declare class rxjs$TimeoutError extends Error { +} + +declare module 'rxjs' { + declare module.exports: { + Observable: typeof rxjs$Observable, + ConnectableObservable: typeof rxjs$ConnectableObservable, + Subject: typeof rxjs$Subject, + BehaviorSubject: typeof rxjs$BehaviorSubject, + ReplaySubject: typeof rxjs$ReplaySubject, + Scheduler: { + asap: rxjs$SchedulerClass, + queue: rxjs$SchedulerClass, + animationFrame: rxjs$SchedulerClass, + async: rxjs$SchedulerClass, + }, + Subscription: typeof rxjs$Subscription, + TimeoutError: typeof rxjs$TimeoutError, + } +} + +declare module 'rxjs/Observable' { + declare module.exports: { + Observable: typeof rxjs$Observable + } +} + +declare module 'rxjs/Observer' { + declare module.exports: { + Observer: typeof rxjs$Observer + } +} + +declare module 'rxjs/BehaviorSubject' { + declare module.exports: { + BehaviorSubject: typeof rxjs$BehaviorSubject + } +} + +declare module 'rxjs/ReplaySubject' { + declare module.exports: { + ReplaySubject: typeof rxjs$ReplaySubject + } +} + +declare module 'rxjs/Subject' { + declare module.exports: { + Subject: typeof rxjs$Subject + } +} + +declare module 'rxjs/Subscription' { + declare module.exports: { + Subscription: typeof rxjs$Subscription + } +} From 13cc8348ad0c99c07a92812729810d8305d29fde Mon Sep 17 00:00:00 2001 From: Laurent De Smet Date: Thu, 1 Jun 2017 18:16:49 +0200 Subject: [PATCH 02/13] implementation and initial tests --- .babelrc | 2 +- .flowconfig | 10 + flow-typed/npm/isomorphic-fetch_v2.x.x.js | 3 + package.json | 18 +- src/concat-eager.js | 76 +++ src/error-codes.js | 56 +- src/error.js | 20 +- src/index.js | 141 +---- src/load-vast.js | 157 +++++ ...ap-vast-load-actions-to-ad-load-actions.js | 85 +++ test/lib/setup.js | 4 + test/unit/load-vast.js | 42 ++ test/unit/node.js | 544 +++++++++--------- test/unit/vast-loader-error.js | 32 ++ 14 files changed, 734 insertions(+), 456 deletions(-) create mode 100644 .flowconfig create mode 100644 flow-typed/npm/isomorphic-fetch_v2.x.x.js create mode 100644 src/concat-eager.js create mode 100644 src/load-vast.js create mode 100644 src/map-vast-load-actions-to-ad-load-actions.js create mode 100644 test/unit/load-vast.js create mode 100644 test/unit/vast-loader-error.js diff --git a/.babelrc b/.babelrc index e403556..be2ad0a 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,5 @@ { - "presets": [["env", {"targets": {"node": 4}}]], + "presets": ["es2015", "flow", "stage-3"], "plugins": [ ["transform-builtin-extend", {"globals": ["Error"]}], "transform-runtime" diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..8f5fbce --- /dev/null +++ b/.flowconfig @@ -0,0 +1,10 @@ +[ignore] +.*/node_modules/documentation/.* +.*/test/.* + +[include] + +[libs] + +[options] +unsafe.enable_getters_and_setters=true diff --git a/flow-typed/npm/isomorphic-fetch_v2.x.x.js b/flow-typed/npm/isomorphic-fetch_v2.x.x.js new file mode 100644 index 0000000..4d49836 --- /dev/null +++ b/flow-typed/npm/isomorphic-fetch_v2.x.x.js @@ -0,0 +1,3 @@ +declare module 'isomorphic-fetch' { + declare module.exports: (input: string | Request, init?: RequestOptions) => Promise; +} diff --git a/package.json b/package.json index 2df00aa..8f526f9 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ "clean": "gulp clean", "build": "gulp build", "prepublish": "in-publish && npm run build || not-in-publish", - "test": "gulp test" + "test": "gulp test", + "test2": "gulp test2", + "flow": "flow" }, "repository": "zentrick/iab-vast-loader", "bugs": "https://github.com/zentrick/iab-vast-loader/issues", @@ -29,13 +31,19 @@ "eventemitter3": "^2.0.0", "iab-vast-model": "^0.4.0", "iab-vast-parser": "^0.4.1", - "isomorphic-fetch": "^2.2.1" + "invariant": "^2.2.2", + "isomorphic-fetch": "^2.2.1", + "rxjs": "^5.4.0", + "xmlhttprequest": "^1.8.0" }, "devDependencies": { + "babel-eslint": "^7.2.3", "babel-plugin-transform-builtin-extend": "^1.1.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.1.8", "babel-preset-es2015": "^6.24.1", + "babel-preset-flow": "^6.23.0", + "babel-preset-stage-3": "^6.24.1", "babel-register": "^6.24.0", "babel-runtime": "^6.23.0", "chai": "^3.5.0", @@ -44,6 +52,7 @@ "del": "^2.2.0", "dirty-chai": "^1.2.2", "express": "^4.15.2", + "flow-bin": "^0.46.0", "fs-promise": "^2.0.0", "gulp": "^3.9.1", "gulp-babel": "^6.1.2", @@ -64,6 +73,7 @@ "yargs": "^7.0.2" }, "standard": { + "parser": "babel-eslint", "globals": [ "describe", "it", @@ -75,7 +85,9 @@ "sinon", "fetch", "XMLHttpRequest", - "Headers" + "$Keys", + "$PropertyType", + "RequestOptions" ] } } diff --git a/src/concat-eager.js b/src/concat-eager.js new file mode 100644 index 0000000..a7f85fd --- /dev/null +++ b/src/concat-eager.js @@ -0,0 +1,76 @@ +// @flow + +import { Observable } from 'rxjs/Observable' + +export const concatEager = (observables: Observable[]): Observable => + Observable.create(subscriber => { + if (observables.length === 0) { + subscriber.complete() + return + } + + let buffer = {} + let complete = {} + let currentIndex = 0 + + const possiblyEmitBufferedValues = () => { + if (observables.length === currentIndex) { + subscriber.complete() + } + + // If the current observable has buffered values, emit them all right now. + if (buffer[currentIndex] != null) { + buffer[currentIndex].forEach(value => subscriber.next(value)) + // These values aren't needed anymore + buffer[currentIndex] = null + } + + // If the current observable is already completed, go on with the next observable. + if (complete[currentIndex]) { + currentIndex++ + possiblyEmitBufferedValues() + } + } + + // We subscribe to all the observables at once, but all events are emitted + // in order. This means we need to buffer values when a previous observable + // is not completed yet. + const subscriptions = observables.map((observable, index) => + observable.subscribe({ + next: value => { + if (index === currentIndex) { + // Currently, this observable is active so we emit immediately. + subscriber.next(value) + } else { + // A previous observable is still emitting, so we need to buffer. + if (buffer[index] == null) { + buffer[index] = [] + } + + buffer[index].push(value) + } + }, + error: err => { + subscriptions.forEach((subscription, index) => { + subscription.unsubscribe() + }) + subscriber.error(err) + }, + complete: () => { + complete[index] = true + if (index === currentIndex) { + // The currently active observable completed, so we possibly need + // to emit buffered values or complete the resulting observable. + currentIndex++ + possiblyEmitBufferedValues() + } + } + }) + ) + + const subscription = () => { + subscriptions.forEach(subscription => subscription.unsubscribe()) + } + + return subscription + }) diff --git a/src/error-codes.js b/src/error-codes.js index 3d6ed74..63d7a96 100644 --- a/src/error-codes.js +++ b/src/error-codes.js @@ -1,31 +1,33 @@ +// @flow + // https://support.google.com/dfp_premium/answer/4442429?hl=en export default { - 100: 'XML parsing error.', - 101: 'VAST schema validation error.', - 102: 'VAST version of response not supported.', - 200: 'Trafficking error.', - 201: 'Video player expecting different linearity.', - 202: 'Video player expecting different duration.', - 203: 'Video player expecting different size.', - 300: 'General Wrapper error.', - 301: 'Timeout.', - 302: 'Wrapper limit reached.', - 303: 'No Ads VAST response after one or more Wrappers.', - 400: 'General Linear error.', - 401: 'File not found.', - 402: 'Timeout of MediaFile URI.', - 403: 'No supported MediaFile found.', - 405: 'Problem displaying MediaFile.', - 500: 'General NonLinearAds error.', - 501: 'Unable to display NonLinear Ad because creative dimensions do not align with creative display area.', - 502: 'Unable to fetch NonLinearAds/NonLinear resource.', - 503: 'Couldn\'t find NonLinear resource with supported type.', - 600: 'General CompanionAds error.', - 601: 'Unable to display Companion because creative dimensions do not fit within Companion display area.', - 602: 'Unable to display Required Companion.', - 603: 'Unable to fetch CompanionAds/Companion resource.', - 604: 'Couldn\'t find Companion resource with supported type.', - 900: 'Undefined error.', - 901: 'General VPAID error.' + '100': 'XML parsing error.', + '101': 'VAST schema validation error.', + '102': 'VAST version of response not supported.', + '200': 'Trafficking error.', + '201': 'Video player expecting different linearity.', + '202': 'Video player expecting different duration.', + '203': 'Video player expecting different size.', + '300': 'General Wrapper error.', + '301': 'Timeout.', + '302': 'Wrapper limit reached.', + '303': 'No Ads VAST response after one or more Wrappers.', + '400': 'General Linear error.', + '401': 'File not found.', + '402': 'Timeout of MediaFile URI.', + '403': 'No supported MediaFile found.', + '405': 'Problem displaying MediaFile.', + '500': 'General NonLinearAds error.', + '501': 'Unable to display NonLinear Ad because creative dimensions do not align with creative display area.', + '502': 'Unable to fetch NonLinearAds/NonLinear resource.', + '503': 'Couldn\'t find NonLinear resource with supported type.', + '600': 'General CompanionAds error.', + '601': 'Unable to display Companion because creative dimensions do not fit within Companion display area.', + '602': 'Unable to display Required Companion.', + '603': 'Unable to fetch CompanionAds/Companion resource.', + '604': 'Couldn\'t find Companion resource with supported type.', + '900': 'Undefined error.', + '901': 'General VPAID error.' } diff --git a/src/error.js b/src/error.js index 28287f0..b44dbea 100644 --- a/src/error.js +++ b/src/error.js @@ -1,26 +1,28 @@ +// @flow + import codeToMessage from './error-codes' +type Code = $Keys + export default class VASTLoaderError extends Error { - constructor (code, cause = null, uri = null) { + _code: Code + _cause: any + + constructor (code: Code, cause: any) { super(codeToMessage[code]) this._code = code this._cause = cause - this._uri = uri } - get code () { + get code (): Code { return this._code } - get cause () { + get cause (): any { return this._cause } - get uri () { - return this._uri - } - - get $type () { + get $type (): 'VASTLoaderError' { return 'VASTLoaderError' } } diff --git a/src/index.js b/src/index.js index c2999e2..b27f3b1 100644 --- a/src/index.js +++ b/src/index.js @@ -1,139 +1,4 @@ -import EventEmitter from 'eventemitter3' -import fetch from 'isomorphic-fetch' -import parse from 'iab-vast-parser' -import { Wrapper } from 'iab-vast-model' -import VASTLoaderError from './error' -import atob from './atob' +// @flow -const RE_DATA_URI = /^data:(.*?)(;\s*base64)?,(.*)/ -const DEFAULT_OPTIONS = { - maxDepth: 10, - credentials: 'omit', - timeout: 10000 -} - -export { VASTLoaderError } - -export default class Loader extends EventEmitter { - constructor (uri, options, parent) { - super() - this._uri = uri - if (parent != null) { - this._root = parent._root - this._options = this._root._options - this._depth = parent._depth + 1 - } else { - this._root = this - this._options = Object.assign({}, DEFAULT_OPTIONS, options) - this._depth = 1 - } - this._fetchOptions = this._buildFetchOptions(uri) - } - - load () { - return this._load(this._uri) - .catch((error) => { - this._emit('error', { error }) - throw error - }) - } - - _load (uri) { - return Promise.resolve() - .then(() => { - this._emit('willFetch', { uri }) - const match = RE_DATA_URI.exec(uri) - return (match == null) ? this._fetchUri(uri) - : this._parseDataUri(match[3], match[1], (match[2] != null)) - }) - .then(({ headers, body }) => { - this._emit('didFetch', { uri, headers, body }) - this._emit('willParse', { uri, body }) - 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) - } - } else if (this._depth > 1) { - throw new VASTLoaderError(303, null, uri) - } - return [vast] - }) - } - - _parseDataUri (data, mimeType, isBase64) { - const headers = new Headers({ 'Content-Type': mimeType }) - const body = isBase64 ? atob(data) : decodeURIComponent(data) - return { headers, body } - } - - _loadWrapped (vastAdTagURI, vast) { - return Promise.resolve() - .then(() => { - const { maxDepth } = this._options - if (maxDepth > 0 && this._depth + 1 >= maxDepth) { - throw new VASTLoaderError(302, null, vastAdTagURI) - } - const childLoader = new Loader(vastAdTagURI, null, this) - return childLoader.load() - }) - .then((children) => ([vast, ...children])) - } - - _fetchUri (uri) { - const fetching = fetch(uri, this._fetchOptions) - const timingOut = this._createTimeouter(fetching, uri) - let headers - return Promise.race([fetching, timingOut]) - .then((response) => { - timingOut.cancel() - if (!response.ok) { - // TODO Convert response to HTTPError - throw new VASTLoaderError(900, response, uri) - } - headers = response.headers - return response.text() - }) - .then((body) => ({ headers, body })) - .catch((err) => { - timingOut.cancel() - if (this._depth > 1) { - throw new VASTLoaderError(301, null, uri) - } else { - throw err - } - }) - } - - _createTimeouter (fetching, uri) { - const ms = this._options.timeout - let timeout = null - const timingOut = new Promise((resolve, reject) => { - timeout = setTimeout(() => { - reject(new VASTLoaderError(301, null, uri)) - }, ms) - }) - timingOut.cancel = () => { - if (timeout != null) { - clearTimeout(timeout) - timeout = null - } - } - return timingOut - } - - _buildFetchOptions (uri) { - const { credentials } = this._options - switch (typeof credentials) { - case 'string': return { credentials } - case 'function': return { credentials: credentials(uri) } - default: throw new Error(`Invalid credentials option: ${credentials}`) - } - } - - _emit (...args) { - this._root.emit(...args) - } -} +export { loadVast } from './load-vast' +export { mapVastLoadActionsToAdLoadActions } from './map-vast-load-actions-to-ad-load-actions' diff --git a/src/load-vast.js b/src/load-vast.js new file mode 100644 index 0000000..722273f --- /dev/null +++ b/src/load-vast.js @@ -0,0 +1,157 @@ +// @flow + +import { type VAST, Wrapper } from 'iab-vast-model' +import parse from 'iab-vast-parser' +import VASTLoaderError from './error' +import { concatEager } from './concat-eager' +import fetch from 'isomorphic-fetch' + +import { Observable } from 'rxjs/Observable' + +// RxJS statics +import 'rxjs/add/observable/empty' +import 'rxjs/add/observable/of' +import 'rxjs/add/observable/defer' +import 'rxjs/add/observable/fromPromise' + +// RxJS operators +import 'rxjs/add/operator/retry' +import 'rxjs/add/operator/timeout' +import 'rxjs/add/operator/catch' +import 'rxjs/add/operator/map' +import 'rxjs/add/operator/mergeMap' +import 'rxjs/add/operator/do' +import 'rxjs/add/operator/share' +import 'rxjs/add/operator/concatMap' +import 'rxjs/add/operator/toArray' +import 'rxjs/add/operator/toPromise' + +type VastLoadedAction = { type: 'VAST_LOADED', vast: VAST } + +type VastLoadingFailedAction = { type: 'VAST_LOADING_FAILED', error: any, wrapper: ?Wrapper } + +export type VastLoadAction = VastLoadedAction | VastLoadingFailedAction + +export const http = (url: string, options?: RequestOptions) => + Observable.defer(() => fetch(url, options)) + .mergeMap(res => Observable.fromPromise(res.text())) + +const defaultSfx = { + http +} + +type Config = { + url: string, + maxDepth?: number, + timeout?: number, + retryCount?: number, + withCredentials?: boolean +} + +type SFX = { + http: typeof http +} + +const DEFAULT_OPTIONS = { + maxDepth: 10, + timeout: 10000, + retryCount: 0, + withCredentials: false +} + +export const loadVast = (config: Config, sfx: SFX = defaultSfx): Observable => + loadVastTree({ + ...DEFAULT_OPTIONS, + ...config, + parent: null + }, sfx) + +type LoadVastConfig = { + url: string, + parent: ?Wrapper, + maxDepth: number, + timeout: number, + retryCount: number, + withCredentials: boolean +} + +// Traverse the tree using a preorder depth first strategy. +const loadVastTree = (config: LoadVastConfig, sfx: SFX): Observable => { + // We add share() because we want a multicast observable here, because + // loadVast$ is subscribed to in multiple places and this would result in + // multiple ajax requests for the same VAST document. + const loadVast$ = fetchVast(config, sfx).share() + + const children$ = loadVast$ + .concatMap(output => { + if (output.type === 'VAST_LOADING_FAILED') { + // When the VAST failed loading, then we can't load its children of course. + return Observable.empty() + } else { + const { vast } = output + + if (!vast.followAdditionalWrappers() && vast.depth > config.maxDepth) { + // We don't fetch additional children. + return Observable.empty() + } else { + // We start fetching all the children. + const results = getWrappers(vast).map(wrapper => + loadVastTree({ + ...config, + url: wrapper.vastAdTagURI, + parent: wrapper + }, sfx) + ) + + // We start fetching them all at once and return the results in order. + return concatEager(results) + } + } + }) + + // We return a stream that will first emit an event for this VAST, + // and then returns all the child VASTS which comes down to a + // preorder depth first traversal. + return concatEager([loadVast$, children$]) +} + +const getWrappers = (vast: VAST): Wrapper[] => + vast.ads + .filter(ad => ad instanceof Wrapper) + +// This function returns a stream with exact one event: a success or error event. +const fetchVast = (config: LoadVastConfig, sfx: SFX): Observable => + sfx.http(config.url, { method: 'GET' }) + .retry(config.retryCount) + .timeout(config.timeout) + .catch(error => { + if (config.parent == null) { + throw new VASTLoaderError('900', error) + } else { + throw new VASTLoaderError('301', error) + } + }) + .map(parseVast) + .catch(error => { + if (error instanceof VASTLoaderError) { + throw error + } else { + throw new VASTLoaderError('100', error) + } + }) + .do(vast => addParentToVast(vast, config.parent)) + .map(vast => ({ + type: 'VAST_LOADED', + vast: vast + })) + .catch(error => Observable.of({ + type: 'VAST_LOADING_FAILED', + error, + wrapper: config.parent + })) + +const parseVast = (res: string) => parse(res) + +const addParentToVast = (vast: VAST, parent: ?Wrapper) => { + vast.parent = parent +} diff --git a/src/map-vast-load-actions-to-ad-load-actions.js b/src/map-vast-load-actions-to-ad-load-actions.js new file mode 100644 index 0000000..e2222b2 --- /dev/null +++ b/src/map-vast-load-actions-to-ad-load-actions.js @@ -0,0 +1,85 @@ +// @flow + +import { Observable } from 'rxjs/Observable' +import { type VAST, type Ad, Wrapper } from 'iab-vast-model' +import { type VastLoadAction } from './load-vast' + +type AdLoadedAction = { type: 'AD_LOADED', ad: Ad } + +type WrapperLoadingFailedAction = { type: 'WRAPPER_LOADING_FAILED', error: any, wrapper: ?Wrapper } + +type AdLoadAction = AdLoadedAction | WrapperLoadingFailedAction + +// This function returns a depth first preorder stream of inline elements of the VAST chain. +export const mapVastLoadActionsToAdLoadActions = (vast$: Observable): Observable => + vast$ + .concatMap(event => { + if (event.type === 'VAST_LOADED') { + // VAST_LOADED + const { vast } = event + + const ads = walkAdsUntilNextWrapper(vast, 0) + + return Observable.from(ads) + .map(ad => ({ + type: 'AD_LOADED', + ad + })) + } else { + // VAST_LOADING_FAILED + const { wrapper, error } = event + + const wrapperLoadingFailedAction = { + type: 'WRAPPER_LOADING_FAILED', + error, + wrapper + } + + const ads = wrapper == null + // There will not be a wrapper when it's the root VAST of the tree. + ? [] + // Emit the inline elements after the failed VAST. + : walkAdsUntilNextWrapper(wrapper.parent, getWrapperIndex(wrapper) + 1) + + const adLoadedActions = ads + .map(ad => ({ + type: 'AD_LOADED', + ad + })) + + return Observable.from([wrapperLoadingFailedAction, ...adLoadedActions]) + } + }) + +const getWrapperIndex = (wrapper: Wrapper): number => + wrapper.parent.ads.indexOf(wrapper) + +const walkAdsUntilNextWrapper = (vast: VAST, fromIndex: number): Ad[] => { + const adsFromIndex = vast.ads.slice(fromIndex) + const toIndex = fromIndex + adsFromIndex.findIndex(ad => ad instanceof Wrapper) + + const ads = toIndex === -1 + // All the inLines until the end of the array. + ? vast.ads.slice(fromIndex) + // An array of inLine ads, ending with one wrapper ad. + : vast.ads.slice(fromIndex, toIndex + 1) + + if (toIndex === -1) { + // This VAST file doesn't have Wrappers anymore so we can continue walking the tree upwards. + const { parent: wrapper } = vast + + if (wrapper == null) { + // We're at the top of the VAST tree so we can just return. + return ads + } else { + // We call the function recursively for our parent with the correct fromIndex. + return [ + ...ads, + ...walkAdsUntilNextWrapper(wrapper.parent, getWrapperIndex(wrapper) + 1) + ] + } + } else { + // We arrived at the next wrapper so we can end our recursion here. + return ads + } +} diff --git a/test/lib/setup.js b/test/lib/setup.js index 6d655f1..ed95c68 100644 --- a/test/lib/setup.js +++ b/test/lib/setup.js @@ -4,6 +4,8 @@ import chaiAsPromised from 'chai-as-promised' import sinon from 'sinon' import sinonChai from 'sinon-chai' import dirtyChai from 'dirty-chai' +// import {XMLHttpRequest} from 'xmlhttprequest' +import XMLHttpRequest from 'xhr2' chai.use(chaiAsPromised) chai.use(sinonChai) @@ -11,3 +13,5 @@ chai.use(dirtyChai) global.expect = chai.expect global.sinon = sinon + +global.XMLHttpRequest = XMLHttpRequest diff --git a/test/unit/load-vast.js b/test/unit/load-vast.js new file mode 100644 index 0000000..57c4ae9 --- /dev/null +++ b/test/unit/load-vast.js @@ -0,0 +1,42 @@ +import { VAST } from 'iab-vast-model' +import { loadVast } from '../../src/load-vast' +import path from 'path' +import express from 'express' + +const fixturesPath = path.resolve(__dirname, '../fixtures') + +describe('#loadVast', () => { + // loadVast({ + // url: '' + // }) + let server, baseUrl + + before(cb => { + const app = express() + + app.use(express.static(fixturesPath)) + + server = app.listen(() => { + baseUrl = 'http://localhost:' + server.address().port + '/' + cb() + }) + }) + + after(cb => { + server.close(cb) + }) + + it('should load a VAST tag', async () => { + const fetchingVast = + loadVast({ + url: baseUrl + 'tremor-video/vast_inline_linear.xml' + }) + .toArray() + .toPromise() + + const events = await fetchingVast + expect(events.length).to.equal(1) + expect(events[0].type).to.equal('VAST_LOADED') + expect(events[0].vast).to.be.an.instanceof(VAST) + }) +}) diff --git a/test/unit/node.js b/test/unit/node.js index 5d385bb..8adce3a 100644 --- a/test/unit/node.js +++ b/test/unit/node.js @@ -1,278 +1,266 @@ -import express from 'express' -import fsp from 'fs-promise' -import path from 'path' -import { default as VASTLoader, VASTLoaderError } from '../../src/' - -const expectLoaderError = (error, code, message, cause) => { - expect(error).to.be.an.instanceof(VASTLoaderError) - expect(error.code).to.equal(code) - expect(error.message).to.equal(message) - if (cause != null) { - expect(error.cause).to.include(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/invalid-ads.xml': 'invalid-ads.xml' - } - - let fetchUriImpl - let server - let baseUrl - let responseDelay - - const createLoader = (file, options) => new VASTLoader(baseUrl + file, options) - - const proxifyFetchUri = function () { - fetchUriImpl = VASTLoader.prototype._fetchUri - VASTLoader.prototype._fetchUri = async function (uri) { - const target = proxyPaths[uri] - if (target == null) { - return await fetchUriImpl.call(this, uri) - } - return await fetchUriImpl.call(this, baseUrl + target) - } - } - - const unproxifyFetchUri = function () { - VASTLoader.prototype._fetchUri = fetchUriImpl - } - - before(function (cb) { - const app = express() - app.use((req, res, next) => { - setTimeout(() => next(), responseDelay) - }) - app.use(express.static(fixturesPath)) - server = app.listen(function () { - baseUrl = 'http://localhost:' + server.address().port + '/' - proxifyFetchUri() - cb() - }) - }) - - after(function (cb) { - unproxifyFetchUri() - server.close(cb) - }) - - beforeEach(function () { - responseDelay = 0 - }) - - 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) - }) - - 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) - }) - - it('loads the InLine as Base64', async function () { - const file = path.join(fixturesPath, 'tremor-video/vast_inline_linear.xml') - 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) - }) - - it('loads the InLine as XML', async function () { - const file = path.join(fixturesPath, 'tremor-video/vast_inline_linear.xml') - 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) - }) - - 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) - }) - - it('throws VAST 303 on empty InLine inside Wrapper', async function () { - let error - try { - const loader = createLoader('no-ads-wrapper.xml') - await loader.load() - } catch (err) { - error = err - } - expectLoaderError(error, 303, 'No Ads VAST response after one or more Wrappers.') - }) - - it('throws VAST 301 on invalid InLine inside Wrapper', async function () { - let error - try { - const loader = createLoader('invalid-ads-wrapper.xml') - await loader.load() - } catch (err) { - error = err - } - expectLoaderError(error, 301, 'Timeout.') - }) - - it('throws on HTTP errors', async function () { - let error - try { - const loader = createLoader('four-oh-four') - await loader.load() - } catch (err) { - error = err - } - expectLoaderError(error, 900, 'Undefined error.', {status: 404, statusText: 'Not Found'}) - }) - }) - - // TODO Test event data - describe('#emit()', function () { - for (const type of ['willFetch', 'didFetch', 'willParse', 'didParse']) { - it(`emits ${type}`, async function () { - const spy = sinon.spy() - const loader = createLoader('tremor-video/vast_inline_linear.xml') - loader.on(type, spy) - await loader.load() - expect(spy.called).to.be.true() - }) - } - - for (const type of ['willFetch', 'didFetch', 'willParse', 'didParse']) { - it(`emits ${type} once per tag`, async function () { - const spy = sinon.spy() - const loader = createLoader('tremor-video/vast_wrapper_linear_1.xml') - loader.on(type, spy) - await loader.load() - expect(spy.calledTwice).to.be.true() - }) - } - - it('emits error on errors', async function () { - const spy = sinon.spy() - const loader = createLoader('four-oh-four') - loader.on('error', spy) - try { - await loader.load() - } catch (err) {} - expect(spy.calledOnce).to.be.true() - }) - }) - - describe('maxDepth option', function () { - it('throws when maxDepth is reached', async function () { - let error - try { - const loader = createLoader('tremor-video/vast_wrapper_linear_1.xml', { - maxDepth: 1 - }) - await loader.load() - } catch (err) { - error = err - } - expectLoaderError(error, 302, 'Wrapper limit reached.') - }) - }) - - describe('timeout option', function () { - it('throws when timeout is reached', async function () { - responseDelay = 100 - let error - try { - const loader = createLoader('no-ads.xml', { - timeout: 10 - }) - await loader.load() - } catch (err) { - error = err - } - expectLoaderError(error, 301, 'Timeout.') - }) - }) - - describe('credentials option', function () { - // TODO Use something nicer than inspecting private _fetchOptions - - it('is "omit" by default', function () { - const loader = createLoader('tremor-video/vast_inline_linear.xml') - expect(loader._fetchOptions).to.eql({ credentials: 'omit' }) - }) - - it('overrides with a string value', function () { - const loader = createLoader('tremor-video/vast_inline_linear.xml', { - credentials: 'include' - }) - expect(loader._fetchOptions).to.eql({ credentials: 'include' }) - }) - - it('overrides with a function value', function () { - const loader = createLoader('tremor-video/vast_inline_linear.xml', { - credentials: (uri) => 'same-origin' - }) - expect(loader._fetchOptions).to.eql({ credentials: 'same-origin' }) - }) - - it('calls the function with the tag URI', function () { - const credentials = sinon.spy((uri) => 'same-origin') - const file = 'tremor-video/vast_inline_linear.xml' - const uri = baseUrl + file - createLoader(file, { - credentials - }) - expect(credentials).to.have.been.calledWith(uri) - }) - - it('throws if neither a string nor a function provided', function () { - expect(function () { - createLoader('tremor-video/vast_inline_linear.xml', { - credentials: true - }) - }).to.throw(Error, 'Invalid credentials option: true') - }) - }) -}) +// 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) +// expect(error.code).to.equal(code) +// expect(error.message).to.equal(message) +// if (cause != null) { +// expect(error.cause).to.include(cause) +// } +// } +// +// 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' +// } +// +// let fetchUriImpl +// let server +// let baseUrl +// let responseDelay +// +// const createLoader = (file, options) => new VASTLoader(baseUrl + file, options) +// +// const proxifyFetchUri = function () { +// fetchUriImpl = VASTLoader.prototype._fetchUri +// VASTLoader.prototype._fetchUri = async function () { +// const target = proxyPaths[this._uri] +// if (target == null) { +// return await fetchUriImpl.call(this) +// } +// const oldUri = this._uri +// this._uri = baseUrl + target +// try { +// return await fetchUriImpl.call(this) +// } finally { +// this._uri = oldUri +// } +// } +// } +// +// const unproxifyFetchUri = function () { +// VASTLoader.prototype._fetchUri = fetchUriImpl +// } +// +// before(function (cb) { +// const app = express() +// app.use((req, res, next) => { +// setTimeout(() => next(), responseDelay) +// }) +// app.use(express.static(fixturesPath)) +// server = app.listen(function () { +// baseUrl = 'http://localhost:' + server.address().port + '/' +// proxifyFetchUri() +// cb() +// }) +// }) +// +// after(function (cb) { +// unproxifyFetchUri() +// server.close(cb) +// }) +// +// beforeEach(function () { +// responseDelay = 0 +// }) +// +// describe('#load()', function () { +// it('loads the InLine', async function () { +// const loader = createLoader('tremor-video/vast_inline_linear.xml') +// 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 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 () { +// const file = path.join(fixturesPath, 'tremor-video/vast_inline_linear.xml') +// const base64 = (await fsp.readFile(file)).toString('base64') +// const dataUri = 'data:text/xml;base64,' + base64 +// const loader = new VASTLoader(dataUri) +// 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 () { +// const file = path.join(fixturesPath, 'tremor-video/vast_inline_linear.xml') +// const xml = (await fsp.readFile(file, 'utf8')).replace(/\r?\n/g, '') +// const dataUri = 'data:text/xml,' + xml +// const loader = new VASTLoader(dataUri) +// 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 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 () { +// let error +// try { +// const loader = createLoader('no-ads-wrapper.xml') +// await loader.load() +// } catch (err) { +// error = err +// } +// expectLoaderError(error, 303, 'No Ads VAST response after one or more Wrappers.') +// }) +// +// it('throws VAST 301 on invalid InLine inside Wrapper', async function () { +// let error +// try { +// const loader = createLoader('invalid-ads-wrapper.xml') +// await loader.load() +// } catch (err) { +// error = err +// } +// expectLoaderError(error, 301, 'Timeout.') +// }) +// +// it('throws on HTTP errors', async function () { +// let error +// try { +// const loader = createLoader('four-oh-four') +// await loader.load() +// } catch (err) { +// error = err +// } +// expectLoaderError(error, 900, 'Undefined error.', {status: 404, statusText: 'Not Found'}) +// }) +// }) +// +// // TODO Test event data +// describe('#emit()', function () { +// for (const type of ['willFetch', 'didFetch', 'willParse', 'didParse']) { +// it(`emits ${type}`, async function () { +// const spy = sinon.spy() +// const loader = createLoader('tremor-video/vast_inline_linear.xml') +// loader.on(type, spy) +// await loader.load() +// expect(spy.called).to.be.true() +// }) +// } +// +// for (const type of ['willFetch', 'didFetch', 'willParse', 'didParse']) { +// it(`emits ${type} once per tag`, async function () { +// const spy = sinon.spy() +// const loader = createLoader('tremor-video/vast_wrapper_linear_1.xml') +// loader.on(type, spy) +// await loader.load() +// expect(spy.calledTwice).to.be.true() +// }) +// } +// +// it('emits error on errors', async function () { +// const spy = sinon.spy() +// const loader = createLoader('four-oh-four') +// loader.on('error', spy) +// try { +// await loader.load() +// } catch (err) {} +// expect(spy.calledOnce).to.be.true() +// }) +// }) +// +// describe('maxDepth option', function () { +// it('throws when maxDepth is reached', async function () { +// let error +// try { +// const loader = createLoader('tremor-video/vast_wrapper_linear_1.xml', { +// maxDepth: 1 +// }) +// await loader.load() +// } catch (err) { +// error = err +// } +// expectLoaderError(error, 302, 'Wrapper limit reached.') +// }) +// }) +// +// describe('timeout option', function () { +// it('throws when timeout is reached', async function () { +// responseDelay = 100 +// let error +// try { +// const loader = createLoader('no-ads.xml', { +// timeout: 10 +// }) +// await loader.load() +// } catch (err) { +// error = err +// } +// expectLoaderError(error, 301, 'Timeout.') +// }) +// }) +// +// describe('credentials option', function () { +// // TODO Use something nicer than inspecting private _fetchOptions +// +// it('is "omit" by default', function () { +// const loader = createLoader('tremor-video/vast_inline_linear.xml') +// expect(loader._fetchOptions).to.eql({ credentials: 'omit' }) +// }) +// +// it('overrides with a string value', function () { +// const loader = createLoader('tremor-video/vast_inline_linear.xml', { +// credentials: 'include' +// }) +// expect(loader._fetchOptions).to.eql({ credentials: 'include' }) +// }) +// +// it('overrides with a function value', function () { +// const loader = createLoader('tremor-video/vast_inline_linear.xml', { +// credentials: (uri) => 'same-origin' +// }) +// expect(loader._fetchOptions).to.eql({ credentials: 'same-origin' }) +// }) +// +// it('calls the function with the tag URI', function () { +// const credentials = sinon.spy((uri) => 'same-origin') +// const file = 'tremor-video/vast_inline_linear.xml' +// const uri = baseUrl + file +// createLoader(file, { +// credentials +// }) +// expect(credentials).to.have.been.calledWith(uri) +// }) +// +// it('throws if neither a string nor a function provided', function () { +// expect(function () { +// createLoader('tremor-video/vast_inline_linear.xml', { +// credentials: true +// }) +// }).to.throw(Error, 'Invalid credentials option: true') +// }) +// }) +// }) diff --git a/test/unit/vast-loader-error.js b/test/unit/vast-loader-error.js new file mode 100644 index 0000000..4fae51f --- /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') +// }) +// }) +// }) From 919cececb2465e23d2b4b13ef286b20f136eeb9e Mon Sep 17 00:00:00 2001 From: Laurent De Smet Date: Fri, 9 Jun 2017 18:27:42 +0200 Subject: [PATCH 03/13] add marble unit tests --- src/load-vast.js | 10 +- test/fixtures/invalid-ads-wrapper.xml | 8 - test/fixtures/no-ads-wrapper.xml | 8 - test/fixtures/no-ads.xml | 2 - test/fixtures/tremor-video/vast2Nonlinear.xml | 155 ---------- .../tremor-video/vast2RegularLinear.xml | 38 --- .../tremor-video/vast2VPAIDLinear.xml | 33 --- .../tremor-video/vast_inline_linear.xml | 50 ---- .../tremor-video/vast_inline_nonlinear.xml | 54 ---- .../tremor-video/vast_wrapper_linear_1.xml | 43 --- .../tremor-video/vast_wrapper_linear_2.xml | 33 --- .../tremor-video/vast_wrapper_nonlinear_1.xml | 30 -- .../tremor-video/vast_wrapper_nonlinear_2.xml | 39 --- test/fixtures/vast-a.xml | 43 +++ test/fixtures/vast-b.xml | 27 ++ test/fixtures/vast-c.xml | 11 + test/fixtures/vast-d.xml | 11 + test/fixtures/vast-e.xml | 11 + test/fixtures/vast-standalone.xml | 11 + test/unit/load-vast.js | 268 ++++++++++++++++-- 20 files changed, 363 insertions(+), 522 deletions(-) delete mode 100644 test/fixtures/invalid-ads-wrapper.xml delete mode 100644 test/fixtures/no-ads-wrapper.xml delete mode 100644 test/fixtures/no-ads.xml delete mode 100644 test/fixtures/tremor-video/vast2Nonlinear.xml delete mode 100644 test/fixtures/tremor-video/vast2RegularLinear.xml delete mode 100644 test/fixtures/tremor-video/vast2VPAIDLinear.xml delete mode 100644 test/fixtures/tremor-video/vast_inline_linear.xml delete mode 100644 test/fixtures/tremor-video/vast_inline_nonlinear.xml delete mode 100644 test/fixtures/tremor-video/vast_wrapper_linear_1.xml delete mode 100644 test/fixtures/tremor-video/vast_wrapper_linear_2.xml delete mode 100644 test/fixtures/tremor-video/vast_wrapper_nonlinear_1.xml delete mode 100644 test/fixtures/tremor-video/vast_wrapper_nonlinear_2.xml create mode 100644 test/fixtures/vast-a.xml create mode 100644 test/fixtures/vast-b.xml create mode 100644 test/fixtures/vast-c.xml create mode 100644 test/fixtures/vast-d.xml create mode 100644 test/fixtures/vast-e.xml create mode 100644 test/fixtures/vast-standalone.xml diff --git a/src/load-vast.js b/src/load-vast.js index 722273f..448a469 100644 --- a/src/load-vast.js +++ b/src/load-vast.js @@ -90,7 +90,7 @@ const loadVastTree = (config: LoadVastConfig, sfx: SFX): Observable config.maxDepth) { + if (!vast.followAdditionalWrappers() || vast.depth === config.maxDepth) { // We don't fetch additional children. return Observable.empty() } else { @@ -124,11 +124,11 @@ const fetchVast = (config: LoadVastConfig, sfx: SFX): Observable sfx.http(config.url, { method: 'GET' }) .retry(config.retryCount) .timeout(config.timeout) - .catch(error => { + .catch(() => { if (config.parent == null) { - throw new VASTLoaderError('900', error) + throw new VASTLoaderError('900') } else { - throw new VASTLoaderError('301', error) + throw new VASTLoaderError('301') } }) .map(parseVast) @@ -136,7 +136,7 @@ const fetchVast = (config: LoadVastConfig, sfx: SFX): Observable if (error instanceof VASTLoaderError) { throw error } else { - throw new VASTLoaderError('100', error) + throw new VASTLoaderError('100') } }) .do(vast => addParentToVast(vast, config.parent)) diff --git a/test/fixtures/invalid-ads-wrapper.xml b/test/fixtures/invalid-ads-wrapper.xml deleted file mode 100644 index 52501a2..0000000 --- a/test/fixtures/invalid-ads-wrapper.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - http://example.com/invalid-ads.xml - - - diff --git a/test/fixtures/no-ads-wrapper.xml b/test/fixtures/no-ads-wrapper.xml deleted file mode 100644 index 88302e4..0000000 --- a/test/fixtures/no-ads-wrapper.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - http://example.com/no-ads.xml - - - diff --git a/test/fixtures/no-ads.xml b/test/fixtures/no-ads.xml deleted file mode 100644 index 2134bff..0000000 --- a/test/fixtures/no-ads.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/test/fixtures/tremor-video/vast2Nonlinear.xml b/test/fixtures/tremor-video/vast2Nonlinear.xml deleted file mode 100644 index 63f807c..0000000 --- a/test/fixtures/tremor-video/vast2Nonlinear.xml +++ /dev/null @@ -1,155 +0,0 @@ - - - - - 2.0 - 5750100 - - - - - - - - - - - - - - 2.0 - 5750092 - - - - - - - - - - - - - - 2.0 - 5750094 - - - - - - - - - - - - - - 2.0 - 5750091 - - - - - - - - - - - - - - 2.0 - 5748118 - - - - - - - - - - - - - - 2.0 - 5750088 - - - - - - - - - - - - - - 2.0 - 5700028 - - - - - - - - - - - - - - 2.0 - 5750098 - - - - - - - - - - - - - - 2.0 - 5750097 - - - - - - - - - - - - - - 2.0 - 5700030 - - - - - - - - - - - - - - diff --git a/test/fixtures/tremor-video/vast2RegularLinear.xml b/test/fixtures/tremor-video/vast2RegularLinear.xml deleted file mode 100644 index 056f579..0000000 --- a/test/fixtures/tremor-video/vast2RegularLinear.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - 2.0 - 5748406 - - - - - 00:00:30 - - - - - - - - - - - - - - -Click Here - - -]]> - - - - - - - - - diff --git a/test/fixtures/tremor-video/vast2VPAIDLinear.xml b/test/fixtures/tremor-video/vast2VPAIDLinear.xml deleted file mode 100644 index eca9977..0000000 --- a/test/fixtures/tremor-video/vast2VPAIDLinear.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - 2.0 - 5773100 - - - - 00:00:01 - - - - - - - - - - Click Here - - ]]> - - - - - - - - - - - diff --git a/test/fixtures/tremor-video/vast_inline_linear.xml b/test/fixtures/tremor-video/vast_inline_linear.xml deleted file mode 100644 index 149030c..0000000 --- a/test/fixtures/tremor-video/vast_inline_linear.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - Acudeo Compatible - VAST 2.0 Instream Test 1 - VAST 2.0 Instream Test 1 - http://myErrorURL/error - http://myTrackingURL/impression - - - - 00:00:30 - - http://myTrackingURL/creativeView - http://myTrackingURL/start - http://myTrackingURL/midpoint - http://myTrackingURL/firstQuartile - http://myTrackingURL/thirdQuartile - http://myTrackingURL/complete - - - http://www.tremormedia.com - http://myTrackingURL/click - - - http://cdnp.tremormedia.com/video/acudeo/Carrot_400x300_500kb.flv - - - - - - - http://demo.tremormedia.com/proddev/vast/Blistex1.jpg - - http://myTrackingURL/firstCompanionCreativeView - - - http://www.tremormedia.com - - - http://demo.tremormedia.com/proddev/vast/728x90_banner1.jpg - http://www.tremormedia.com - - - - - - - diff --git a/test/fixtures/tremor-video/vast_inline_nonlinear.xml b/test/fixtures/tremor-video/vast_inline_nonlinear.xml deleted file mode 100644 index e799cc8..0000000 --- a/test/fixtures/tremor-video/vast_inline_nonlinear.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - Acudeo Compatible - NonLinear Test Campaign 1 - NonLinear Test Campaign 1 - http://mySurveyURL/survey - http://myErrorURL/error - http://myTrackingURL/impression - - - - - http://myTrackingURL/nonlinear/creativeView - http://myTrackingURL/nonlinear/expand - http://myTrackingURL/nonlinear/collapse - http://myTrackingURL/nonlinear/acceptInvitation - http://myTrackingURL/nonlinear/close - - - - http://demo.tremormedia.com/proddev/vast/50x300_static.jpg - - http://www.tremormedia.com - - - - http://demo.tremormedia.com/proddev/vast/50x450_static.jpg - - http://www.tremormedia.com - - - - - - - http://demo.tremormedia.com/proddev/vast/300x250_companion_1.swf - - http://www.tremormedia.com - - - http://demo.tremormedia.com/proddev/vast/728x90_banner1.jpg - - http://myTrackingURL/secondCompanion - - http://www.tremormedia.com - - - - - - - diff --git a/test/fixtures/tremor-video/vast_wrapper_linear_1.xml b/test/fixtures/tremor-video/vast_wrapper_linear_1.xml deleted file mode 100644 index c7a276f..0000000 --- a/test/fixtures/tremor-video/vast_wrapper_linear_1.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - Acudeo Compatible - http://demo.tremormedia.com/proddev/vast/vast_inline_linear.xml - http://myErrorURL/wrapper/error - http://myTrackingURL/wrapper/impression - - - - - http://myTrackingURL/wrapper/creativeView - http://myTrackingURL/wrapper/start - http://myTrackingURL/wrapper/midpoint - http://myTrackingURL/wrapper/firstQuartile - http://myTrackingURL/wrapper/thirdQuartile - http://myTrackingURL/wrapper/complete - http://myTrackingURL/wrapper/mute - http://myTrackingURL/wrapper/unmute - http://myTrackingURL/wrapper/pause - http://myTrackingURL/wrapper/resume - http://myTrackingURL/wrapper/fullscreen - - - - - - - http://myTrackingURL/wrapper/click - - - - - - - - - - - - - diff --git a/test/fixtures/tremor-video/vast_wrapper_linear_2.xml b/test/fixtures/tremor-video/vast_wrapper_linear_2.xml deleted file mode 100644 index 4dae09f..0000000 --- a/test/fixtures/tremor-video/vast_wrapper_linear_2.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - Acudeo Compatible - http://demo.tremormedia.com/proddev/vast/vast_inline_linear.xml - http://myTrackingURL/wrapper/impression - - - - - - - - - - - http://demo.tremormedia.com/proddev/vast/300x250_banner1.jpg - - http://myTrackingURL/wrapper/firstCompanionCreativeView - - http://www.tremormedia.com - - - http://demo.tremormedia.com/proddev/vast/728x90_banner1.jpg - http://www.tremormedia.com - - - - - - - diff --git a/test/fixtures/tremor-video/vast_wrapper_nonlinear_1.xml b/test/fixtures/tremor-video/vast_wrapper_nonlinear_1.xml deleted file mode 100644 index 5a10db3..0000000 --- a/test/fixtures/tremor-video/vast_wrapper_nonlinear_1.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - Acudeo Compatible - http://demo.tremormedia.com/proddev/vast/vast_inline_nonlinear2.xml - http://myErrorURL/wrapper/error - http://myTrackingURL/wrapper/impression - - - - - - - - - - - http://myTrackingURL/wrapper/nonlinear/creativeView/creativeView - http://myTrackingURL/wrapper/nonlinear/creativeView/expand - http://myTrackingURL/wrapper/nonlinear/creativeView/collapse - http://myTrackingURL/wrapper/nonlinear/creativeView/acceptInvitation - http://myTrackingURL/wrapper/nonlinear/creativeView/close - - - - - - - diff --git a/test/fixtures/tremor-video/vast_wrapper_nonlinear_2.xml b/test/fixtures/tremor-video/vast_wrapper_nonlinear_2.xml deleted file mode 100644 index eabd2a4..0000000 --- a/test/fixtures/tremor-video/vast_wrapper_nonlinear_2.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - Acudeo Compatible - http://demo.tremormedia.com/proddev/vast/vast_inline_nonlinear3.xml - http://myTrackingURL/wrapper/impression - - - - - - - - - - - - - - - - - http://demo.tremormedia.com/proddev/vast/300x250_banner1.jpg - - http://myTrackingURL/wrapper/firstCompanionCreativeView - - http://www.tremormedia.com - - - http://demo.tremormedia.com/proddev/vast/728x90_banner1.jpg - http://www.tremormedia.com - - - - - - - diff --git a/test/fixtures/vast-a.xml b/test/fixtures/vast-a.xml new file mode 100644 index 0000000..6555a9f --- /dev/null +++ b/test/fixtures/vast-a.xml @@ -0,0 +1,43 @@ + + + + + Acudeo Compatible + VAST 2.0 Instream Test 1 + http://myErrorURL/error + http://myTrackingURL/impression + + + + + Acudeo Compatible + http://192.168.1.200:8080/vast-b.xml + http://myErrorURL/wrapper/error + http://myTrackingURL/wrapper/impression + + + + + Acudeo Compatible + VAST 2.0 Instream Test 1 + http://myErrorURL/error + http://myTrackingURL/impression + + + + + Acudeo Compatible + http://192.168.1.200:8080/vast-c.xml + http://myErrorURL/wrapper/error + http://myTrackingURL/wrapper/impression + + + + + Acudeo Compatible + VAST 2.0 Instream Test 1 + http://myErrorURL/error + http://myTrackingURL/impression + + + diff --git a/test/fixtures/vast-b.xml b/test/fixtures/vast-b.xml new file mode 100644 index 0000000..b81e86e --- /dev/null +++ b/test/fixtures/vast-b.xml @@ -0,0 +1,27 @@ + + + + + Acudeo Compatible + http://192.168.1.200:8080/vast-d.xml + http://myErrorURL/wrapper/error + http://myTrackingURL/wrapper/impression + + + + + Acudeo Compatible + VAST 2.0 Instream Test 1 + http://myErrorURL/error + http://myTrackingURL/impression + + + + + Acudeo Compatible + http://192.168.1.200:8080/vast-e.xml + http://myErrorURL/wrapper/error + http://myTrackingURL/wrapper/impression + + + diff --git a/test/fixtures/vast-c.xml b/test/fixtures/vast-c.xml new file mode 100644 index 0000000..caf44a1 --- /dev/null +++ b/test/fixtures/vast-c.xml @@ -0,0 +1,11 @@ + + + + + Acudeo Compatible + VAST 2.0 Instream Test 1 + http://myErrorURL/error + http://myTrackingURL/impression + + + diff --git a/test/fixtures/vast-d.xml b/test/fixtures/vast-d.xml new file mode 100644 index 0000000..caf44a1 --- /dev/null +++ b/test/fixtures/vast-d.xml @@ -0,0 +1,11 @@ + + + + + Acudeo Compatible + VAST 2.0 Instream Test 1 + http://myErrorURL/error + http://myTrackingURL/impression + + + diff --git a/test/fixtures/vast-e.xml b/test/fixtures/vast-e.xml new file mode 100644 index 0000000..caf44a1 --- /dev/null +++ b/test/fixtures/vast-e.xml @@ -0,0 +1,11 @@ + + + + + Acudeo Compatible + VAST 2.0 Instream Test 1 + http://myErrorURL/error + http://myTrackingURL/impression + + + diff --git a/test/fixtures/vast-standalone.xml b/test/fixtures/vast-standalone.xml new file mode 100644 index 0000000..caf44a1 --- /dev/null +++ b/test/fixtures/vast-standalone.xml @@ -0,0 +1,11 @@ + + + + + Acudeo Compatible + VAST 2.0 Instream Test 1 + http://myErrorURL/error + http://myTrackingURL/impression + + + diff --git a/test/unit/load-vast.js b/test/unit/load-vast.js index 57c4ae9..bec44a3 100644 --- a/test/unit/load-vast.js +++ b/test/unit/load-vast.js @@ -1,42 +1,262 @@ import { VAST } from 'iab-vast-model' +import parseVast from 'iab-vast-parser' import { loadVast } from '../../src/load-vast' import path from 'path' +import fs from 'fs' import express from 'express' +import VASTLoaderError from '../../src/error' + +import { TestScheduler } from 'rxjs/testing/TestScheduler' +import { Observable } from 'rxjs/Observable' +import 'rxjs/add/observable/timer' +import 'rxjs/add/operator/mergeMapTo' + +import times from 'lodash/times' const fixturesPath = path.resolve(__dirname, '../fixtures') -describe('#loadVast', () => { - // loadVast({ - // url: '' - // }) - let server, baseUrl +const buildVastVars = vastPath => { + const url = 'http://192.168.1.200:8080/' + vastPath + const str = fs.readFileSync(path.join(fixturesPath, vastPath), 'utf8') + const model = parseVast(str) + + return { url, str, model } +} + +let originalTimeout + +const monkeyPatch = (scheduler) => { + originalTimeout = Observable.prototype.timeout + + Observable.prototype.timeout = function (due) { + return originalTimeout.call(this, due, scheduler) + } +} + +const restore = () => { + Observable.prototype.timeout = originalTimeout +} + +const fxFactory = mock => (...args) => Observable.defer(() => mock(...args)) + +const assertDeepEqual = (actual, expected) => { + expect(actual).to.deep.equal(expected) +} + +describe('#loadVast()', () => { + let scheduler, cold + + let vastA, vastB, vastC, vastD, vastE, vastStandalone + + before(() => { + vastA = buildVastVars('vast-a.xml') + vastB = buildVastVars('vast-b.xml') + vastC = buildVastVars('vast-c.xml') + vastD = buildVastVars('vast-d.xml') + vastE = buildVastVars('vast-e.xml') + vastStandalone = buildVastVars('vast-standalone.xml') + + vastB.model.parent = vastA.model.ads[1] + vastC.model.parent = vastA.model.ads[3] + vastD.model.parent = vastB.model.ads[0] + vastE.model.parent = vastB.model.ads[2] + }) + + beforeEach(() => { + scheduler = new TestScheduler(assertDeepEqual) + monkeyPatch(scheduler) + cold = scheduler.createColdObservable.bind(scheduler) + }) + + afterEach(() => { + restore() + }) + + it('should load a VAST document', () => { + const httpStub = sinon.stub() + + httpStub + .onCall(0) + .returns(cold('---(a|)', { a: vastStandalone.str })) + + httpStub.throws() + + const fx = { + http: fxFactory(httpStub) + } + + const actual$ = loadVast({ url: vastStandalone.url }, fx) + + const expected = '---(a|)' + const values = { a: { type: 'VAST_LOADED', vast: vastStandalone.model } } + + scheduler.expectObservable(actual$).toBe(expected, values) + scheduler.flush() + + expect(httpStub.callCount).to.equal(1) + expect(httpStub.getCall(0).args).to.deep.equal([vastStandalone.url, { method: 'GET' }]) + }) + + it('should retry loading a VAST document', () => { + const httpStub = sinon.stub() + + httpStub + .onCall(0) + .returns(cold('---(#|)', null, new Error('http failed'))) + + httpStub + .onCall(1) + .returns(cold('---(#|)', null, new Error('http failed'))) + + httpStub + .onCall(2) + .returns(cold('---(a|)', { a: vastStandalone.str })) - before(cb => { - const app = express() + httpStub.throws() - app.use(express.static(fixturesPath)) + const fx = { + http: fxFactory(httpStub) + } - server = app.listen(() => { - baseUrl = 'http://localhost:' + server.address().port + '/' - cb() + const actual$ = loadVast({ url: vastStandalone.url, retryCount: 2 }, fx) + + const expected = '---------(a|)' + const values = { a: { type: 'VAST_LOADED', vast: vastStandalone.model } } + + scheduler.expectObservable(actual$).toBe(expected, values) + scheduler.flush() + + expect(httpStub.callCount).to.equal(3) + + times(3, i => { + expect(httpStub.getCall(i).args).to.deep.equal([vastStandalone.url, { method: 'GET' }]) }) }) - after(cb => { - server.close(cb) + it('should load a complete VAST tree', async () => { + // --(a|) + // -----(b|) + // --(c|) + // --(d|) + // -----(e|) + // --a----b-d--(ec|) + + const httpStub = sinon.stub() + + const vastA$ = cold('--(a|)', { a: vastA.str }) + httpStub + .onCall(0) + .returns(vastA$) + + const vastB$ = cold('-----(b|)', { b: vastB.str }) + httpStub + .onCall(1) + .returns(vastB$) + + httpStub + .onCall(2) + .returns(cold('--(c|)', { c: vastC.str })) + + httpStub + .onCall(3) + .returns(cold('--(d|)', { d: vastD.str })) + + httpStub + .onCall(4) + .returns(cold('-----(e|)', { e: vastE.str })) + + httpStub.throws() + + const fx = { + http: fxFactory(httpStub) + } + + const actual$ = loadVast({ url: vastA.url }, fx) + const expected = '--a----b-d--(ec|)' + const values = { + a: { type: 'VAST_LOADED', vast: vastA.model }, + b: { type: 'VAST_LOADED', vast: vastB.model }, + c: { type: 'VAST_LOADED', vast: vastC.model }, + d: { type: 'VAST_LOADED', vast: vastD.model }, + e: { type: 'VAST_LOADED', vast: vastE.model } + } + + scheduler.expectObservable(actual$).toBe(expected, values) + scheduler.expectSubscriptions(vastA$.subscriptions).toBe('^-!') + scheduler.expectSubscriptions(vastB$.subscriptions).toBe('--^----!') + scheduler.flush() + + expect(httpStub.callCount).to.equal(5) + expect(httpStub.getCall(0).args).to.deep.equal([vastA.url, { method: 'GET' }]) + expect(httpStub.getCall(1).args).to.deep.equal([vastB.url, { method: 'GET' }]) + expect(httpStub.getCall(2).args).to.deep.equal([vastC.url, { method: 'GET' }]) + expect(httpStub.getCall(3).args).to.deep.equal([vastD.url, { method: 'GET' }]) + expect(httpStub.getCall(4).args).to.deep.equal([vastE.url, { method: 'GET' }]) }) - it('should load a VAST tag', async () => { - const fetchingVast = - loadVast({ - url: baseUrl + 'tremor-video/vast_inline_linear.xml' - }) - .toArray() - .toPromise() + it('should timeout when loading the VAST document', () => { + const httpStub = sinon.stub() + + httpStub + .onCall(0) + .returns(cold('---(a|)', { a: vastStandalone.str })) + + httpStub.throws() + + const fx = { + http: fxFactory(httpStub) + } + + const actual$ = loadVast({ url: vastStandalone.url, timeout: 20 }, fx) + const expected = '--(a|)' + const values = { + a: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: null } + } + + scheduler.expectObservable(actual$).toBe(expected, values) + scheduler.flush() + + expect(httpStub.callCount).to.equal(1) + expect(httpStub.getCall(0).args).to.deep.equal([vastStandalone.url, { method: 'GET' }]) + }) + + it('should not fetch additional documents if maxDepth is exceeded', () => { + // --(a|) + // -----(b|) + // --(c|) + // --a----(bc|) + + const httpStub = sinon.stub() + + httpStub + .onCall(0) + .returns(cold('--(a|)', { a: vastA.str })) + + httpStub + .onCall(1) + .returns(cold('-----(b|)', { b: vastB.str })) + + httpStub + .onCall(2) + .returns(cold('--(c|)', { c: vastC.str })) + + httpStub.throws() + + const fx = { + http: fxFactory(httpStub) + } + const actual$ = loadVast({ url: vastA.url, maxDepth: 1 }, fx) + + const expected = '--a----(bc|)' + const values = { + a: { type: 'VAST_LOADED', vast: vastA.model }, + b: { type: 'VAST_LOADED', vast: vastB.model }, + c: { type: 'VAST_LOADED', vast: vastC.model } + } + + scheduler.expectObservable(actual$).toBe(expected, values) + scheduler.flush() - const events = await fetchingVast - expect(events.length).to.equal(1) - expect(events[0].type).to.equal('VAST_LOADED') - expect(events[0].vast).to.be.an.instanceof(VAST) + expect(httpStub.callCount).to.equal(3) }) }) From 49de38927259c5bfe99d6e6e8b8431078780e02f Mon Sep 17 00:00:00 2001 From: Laurent De Smet Date: Fri, 16 Jun 2017 14:05:19 +0200 Subject: [PATCH 04/13] Add lots of additional tests --- package.json | 3 +- src/load-vast.js | 64 +++-- ...ns-to-ad-load-actions.js => vast-to-ad.js} | 12 +- test/lib/build-vast.js | 40 +++ test/unit/load-vast.js | 261 ++++++++++++------ test/unit/vast-to-ad.js | 104 +++++++ 6 files changed, 382 insertions(+), 102 deletions(-) rename src/{map-vast-load-actions-to-ad-load-actions.js => vast-to-ad.js} (83%) create mode 100644 test/lib/build-vast.js create mode 100644 test/unit/vast-to-ad.js diff --git a/package.json b/package.json index 8f526f9..995b959 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,8 @@ "XMLHttpRequest", "$Keys", "$PropertyType", - "RequestOptions" + "RequestOptions", + "CredentialsType" ] } } diff --git a/src/load-vast.js b/src/load-vast.js index 448a469..fb6b608 100644 --- a/src/load-vast.js +++ b/src/load-vast.js @@ -13,6 +13,7 @@ import 'rxjs/add/observable/empty' import 'rxjs/add/observable/of' import 'rxjs/add/observable/defer' import 'rxjs/add/observable/fromPromise' +import 'rxjs/add/observable/from' // RxJS operators import 'rxjs/add/operator/retry' @@ -25,12 +26,15 @@ import 'rxjs/add/operator/share' import 'rxjs/add/operator/concatMap' import 'rxjs/add/operator/toArray' import 'rxjs/add/operator/toPromise' +import 'rxjs/add/operator/elementAt' type VastLoadedAction = { type: 'VAST_LOADED', vast: VAST } -type VastLoadingFailedAction = { type: 'VAST_LOADING_FAILED', error: any, wrapper: ?Wrapper } +type VastLoadingFailedAction = { type: 'VAST_LOADING_FAILED', error: VASTLoaderError, wrapper: ?Wrapper } -export type VastLoadAction = VastLoadedAction | VastLoadingFailedAction +export type VastLoadAction = + VastLoadedAction | + VastLoadingFailedAction export const http = (url: string, options?: RequestOptions) => Observable.defer(() => fetch(url, options)) @@ -40,12 +44,14 @@ const defaultSfx = { http } +type Credentials = CredentialsType | (url: string) => CredentialsType + type Config = { url: string, maxDepth?: number, timeout?: number, retryCount?: number, - withCredentials?: boolean + credentials: Credentials[] } type SFX = { @@ -56,7 +62,7 @@ const DEFAULT_OPTIONS = { maxDepth: 10, timeout: 10000, retryCount: 0, - withCredentials: false + credentials: ['omit'] } export const loadVast = (config: Config, sfx: SFX = defaultSfx): Observable => @@ -72,7 +78,7 @@ type LoadVastConfig = { maxDepth: number, timeout: number, retryCount: number, - withCredentials: boolean + credentials: Credentials[] } // Traverse the tree using a preorder depth first strategy. @@ -89,10 +95,16 @@ const loadVastTree = (config: LoadVastConfig, sfx: SFX): Observable ({ + type: 'VAST_LOADING_FAILED', + wrapper, + error: new VASTLoaderError('302') + })) + ) } else { // We start fetching all the children. const results = getWrappers(vast).map(wrapper => @@ -121,9 +133,26 @@ const getWrappers = (vast: VAST): Wrapper[] => // This function returns a stream with exact one event: a success or error event. const fetchVast = (config: LoadVastConfig, sfx: SFX): Observable => - sfx.http(config.url, { method: 'GET' }) - .retry(config.retryCount) - .timeout(config.timeout) + Observable.from(config.credentials) + .map(credentials => + typeof credentials === 'string' + ? credentials + : credentials(config.url) + ) + .mergeMap(credentials => + sfx + .http(config.url, { + method: 'GET', + credentials + }) + .retry(config.retryCount) + .timeout(config.timeout) + // Swallow errors. + .catch(() => Observable.empty()) + ) + // When there are only errors, the resulting stream will be empty, which + // will make elemetAt(0) fail, which is the expected behavior. + .elementAt(0) .catch(() => { if (config.parent == null) { throw new VASTLoaderError('900') @@ -132,13 +161,6 @@ const fetchVast = (config: LoadVastConfig, sfx: SFX): Observable } }) .map(parseVast) - .catch(error => { - if (error instanceof VASTLoaderError) { - throw error - } else { - throw new VASTLoaderError('100') - } - }) .do(vast => addParentToVast(vast, config.parent)) .map(vast => ({ type: 'VAST_LOADED', @@ -150,7 +172,13 @@ const fetchVast = (config: LoadVastConfig, sfx: SFX): Observable wrapper: config.parent })) -const parseVast = (res: string) => parse(res) +const parseVast = (res: string) => { + try { + return parse(res) + } catch (err) { + throw new VASTLoaderError('100') + } +} const addParentToVast = (vast: VAST, parent: ?Wrapper) => { vast.parent = parent diff --git a/src/map-vast-load-actions-to-ad-load-actions.js b/src/vast-to-ad.js similarity index 83% rename from src/map-vast-load-actions-to-ad-load-actions.js rename to src/vast-to-ad.js index e2222b2..b642b12 100644 --- a/src/map-vast-load-actions-to-ad-load-actions.js +++ b/src/vast-to-ad.js @@ -6,12 +6,12 @@ import { type VastLoadAction } from './load-vast' type AdLoadedAction = { type: 'AD_LOADED', ad: Ad } -type WrapperLoadingFailedAction = { type: 'WRAPPER_LOADING_FAILED', error: any, wrapper: ?Wrapper } +type AdLoadingFailedAction = { type: 'AD_LOADING_FAILED', error: any, wrapper: ?Wrapper } -type AdLoadAction = AdLoadedAction | WrapperLoadingFailedAction +type AdLoadAction = AdLoadedAction | AdLoadingFailedAction // This function returns a depth first preorder stream of inline elements of the VAST chain. -export const mapVastLoadActionsToAdLoadActions = (vast$: Observable): Observable => +export const vastToAd = (vast$: Observable): Observable => vast$ .concatMap(event => { if (event.type === 'VAST_LOADED') { @@ -30,7 +30,7 @@ export const mapVastLoadActionsToAdLoadActions = (vast$: Observable const walkAdsUntilNextWrapper = (vast: VAST, fromIndex: number): Ad[] => { const adsFromIndex = vast.ads.slice(fromIndex) - const toIndex = fromIndex + adsFromIndex.findIndex(ad => ad instanceof Wrapper) + const toIndex = adsFromIndex.findIndex(ad => ad instanceof Wrapper) const ads = toIndex === -1 // All the inLines until the end of the array. ? vast.ads.slice(fromIndex) // An array of inLine ads, ending with one wrapper ad. - : vast.ads.slice(fromIndex, toIndex + 1) + : vast.ads.slice(fromIndex, fromIndex + toIndex + 1) if (toIndex === -1) { // This VAST file doesn't have Wrappers anymore so we can continue walking the tree upwards. diff --git a/test/lib/build-vast.js b/test/lib/build-vast.js new file mode 100644 index 0000000..63231d2 --- /dev/null +++ b/test/lib/build-vast.js @@ -0,0 +1,40 @@ +import fs from 'fs' +import path from 'path' +import parseVast from 'iab-vast-parser' + +const fixturesPath = path.resolve(__dirname, '../fixtures') + +const buildVastVars = vastPath => { + const url = 'http://192.168.1.200:8080/' + vastPath + const str = fs.readFileSync(path.join(fixturesPath, vastPath), 'utf8') + const model = parseVast(str) + + return { url, str, model } +} + +export const buildVast = () => { + const a = buildVastVars('vast-a.xml') + const b = buildVastVars('vast-b.xml') + const c = buildVastVars('vast-c.xml') + const d = buildVastVars('vast-d.xml') + const e = buildVastVars('vast-e.xml') + const standalone = buildVastVars('vast-standalone.xml') + + // Setup the Wrapper => VAST links. + b.model.parent = a.model.ads[1] + c.model.parent = a.model.ads[3] + d.model.parent = b.model.ads[0] + e.model.parent = b.model.ads[2] + + return { a, b, c, d, e, standalone } +} + +export const buildAd = (vast) => { + const [p, q, r, s, t] = vast.a.model.ads + const [u, v, w] = vast.b.model.ads + const [x] = vast.c.model.ads + const [y] = vast.d.model.ads + const [z] = vast.e.model.ads + + return { p, q, r, s, t, u, v, w, x, y, z } +} diff --git a/test/unit/load-vast.js b/test/unit/load-vast.js index bec44a3..167ce0f 100644 --- a/test/unit/load-vast.js +++ b/test/unit/load-vast.js @@ -1,28 +1,12 @@ -import { VAST } from 'iab-vast-model' -import parseVast from 'iab-vast-parser' import { loadVast } from '../../src/load-vast' -import path from 'path' -import fs from 'fs' -import express from 'express' import VASTLoaderError from '../../src/error' +import { buildVast } from '../lib/build-vast' import { TestScheduler } from 'rxjs/testing/TestScheduler' import { Observable } from 'rxjs/Observable' -import 'rxjs/add/observable/timer' -import 'rxjs/add/operator/mergeMapTo' import times from 'lodash/times' -const fixturesPath = path.resolve(__dirname, '../fixtures') - -const buildVastVars = vastPath => { - const url = 'http://192.168.1.200:8080/' + vastPath - const str = fs.readFileSync(path.join(fixturesPath, vastPath), 'utf8') - const model = parseVast(str) - - return { url, str, model } -} - let originalTimeout const monkeyPatch = (scheduler) => { @@ -43,25 +27,11 @@ const assertDeepEqual = (actual, expected) => { expect(actual).to.deep.equal(expected) } +const vast = buildVast() + describe('#loadVast()', () => { let scheduler, cold - let vastA, vastB, vastC, vastD, vastE, vastStandalone - - before(() => { - vastA = buildVastVars('vast-a.xml') - vastB = buildVastVars('vast-b.xml') - vastC = buildVastVars('vast-c.xml') - vastD = buildVastVars('vast-d.xml') - vastE = buildVastVars('vast-e.xml') - vastStandalone = buildVastVars('vast-standalone.xml') - - vastB.model.parent = vastA.model.ads[1] - vastC.model.parent = vastA.model.ads[3] - vastD.model.parent = vastB.model.ads[0] - vastE.model.parent = vastB.model.ads[2] - }) - beforeEach(() => { scheduler = new TestScheduler(assertDeepEqual) monkeyPatch(scheduler) @@ -77,7 +47,7 @@ describe('#loadVast()', () => { httpStub .onCall(0) - .returns(cold('---(a|)', { a: vastStandalone.str })) + .returns(cold('---(a|)', { a: vast.standalone.str })) httpStub.throws() @@ -85,16 +55,73 @@ describe('#loadVast()', () => { http: fxFactory(httpStub) } - const actual$ = loadVast({ url: vastStandalone.url }, fx) + const actual$ = loadVast({ url: vast.standalone.url }, fx) const expected = '---(a|)' - const values = { a: { type: 'VAST_LOADED', vast: vastStandalone.model } } + const values = { a: { type: 'VAST_LOADED', vast: vast.standalone.model } } scheduler.expectObservable(actual$).toBe(expected, values) scheduler.flush() expect(httpStub.callCount).to.equal(1) - expect(httpStub.getCall(0).args).to.deep.equal([vastStandalone.url, { method: 'GET' }]) + expect(httpStub.getCall(0).args).to.deep.equal([vast.standalone.url, { method: 'GET', credentials: 'omit' }]) + }) + + it('should load a VAST document by racing the passed credentials strategies and ignoring failed requests', () => { + // ----(a|) + // ---(a|) + // --(#|) + // ---(a|) + + const httpStub = sinon.stub() + const credentialStub = sinon.stub() + + httpStub + .onCall(0) + .returns(cold('----(a|)', { a: vast.standalone.str })) + + httpStub + .onCall(1) + .returns(cold('---(a|)', { a: vast.standalone.str })) + + httpStub + .onCall(2) + .returns(cold('--(#|)', null, new Error('http failed'))) + + httpStub.throws() + + credentialStub + .onCall(0) + .returns('include') + + credentialStub.throws() + + const fx = { + http: fxFactory(httpStub) + } + + const actual$ = loadVast({ + url: vast.standalone.url, + credentials: [ + 'omit', + 'same-origin', + credentialStub + ] + }, fx) + + const expected = '---(a|)' + const values = { a: { type: 'VAST_LOADED', vast: vast.standalone.model } } + + scheduler.expectObservable(actual$).toBe(expected, values) + scheduler.flush() + + expect(httpStub.callCount).to.equal(3) + expect(httpStub.getCall(0).args).to.deep.equal([vast.standalone.url, { method: 'GET', credentials: 'omit' }]) + expect(httpStub.getCall(1).args).to.deep.equal([vast.standalone.url, { method: 'GET', credentials: 'same-origin' }]) + expect(httpStub.getCall(2).args).to.deep.equal([vast.standalone.url, { method: 'GET', credentials: 'include' }]) + + expect(credentialStub.callCount).to.equal(1) + expect(credentialStub.getCall(0).args).to.deep.equal([vast.standalone.url]) }) it('should retry loading a VAST document', () => { @@ -110,7 +137,7 @@ describe('#loadVast()', () => { httpStub .onCall(2) - .returns(cold('---(a|)', { a: vastStandalone.str })) + .returns(cold('---(a|)', { a: vast.standalone.str })) httpStub.throws() @@ -118,10 +145,10 @@ describe('#loadVast()', () => { http: fxFactory(httpStub) } - const actual$ = loadVast({ url: vastStandalone.url, retryCount: 2 }, fx) + const actual$ = loadVast({ url: vast.standalone.url, retryCount: 2 }, fx) const expected = '---------(a|)' - const values = { a: { type: 'VAST_LOADED', vast: vastStandalone.model } } + const values = { a: { type: 'VAST_LOADED', vast: vast.standalone.model } } scheduler.expectObservable(actual$).toBe(expected, values) scheduler.flush() @@ -129,41 +156,34 @@ describe('#loadVast()', () => { expect(httpStub.callCount).to.equal(3) times(3, i => { - expect(httpStub.getCall(i).args).to.deep.equal([vastStandalone.url, { method: 'GET' }]) + expect(httpStub.getCall(i).args).to.deep.equal([vast.standalone.url, { method: 'GET', credentials: 'omit' }]) }) }) it('should load a complete VAST tree', async () => { - // --(a|) - // -----(b|) - // --(c|) - // --(d|) - // -----(e|) - // --a----b-d--(ec|) - const httpStub = sinon.stub() - const vastA$ = cold('--(a|)', { a: vastA.str }) + const vastA$ = cold('--(a|)', { a: vast.a.str }) httpStub .onCall(0) .returns(vastA$) - const vastB$ = cold('-----(b|)', { b: vastB.str }) + const vastB$ = cold('-----(b|)', { b: vast.b.str }) httpStub .onCall(1) .returns(vastB$) httpStub .onCall(2) - .returns(cold('--(c|)', { c: vastC.str })) + .returns(cold('--(c|)', { c: vast.c.str })) httpStub .onCall(3) - .returns(cold('--(d|)', { d: vastD.str })) + .returns(cold('--(d|)', { d: vast.d.str })) httpStub .onCall(4) - .returns(cold('-----(e|)', { e: vastE.str })) + .returns(cold('-----(e|)', { e: vast.e.str })) httpStub.throws() @@ -171,14 +191,14 @@ describe('#loadVast()', () => { http: fxFactory(httpStub) } - const actual$ = loadVast({ url: vastA.url }, fx) + const actual$ = loadVast({ url: vast.a.url }, fx) const expected = '--a----b-d--(ec|)' const values = { - a: { type: 'VAST_LOADED', vast: vastA.model }, - b: { type: 'VAST_LOADED', vast: vastB.model }, - c: { type: 'VAST_LOADED', vast: vastC.model }, - d: { type: 'VAST_LOADED', vast: vastD.model }, - e: { type: 'VAST_LOADED', vast: vastE.model } + a: { type: 'VAST_LOADED', vast: vast.a.model }, + b: { type: 'VAST_LOADED', vast: vast.b.model }, + c: { type: 'VAST_LOADED', vast: vast.c.model }, + d: { type: 'VAST_LOADED', vast: vast.d.model }, + e: { type: 'VAST_LOADED', vast: vast.e.model } } scheduler.expectObservable(actual$).toBe(expected, values) @@ -187,19 +207,19 @@ describe('#loadVast()', () => { scheduler.flush() expect(httpStub.callCount).to.equal(5) - expect(httpStub.getCall(0).args).to.deep.equal([vastA.url, { method: 'GET' }]) - expect(httpStub.getCall(1).args).to.deep.equal([vastB.url, { method: 'GET' }]) - expect(httpStub.getCall(2).args).to.deep.equal([vastC.url, { method: 'GET' }]) - expect(httpStub.getCall(3).args).to.deep.equal([vastD.url, { method: 'GET' }]) - expect(httpStub.getCall(4).args).to.deep.equal([vastE.url, { method: 'GET' }]) + expect(httpStub.getCall(0).args).to.deep.equal([vast.a.url, { method: 'GET', credentials: 'omit' }]) + expect(httpStub.getCall(1).args).to.deep.equal([vast.b.url, { method: 'GET', credentials: 'omit' }]) + expect(httpStub.getCall(2).args).to.deep.equal([vast.c.url, { method: 'GET', credentials: 'omit' }]) + expect(httpStub.getCall(3).args).to.deep.equal([vast.d.url, { method: 'GET', credentials: 'omit' }]) + expect(httpStub.getCall(4).args).to.deep.equal([vast.e.url, { method: 'GET', credentials: 'omit' }]) }) - it('should timeout when loading the VAST document', () => { + it('should return a VAST_LOADING_FAILED action when the root VAST document timeouts', () => { const httpStub = sinon.stub() httpStub .onCall(0) - .returns(cold('---(a|)', { a: vastStandalone.str })) + .returns(cold('---(a|)', { a: vast.standalone.str })) httpStub.throws() @@ -207,7 +227,7 @@ describe('#loadVast()', () => { http: fxFactory(httpStub) } - const actual$ = loadVast({ url: vastStandalone.url, timeout: 20 }, fx) + const actual$ = loadVast({ url: vast.standalone.url, timeout: 20 }, fx) const expected = '--(a|)' const values = { a: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: null } @@ -217,46 +237,133 @@ describe('#loadVast()', () => { scheduler.flush() expect(httpStub.callCount).to.equal(1) - expect(httpStub.getCall(0).args).to.deep.equal([vastStandalone.url, { method: 'GET' }]) + expect(httpStub.getCall(0).args).to.deep.equal([vast.standalone.url, { method: 'GET', credentials: 'omit' }]) + }) + + describe('', () => { + let vastBParent + before(() => { + // Temporarily reset the parent of vast.b + vastBParent = vast.b.model.parent + vast.b.model.parent = null + }) + + it('should return a VAST_LOADING_FAILED action when a non-root VAST document timeouts', () => { + // --(b|) + // -----(#|) + // --(e|) + // --b----(de|) + + const httpStub = sinon.stub() + + httpStub + .onCall(0) + .returns(cold('--(b|)', { b: vast.b.str })) + + httpStub + .onCall(1) + .returns(cold('-----(#|)', null, new Error('http failed'))) + + httpStub + .onCall(2) + .returns(cold('--(e|)', { e: vast.e.str })) + + httpStub.throws() + + const fx = { + http: fxFactory(httpStub) + } + + const actual$ = loadVast({ url: vast.b.url }, fx) + const expected = '--b----(de|)' + const values = { + b: { type: 'VAST_LOADED', vast: vast.b.model }, + d: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('301'), wrapper: vast.b.model.ads[0] }, + e: { type: 'VAST_LOADED', vast: vast.e.model } + } + + scheduler.expectObservable(actual$).toBe(expected, values) + scheduler.flush() + + expect(httpStub.callCount).to.equal(3) + expect(httpStub.getCall(0).args).to.deep.equal([vast.b.url, { method: 'GET', credentials: 'omit' }]) + expect(httpStub.getCall(1).args).to.deep.equal([vast.d.url, { method: 'GET', credentials: 'omit' }]) + expect(httpStub.getCall(2).args).to.deep.equal([vast.e.url, { method: 'GET', credentials: 'omit' }]) + }) + + after(() => { + vast.b.model.parent = vastBParent + }) + }) + + it('should return a VAST_LOADING_FAILED action when the XML document is not valid', () => { + const httpStub = sinon.stub() + + httpStub + .onCall(0) + .returns(cold('---(a|)', { a: 'malformed xml' })) + + httpStub.throws() + + const fx = { + http: fxFactory(httpStub) + } + + const actual$ = loadVast({ url: vast.standalone.url }, fx) + const expected = '---(a|)' + const values = { + a: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('100'), wrapper: null } + } + + scheduler.expectObservable(actual$).toBe(expected, values) + scheduler.flush() + + expect(httpStub.callCount).to.equal(1) + expect(httpStub.getCall(0).args).to.deep.equal([vast.standalone.url, { method: 'GET', credentials: 'omit' }]) }) it('should not fetch additional documents if maxDepth is exceeded', () => { // --(a|) // -----(b|) // --(c|) - // --a----(bc|) + // --a----(bdec|) const httpStub = sinon.stub() httpStub .onCall(0) - .returns(cold('--(a|)', { a: vastA.str })) + .returns(cold('--(a|)', { a: vast.a.str })) httpStub .onCall(1) - .returns(cold('-----(b|)', { b: vastB.str })) + .returns(cold('-----(b|)', { b: vast.b.str })) httpStub .onCall(2) - .returns(cold('--(c|)', { c: vastC.str })) + .returns(cold('--(c|)', { c: vast.c.str })) httpStub.throws() const fx = { http: fxFactory(httpStub) } - const actual$ = loadVast({ url: vastA.url, maxDepth: 1 }, fx) + const actual$ = loadVast({ url: vast.a.url, maxDepth: 1 }, fx) - const expected = '--a----(bc|)' + const expected = '--a----(bdec|)' const values = { - a: { type: 'VAST_LOADED', vast: vastA.model }, - b: { type: 'VAST_LOADED', vast: vastB.model }, - c: { type: 'VAST_LOADED', vast: vastC.model } + a: { type: 'VAST_LOADED', vast: vast.a.model }, + b: { type: 'VAST_LOADED', vast: vast.b.model }, + d: { type: 'VAST_LOADING_FAILED', wrapper: vast.b.model.ads[0], error: new VASTLoaderError('302') }, + e: { type: 'VAST_LOADING_FAILED', wrapper: vast.b.model.ads[2], error: new VASTLoaderError('302') }, + c: { type: 'VAST_LOADED', vast: vast.c.model } } scheduler.expectObservable(actual$).toBe(expected, values) scheduler.flush() expect(httpStub.callCount).to.equal(3) + expect(httpStub.getCall(0).args).to.deep.equal([vast.a.url, { method: 'GET', credentials: 'omit' }]) + expect(httpStub.getCall(1).args).to.deep.equal([vast.b.url, { method: 'GET', credentials: 'omit' }]) + expect(httpStub.getCall(2).args).to.deep.equal([vast.c.url, { method: 'GET', credentials: 'omit' }]) }) }) diff --git a/test/unit/vast-to-ad.js b/test/unit/vast-to-ad.js new file mode 100644 index 0000000..634d217 --- /dev/null +++ b/test/unit/vast-to-ad.js @@ -0,0 +1,104 @@ +import { vastToAd } from '../../src/vast-to-ad' +import { buildVast, buildAd } from '../lib/build-vast' +import { TestScheduler } from 'rxjs/testing/TestScheduler' +import VASTLoaderError from '../../src/error' + +const vast = buildVast() +const ad = buildAd(vast) + +const assertDeepEqual = (actual, expected) => { + expect(actual).to.deep.equal(expected) +} + +// const createInputValues = vast => +// Object.keys(vast) +// .reduce((acc, prop) => ({ +// ...acc, +// [prop]: { type: 'VAST_LOADED', vast: vast[prop].model } +// })) + +describe('#vastToAd()', () => { + let scheduler, cold + + beforeEach(() => { + scheduler = new TestScheduler(assertDeepEqual) + cold = scheduler.createColdObservable.bind(scheduler) + }) + + it('should map the vast$ to an ad$', () => { + // --a----b--d-----e------(c|) + // --(pq)-u--(yvw)-(zrs)--(xt|) + + const input$ = cold('--a----b--d-----e------(c|)', { + a: { type: 'VAST_LOADED', vast: vast.a.model }, + b: { type: 'VAST_LOADED', vast: vast.b.model }, + d: { type: 'VAST_LOADED', vast: vast.d.model }, + e: { type: 'VAST_LOADED', vast: vast.e.model }, + c: { type: 'VAST_LOADED', vast: vast.c.model } + }) + + const actual$ = vastToAd(input$) + const expected = '--(pq)-u--(yvw)-(zrs)--(xt|)' + const values = { + p: { type: 'AD_LOADED', ad: ad.p }, + q: { type: 'AD_LOADED', ad: ad.q }, + u: { type: 'AD_LOADED', ad: ad.u }, + y: { type: 'AD_LOADED', ad: ad.y }, + v: { type: 'AD_LOADED', ad: ad.v }, + w: { type: 'AD_LOADED', ad: ad.w }, + z: { type: 'AD_LOADED', ad: ad.z }, + r: { type: 'AD_LOADED', ad: ad.r }, + s: { type: 'AD_LOADED', ad: ad.s }, + x: { type: 'AD_LOADED', ad: ad.x }, + t: { type: 'AD_LOADED', ad: ad.t } + } + scheduler.expectObservable(actual$).toBe(expected, values) + scheduler.flush() + }) + + it('should correctly map VAST_LOADING_FAILED actions to AD_LOADING_FAILED actions', () => { + // --a----b--d-----e------(c|) + // --(pq)-u--(yvw)-(zrs)--(xt|) + + const input$ = cold('--a----b--d-----e------(c|)', { + a: { type: 'VAST_LOADED', vast: vast.a.model }, + b: { type: 'VAST_LOADED', vast: vast.b.model }, + d: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: vast.b.model.ads[0] }, + e: { type: 'VAST_LOADED', vast: vast.e.model }, + c: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: vast.a.model.ads[3] } + }) + + const actual$ = vastToAd(input$) + const expected = '--(pq)-u--(yvw)-(zrs)--(xt|)' + const values = { + p: { type: 'AD_LOADED', ad: ad.p }, + q: { type: 'AD_LOADED', ad: ad.q }, + u: { type: 'AD_LOADED', ad: ad.u }, + y: { type: 'AD_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: vast.b.model.ads[0] }, + v: { type: 'AD_LOADED', ad: ad.v }, + w: { type: 'AD_LOADED', ad: ad.w }, + z: { type: 'AD_LOADED', ad: ad.z }, + r: { type: 'AD_LOADED', ad: ad.r }, + s: { type: 'AD_LOADED', ad: ad.s }, + x: { type: 'AD_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: vast.a.model.ads[3] }, + t: { type: 'AD_LOADED', ad: ad.t } + } + scheduler.expectObservable(actual$).toBe(expected, values) + scheduler.flush() + }) + + it('should work correctly when the root VAST fails to download', () => { + const input$ = cold('--(a|)', { + a: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: null } + }) + + const actual$ = vastToAd(input$) + const expected = '--(p|)' + const values = { + p: { type: 'AD_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: null } + } + + scheduler.expectObservable(actual$).toBe(expected, values) + scheduler.flush() + }) +}) From c8dca54001854aecfdbeeb7d71576f7a63b9edf0 Mon Sep 17 00:00:00 2001 From: Laurent De Smet Date: Mon, 19 Jun 2017 17:49:45 +0200 Subject: [PATCH 05/13] add tests for rxjs-fx --- package.json | 3 ++- src/index.js | 2 +- src/load-vast.js | 30 ++++++---------------- src/rxjs-fx.js | 15 +++++++++++ test/unit/load-vast.js | 57 +++++++++++++++++------------------------ test/unit/rxjs-fx.js | 46 +++++++++++++++++++++++++++++++++ test/unit/vast-to-ad.js | 2 +- 7 files changed, 96 insertions(+), 59 deletions(-) create mode 100644 src/rxjs-fx.js create mode 100644 test/unit/rxjs-fx.js diff --git a/package.json b/package.json index 995b959..afc57bc 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "jsnext:main": "src/index.js", "author": "Zentrick nv (https://www.zentrick.com/)", "contributors": [ - "Tim De Pauw " + "Tim De Pauw ", + "Laurent De Smet " ], "engines": { "node": ">=4" diff --git a/src/index.js b/src/index.js index b27f3b1..abfc908 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ // @flow export { loadVast } from './load-vast' -export { mapVastLoadActionsToAdLoadActions } from './map-vast-load-actions-to-ad-load-actions' +export { vastToAd } from './vast-to-ad' diff --git a/src/load-vast.js b/src/load-vast.js index fb6b608..de762a8 100644 --- a/src/load-vast.js +++ b/src/load-vast.js @@ -4,15 +4,13 @@ import { type VAST, Wrapper } from 'iab-vast-model' import parse from 'iab-vast-parser' import VASTLoaderError from './error' import { concatEager } from './concat-eager' -import fetch from 'isomorphic-fetch' +import { fx } from './rxjs-fx' import { Observable } from 'rxjs/Observable' // RxJS statics import 'rxjs/add/observable/empty' import 'rxjs/add/observable/of' -import 'rxjs/add/observable/defer' -import 'rxjs/add/observable/fromPromise' import 'rxjs/add/observable/from' // RxJS operators @@ -36,14 +34,6 @@ export type VastLoadAction = VastLoadedAction | VastLoadingFailedAction -export const http = (url: string, options?: RequestOptions) => - Observable.defer(() => fetch(url, options)) - .mergeMap(res => Observable.fromPromise(res.text())) - -const defaultSfx = { - http -} - type Credentials = CredentialsType | (url: string) => CredentialsType type Config = { @@ -54,10 +44,6 @@ type Config = { credentials: Credentials[] } -type SFX = { - http: typeof http -} - const DEFAULT_OPTIONS = { maxDepth: 10, timeout: 10000, @@ -65,12 +51,12 @@ const DEFAULT_OPTIONS = { credentials: ['omit'] } -export const loadVast = (config: Config, sfx: SFX = defaultSfx): Observable => +export const loadVast = (config: Config): Observable => loadVastTree({ ...DEFAULT_OPTIONS, ...config, parent: null - }, sfx) + }) type LoadVastConfig = { url: string, @@ -82,11 +68,11 @@ type LoadVastConfig = { } // Traverse the tree using a preorder depth first strategy. -const loadVastTree = (config: LoadVastConfig, sfx: SFX): Observable => { +const loadVastTree = (config: LoadVastConfig): Observable => { // We add share() because we want a multicast observable here, because // loadVast$ is subscribed to in multiple places and this would result in // multiple ajax requests for the same VAST document. - const loadVast$ = fetchVast(config, sfx).share() + const loadVast$ = fetchVast(config).share() const children$ = loadVast$ .concatMap(output => { @@ -112,7 +98,7 @@ const loadVastTree = (config: LoadVastConfig, sfx: SFX): Observable .filter(ad => ad instanceof Wrapper) // This function returns a stream with exact one event: a success or error event. -const fetchVast = (config: LoadVastConfig, sfx: SFX): Observable => +const fetchVast = (config: LoadVastConfig): Observable => Observable.from(config.credentials) .map(credentials => typeof credentials === 'string' @@ -140,7 +126,7 @@ const fetchVast = (config: LoadVastConfig, sfx: SFX): Observable : credentials(config.url) ) .mergeMap(credentials => - sfx + fx .http(config.url, { method: 'GET', credentials diff --git a/src/rxjs-fx.js b/src/rxjs-fx.js new file mode 100644 index 0000000..1fb0fd2 --- /dev/null +++ b/src/rxjs-fx.js @@ -0,0 +1,15 @@ +// @flow + +import fetch from 'isomorphic-fetch' +import { Observable } from 'rxjs/Observable' +import 'rxjs/add/observable/defer' +import 'rxjs/add/operator/mergeMap' +import 'rxjs/add/observable/fromPromise' + +const http = (url: string, options?: RequestOptions) => + Observable.defer(() => fetch(url, options)) + .mergeMap(res => res.ok ? Observable.fromPromise(res.text()) : Observable.throw(new Error('Http Failed'))) + +export const fx = { + http +} diff --git a/test/unit/load-vast.js b/test/unit/load-vast.js index 167ce0f..55ce80c 100644 --- a/test/unit/load-vast.js +++ b/test/unit/load-vast.js @@ -1,6 +1,7 @@ -import { loadVast } from '../../src/load-vast' +import { loadVast } from '../../src' import VASTLoaderError from '../../src/error' import { buildVast } from '../lib/build-vast' +import { fx } from '../../src/rxjs-fx' import { TestScheduler } from 'rxjs/testing/TestScheduler' import { Observable } from 'rxjs/Observable' @@ -30,16 +31,20 @@ const assertDeepEqual = (actual, expected) => { const vast = buildVast() describe('#loadVast()', () => { - let scheduler, cold + let scheduler, cold, http beforeEach(() => { scheduler = new TestScheduler(assertDeepEqual) monkeyPatch(scheduler) cold = scheduler.createColdObservable.bind(scheduler) + // http will be stubbed in each test so we keep a reference to the original. + http = fx.http }) afterEach(() => { restore() + // We restore the original http function. + fx.http = http }) it('should load a VAST document', () => { @@ -51,11 +56,9 @@ describe('#loadVast()', () => { httpStub.throws() - const fx = { - http: fxFactory(httpStub) - } + fx.http = fxFactory(httpStub) - const actual$ = loadVast({ url: vast.standalone.url }, fx) + const actual$ = loadVast({ url: vast.standalone.url }) const expected = '---(a|)' const values = { a: { type: 'VAST_LOADED', vast: vast.standalone.model } } @@ -96,9 +99,7 @@ describe('#loadVast()', () => { credentialStub.throws() - const fx = { - http: fxFactory(httpStub) - } + fx.http = fxFactory(httpStub) const actual$ = loadVast({ url: vast.standalone.url, @@ -107,7 +108,7 @@ describe('#loadVast()', () => { 'same-origin', credentialStub ] - }, fx) + }) const expected = '---(a|)' const values = { a: { type: 'VAST_LOADED', vast: vast.standalone.model } } @@ -141,11 +142,9 @@ describe('#loadVast()', () => { httpStub.throws() - const fx = { - http: fxFactory(httpStub) - } + fx.http = fxFactory(httpStub) - const actual$ = loadVast({ url: vast.standalone.url, retryCount: 2 }, fx) + const actual$ = loadVast({ url: vast.standalone.url, retryCount: 2 }) const expected = '---------(a|)' const values = { a: { type: 'VAST_LOADED', vast: vast.standalone.model } } @@ -187,11 +186,9 @@ describe('#loadVast()', () => { httpStub.throws() - const fx = { - http: fxFactory(httpStub) - } + fx.http = fxFactory(httpStub) - const actual$ = loadVast({ url: vast.a.url }, fx) + const actual$ = loadVast({ url: vast.a.url }) const expected = '--a----b-d--(ec|)' const values = { a: { type: 'VAST_LOADED', vast: vast.a.model }, @@ -223,11 +220,9 @@ describe('#loadVast()', () => { httpStub.throws() - const fx = { - http: fxFactory(httpStub) - } + fx.http = fxFactory(httpStub) - const actual$ = loadVast({ url: vast.standalone.url, timeout: 20 }, fx) + const actual$ = loadVast({ url: vast.standalone.url, timeout: 20 }) const expected = '--(a|)' const values = { a: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: null } @@ -270,11 +265,9 @@ describe('#loadVast()', () => { httpStub.throws() - const fx = { - http: fxFactory(httpStub) - } + fx.http = fxFactory(httpStub) - const actual$ = loadVast({ url: vast.b.url }, fx) + const actual$ = loadVast({ url: vast.b.url }) const expected = '--b----(de|)' const values = { b: { type: 'VAST_LOADED', vast: vast.b.model }, @@ -305,11 +298,9 @@ describe('#loadVast()', () => { httpStub.throws() - const fx = { - http: fxFactory(httpStub) - } + fx.http = fxFactory(httpStub) - const actual$ = loadVast({ url: vast.standalone.url }, fx) + const actual$ = loadVast({ url: vast.standalone.url }) const expected = '---(a|)' const values = { a: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('100'), wrapper: null } @@ -344,10 +335,8 @@ describe('#loadVast()', () => { httpStub.throws() - const fx = { - http: fxFactory(httpStub) - } - const actual$ = loadVast({ url: vast.a.url, maxDepth: 1 }, fx) + fx.http = fxFactory(httpStub) + const actual$ = loadVast({ url: vast.a.url, maxDepth: 1 }) const expected = '--a----(bdec|)' const values = { diff --git a/test/unit/rxjs-fx.js b/test/unit/rxjs-fx.js new file mode 100644 index 0000000..ee55278 --- /dev/null +++ b/test/unit/rxjs-fx.js @@ -0,0 +1,46 @@ +import express from 'express' +import path from 'path' +import { fx } from '../../src/rxjs-fx' +import { buildVast } from '../lib/build-vast' +import { Observable } from 'rxjs/Observable' + +const fixturesPath = path.resolve(__dirname, '../fixtures') +const vast = buildVast() + +describe('fx', () => { + describe('#http()', () => { + let baseUrl, server + + before((cb) => { + const app = express() + app.use(express.static(fixturesPath)) + server = app.listen(() => { + baseUrl = 'http://localhost:' + server.address().port + '/' + cb() + }) + }) + + after((cb) => { + server.close(cb) + }) + + it('should fetch the correct resource', async () => { + const http$ = fx.http(baseUrl + 'vast-standalone.xml') + + // First we map the Observable to an Observable with all the observed events in one array, + // which we then convert to a promise. + const actual = await http$.toArray().toPromise() + + expect(actual).to.deep.equal([vast.standalone.str]) + }) + + it('should emit an error when fetching fails', async () => { + const http$ = fx.http(baseUrl + 'non-existent.xml') + const error = new Error('http failed') + + const actual = await http$.catch(() => Observable.of(error)).toArray().toPromise() + + expect(actual).to.deep.equal([error]) + }) + }) +}) diff --git a/test/unit/vast-to-ad.js b/test/unit/vast-to-ad.js index 634d217..d4cd822 100644 --- a/test/unit/vast-to-ad.js +++ b/test/unit/vast-to-ad.js @@ -1,4 +1,4 @@ -import { vastToAd } from '../../src/vast-to-ad' +import { vastToAd } from '../../src' import { buildVast, buildAd } from '../lib/build-vast' import { TestScheduler } from 'rxjs/testing/TestScheduler' import VASTLoaderError from '../../src/error' From 2475befff88619d22af129339a4242c9be4c771d Mon Sep 17 00:00:00 2001 From: Laurent De Smet Date: Tue, 20 Jun 2017 09:28:52 +0200 Subject: [PATCH 06/13] remove package-lock.json file --- package-lock.json | 3504 --------------------------------------------- 1 file changed, 3504 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index cafd10f..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3504 +0,0 @@ -{ - "name": "iab-vast-loader", - "version": "0.8.0", - "lockfileVersion": 1, - "dependencies": { - "@gulp-sourcemaps/identity-map": { - "version": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.1.tgz", - "integrity": "sha1-z6I7xYQPkQTOMqZedNt+epdLvuE=", - "dev": true, - "dependencies": { - "acorn": { - "version": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz", - "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=", - "dev": true - } - } - }, - "@gulp-sourcemaps/map-sources": { - "version": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", - "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", - "dev": true - }, - "abbrev": { - "version": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true - }, - "accepts": { - "version": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", - "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", - "dev": true - }, - "acorn": { - "version": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", - "dev": true - }, - "acorn-jsx": { - "version": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "dependencies": { - "acorn": { - "version": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "ajv": { - "version": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true - }, - "ajv-keywords": { - "version": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "dev": true - }, - "align-text": { - "version": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true - }, - "amdefine": { - "version": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, - "ansi-escapes": { - "version": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "ansi-regex": { - "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "any-promise": { - "version": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", - "dev": true - }, - "app-root-path": { - "version": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz", - "integrity": "sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=", - "dev": true - }, - "archy": { - "version": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, - "argparse": { - "version": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", - "dev": true - }, - "arr-diff": { - "version": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true - }, - "arr-flatten": { - "version": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.3.tgz", - "integrity": "sha1-onTthawIhJtr14R8RYB0XcUa37E=", - "dev": true - }, - "array-differ": { - "version": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true - }, - "array-flatten": { - "version": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true - }, - "array-union": { - "version": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true - }, - "array-uniq": { - "version": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "array.prototype.find": { - "version": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.0.4.tgz", - "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=", - "dev": true - }, - "arrify": { - "version": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asn1": { - "version": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true - }, - "assert-plus": { - "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", - "dev": true - }, - "assertion-error": { - "version": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", - "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", - "dev": true - }, - "async": { - "version": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "asynckit": { - "version": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "https://registry.npmjs.org/atob/-/atob-1.1.3.tgz", - "integrity": "sha1-lfE2KbEsOlGl0hWr3OKqnzL4B3M=", - "dev": true - }, - "aws-sign2": { - "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", - "dev": true - }, - "aws4": { - "version": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", - "dev": true - }, - "babel-code-frame": { - "version": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", - "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", - "dev": true - }, - "babel-core": { - "version": "https://registry.npmjs.org/babel-core/-/babel-core-6.24.1.tgz", - "integrity": "sha1-jEKFZNzh4fQfszfsNPTDsCK1rYM=", - "dev": true - }, - "babel-generator": { - "version": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.24.1.tgz", - "integrity": "sha1-5xX0hsWN7SVknYiJRNUqoHxdlJc=", - "dev": true, - "dependencies": { - "jsesc": { - "version": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - } - } - }, - "babel-helper-builder-binary-assignment-operator-visitor": { - "version": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", - "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", - "dev": true - }, - "babel-helper-call-delegate": { - "version": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", - "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", - "dev": true - }, - "babel-helper-define-map": { - "version": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz", - "integrity": "sha1-epdH8ljYlH0y1RX2qhx70CIEoIA=", - "dev": true - }, - "babel-helper-explode-assignable-expression": { - "version": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", - "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", - "dev": true - }, - "babel-helper-function-name": { - "version": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", - "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", - "dev": true - }, - "babel-helper-get-function-arity": { - "version": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", - "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", - "dev": true - }, - "babel-helper-hoist-variables": { - "version": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", - "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", - "dev": true - }, - "babel-helper-optimise-call-expression": { - "version": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", - "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", - "dev": true - }, - "babel-helper-regex": { - "version": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz", - "integrity": "sha1-024i+rEAjXnYhkjjIRaGgShFbOg=", - "dev": true - }, - "babel-helper-remap-async-to-generator": { - "version": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", - "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", - "dev": true - }, - "babel-helper-replace-supers": { - "version": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", - "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", - "dev": true - }, - "babel-helpers": { - "version": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true - }, - "babel-messages": { - "version": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true - }, - "babel-plugin-check-es2015-constants": { - "version": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", - "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", - "dev": true - }, - "babel-plugin-syntax-async-functions": { - "version": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", - "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", - "dev": true - }, - "babel-plugin-syntax-exponentiation-operator": { - "version": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", - "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", - "dev": true - }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", - "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", - "dev": true - }, - "babel-plugin-transform-async-to-generator": { - "version": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", - "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", - "dev": true - }, - "babel-plugin-transform-builtin-extend": { - "version": "https://registry.npmjs.org/babel-plugin-transform-builtin-extend/-/babel-plugin-transform-builtin-extend-1.1.2.tgz", - "integrity": "sha1-Xpb+z1i4+h7XTvytiEdbKvPJEW4=", - "dev": true - }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", - "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", - "dev": true - }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", - "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", - "dev": true - }, - "babel-plugin-transform-es2015-block-scoping": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz", - "integrity": "sha1-dsKV3DpHQbFmWt/TFnIV3P8ypXY=", - "dev": true - }, - "babel-plugin-transform-es2015-classes": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", - "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", - "dev": true - }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", - "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", - "dev": true - }, - "babel-plugin-transform-es2015-destructuring": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", - "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", - "dev": true - }, - "babel-plugin-transform-es2015-duplicate-keys": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", - "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", - "dev": true - }, - "babel-plugin-transform-es2015-for-of": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", - "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", - "dev": true - }, - "babel-plugin-transform-es2015-function-name": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", - "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", - "dev": true - }, - "babel-plugin-transform-es2015-literals": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", - "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", - "dev": true - }, - "babel-plugin-transform-es2015-modules-amd": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", - "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", - "dev": true - }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz", - "integrity": "sha1-0+MQtA72ZKNmIiAAl8bUQCmPK/4=", - "dev": true - }, - "babel-plugin-transform-es2015-modules-systemjs": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", - "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", - "dev": true - }, - "babel-plugin-transform-es2015-modules-umd": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", - "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", - "dev": true - }, - "babel-plugin-transform-es2015-object-super": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", - "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", - "dev": true - }, - "babel-plugin-transform-es2015-parameters": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", - "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", - "dev": true - }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", - "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", - "dev": true - }, - "babel-plugin-transform-es2015-spread": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", - "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", - "dev": true - }, - "babel-plugin-transform-es2015-sticky-regex": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", - "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", - "dev": true - }, - "babel-plugin-transform-es2015-template-literals": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", - "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", - "dev": true - }, - "babel-plugin-transform-es2015-typeof-symbol": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", - "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", - "dev": true - }, - "babel-plugin-transform-es2015-unicode-regex": { - "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", - "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", - "dev": true - }, - "babel-plugin-transform-exponentiation-operator": { - "version": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", - "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", - "dev": true - }, - "babel-plugin-transform-regenerator": { - "version": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz", - "integrity": "sha1-uNowWtQ8PJm0hI5P5AN7dw0jxBg=", - "dev": true - }, - "babel-plugin-transform-runtime": { - "version": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", - "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", - "dev": true - }, - "babel-plugin-transform-strict-mode": { - "version": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", - "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", - "dev": true - }, - "babel-preset-env": { - "version": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.5.2.tgz", - "integrity": "sha1-zUrpCm6Utwn5c3SzPl+LmDVWre8=", - "dev": true - }, - "babel-preset-es2015": { - "version": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", - "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", - "dev": true - }, - "babel-register": { - "version": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz", - "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118=", - "dev": true - }, - "babel-runtime": { - "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", - "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=", - "dev": true - }, - "babel-template": { - "version": "https://registry.npmjs.org/babel-template/-/babel-template-6.24.1.tgz", - "integrity": "sha1-BK5RTx+Ts6JTfyoPYKWkX7gwgzM=", - "dev": true - }, - "babel-traverse": { - "version": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.24.1.tgz", - "integrity": "sha1-qzZnP9NW+aCUhlnnszjV/q2zFpU=", - "dev": true - }, - "babel-types": { - "version": "https://registry.npmjs.org/babel-types/-/babel-types-6.24.1.tgz", - "integrity": "sha1-oTaHncFbNga9oNkMH8dDBML/CXU=", - "dev": true - }, - "babylon": { - "version": "https://registry.npmjs.org/babylon/-/babylon-6.17.2.tgz", - "integrity": "sha1-IB0l71+JLEG65JSIsI2w3Udun1w=", - "dev": true - }, - "balanced-match": { - "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "dev": true, - "optional": true - }, - "beeper": { - "version": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true - }, - "boom": { - "version": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "dev": true - }, - "brace-expansion": { - "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", - "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", - "dev": true - }, - "braces": { - "version": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true - }, - "browser-stdout": { - "version": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", - "dev": true - }, - "browserslist": { - "version": "https://registry.npmjs.org/browserslist/-/browserslist-2.1.4.tgz", - "integrity": "sha1-zFJq9KExK30uBWU+VtDIq3DA4FM=", - "dev": true - }, - "builtin-modules": { - "version": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "caller-path": { - "version": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true - }, - "callsites": { - "version": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "camelcase": { - "version": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true, - "optional": true - }, - "caniuse-lite": { - "version": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000680.tgz", - "integrity": "sha1-2U2BKURxYX6GUA8Kq5DxHSK8iTQ=", - "dev": true - }, - "caseless": { - "version": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", - "dev": true - }, - "center-align": { - "version": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "optional": true - }, - "chai": { - "version": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", - "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", - "dev": true - }, - "chai-as-promised": { - "version": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-6.0.0.tgz", - "integrity": "sha1-GgKkM6byTa+sY7nJb6FoTbGqjaY=", - "dev": true - }, - "chalk": { - "version": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true - }, - "charenc": { - "version": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", - "dev": true - }, - "check-error": { - "version": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "circular-json": { - "version": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz", - "integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=", - "dev": true - }, - "cli-cursor": { - "version": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true - }, - "cli-width": { - "version": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz", - "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=", - "dev": true - }, - "cliui": { - "version": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "optional": true, - "dependencies": { - "wordwrap": { - "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true, - "optional": true - } - } - }, - "clone": { - "version": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", - "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=", - "dev": true - }, - "clone-stats": { - "version": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "co": { - "version": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "colors": { - "version": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true - }, - "combined-stream": { - "version": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", - "dev": true - }, - "commander": { - "version": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true - }, - "concat-map": { - "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", - "dev": true, - "dependencies": { - "isarray": { - "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.11.tgz", - "integrity": "sha1-B5azH412iAB/8Lk6gIjTSqF8D3I=", - "dev": true - }, - "string_decoder": { - "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", - "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=", - "dev": true - } - } - }, - "content-disposition": { - "version": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true - }, - "content-type": { - "version": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", - "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=", - "dev": true - }, - "convert-source-map": { - "version": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", - "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=", - "dev": true - }, - "cookie": { - "version": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "cookie-signature": { - "version": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "core-js": { - "version": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", - "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=", - "dev": true - }, - "core-util-is": { - "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "coveralls": { - "version": "https://registry.npmjs.org/coveralls/-/coveralls-2.13.1.tgz", - "integrity": "sha1-1wu5rMGDXsTwY/+drFQjwXsR8Xg=", - "dev": true, - "dependencies": { - "minimist": { - "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "crypt": { - "version": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", - "dev": true - }, - "cryptiles": { - "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "dev": true - }, - "css": { - "version": "https://registry.npmjs.org/css/-/css-2.2.1.tgz", - "integrity": "sha1-c6TIHehdtmTU7mdPfUcIXjstVdw=", - "dev": true, - "dependencies": { - "source-map": { - "version": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "dev": true - } - } - }, - "d": { - "version": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true - }, - "dashdash": { - "version": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "dependencies": { - "assert-plus": { - "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, - "dateformat": { - "version": "https://registry.npmjs.org/dateformat/-/dateformat-2.0.0.tgz", - "integrity": "sha1-J0Pjq7XD/CRi5SfcpEXgTp9N7hc=", - "dev": true - }, - "debug": { - "version": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", - "dev": true - }, - "debug-fabulous": { - "version": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-0.1.0.tgz", - "integrity": "sha1-rQ6gel1RkyT7VYQqjzTuWcf4/2w=", - "dev": true, - "dependencies": { - "object-assign": { - "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", - "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", - "dev": true - } - } - }, - "debug-log": { - "version": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", - "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=", - "dev": true - }, - "decamelize": { - "version": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "deep-eql": { - "version": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", - "dev": true, - "dependencies": { - "type-detect": { - "version": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", - "dev": true - } - } - }, - "deep-is": { - "version": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "defaults": { - "version": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true - }, - "define-properties": { - "version": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", - "dev": true - }, - "deglob": { - "version": "https://registry.npmjs.org/deglob/-/deglob-2.1.0.tgz", - "integrity": "sha1-TUSr4W7zLHebSXK9FBqAMlApoUo=", - "dev": true - }, - "del": { - "version": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true - }, - "delayed-stream": { - "version": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "depd": { - "version": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", - "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=", - "dev": true - }, - "deprecated": { - "version": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz", - "integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=", - "dev": true - }, - "destroy": { - "version": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "detect-file": { - "version": "https://registry.npmjs.org/detect-file/-/detect-file-0.1.0.tgz", - "integrity": "sha1-STXe39lIhkjgBrASlWbpOGcR6mM=", - "dev": true - }, - "detect-indent": { - "version": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true - }, - "detect-newline": { - "version": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true - }, - "diff": { - "version": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", - "dev": true - }, - "dirty-chai": { - "version": "https://registry.npmjs.org/dirty-chai/-/dirty-chai-1.2.2.tgz", - "integrity": "sha1-eEleYZY19/5EIZqkyDeEm/GDFC4=", - "dev": true - }, - "doctrine": { - "version": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", - "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", - "dev": true, - "dependencies": { - "isarray": { - "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "duplexer2": { - "version": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true - }, - "duplexify": { - "version": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.0.tgz", - "integrity": "sha1-GqdzAC4VeEV+nZ1KULDMquvL1gQ=", - "dev": true, - "dependencies": { - "end-of-stream": { - "version": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz", - "integrity": "sha1-1FlucCc0qT5A6a+GQxnqvZn/Lw4=", - "dev": true - }, - "isarray": { - "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "once": { - "version": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", - "dev": true - }, - "readable-stream": { - "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.11.tgz", - "integrity": "sha1-B5azH412iAB/8Lk6gIjTSqF8D3I=", - "dev": true - }, - "string_decoder": { - "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", - "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=", - "dev": true - } - } - }, - "ecc-jsbn": { - "version": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, - "optional": true - }, - "ee-first": { - "version": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "electron-to-chromium": { - "version": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.13.tgz", - "integrity": "sha1-GzperObgh7teJXoQCwy/6Bsokfw=", - "dev": true - }, - "encodeurl": { - "version": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=", - "dev": true - }, - "encoding": { - "version": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=" - }, - "end-of-stream": { - "version": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", - "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", - "dev": true, - "dependencies": { - "once": { - "version": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", - "dev": true - } - } - }, - "error-ex": { - "version": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true - }, - "es-abstract": { - "version": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.7.0.tgz", - "integrity": "sha1-363ndOAb/Nl/lhgCmMRJyGI/uUw=", - "dev": true - }, - "es-to-primitive": { - "version": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", - "dev": true - }, - "es5-ext": { - "version": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.23.tgz", - "integrity": "sha1-dXi1G+l0IHpUh4IbVlOMIk5Oezg=", - "dev": true - }, - "es6-iterator": { - "version": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", - "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=", - "dev": true - }, - "es6-map": { - "version": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true - }, - "es6-set": { - "version": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true - }, - "es6-symbol": { - "version": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true - }, - "es6-weak-map": { - "version": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "dev": true - }, - "escape-html": { - "version": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "dev": true, - "dependencies": { - "source-map": { - "version": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "dev": true, - "optional": true - } - } - }, - "escope": { - "version": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "dependencies": { - "estraverse": { - "version": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - } - } - }, - "eslint": { - "version": "https://registry.npmjs.org/eslint/-/eslint-3.18.0.tgz", - "integrity": "sha1-ZH6YXErnFQLSCsYsEJ9m1RBMiks=", - "dev": true, - "dependencies": { - "estraverse": { - "version": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "strip-bom": { - "version": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "user-home": { - "version": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "dev": true - } - } - }, - "eslint-config-standard": { - "version": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-7.1.0.tgz", - "integrity": "sha1-R+dp6gc59bLVaTsaUBwhyWUPr88=", - "dev": true - }, - "eslint-config-standard-jsx": { - "version": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-3.3.0.tgz", - "integrity": "sha1-yrCAGhWjYL9j+suXqyL73YjYpeA=", - "dev": true - }, - "eslint-plugin-promise": { - "version": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.4.2.tgz", - "integrity": "sha1-G+J5Pq/i0YtbEjuBNsJp+AT+cSI=", - "dev": true - }, - "eslint-plugin-react": { - "version": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-6.9.0.tgz", - "integrity": "sha1-VMLpkGt2+dEBQgML3DTp1oQKC7I=", - "dev": true, - "dependencies": { - "doctrine": { - "version": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true - }, - "isarray": { - "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "eslint-plugin-standard": { - "version": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-2.0.1.tgz", - "integrity": "sha1-NYlpn/nJF/LCX3apFmh/ZBw2n/M=", - "dev": true - }, - "espree": { - "version": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz", - "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=", - "dev": true, - "dependencies": { - "acorn": { - "version": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz", - "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=", - "dev": true - } - } - }, - "esprima": { - "version": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "esquery": { - "version": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", - "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", - "dev": true, - "dependencies": { - "estraverse": { - "version": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - } - } - }, - "esrecurse": { - "version": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.1.0.tgz", - "integrity": "sha1-RxO2U2rffyrE8yfVWed1a/9kgiA=", - "dev": true, - "dependencies": { - "estraverse": { - "version": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz", - "integrity": "sha1-9srKcokzqFDvkGYdDheYK6RxEaI=", - "dev": true - } - } - }, - "estraverse": { - "version": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true - }, - "esutils": { - "version": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "etag": { - "version": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", - "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=", - "dev": true - }, - "event-emitter": { - "version": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true - }, - "eventemitter3": { - "version": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", - "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=" - }, - "exit-hook": { - "version": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, - "expand-brackets": { - "version": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true - }, - "expand-range": { - "version": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true - }, - "expand-tilde": { - "version": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", - "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=", - "dev": true - }, - "express": { - "version": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", - "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=", - "dev": true, - "dependencies": { - "debug": { - "version": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", - "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", - "dev": true - } - } - }, - "extend": { - "version": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "dev": true - }, - "extglob": { - "version": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true - }, - "extsprintf": { - "version": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", - "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=", - "dev": true - }, - "fancy-log": { - "version": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.0.tgz", - "integrity": "sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg=", - "dev": true - }, - "fast-levenshtein": { - "version": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true - }, - "file-entry-cache": { - "version": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true - }, - "filename-regex": { - "version": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fill-range": { - "version": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", - "dev": true - }, - "finalhandler": { - "version": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", - "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=", - "dev": true, - "dependencies": { - "debug": { - "version": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", - "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", - "dev": true - } - } - }, - "find-index": { - "version": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", - "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=", - "dev": true - }, - "find-root": { - "version": "https://registry.npmjs.org/find-root/-/find-root-1.0.0.tgz", - "integrity": "sha1-li/yEaqyXGUg/u641ih/j26VgHo=", - "dev": true - }, - "find-up": { - "version": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true - }, - "findup-sync": { - "version": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.4.3.tgz", - "integrity": "sha1-QAQ5Kee8YK3wt/SCfExudaDeyhI=", - "dev": true - }, - "fined": { - "version": "https://registry.npmjs.org/fined/-/fined-1.0.2.tgz", - "integrity": "sha1-WyhCS3YNdZiWC374SA3/itNmDpc=", - "dev": true - }, - "first-chunk-stream": { - "version": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", - "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=", - "dev": true - }, - "flagged-respawn": { - "version": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-0.3.2.tgz", - "integrity": "sha1-/xke3c1wiKZ1smEP/8l2vpuAdLU=", - "dev": true - }, - "flat-cache": { - "version": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", - "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=", - "dev": true - }, - "for-in": { - "version": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true - }, - "foreach": { - "version": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "forever-agent": { - "version": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "fork-stream": { - "version": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz", - "integrity": "sha1-24Sfznf2cIpfjzhq5TOgkHtUrnA=", - "dev": true - }, - "form-data": { - "version": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "dev": true - }, - "formatio": { - "version": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", - "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", - "dev": true - }, - "forwarded": { - "version": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", - "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=", - "dev": true - }, - "fresh": { - "version": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", - "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=", - "dev": true - }, - "fs-exists-sync": { - "version": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", - "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=", - "dev": true - }, - "fs-extra": { - "version": "https://registry.npmjs.org/fs-extra/-/fs-extra-2.1.2.tgz", - "integrity": "sha1-BGxwFjzvmq1GsOSn+kZ/si1x3jU=", - "dev": true - }, - "fs-promise": { - "version": "https://registry.npmjs.org/fs-promise/-/fs-promise-2.0.3.tgz", - "integrity": "sha1-9k5PhUvPaJqovdy6JokW2z20aFQ=", - "dev": true - }, - "fs.realpath": { - "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "function-bind": { - "version": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", - "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=", - "dev": true - }, - "gaze": { - "version": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", - "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", - "dev": true - }, - "generate-function": { - "version": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", - "dev": true - }, - "generate-object-property": { - "version": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true - }, - "get-caller-file": { - "version": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "dev": true - }, - "get-stdin": { - "version": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", - "dev": true - }, - "getpass": { - "version": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "dependencies": { - "assert-plus": { - "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, - "glob": { - "version": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", - "dev": true - }, - "glob-base": { - "version": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true - }, - "glob-parent": { - "version": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true - }, - "glob-stream": { - "version": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", - "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", - "dev": true, - "dependencies": { - "glob": { - "version": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", - "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", - "dev": true - }, - "minimatch": { - "version": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", - "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", - "dev": true - }, - "readable-stream": { - "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true - }, - "through2": { - "version": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "dev": true - } - } - }, - "glob-watcher": { - "version": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", - "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", - "dev": true - }, - "glob2base": { - "version": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", - "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", - "dev": true - }, - "global-modules": { - "version": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", - "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", - "dev": true - }, - "global-prefix": { - "version": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", - "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", - "dev": true - }, - "globals": { - "version": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", - "dev": true - }, - "globby": { - "version": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true - }, - "globule": { - "version": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", - "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", - "dev": true, - "dependencies": { - "glob": { - "version": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", - "dev": true - }, - "graceful-fs": { - "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", - "dev": true - }, - "inherits": { - "version": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", - "dev": true - }, - "lodash": { - "version": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", - "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", - "dev": true - }, - "minimatch": { - "version": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "dev": true - } - } - }, - "glogg": { - "version": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", - "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=", - "dev": true - }, - "graceful-fs": { - "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "graceful-readlink": { - "version": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, - "growl": { - "version": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", - "dev": true - }, - "gulp": { - "version": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", - "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", - "dev": true, - "dependencies": { - "minimist": { - "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "semver": { - "version": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", - "dev": true - } - } - }, - "gulp-babel": { - "version": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-6.1.2.tgz", - "integrity": "sha1-fAF25Lo/JExgWIoMSzIKRdGt784=", - "dev": true - }, - "gulp-coveralls": { - "version": "https://registry.npmjs.org/gulp-coveralls/-/gulp-coveralls-0.1.4.tgz", - "integrity": "sha1-L2IKyN9i0LhrS73mTaNnzEGhkMk=", - "dev": true, - "dependencies": { - "through2": { - "version": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", - "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", - "dev": true - } - } - }, - "gulp-if": { - "version": "https://registry.npmjs.org/gulp-if/-/gulp-if-2.0.2.tgz", - "integrity": "sha1-pJe351cwBQQcqivIt92jyARE1ik=", - "dev": true - }, - "gulp-istanbul": { - "version": "https://registry.npmjs.org/gulp-istanbul/-/gulp-istanbul-1.1.2.tgz", - "integrity": "sha1-r2X6KL/bNXbaq5Xc+qcypqJ8Wgc=", - "dev": true - }, - "gulp-load-plugins": { - "version": "https://registry.npmjs.org/gulp-load-plugins/-/gulp-load-plugins-1.5.0.tgz", - "integrity": "sha1-TEGffldk2aDjMGG6uWGPgbc9QXE=", - "dev": true - }, - "gulp-match": { - "version": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.0.3.tgz", - "integrity": "sha1-kcfA1/Kb7NZgbVfYCn+Hdqh6uo4=", - "dev": true - }, - "gulp-mocha": { - "version": "https://registry.npmjs.org/gulp-mocha/-/gulp-mocha-3.0.1.tgz", - "integrity": "sha1-qwyiw5QDcYF03drXUOY6Yb4X4EE=", - "dev": true - }, - "gulp-sourcemaps": { - "version": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.0.tgz", - "integrity": "sha1-fMzomaijv8oVk6M0jQ+/Qd0/UeU=", - "dev": true, - "dependencies": { - "vinyl": { - "version": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", - "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", - "dev": true - } - } - }, - "gulp-standard": { - "version": "https://registry.npmjs.org/gulp-standard/-/gulp-standard-9.0.0.tgz", - "integrity": "sha1-V1mdtXRJrreZIkAxW1Yx1RwHnMc=", - "dev": true - }, - "gulp-util": { - "version": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "dev": true, - "dependencies": { - "minimist": { - "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "object-assign": { - "version": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true - } - } - }, - "gulplog": { - "version": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", - "dev": true - }, - "handlebars": { - "version": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", - "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", - "dev": true, - "dependencies": { - "source-map": { - "version": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true - } - } - }, - "har-validator": { - "version": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", - "dev": true - }, - "has": { - "version": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", - "dev": true - }, - "has-ansi": { - "version": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true - }, - "has-color": { - "version": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", - "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", - "dev": true - }, - "has-flag": { - "version": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "has-gulplog": { - "version": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "dev": true - }, - "hawk": { - "version": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "dev": true - }, - "hoek": { - "version": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", - "dev": true - }, - "home-or-tmp": { - "version": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true - }, - "homedir-polyfill": { - "version": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", - "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", - "dev": true - }, - "hosted-git-info": { - "version": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.4.2.tgz", - "integrity": "sha1-AHa59GonBQbduq6lZJaJdGBhKmc=", - "dev": true - }, - "http-errors": { - "version": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", - "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=", - "dev": true - }, - "http-signature": { - "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "dev": true - }, - "iab-vast-model": { - "version": "https://registry.npmjs.org/iab-vast-model/-/iab-vast-model-0.4.0.tgz", - "integrity": "sha1-P5GOVzyBxtEHprwyI0JZHY4kMGg=" - }, - "iab-vast-parser": { - "version": "https://registry.npmjs.org/iab-vast-parser/-/iab-vast-parser-0.4.1.tgz", - "integrity": "sha1-QySUGbljP4aryx2w3/mo8a6Dnv8=" - }, - "iconv-lite": { - "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.17.tgz", - "integrity": "sha1-T9qjs4rLwsAxsEXQ7c3+HsqxjI0=" - }, - "ignore": { - "version": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz", - "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=", - "dev": true - }, - "imurmurhash": { - "version": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "in-publish": { - "version": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", - "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", - "dev": true - }, - "inflight": { - "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true - }, - "inherits": { - "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ini": { - "version": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", - "dev": true - }, - "inquirer": { - "version": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", - "dev": true - }, - "interpret": { - "version": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz", - "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=", - "dev": true - }, - "invariant": { - "version": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", - "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", - "dev": true - }, - "invert-kv": { - "version": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "ipaddr.js": { - "version": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz", - "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew=", - "dev": true - }, - "irregular-plurals": { - "version": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.2.0.tgz", - "integrity": "sha1-OPKZg0uowAwwvpxVThNyaXUv86w=", - "dev": true - }, - "is-absolute": { - "version": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz", - "integrity": "sha1-IN5p89uULvLYe5wto28XIjWxtes=", - "dev": true - }, - "is-arrayish": { - "version": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-buffer": { - "version": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", - "dev": true - }, - "is-builtin-module": { - "version": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true - }, - "is-callable": { - "version": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", - "dev": true - }, - "is-date-object": { - "version": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-dotfile": { - "version": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true - }, - "is-extendable": { - "version": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-finite": { - "version": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true - }, - "is-glob": { - "version": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true - }, - "is-my-json-valid": { - "version": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", - "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=", - "dev": true - }, - "is-number": { - "version": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true - }, - "is-path-cwd": { - "version": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", - "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", - "dev": true - }, - "is-path-inside": { - "version": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", - "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", - "dev": true - }, - "is-posix-bracket": { - "version": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-property": { - "version": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, - "is-regex": { - "version": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true - }, - "is-relative": { - "version": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz", - "integrity": "sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU=", - "dev": true - }, - "is-resolvable": { - "version": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", - "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", - "dev": true - }, - "is-stream": { - "version": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-symbol": { - "version": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true - }, - "is-typedarray": { - "version": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-unc-path": { - "version": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-0.1.2.tgz", - "integrity": "sha1-arBTpyVzwQJQ/0FqOBTDUXivObk=", - "dev": true - }, - "is-utf8": { - "version": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-windows": { - "version": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", - "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", - "dev": true - }, - "isarray": { - "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "isexe": { - "version": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "dependencies": { - "isarray": { - "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "isomorphic-fetch": { - "version": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=" - }, - "isparta": { - "version": "https://registry.npmjs.org/isparta/-/isparta-4.0.0.tgz", - "integrity": "sha1-HekZlvSAsi3LGsqFECVbrhV0RG4=", - "dev": true - }, - "isstream": { - "version": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul": { - "version": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "dev": true, - "dependencies": { - "glob": { - "version": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true - }, - "resolve": { - "version": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, - "supports-color": { - "version": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true - } - } - }, - "istanbul-threshold-checker": { - "version": "https://registry.npmjs.org/istanbul-threshold-checker/-/istanbul-threshold-checker-0.2.1.tgz", - "integrity": "sha1-xdyU6PLMXNP/0zVFL4S1U8QkgzE=", - "dev": true - }, - "js-tokens": { - "version": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz", - "integrity": "sha1-COnxMkhKLEWjCQfp3E1VZ7fxFNc=", - "dev": true - }, - "js-yaml": { - "version": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz", - "integrity": "sha1-bl/mfYsgXOTSL60Ft3geja3MSzA=", - "dev": true - }, - "jsbn": { - "version": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, - "optional": true - }, - "jsesc": { - "version": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - }, - "json-schema": { - "version": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-stable-stringify": { - "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true - }, - "json-stringify-safe": { - "version": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json3": { - "version": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "dev": true - }, - "json5": { - "version": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "jsonfile": { - "version": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "dev": true - }, - "jsonify": { - "version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, - "jsonpointer": { - "version": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", - "dev": true - }, - "jsprim": { - "version": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", - "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", - "dev": true, - "dependencies": { - "assert-plus": { - "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, - "jsx-ast-utils": { - "version": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", - "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=", - "dev": true - }, - "kind-of": { - "version": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true - }, - "lazy-cache": { - "version": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "optional": true - }, - "lcid": { - "version": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true - }, - "lcov-parse": { - "version": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", - "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", - "dev": true - }, - "levn": { - "version": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true - }, - "liftoff": { - "version": "https://registry.npmjs.org/liftoff/-/liftoff-2.3.0.tgz", - "integrity": "sha1-qY8v9nGD2Lp8+soQVIvX/wVQs4U=", - "dev": true - }, - "load-json-file": { - "version": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "dependencies": { - "strip-bom": { - "version": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "locate-path": { - "version": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true - }, - "lodash": { - "version": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", - "dev": true - }, - "lodash._baseassign": { - "version": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true - }, - "lodash._basecopy": { - "version": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basecreate": { - "version": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", - "dev": true - }, - "lodash._basetostring": { - "version": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "lodash._basevalues": { - "version": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "lodash._getnative": { - "version": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash._reescape": { - "version": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "lodash._reevaluate": { - "version": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", - "dev": true - }, - "lodash._reinterpolate": { - "version": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "lodash._root": { - "version": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, - "lodash.assignwith": { - "version": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", - "integrity": "sha1-EnqX8CrcQXUalU0ksN4X4QDgOOs=", - "dev": true - }, - "lodash.create": { - "version": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", - "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "dev": true - }, - "lodash.escape": { - "version": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true - }, - "lodash.isarguments": { - "version": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.isempty": { - "version": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=", - "dev": true - }, - "lodash.isplainobject": { - "version": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true - }, - "lodash.isstring": { - "version": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", - "dev": true - }, - "lodash.keys": { - "version": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true - }, - "lodash.mapvalues": { - "version": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", - "integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=", - "dev": true - }, - "lodash.pick": { - "version": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", - "dev": true - }, - "lodash.restparam": { - "version": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true - }, - "lodash.template": { - "version": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true - }, - "lodash.templatesettings": { - "version": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "dev": true - }, - "log-driver": { - "version": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.5.tgz", - "integrity": "sha1-euTsJXMC/XkNVXyxDJcQDYV7AFY=", - "dev": true - }, - "log-symbols": { - "version": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true - }, - "lolex": { - "version": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", - "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=", - "dev": true - }, - "longest": { - "version": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, - "loose-envify": { - "version": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", - "dev": true - }, - "lru-cache": { - "version": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", - "dev": true - }, - "map-cache": { - "version": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "md5": { - "version": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", - "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", - "dev": true - }, - "media-typer": { - "version": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "merge-descriptors": { - "version": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "merge-stream": { - "version": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", - "dev": true, - "dependencies": { - "isarray": { - "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.11.tgz", - "integrity": "sha1-B5azH412iAB/8Lk6gIjTSqF8D3I=", - "dev": true - }, - "string_decoder": { - "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", - "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=", - "dev": true - } - } - }, - "methods": { - "version": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "micromatch": { - "version": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true - }, - "mime": { - "version": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", - "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=", - "dev": true - }, - "mime-db": { - "version": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", - "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=", - "dev": true - }, - "mime-types": { - "version": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", - "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", - "dev": true - }, - "minimatch": { - "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", - "dev": true - }, - "minimist": { - "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true - }, - "mocha": { - "version": "https://registry.npmjs.org/mocha/-/mocha-3.4.2.tgz", - "integrity": "sha1-0O9NMyEm2/GNDWQMmzgt1IvpdZQ=", - "dev": true, - "dependencies": { - "debug": { - "version": "https://registry.npmjs.org/debug/-/debug-2.6.0.tgz", - "integrity": "sha1-vFlryr52F/Edn6FTYe3tVgi4SZs=", - "dev": true - }, - "glob": { - "version": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", - "dev": true - }, - "ms": { - "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true - }, - "supports-color": { - "version": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", - "dev": true - } - } - }, - "mocha-junit-reporter": { - "version": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.13.0.tgz", - "integrity": "sha1-Aw24xTCyRGZyU7A4YdTNM29+Vsg=", - "dev": true - }, - "ms": { - "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "multipipe": { - "version": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true - }, - "mute-stream": { - "version": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", - "dev": true - }, - "mz": { - "version": "https://registry.npmjs.org/mz/-/mz-2.6.0.tgz", - "integrity": "sha1-yLhSHZWN8KTydoAl22nHGe5O8c4=", - "dev": true - }, - "natives": { - "version": "https://registry.npmjs.org/natives/-/natives-1.1.0.tgz", - "integrity": "sha1-6f+EFBimsux6SV6TmYT3jxY+bjE=", - "dev": true - }, - "natural-compare": { - "version": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "dev": true - }, - "node-fetch": { - "version": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz", - "integrity": "sha1-iZyz0KPJL5UsR/G4dvTIrqvUANU=" - }, - "nomnomnomnom": { - "version": "https://registry.npmjs.org/nomnomnomnom/-/nomnomnomnom-2.0.1.tgz", - "integrity": "sha1-siOfAxyNBNpn4yg24eMZnhL3qOI=", - "dev": true, - "dependencies": { - "ansi-styles": { - "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", - "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", - "dev": true - }, - "chalk": { - "version": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", - "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", - "dev": true - }, - "strip-ansi": { - "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", - "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", - "dev": true - } - } - }, - "nopt": { - "version": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true - }, - "normalize-package-data": { - "version": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.8.tgz", - "integrity": "sha1-2Bntoqne29H/pWPqQHHZNngilbs=", - "dev": true - }, - "normalize-path": { - "version": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true - }, - "number-is-nan": { - "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "oauth-sign": { - "version": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true - }, - "object-assign": { - "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-keys": { - "version": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", - "dev": true - }, - "object.omit": { - "version": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true - }, - "on-finished": { - "version": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true - }, - "once": { - "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true - }, - "onetime": { - "version": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "optimist": { - "version": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "dependencies": { - "wordwrap": { - "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } - } - }, - "optionator": { - "version": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true - }, - "orchestrator": { - "version": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.8.tgz", - "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", - "dev": true - }, - "ordered-read-streams": { - "version": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz", - "integrity": "sha1-/VZamvjrRHO6abbtijQ1LLVS8SY=", - "dev": true - }, - "os-homedir": { - "version": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-locale": { - "version": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true - }, - "os-tmpdir": { - "version": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-limit": { - "version": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", - "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=", - "dev": true - }, - "p-locate": { - "version": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true - }, - "parse-filepath": { - "version": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.1.tgz", - "integrity": "sha1-FZ1hVdQ5BNFsEO9piRHaHpGWm3M=", - "dev": true - }, - "parse-glob": { - "version": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true - }, - "parse-json": { - "version": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true - }, - "parse-passwd": { - "version": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, - "parseurl": { - "version": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", - "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=", - "dev": true - }, - "path": { - "version": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", - "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", - "dev": true - }, - "path-exists": { - "version": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-parse": { - "version": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true - }, - "path-root": { - "version": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", - "dev": true - }, - "path-root-regex": { - "version": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", - "dev": true - }, - "path-to-regexp": { - "version": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - }, - "path-type": { - "version": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true - }, - "pify": { - "version": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true - }, - "pkg-conf": { - "version": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.0.0.tgz", - "integrity": "sha1-BxyHZQQDvM+5xif1h1G/5HwGcnk=", - "dev": true - }, - "pkg-config": { - "version": "https://registry.npmjs.org/pkg-config/-/pkg-config-1.1.1.tgz", - "integrity": "sha1-VX7yLXPaPIg3EHdmxS6tq94pj+Q=", - "dev": true - }, - "plur": { - "version": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", - "integrity": "sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=", - "dev": true - }, - "pluralize": { - "version": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", - "dev": true - }, - "prelude-ls": { - "version": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "preserve": { - "version": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "pretty-hrtime": { - "version": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", - "dev": true - }, - "private": { - "version": "https://registry.npmjs.org/private/-/private-0.1.7.tgz", - "integrity": "sha1-aM5eih7woju1cMwoU3tTMqumPvE=", - "dev": true - }, - "process": { - "version": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "process-nextick-args": { - "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "progress": { - "version": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - }, - "proxy-addr": { - "version": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz", - "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=", - "dev": true - }, - "punycode": { - "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "qs": { - "version": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", - "dev": true - }, - "randomatic": { - "version": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.6.tgz", - "integrity": "sha1-EQ3Kv/OX6dz/fAeJzMCkmt8exbs=", - "dev": true - }, - "range-parser": { - "version": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true - }, - "read-pkg": { - "version": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "dependencies": { - "load-json-file": { - "version": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true - }, - "strip-bom": { - "version": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "dependencies": { - "find-up": { - "version": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true - }, - "path-exists": { - "version": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true - } - } - }, - "readable-stream": { - "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true - }, - "readline2": { - "version": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "dev": true - }, - "rechoir": { - "version": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true - }, - "regenerate": { - "version": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.2.tgz", - "integrity": "sha1-0ZQcZ7rUN+G+dkM63Vs4X5WxkmA=", - "dev": true - }, - "regenerator-runtime": { - "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", - "dev": true - }, - "regenerator-transform": { - "version": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.9.11.tgz", - "integrity": "sha1-On0GdSDLe3F2dp61/4aGkb7+EoM=", - "dev": true - }, - "regex-cache": { - "version": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", - "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=", - "dev": true - }, - "regexpu-core": { - "version": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", - "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", - "dev": true - }, - "regjsgen": { - "version": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", - "dev": true - }, - "regjsparser": { - "version": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "dev": true - }, - "remove-trailing-separator": { - "version": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz", - "integrity": "sha1-abBi2XhyetFNxrVrpKt3L9jXBRE=", - "dev": true - }, - "repeat-element": { - "version": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", - "dev": true - }, - "repeat-string": { - "version": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true - }, - "replace-ext": { - "version": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, - "req-cwd": { - "version": "https://registry.npmjs.org/req-cwd/-/req-cwd-1.0.1.tgz", - "integrity": "sha1-DXOurpJm5penj3l2AZZ352rPD/8=", - "dev": true - }, - "req-from": { - "version": "https://registry.npmjs.org/req-from/-/req-from-1.0.1.tgz", - "integrity": "sha1-v4HaUUeUfTLRO5R9wSpYrUWHNQ4=", - "dev": true - }, - "request": { - "version": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", - "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", - "dev": true, - "dependencies": { - "qs": { - "version": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", - "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=", - "dev": true - } - } - }, - "require-directory": { - "version": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "require-uncached": { - "version": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "dependencies": { - "resolve-from": { - "version": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - } - } - }, - "resolve": { - "version": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz", - "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=", - "dev": true - }, - "resolve-dir": { - "version": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", - "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=", - "dev": true - }, - "resolve-from": { - "version": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", - "dev": true - }, - "resolve-url": { - "version": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "restore-cursor": { - "version": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true - }, - "right-align": { - "version": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "optional": true - }, - "rimraf": { - "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", - "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", - "dev": true - }, - "run-async": { - "version": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", - "dev": true - }, - "run-parallel": { - "version": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.6.tgz", - "integrity": "sha1-KQA8miFj4B4tLfyQV18sbB1hoDk=", - "dev": true - }, - "run-sequence": { - "version": "https://registry.npmjs.org/run-sequence/-/run-sequence-1.2.2.tgz", - "integrity": "sha1-UJWgvr6YczsBQL0I3YDsAw3azes=", - "dev": true - }, - "rx-lite": { - "version": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", - "dev": true - }, - "safe-buffer": { - "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", - "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=", - "dev": true - }, - "samsam": { - "version": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", - "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=", - "dev": true - }, - "semver": { - "version": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", - "dev": true - }, - "send": { - "version": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", - "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=", - "dev": true, - "dependencies": { - "debug": { - "version": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", - "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", - "dev": true - } - } - }, - "sequencify": { - "version": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz", - "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=", - "dev": true - }, - "serve-static": { - "version": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", - "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=", - "dev": true - }, - "set-blocking": { - "version": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "setprototypeof": { - "version": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", - "dev": true - }, - "shelljs": { - "version": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", - "dev": true - }, - "sigmund": { - "version": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", - "dev": true - }, - "sinon": { - "version": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", - "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=", - "dev": true - }, - "sinon-chai": { - "version": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-2.10.0.tgz", - "integrity": "sha1-arMAi7jK6ZKedE12ZXS0zzXzS1s=", - "dev": true - }, - "slash": { - "version": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "slice-ansi": { - "version": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "sntp": { - "version": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "dev": true - }, - "source-map": { - "version": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", - "dev": true - }, - "source-map-resolve": { - "version": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.3.1.tgz", - "integrity": "sha1-YQ9hIqRFuN1RU1oqcbeD38Ekh2E=", - "dev": true - }, - "source-map-support": { - "version": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", - "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=", - "dev": true - }, - "source-map-url": { - "version": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.3.0.tgz", - "integrity": "sha1-fsrxO1e80J2opAxdJp2zN5nUqvk=", - "dev": true - }, - "sparkles": { - "version": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", - "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=", - "dev": true - }, - "spdx-correct": { - "version": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", - "dev": true - }, - "spdx-expression-parse": { - "version": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", - "dev": true - }, - "spdx-license-ids": { - "version": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", - "dev": true - }, - "sprintf-js": { - "version": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", - "dev": true, - "dependencies": { - "assert-plus": { - "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, - "standard": { - "version": "https://registry.npmjs.org/standard/-/standard-9.0.2.tgz", - "integrity": "sha1-m9O5RnSS4hKxkU14VTlD/5tI/Zk=", - "dev": true - }, - "standard-engine": { - "version": "https://registry.npmjs.org/standard-engine/-/standard-engine-5.4.0.tgz", - "integrity": "sha1-4OhpWeoHhkJdM4PkDBv3DS+YVXk=", - "dev": true, - "dependencies": { - "minimist": { - "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "statuses": { - "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", - "dev": true - }, - "stream-consume": { - "version": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.0.tgz", - "integrity": "sha1-pB6tGm1ggc63n2WwYZAbbY89HQ8=", - "dev": true - }, - "stream-shift": { - "version": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true - }, - "string_decoder": { - "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "string-width": { - "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true - }, - "stringstream": { - "version": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", - "dev": true - }, - "strip-ansi": { - "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true - }, - "strip-bom": { - "version": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz", - "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", - "dev": true - }, - "strip-bom-string": { - "version": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", - "dev": true - }, - "strip-json-comments": { - "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "table": { - "version": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "dev": true, - "dependencies": { - "is-fullwidth-code-point": { - "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz", - "integrity": "sha1-Y1xUNsxypuDDh87KJ41OLuxSaH4=", - "dev": true - } - } - }, - "temp": { - "version": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", - "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", - "dev": true, - "dependencies": { - "rimraf": { - "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true - } - } - }, - "ternary-stream": { - "version": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.0.1.tgz", - "integrity": "sha1-Bk5Im0tb9gumpre8fy9cJ07Pgmk=", - "dev": true - }, - "text-table": { - "version": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "thenify": { - "version": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", - "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", - "dev": true - }, - "thenify-all": { - "version": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", - "dev": true - }, - "through": { - "version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "dev": true, - "dependencies": { - "isarray": { - "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.11.tgz", - "integrity": "sha1-B5azH412iAB/8Lk6gIjTSqF8D3I=", - "dev": true - }, - "string_decoder": { - "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", - "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=", - "dev": true - } - } - }, - "tildify": { - "version": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", - "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", - "dev": true - }, - "time-stamp": { - "version": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", - "dev": true - }, - "to-fast-properties": { - "version": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, - "tough-cookie": { - "version": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", - "dev": true - }, - "trim-right": { - "version": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "tryit": { - "version": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", - "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", - "dev": true - }, - "tunnel-agent": { - "version": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true - }, - "tweetnacl": { - "version": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, - "optional": true - }, - "type-check": { - "version": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true - }, - "type-detect": { - "version": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", - "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", - "dev": true - }, - "type-is": { - "version": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", - "dev": true - }, - "typedarray": { - "version": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "uglify-js": { - "version": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.28.tgz", - "integrity": "sha1-4zUDLfm7INy5GPFkWJ1a9H84g0o=", - "dev": true, - "optional": true, - "dependencies": { - "yargs": { - "version": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "optional": true - } - } - }, - "uglify-to-browserify": { - "version": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, - "unc-path-regex": { - "version": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true - }, - "underscore": { - "version": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", - "dev": true - }, - "uniq": { - "version": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true - }, - "unique-stream": { - "version": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz", - "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=", - "dev": true - }, - "unpipe": { - "version": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "urix": { - "version": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "user-home": { - "version": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", - "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", - "dev": true - }, - "util": { - "version": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "dependencies": { - "inherits": { - "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - } - } - }, - "util-deprecate": { - "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "utils-merge": { - "version": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", - "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=", - "dev": true - }, - "uuid": { - "version": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", - "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=", - "dev": true - }, - "v8flags": { - "version": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", - "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", - "dev": true - }, - "validate-npm-package-license": { - "version": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", - "dev": true - }, - "vary": { - "version": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", - "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=", - "dev": true - }, - "verror": { - "version": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", - "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", - "dev": true - }, - "vinyl": { - "version": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", - "dev": true - }, - "vinyl-fs": { - "version": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz", - "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", - "dev": true, - "dependencies": { - "clone": { - "version": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", - "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true - }, - "graceful-fs": { - "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", - "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", - "dev": true - }, - "readable-stream": { - "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true - }, - "through2": { - "version": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "dev": true - }, - "vinyl": { - "version": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", - "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", - "dev": true - } - } - }, - "vinyl-sourcemaps-apply": { - "version": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", - "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", - "dev": true - }, - "whatwg-fetch": { - "version": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", - "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" - }, - "which": { - "version": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true - }, - "which-module": { - "version": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "window-size": { - "version": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "optional": true - }, - "wordwrap": { - "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrap-ansi": { - "version": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true - }, - "wrappy": { - "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true - }, - "xml": { - "version": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", - "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", - "dev": true - }, - "xmldom": { - "version": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", - "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", - "optional": true - }, - "xtend": { - "version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - }, - "y18n": { - "version": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", - "dev": true, - "dependencies": { - "camelcase": { - "version": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "cliui": { - "version": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true - } - } - }, - "yargs-parser": { - "version": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", - "dev": true, - "dependencies": { - "camelcase": { - "version": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - } - } - } - } -} From 4e66ea6e81012bfd7117591402114fe0bac20289 Mon Sep 17 00:00:00 2001 From: Laurent De Smet Date: Tue, 20 Jun 2017 11:45:47 +0200 Subject: [PATCH 07/13] update readme --- README.md | 158 ++++++++++---------- package.json | 1 + src/error.js | 2 +- src/index.js | 1 + src/load-vast.js | 8 +- test/lib/write-test-tree.js | 57 +++++++ test/unit/load-vast.js | 22 ++- test/unit/node.js | 266 --------------------------------- test/unit/vast-loader-error.js | 32 ---- 9 files changed, 162 insertions(+), 385 deletions(-) create mode 100644 test/lib/write-test-tree.js delete mode 100644 test/unit/node.js delete mode 100644 test/unit/vast-loader-error.js diff --git a/README.md b/README.md index 90ca18b..2681f9c 100644 --- a/README.md +++ b/README.md @@ -2,137 +2,133 @@ [![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 that asynchronously fetches the complete VAST document tree. The previous version of this package used JS Promises and waited for the complete VAST tree to be fetched. One failing VAST document within the tree, made it fail completely. The new implementation 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. ## Usage ```js -import VASTLoader from 'iab-vast-loader' - -const tagUrl = 'https://example.com/vast.xml' +import { loadVast } from 'iab-vast-loader' -// Create the loader -const loader = new VASTLoader(tagUrl) +const loadVast$ = 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) - }) - .catch((err) => { - console.error('Error loading tag:', err) +// Load the VAST tree and log all the VAST tags in the tree. +loadVast$ + .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.wrapper) + break; + } + }, + complete: () => { + console.info('Finished loading the complete VAST tree') + } }) ``` ## API ```js -new VASTLoader(tagUrl[, options]) +const loadVast$ = loadVast(config) ``` -Creates a VAST loader. +Creates a stream with 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() +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). +The stream returned consists of VastLoadAction objects: -## Error Handling +```js +type VastLoadedAction = { + type: 'VAST_LOADED', + vast: VAST +} + +type VastLoadingFailedAction = { + type: 'VAST_LOADING_FAILED', + error: VASTLoaderError, + wrapper: ?Wrapper +} + +export type VastLoadAction = VastLoadedAction | VastLoadingFailedAction +``` -In addition to the default export `VASTLoader`, the main module also exports -the `VASTLoaderError` class, which maps errors to the VAST specification: +The `VAST` class is provided by the new, typed version of [iab-vast-model](https://www.npmjs.com/package/iab-vast-model). -```js -import { default as VASTLoader, VASTLoaderError } from 'iab-vast-loader' +## Error Handling -const loader = new VASTLoader(tagUrl) +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. -loader.load() - .catch((err) => { - if (err instanceof VASTLoaderError) { - console.error('VAST error: ' + err.code + ' ' + err.message) - } else { - console.error('Unknown error: ' + err) - } - }) -``` +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. -## Options +## Configuration -### `maxDepth` +### `url: string` + +The url that points to the root VAST document of the VAST document tree that we need to fetch. + +### `maxDepth?: number` The maximum number of VAST documents to load within one chain. The default is 10. -### `timeout` +### `timeout?: number` The maximum number of milliseconds to spend per HTTP request. The default is 10,000. -### `credentials` - -Controls [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) -behavior. You can either pass a string or a function. +### `retryCount?: number` -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'`. +The amount of times it will retry fetching a VAST document in case of failure. The default is 0. -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: +### `credentials: Credentials` ```js -const loader = new VASTLoader(wrapperUrl, { - credentials (uri) { - if (uri.indexOf('.doubleclick.net/') >= 0) { - return 'include' - } else { - return 'omit' - } - } -}) +type Credentials = (CredentialsType | (url: string) => CredentialsType)[] +type CredentialsType = 'omit' | 'same-origin' | 'include' ``` -## Events +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'`. -A `VASTLoader` is an `EventEmitter`. To be notified about progress, you can -subscribe to the events `willFetch`, `didFetch`, `willParse`, and `didParse` -as follows: +You can use this option to control the behavior on a per-request basis. For example: ```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) - }) +const loadVast$ = loadVast({ + url: 'https://example.com/vast.xml', + credentials: [ + url => uri.indexOf('.doubleclick.net/') !== 0 ? 'include' : 'omit' + ] +}) ``` -## Maintainer +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. + +The default value is: `['omit']` + +## Maintainers -[Tim De Pauw](https://github.com/timdp) +- [Tim De Pauw](https://github.com/timdp) +- [Laurent De Smet](https://github.com/laurentdesmet) ## License diff --git a/package.json b/package.json index afc57bc..d47a64b 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "gulp-standard": "^9.0.0", "in-publish": "^2.0.0", "isparta": "^4.0.0", + "lodash": "^4.17.4", "mocha-junit-reporter": "^1.13.0", "run-sequence": "^1.1.5", "sinon": "^1.17.7", diff --git a/src/error.js b/src/error.js index b44dbea..15c023b 100644 --- a/src/error.js +++ b/src/error.js @@ -4,7 +4,7 @@ import codeToMessage from './error-codes' type Code = $Keys -export default class VASTLoaderError extends Error { +export class VASTLoaderError extends Error { _code: Code _cause: any diff --git a/src/index.js b/src/index.js index abfc908..b72015f 100644 --- a/src/index.js +++ b/src/index.js @@ -2,3 +2,4 @@ export { loadVast } from './load-vast' export { vastToAd } from './vast-to-ad' +export { VASTLoaderError } from './error' diff --git a/src/load-vast.js b/src/load-vast.js index de762a8..9a7cf1d 100644 --- a/src/load-vast.js +++ b/src/load-vast.js @@ -2,7 +2,7 @@ import { type VAST, Wrapper } from 'iab-vast-model' import parse from 'iab-vast-parser' -import VASTLoaderError from './error' +import { VASTLoaderError } from './error' import { concatEager } from './concat-eager' import { fx } from './rxjs-fx' @@ -34,14 +34,14 @@ export type VastLoadAction = VastLoadedAction | VastLoadingFailedAction -type Credentials = CredentialsType | (url: string) => CredentialsType +type Credentials = (CredentialsType | (url: string) => CredentialsType)[] type Config = { url: string, maxDepth?: number, timeout?: number, retryCount?: number, - credentials: Credentials[] + credentials: Credentials } const DEFAULT_OPTIONS = { @@ -64,7 +64,7 @@ type LoadVastConfig = { maxDepth: number, timeout: number, retryCount: number, - credentials: Credentials[] + credentials: Credentials } // Traverse the tree using a preorder depth first strategy. diff --git a/test/lib/write-test-tree.js b/test/lib/write-test-tree.js new file mode 100644 index 0000000..9eaffa9 --- /dev/null +++ b/test/lib/write-test-tree.js @@ -0,0 +1,57 @@ +import archy from 'archy' + +// This prints the VAST tree, used to generate the comment section of test/unit/load-vast.js +const vastTree = archy({ + label: 'a: VAST', + nodes: [ + { label: 'p: InLine' }, + { + label: 'q: Wrapper', + nodes: [ + { + label: 'b: VAST', + nodes: [ + { + label: 'u: Wrapper', + nodes: [ + { + label: 'd: VAST', + nodes: [ + { label: 'y: InLine' } + ] + } + ] + }, + { label: 'v: InLine' }, + { + label: 'w: Wrapper', + nodes: [ + { + label: 'e: VAST', + nodes: [ + { label: 'z: InLine' } + ] + } + ] + } + ] + } + ] + }, + { label: 'r: InLine ' }, + { + label: 's: Wrapper', + nodes: [ + { + label: 'c: VAST', + nodes: [ + { label: 'x: InLine' } + ] + } + ] + }, + { label: 't: InLine' } + ] +}) + +console.log(vastTree) diff --git a/test/unit/load-vast.js b/test/unit/load-vast.js index 55ce80c..4fe426f 100644 --- a/test/unit/load-vast.js +++ b/test/unit/load-vast.js @@ -1,5 +1,5 @@ import { loadVast } from '../../src' -import VASTLoaderError from '../../src/error' +import { VASTLoaderError } from '../../src/error' import { buildVast } from '../lib/build-vast' import { fx } from '../../src/rxjs-fx' @@ -8,6 +8,26 @@ import { Observable } from 'rxjs/Observable' import times from 'lodash/times' +// Throughout the tests we make use of the following VAST document tree. +// This ASCII tree is generated using the test/lib/write-test-tree.js script. +// +// a: VAST +// ├── p: InLine +// ├─┬ q: Wrapper +// │ └─┬ b: VAST +// │ ├─┬ u: Wrapper +// │ │ └─┬ d: VAST +// │ │ └── y: InLine +// │ ├── v: InLine +// │ └─┬ w: Wrapper +// │ └─┬ e: VAST +// │ └── z: InLine +// ├── r: InLine +// ├─┬ s: Wrapper +// │ └─┬ c: VAST +// │ └── x: InLine +// └── t: InLine + let originalTimeout const monkeyPatch = (scheduler) => { diff --git a/test/unit/node.js b/test/unit/node.js deleted file mode 100644 index 8adce3a..0000000 --- a/test/unit/node.js +++ /dev/null @@ -1,266 +0,0 @@ -// 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) -// expect(error.code).to.equal(code) -// expect(error.message).to.equal(message) -// if (cause != null) { -// expect(error.cause).to.include(cause) -// } -// } -// -// 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' -// } -// -// let fetchUriImpl -// let server -// let baseUrl -// let responseDelay -// -// const createLoader = (file, options) => new VASTLoader(baseUrl + file, options) -// -// const proxifyFetchUri = function () { -// fetchUriImpl = VASTLoader.prototype._fetchUri -// VASTLoader.prototype._fetchUri = async function () { -// const target = proxyPaths[this._uri] -// if (target == null) { -// return await fetchUriImpl.call(this) -// } -// const oldUri = this._uri -// this._uri = baseUrl + target -// try { -// return await fetchUriImpl.call(this) -// } finally { -// this._uri = oldUri -// } -// } -// } -// -// const unproxifyFetchUri = function () { -// VASTLoader.prototype._fetchUri = fetchUriImpl -// } -// -// before(function (cb) { -// const app = express() -// app.use((req, res, next) => { -// setTimeout(() => next(), responseDelay) -// }) -// app.use(express.static(fixturesPath)) -// server = app.listen(function () { -// baseUrl = 'http://localhost:' + server.address().port + '/' -// proxifyFetchUri() -// cb() -// }) -// }) -// -// after(function (cb) { -// unproxifyFetchUri() -// server.close(cb) -// }) -// -// beforeEach(function () { -// responseDelay = 0 -// }) -// -// describe('#load()', function () { -// it('loads the InLine', async function () { -// const loader = createLoader('tremor-video/vast_inline_linear.xml') -// 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 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 () { -// const file = path.join(fixturesPath, 'tremor-video/vast_inline_linear.xml') -// const base64 = (await fsp.readFile(file)).toString('base64') -// const dataUri = 'data:text/xml;base64,' + base64 -// const loader = new VASTLoader(dataUri) -// 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 () { -// const file = path.join(fixturesPath, 'tremor-video/vast_inline_linear.xml') -// const xml = (await fsp.readFile(file, 'utf8')).replace(/\r?\n/g, '') -// const dataUri = 'data:text/xml,' + xml -// const loader = new VASTLoader(dataUri) -// 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 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 () { -// let error -// try { -// const loader = createLoader('no-ads-wrapper.xml') -// await loader.load() -// } catch (err) { -// error = err -// } -// expectLoaderError(error, 303, 'No Ads VAST response after one or more Wrappers.') -// }) -// -// it('throws VAST 301 on invalid InLine inside Wrapper', async function () { -// let error -// try { -// const loader = createLoader('invalid-ads-wrapper.xml') -// await loader.load() -// } catch (err) { -// error = err -// } -// expectLoaderError(error, 301, 'Timeout.') -// }) -// -// it('throws on HTTP errors', async function () { -// let error -// try { -// const loader = createLoader('four-oh-four') -// await loader.load() -// } catch (err) { -// error = err -// } -// expectLoaderError(error, 900, 'Undefined error.', {status: 404, statusText: 'Not Found'}) -// }) -// }) -// -// // TODO Test event data -// describe('#emit()', function () { -// for (const type of ['willFetch', 'didFetch', 'willParse', 'didParse']) { -// it(`emits ${type}`, async function () { -// const spy = sinon.spy() -// const loader = createLoader('tremor-video/vast_inline_linear.xml') -// loader.on(type, spy) -// await loader.load() -// expect(spy.called).to.be.true() -// }) -// } -// -// for (const type of ['willFetch', 'didFetch', 'willParse', 'didParse']) { -// it(`emits ${type} once per tag`, async function () { -// const spy = sinon.spy() -// const loader = createLoader('tremor-video/vast_wrapper_linear_1.xml') -// loader.on(type, spy) -// await loader.load() -// expect(spy.calledTwice).to.be.true() -// }) -// } -// -// it('emits error on errors', async function () { -// const spy = sinon.spy() -// const loader = createLoader('four-oh-four') -// loader.on('error', spy) -// try { -// await loader.load() -// } catch (err) {} -// expect(spy.calledOnce).to.be.true() -// }) -// }) -// -// describe('maxDepth option', function () { -// it('throws when maxDepth is reached', async function () { -// let error -// try { -// const loader = createLoader('tremor-video/vast_wrapper_linear_1.xml', { -// maxDepth: 1 -// }) -// await loader.load() -// } catch (err) { -// error = err -// } -// expectLoaderError(error, 302, 'Wrapper limit reached.') -// }) -// }) -// -// describe('timeout option', function () { -// it('throws when timeout is reached', async function () { -// responseDelay = 100 -// let error -// try { -// const loader = createLoader('no-ads.xml', { -// timeout: 10 -// }) -// await loader.load() -// } catch (err) { -// error = err -// } -// expectLoaderError(error, 301, 'Timeout.') -// }) -// }) -// -// describe('credentials option', function () { -// // TODO Use something nicer than inspecting private _fetchOptions -// -// it('is "omit" by default', function () { -// const loader = createLoader('tremor-video/vast_inline_linear.xml') -// expect(loader._fetchOptions).to.eql({ credentials: 'omit' }) -// }) -// -// it('overrides with a string value', function () { -// const loader = createLoader('tremor-video/vast_inline_linear.xml', { -// credentials: 'include' -// }) -// expect(loader._fetchOptions).to.eql({ credentials: 'include' }) -// }) -// -// it('overrides with a function value', function () { -// const loader = createLoader('tremor-video/vast_inline_linear.xml', { -// credentials: (uri) => 'same-origin' -// }) -// expect(loader._fetchOptions).to.eql({ credentials: 'same-origin' }) -// }) -// -// it('calls the function with the tag URI', function () { -// const credentials = sinon.spy((uri) => 'same-origin') -// const file = 'tremor-video/vast_inline_linear.xml' -// const uri = baseUrl + file -// createLoader(file, { -// credentials -// }) -// expect(credentials).to.have.been.calledWith(uri) -// }) -// -// it('throws if neither a string nor a function provided', function () { -// expect(function () { -// createLoader('tremor-video/vast_inline_linear.xml', { -// credentials: true -// }) -// }).to.throw(Error, 'Invalid credentials option: true') -// }) -// }) -// }) diff --git a/test/unit/vast-loader-error.js b/test/unit/vast-loader-error.js deleted file mode 100644 index 4fae51f..0000000 --- a/test/unit/vast-loader-error.js +++ /dev/null @@ -1,32 +0,0 @@ -// 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') -// }) -// }) -// }) From c5f62c90a75afff90072ba148fc3e641fcb28486 Mon Sep 17 00:00:00 2001 From: Laurent De Smet Date: Tue, 20 Jun 2017 12:56:13 +0200 Subject: [PATCH 08/13] update readme --- README.md | 168 +++++++++++++++++++++++++++------------- gulpfile.babel.js | 5 +- index.js | 2 - package.json | 6 +- src/load-vast.js | 6 +- src/vast-to-ad.js | 11 +-- test/unit/vast-to-ad.js | 2 +- 7 files changed, 129 insertions(+), 71 deletions(-) delete mode 100644 index.js diff --git a/README.md b/README.md index 2681f9c..1ae9564 100644 --- a/README.md +++ b/README.md @@ -4,27 +4,51 @@ 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 that asynchronously fetches the complete VAST document tree. The previous version of this package used JS Promises and waited for the complete VAST tree to be fetched. One failing VAST document within the tree, made it fail completely. The new implementation 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. +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 { loadVast } from 'iab-vast-loader' -const loadVast$ = loadVast({ +const vast$ = loadVast({ url: 'https://example.com/vast.xml' }) // Load the VAST tree and log all the VAST tags in the tree. -loadVast$ +vast$ .subscribe({ next: action => { - switch(action.type) { + 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.wrapper) + console.info('Loading next VAST tag failed: ', action.error, action.wrapper) + break; + } + }, + complete: () => { + console.info('Finished loading the complete VAST tree') + } + }) + +// 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; } }, @@ -36,20 +60,72 @@ loadVast$ ## API +### `#loadVast()` + ```js -const loadVast$ = loadVast(config) +type LoadVast = (config: Config) => Observable ``` -Creates a stream with 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: +`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 +import { loadVast } from 'iab-vast-loader' +const vast$ = loadVast(config) + loadVast$.subscribe({ next: value => { }, complete: () => { } }) ``` -The stream returned consists of VastLoadAction objects: +#### Configuration + +`loadVast` accepts the following `Config` object: + +```js +type Config = { + url: string, + maxDepth?: number, + timeout?: number, + retryCount?: number, + credentials: Credentials +} +``` + +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'`. + + ```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' + ] + }) + ``` + + 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. + + The default is `['omit']`. + +#### Output + +The output of loadVast is a stream of VastLoadAction objects: ```js type VastLoadedAction = { @@ -63,67 +139,53 @@ type VastLoadingFailedAction = { wrapper: ?Wrapper } -export type VastLoadAction = VastLoadedAction | VastLoadingFailedAction +type VastLoadAction = VastLoadedAction | VastLoadingFailedAction ``` -The `VAST` class is provided by the new, typed version of [iab-vast-model](https://www.npmjs.com/package/iab-vast-model). - -## Error Handling - -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. +The `VAST` and `Wrapper` types are provided by [iab-vast-model](https://www.npmjs.com/package/iab-vast-model). -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. +### `#vastToAd()` -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. - -## Configuration - -### `url: string` - -The url that points to the root VAST document of the VAST document tree that we need to fetch. - -### `maxDepth?: number` - -The maximum number of VAST documents to load within one chain. The default is -10. +```js +import { loadVast, vastToAd } from 'iab-vast-loader' +const vast$ = loadVast(config) +const ad$ = vastToAd(vast$) +``` -### `timeout?: number` +`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`. -The maximum number of milliseconds to spend per HTTP request. The default is -10,000. +#### Output -### `retryCount?: number` +The output of `vastToAd` is a stream of `AdLoadAction` objects: -The amount of times it will retry fetching a VAST document in case of failure. The default is 0. +```js +type AdLoadedAction = { + type: 'AD_LOADED', + ad: Ad +} -### `credentials: Credentials` +type AdLoadingFailedAction = { + type: 'AD_LOADING_FAILED', + error: VASTLoaderError, + wrapper: ?Wrapper +} -```js -type Credentials = (CredentialsType | (url: string) => CredentialsType)[] -type CredentialsType = 'omit' | 'same-origin' | 'include' +type AdLoadAction = AdLoadedAction | AdLoadingFailedAction ``` -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'`. +The `Ad` and `Wrapper` types are provided by [iab-vast-model](https://www.npmjs.com/package/iab-vast-model). -You can use this option to control the behavior on a per-request basis. For example: +## Error Handling -```js -const loadVast$ = loadVast({ - url: 'https://example.com/vast.xml', - credentials: [ - url => uri.indexOf('.doubleclick.net/') !== 0 ? 'include' : 'omit' - ] -}) -``` +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. -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. +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. -The default value is: `['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. ## Maintainers diff --git a/gulpfile.babel.js b/gulpfile.babel.js index f68b615..bc38aae 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -29,10 +29,7 @@ gulp.task('clean', () => del('lib')) gulp.task('build', ['clean'], () => { return gulp.src('src/**/*.js') .pipe($.sourcemaps.init()) - .pipe($.babel({ - presets: ['es2015'], - babelrc: false - })) + .pipe($.babel()) .pipe($.sourcemaps.write()) .pipe(gulp.dest('lib')) }) diff --git a/index.js b/index.js deleted file mode 100644 index d1e6949..0000000 --- a/index.js +++ /dev/null @@ -1,2 +0,0 @@ -module.exports = require('./lib/').default -module.exports.VASTLoaderError = require('./lib/error') diff --git a/package.json b/package.json index d47a64b..d739dbf 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "iab-vast-loader", "version": "0.8.0", "description": "Loads and parses IAB VAST tags, resolving wrapped tags along the way.", - "main": "index.js", + "main": "lib/index.js", "jsnext:main": "src/index.js", "author": "Zentrick nv (https://www.zentrick.com/)", "contributors": [ @@ -22,9 +22,7 @@ "clean": "gulp clean", "build": "gulp build", "prepublish": "in-publish && npm run build || not-in-publish", - "test": "gulp test", - "test2": "gulp test2", - "flow": "flow" + "test": "flow check && gulp test" }, "repository": "zentrick/iab-vast-loader", "bugs": "https://github.com/zentrick/iab-vast-loader/issues", diff --git a/src/load-vast.js b/src/load-vast.js index 9a7cf1d..4fc2ae1 100644 --- a/src/load-vast.js +++ b/src/load-vast.js @@ -114,8 +114,10 @@ const loadVastTree = (config: LoadVastConfig): Observable => { } const getWrappers = (vast: VAST): Wrapper[] => - vast.ads - .filter(ad => ad instanceof Wrapper) + // We cast to any here, because Flow is pessimistic and doesn't allow downcasting. + // Another approach could be to add an additional map, and perform an invariant check. + // This has however (a very minor) performance impact at runtime and it adds unnecessary complexity. + (vast.ads.filter(ad => ad instanceof Wrapper): any) // This function returns a stream with exact one event: a success or error event. const fetchVast = (config: LoadVastConfig): Observable => diff --git a/src/vast-to-ad.js b/src/vast-to-ad.js index b642b12..8c6cff2 100644 --- a/src/vast-to-ad.js +++ b/src/vast-to-ad.js @@ -3,20 +3,21 @@ import { Observable } from 'rxjs/Observable' import { type VAST, type Ad, Wrapper } from 'iab-vast-model' import { type VastLoadAction } from './load-vast' +import { type VASTLoaderError } from './error' type AdLoadedAction = { type: 'AD_LOADED', ad: Ad } -type AdLoadingFailedAction = { type: 'AD_LOADING_FAILED', error: any, wrapper: ?Wrapper } +type AdLoadingFailedAction = { type: 'AD_LOADING_FAILED', error: VASTLoaderError, wrapper: ?Wrapper } type AdLoadAction = AdLoadedAction | AdLoadingFailedAction // This function returns a depth first preorder stream of inline elements of the VAST chain. export const vastToAd = (vast$: Observable): Observable => vast$ - .concatMap(event => { - if (event.type === 'VAST_LOADED') { + .concatMap(action => { + if (action.type === 'VAST_LOADED') { // VAST_LOADED - const { vast } = event + const { vast } = action const ads = walkAdsUntilNextWrapper(vast, 0) @@ -27,7 +28,7 @@ export const vastToAd = (vast$: Observable): Observable Date: Tue, 20 Jun 2017 13:05:11 +0200 Subject: [PATCH 09/13] add flow-copy-source step to build process --- package.json | 3 ++- src/atob.js | 22 ---------------------- 2 files changed, 2 insertions(+), 23 deletions(-) delete mode 100644 src/atob.js diff --git a/package.json b/package.json index d739dbf..9020459 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ ], "scripts": { "clean": "gulp clean", - "build": "gulp build", + "build": "gulp build && flow-copy-source src lib", "prepublish": "in-publish && npm run build || not-in-publish", "test": "flow check && gulp test" }, @@ -52,6 +52,7 @@ "dirty-chai": "^1.2.2", "express": "^4.15.2", "flow-bin": "^0.46.0", + "flow-copy-source": "^1.1.0", "fs-promise": "^2.0.0", "gulp": "^3.9.1", "gulp-babel": "^6.1.2", diff --git a/src/atob.js b/src/atob.js deleted file mode 100644 index db6003c..0000000 --- a/src/atob.js +++ /dev/null @@ -1,22 +0,0 @@ -// Based on https://gist.github.com/stubbetje/229984 - -const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' - -export default (string) => { - let result = '' - let i = 0 - do { - const b1 = characters.indexOf(string.charAt(i++)) - const b2 = characters.indexOf(string.charAt(i++)) - const b3 = characters.indexOf(string.charAt(i++)) - const b4 = characters.indexOf(string.charAt(i++)) - const a = ((b1 & 0x3F) << 2) | ((b2 >> 4) & 0x3) - const b = ((b2 & 0xF) << 4) | ((b3 >> 2) & 0xF) - const c = ((b3 & 0x3) << 6) | (b4 & 0x3F) - /* istanbul ignore next */ - result += String.fromCharCode(a) + - (b ? String.fromCharCode(b) : '') + - (c ? String.fromCharCode(c) : '') - } while (i < string.length) - return result -} From d1e771cf3f29af5fdeaa89a518adb50d30fd7038 Mon Sep 17 00:00:00 2001 From: Laurent De Smet Date: Tue, 20 Jun 2017 13:57:54 +0200 Subject: [PATCH 10/13] add flow command to scripts --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9020459..353e1e6 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "clean": "gulp clean", "build": "gulp build && flow-copy-source src lib", "prepublish": "in-publish && npm run build || not-in-publish", - "test": "flow check && gulp test" + "test": "flow check && gulp test", + "flow": "flow check" }, "repository": "zentrick/iab-vast-loader", "bugs": "https://github.com/zentrick/iab-vast-loader/issues", From 390bc6855405170d7e7812ebcf358b47e10ac81c Mon Sep 17 00:00:00 2001 From: Laurent De Smet Date: Mon, 4 Sep 2017 16:53:36 +0200 Subject: [PATCH 11/13] allow strings for the credentials config and refactor parallel vast loading to sequential vast loading --- src/load-vast.js | 14 ++++++++++---- test/unit/load-vast.js | 26 ++++++++++---------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/load-vast.js b/src/load-vast.js index 4fc2ae1..d845bed 100644 --- a/src/load-vast.js +++ b/src/load-vast.js @@ -34,7 +34,9 @@ export type VastLoadAction = VastLoadedAction | VastLoadingFailedAction -type Credentials = (CredentialsType | (url: string) => CredentialsType)[] +type CredentialsTypeOrFn = CredentialsType | (url: string) => CredentialsType + +type Credentials = CredentialsTypeOrFn | CredentialsTypeOrFn[] type Config = { url: string, @@ -48,7 +50,7 @@ const DEFAULT_OPTIONS = { maxDepth: 10, timeout: 10000, retryCount: 0, - credentials: ['omit'] + credentials: 'omit' } export const loadVast = (config: Config): Observable => @@ -119,15 +121,19 @@ const getWrappers = (vast: VAST): Wrapper[] => // This has however (a very minor) performance impact at runtime and it adds unnecessary complexity. (vast.ads.filter(ad => ad instanceof Wrapper): any) +const normalizeCredentials = (credentials: Credentials): CredentialsTypeOrFn[] => Array.isArray(credentials) + ? credentials + : [credentials] + // This function returns a stream with exact one event: a success or error event. const fetchVast = (config: LoadVastConfig): Observable => - Observable.from(config.credentials) + Observable.from(normalizeCredentials(config.credentials)) .map(credentials => typeof credentials === 'string' ? credentials : credentials(config.url) ) - .mergeMap(credentials => + .concatMap(credentials => fx .http(config.url, { method: 'GET', diff --git a/test/unit/load-vast.js b/test/unit/load-vast.js index 4fe426f..92ee09c 100644 --- a/test/unit/load-vast.js +++ b/test/unit/load-vast.js @@ -90,27 +90,22 @@ describe('#loadVast()', () => { expect(httpStub.getCall(0).args).to.deep.equal([vast.standalone.url, { method: 'GET', credentials: 'omit' }]) }) - it('should load a VAST document by racing the passed credentials strategies and ignoring failed requests', () => { - // ----(a|) - // ---(a|) - // --(#|) - // ---(a|) + it('should load a VAST document by trying the passed credentials strategies one by one and ignoring failed requests', () => { + // ----(#|) + // ---(a|) + // -------(a|) const httpStub = sinon.stub() const credentialStub = sinon.stub() httpStub .onCall(0) - .returns(cold('----(a|)', { a: vast.standalone.str })) + .returns(cold('----(#|)', null, new Error('http failed'))) httpStub .onCall(1) .returns(cold('---(a|)', { a: vast.standalone.str })) - httpStub - .onCall(2) - .returns(cold('--(#|)', null, new Error('http failed'))) - httpStub.throws() credentialStub @@ -125,21 +120,20 @@ describe('#loadVast()', () => { url: vast.standalone.url, credentials: [ 'omit', - 'same-origin', - credentialStub + credentialStub, + 'same-origin' ] }) - const expected = '---(a|)' + const expected = '-------(a|)' const values = { a: { type: 'VAST_LOADED', vast: vast.standalone.model } } scheduler.expectObservable(actual$).toBe(expected, values) scheduler.flush() - expect(httpStub.callCount).to.equal(3) + expect(httpStub.callCount).to.equal(2) expect(httpStub.getCall(0).args).to.deep.equal([vast.standalone.url, { method: 'GET', credentials: 'omit' }]) - expect(httpStub.getCall(1).args).to.deep.equal([vast.standalone.url, { method: 'GET', credentials: 'same-origin' }]) - expect(httpStub.getCall(2).args).to.deep.equal([vast.standalone.url, { method: 'GET', credentials: 'include' }]) + expect(httpStub.getCall(1).args).to.deep.equal([vast.standalone.url, { method: 'GET', credentials: 'include' }]) expect(credentialStub.callCount).to.equal(1) expect(credentialStub.getCall(0).args).to.deep.equal([vast.standalone.url]) From 544207c446f98c4bd5d00467e8017a2fad7274f6 Mon Sep 17 00:00:00 2001 From: Laurent De Smet Date: Tue, 5 Sep 2017 10:55:46 +0200 Subject: [PATCH 12/13] Update README and remove unused code --- README.md | 20 ++++++++++---------- test/lib/setup.js | 4 ---- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 1ae9564..edb9630 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ 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. +This package exposes two functions: `loadVast()` and `vastToAd()`. -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. +`loadVast()` is responsible for actually loading the VAST tree, it returns a stream of VAST objects, 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 new 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. +It also gives you a `vastToAd()` function to map the 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 @@ -100,11 +100,13 @@ An overview of its properties: - `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'`. +- `credentials`: The credentials value defines if cookies will be sent with the request. You should pass a [`CredentialsType`](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) string (`'omit'`, `'same-origin'` or `'include'`) or a function which calculcates the `CredentialsType` using the passed url. You can also pass an array of these values: in this case it will try the first credentials strategy, when this call fails it will go on with the next, until a strategy succeeds. If none of the credentials strategies succeed, it will result in a `VAST_LOADING_FAILED` action. Notice that passing an empty array doesn't make sense, because it will always result in a `VAST_LOADING_FAILED` action. + + With Flow, we can describe this more formally: ```js - type Credentials = (CredentialsType | (url: string) => CredentialsType)[] + type Credentials = CredentialsTypeOrFn | CredentialsTypeOrFn[] + type CredentialsTypeOrFn = CredentialsType | (url: string) => CredentialsType type CredentialsType = 'omit' | 'same-origin' | 'include' ``` @@ -119,13 +121,11 @@ behavior. You should pass an array of CredentialsType or functions that return a }) ``` - 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. - - The default is `['omit']`. + The default is `'omit'`. #### Output -The output of loadVast is a stream of VastLoadAction objects: +The output of loadVast is a stream of `VastLoadAction` objects: ```js type VastLoadedAction = { diff --git a/test/lib/setup.js b/test/lib/setup.js index ed95c68..6d655f1 100644 --- a/test/lib/setup.js +++ b/test/lib/setup.js @@ -4,8 +4,6 @@ import chaiAsPromised from 'chai-as-promised' import sinon from 'sinon' import sinonChai from 'sinon-chai' import dirtyChai from 'dirty-chai' -// import {XMLHttpRequest} from 'xmlhttprequest' -import XMLHttpRequest from 'xhr2' chai.use(chaiAsPromised) chai.use(sinonChai) @@ -13,5 +11,3 @@ chai.use(dirtyChai) global.expect = chai.expect global.sinon = sinon - -global.XMLHttpRequest = XMLHttpRequest From 8e7f8cc00cbbe603821516b79617c3851f13902c Mon Sep 17 00:00:00 2001 From: Laurent De Smet Date: Tue, 5 Sep 2017 13:39:38 +0200 Subject: [PATCH 13/13] Update to make use of update VAST model structure --- src/load-vast.js | 2 +- src/vast-to-ad.js | 9 +++++---- test/lib/build-vast.js | 18 +++++++++--------- test/unit/load-vast.js | 6 +++--- test/unit/vast-to-ad.js | 8 ++++---- 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/load-vast.js b/src/load-vast.js index d845bed..9336022 100644 --- a/src/load-vast.js +++ b/src/load-vast.js @@ -119,7 +119,7 @@ const getWrappers = (vast: VAST): Wrapper[] => // We cast to any here, because Flow is pessimistic and doesn't allow downcasting. // Another approach could be to add an additional map, and perform an invariant check. // This has however (a very minor) performance impact at runtime and it adds unnecessary complexity. - (vast.ads.filter(ad => ad instanceof Wrapper): any) + (vast.adBuffet.toArray().filter(ad => ad instanceof Wrapper): any) const normalizeCredentials = (credentials: Credentials): CredentialsTypeOrFn[] => Array.isArray(credentials) ? credentials diff --git a/src/vast-to-ad.js b/src/vast-to-ad.js index 8c6cff2..9252d97 100644 --- a/src/vast-to-ad.js +++ b/src/vast-to-ad.js @@ -53,17 +53,18 @@ export const vastToAd = (vast$: Observable): Observable - wrapper.parent.ads.indexOf(wrapper) + wrapper.parent.adBuffet.toArray().indexOf(wrapper) const walkAdsUntilNextWrapper = (vast: VAST, fromIndex: number): Ad[] => { - const adsFromIndex = vast.ads.slice(fromIndex) + const adBuffet = vast.adBuffet.toArray() + const adsFromIndex = adBuffet.slice(fromIndex) const toIndex = adsFromIndex.findIndex(ad => ad instanceof Wrapper) const ads = toIndex === -1 // All the inLines until the end of the array. - ? vast.ads.slice(fromIndex) + ? adBuffet.slice(fromIndex) // An array of inLine ads, ending with one wrapper ad. - : vast.ads.slice(fromIndex, fromIndex + toIndex + 1) + : adBuffet.slice(fromIndex, fromIndex + toIndex + 1) if (toIndex === -1) { // This VAST file doesn't have Wrappers anymore so we can continue walking the tree upwards. diff --git a/test/lib/build-vast.js b/test/lib/build-vast.js index 63231d2..40194ba 100644 --- a/test/lib/build-vast.js +++ b/test/lib/build-vast.js @@ -21,20 +21,20 @@ export const buildVast = () => { const standalone = buildVastVars('vast-standalone.xml') // Setup the Wrapper => VAST links. - b.model.parent = a.model.ads[1] - c.model.parent = a.model.ads[3] - d.model.parent = b.model.ads[0] - e.model.parent = b.model.ads[2] + b.model.parent = a.model.adBuffet.toArray()[1] + c.model.parent = a.model.adBuffet.toArray()[3] + d.model.parent = b.model.adBuffet.toArray()[0] + e.model.parent = b.model.adBuffet.toArray()[2] return { a, b, c, d, e, standalone } } export const buildAd = (vast) => { - const [p, q, r, s, t] = vast.a.model.ads - const [u, v, w] = vast.b.model.ads - const [x] = vast.c.model.ads - const [y] = vast.d.model.ads - const [z] = vast.e.model.ads + const [p, q, r, s, t] = vast.a.model.adBuffet.toArray() + const [u, v, w] = vast.b.model.adBuffet.toArray() + const [x] = vast.c.model.adBuffet.toArray() + const [y] = vast.d.model.adBuffet.toArray() + const [z] = vast.e.model.adBuffet.toArray() return { p, q, r, s, t, u, v, w, x, y, z } } diff --git a/test/unit/load-vast.js b/test/unit/load-vast.js index 92ee09c..892dd4b 100644 --- a/test/unit/load-vast.js +++ b/test/unit/load-vast.js @@ -285,7 +285,7 @@ describe('#loadVast()', () => { const expected = '--b----(de|)' const values = { b: { type: 'VAST_LOADED', vast: vast.b.model }, - d: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('301'), wrapper: vast.b.model.ads[0] }, + d: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('301'), wrapper: vast.b.model.adBuffet.toArray()[0] }, e: { type: 'VAST_LOADED', vast: vast.e.model } } @@ -356,8 +356,8 @@ describe('#loadVast()', () => { const values = { a: { type: 'VAST_LOADED', vast: vast.a.model }, b: { type: 'VAST_LOADED', vast: vast.b.model }, - d: { type: 'VAST_LOADING_FAILED', wrapper: vast.b.model.ads[0], error: new VASTLoaderError('302') }, - e: { type: 'VAST_LOADING_FAILED', wrapper: vast.b.model.ads[2], error: new VASTLoaderError('302') }, + d: { type: 'VAST_LOADING_FAILED', wrapper: vast.b.model.adBuffet.toArray()[0], error: new VASTLoaderError('302') }, + e: { type: 'VAST_LOADING_FAILED', wrapper: vast.b.model.adBuffet.toArray()[2], error: new VASTLoaderError('302') }, c: { type: 'VAST_LOADED', vast: vast.c.model } } diff --git a/test/unit/vast-to-ad.js b/test/unit/vast-to-ad.js index 3708c8c..0cbef18 100644 --- a/test/unit/vast-to-ad.js +++ b/test/unit/vast-to-ad.js @@ -63,9 +63,9 @@ describe('#vastToAd()', () => { const input$ = cold('--a----b--d-----e------(c|)', { a: { type: 'VAST_LOADED', vast: vast.a.model }, b: { type: 'VAST_LOADED', vast: vast.b.model }, - d: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: vast.b.model.ads[0] }, + d: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: vast.b.model.adBuffet.toArray()[0] }, e: { type: 'VAST_LOADED', vast: vast.e.model }, - c: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: vast.a.model.ads[3] } + c: { type: 'VAST_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: vast.a.model.adBuffet.toArray()[3] } }) const actual$ = vastToAd(input$) @@ -74,13 +74,13 @@ describe('#vastToAd()', () => { p: { type: 'AD_LOADED', ad: ad.p }, q: { type: 'AD_LOADED', ad: ad.q }, u: { type: 'AD_LOADED', ad: ad.u }, - y: { type: 'AD_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: vast.b.model.ads[0] }, + y: { type: 'AD_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: vast.b.model.adBuffet.toArray()[0] }, v: { type: 'AD_LOADED', ad: ad.v }, w: { type: 'AD_LOADED', ad: ad.w }, z: { type: 'AD_LOADED', ad: ad.z }, r: { type: 'AD_LOADED', ad: ad.r }, s: { type: 'AD_LOADED', ad: ad.s }, - x: { type: 'AD_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: vast.a.model.ads[3] }, + x: { type: 'AD_LOADING_FAILED', error: new VASTLoaderError('900'), wrapper: vast.a.model.adBuffet.toArray()[3] }, t: { type: 'AD_LOADED', ad: ad.t } } scheduler.expectObservable(actual$).toBe(expected, values)