/**
 * Copyright 2018 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ import * as asyncHook from 'async_hooks' ; import { Context , Func , Namespace as CLSNamespace , } from 'continuation-local-storage' ; import { EventEmitter } from 'events' ; import * as shimmer from 'shimmer' ; const WRAPPED = Symbol ( 'context_wrapped' ) ; /** A map of AsyncResource IDs to Context objects. */ let contexts = new Map ( ) ; let current = { } ; asyncHook . createHook ( { init , before , destroy } ) . enable ( ) ; const EVENT_EMITTER_METHODS = [ 'addListener' , 'on' , 'once' , 'prependListener' , 'prependOnceListener' , ] ; class AsyncHooksNamespace implements CLSNamespace { get name ( ) : string { throw new Error ( 'Not implemented' ) ; } get active ( ) : Context { return current ; } createContext ( ) { throw new Error ( 'Not implemented' ) ; } get ( k ) { return current [ k ] ; } set < T > ( k , v ) { current [ k ] = v ; return v ; } run < T > ( fn ) { this . runAndReturn ( fn ) ; return current ; } runAndReturn < T > ( fn ) { const oldContext = current ; current = { } ; if ( oldContext [ 'current_tag_map' ] ) { current [ 'current_tag_map' ] = oldContext [ 'current_tag_map' ] ; } const res = fn ( ) ; current = oldContext ; return res ; } bind < T > ( cb ) { if ( ( ( cb as any ) [ WRAPPED ] as boolean ) || ! current ) { return cb ; } const boundContext = current ; const contextWrapper = function ( this ) { const oldContext = current ; current = boundContext ; const res = cb . apply ( this , arguments ) as T ; current = oldContext ; return res ; } ; ( contextWrapper as any ) [ WRAPPED ] = true ; Object . defineProperty ( contextWrapper , 'length' , { enumerable : false , configurable : true , writable : false , value : cb . length , } ) ; return contextWrapper ; } bindEmitter ( ee ) { const ns = this ; EVENT_EMITTER_METHODS . forEach ( method => { if ( ee [ method ] ) { shimmer . wrap ( ee , method , oldMethod => { return function ( this , event , cb ) { return oldMethod . call ( this , event , ns . bind ( cb ) ) ; } ; } ) ; } } ) ; } } const namespace = new AsyncHooksNamespace ( ) ; /** init is called during object construction. */ function init ( uid , provider , parentUid , parentHandle ) { contexts . set ( uid , current ) ; } /** before is called just before the resource's callback is called. */ function before ( uid ) { const maybeCurrent = contexts . get ( uid ) ; if ( maybeCurrent !== undefined ) { current = maybeCurrent ; } } /**
 * destroy is called when the object is no longer used, so also delete
 * its entry in the map.
 */ function destroy ( uid ) { contexts . delete ( uid ) ; } export function createNamespace ( ) { return namespace ; } export function destroyNamespace ( ) { current = { } ; contexts = new Map ( ) ; } export function getNamespace ( ) { return namespace ; } export function reset ( ) { throw new Error ( 'Not implemented' ) ; }