import assert from 'assert' ; import buildDebug from 'debug' ; import _ from 'lodash' ; import { PassThrough } from 'stream' ; import UrlNode from 'url' ; import { errorUtils , pkgUtils , pluginUtils , searchUtils , validatioUtils } from '@verdaccio/core' ; import { API_ERROR , DIST_TAGS , HTTP_STATUS , SUPPORT_ERRORS , USERS } from '@verdaccio/core' ; import { VerdaccioError } from '@verdaccio/core' ; import { loadPlugin } from '@verdaccio/loaders' ; import LocalDatabase from '@verdaccio/local-storage' ; import { ReadTarball , UploadTarball } from '@verdaccio/streams' ; import { Author , Callback , CallbackAction , Config , DistFile , IPackageStorage , IReadTarball , IUploadTarball , Logger , MergeTags , Package , StorageUpdateCallback , StringValue , Token , TokenFilter , Version , } from '@verdaccio/types' ; import { getLatestVersion , isObject } from '@verdaccio/utils' ; import { createTarballHash , normalizeContributors } from '@verdaccio/utils' ; import { STORAGE , cleanUpReadme , generatePackageTemplate , generateRevision , getLatestReadme , normalizePackage , tagVersion , } from './storage-utils' ; const debug = buildDebug ( 'verdaccio:storage:local' ) ; export type IPluginStorage = pluginUtils . IPluginStorage < Config > ; export function normalizeSearchPackage ( pkg , searchItem ) { const latest = pkgUtils . getLatest ( pkg ) ; const version = pkg . versions [ latest ] ; const result = { name : version . name , scope : '' , description : version . description , version : latest , keywords : version . keywords , date : pkg . time [ latest ] , author : version . author as any , publisher : { } , maintainers : version . maintainers as any , links : { npm : '' , homepage : version . homepage , repository : version . repository , bugs : version . bugs , } , } ; if ( typeof searchItem . package . scoped === 'string' ) { result . scope = searchItem . package . scoped ; } return result ; } export const PROTO_NAME = '__proto__' ; /**
 * Implements Storage interface (same for storage.js, local-storage.js, up-storage.js).
 */ class LocalStorage { public config : Config ; public storagePlugin : IPluginStorage ; public logger : Logger ; public constructor ( config , logger ) { debug ( 'local storage created' ) ; this . logger = logger . child ( { sub : 'fs' } ) ; this . config = config ; this . storagePlugin = null ; } public async init ( ) { if ( this . storagePlugin === null ) { this . storagePlugin = this . _loadStorage ( this . config , this . logger ) ; debug ( 'storage plugin init' ) ; await this . storagePlugin . init ( ) ; debug ( 'storage plugin initialized' ) ; } else { this . logger . warn ( 'storage plugin has been already initialized' ) ; } return ; } public addPackage ( name , pkg , callback ) { debug ( `creating a package for` , name ) ; const storage = this . _getLocalStorage ( name ) ; if ( _ . isNil ( storage ) ) { debug ( `storage is missing for %o package cannot be added` , name ) ; return callback ( errorUtils . getNotFound ( 'this package cannot be added' ) ) ; } storage . createPackage ( name , generatePackageTemplate ( name ) , ( err ) => { if ( _ . isNull ( err ) === false && ( err . code === STORAGE . FILE_EXIST_ERROR || err . code === HTTP_STATUS . CONFLICT ) ) { debug ( `error on creating a package for %o with error %o` , name , err . message ) ; return callback ( errorUtils . getConflict ( ) ) ; } const latest = getLatestVersion ( pkg ) ; if ( _ . isNil ( latest ) === false && pkg . versions [ latest ] ) { debug ( 'latest version found %o for %o' , latest , name ) ; return callback ( null , pkg . versions [ latest ] ) ; } debug ( 'no latest version found for %o' , name ) ; return callback ( ) ; } ) ; } /**
     * Remove package with all it contents.
     */ public async removePackage ( name ) { debug ( 'remove package %s' , name ) ; const storage = this . _getLocalStorage ( name ) ; if ( _ . isNil ( storage ) ) { throw errorUtils . getNotFound ( ) ; } return new Promise ( ( resolve , reject ) => { storage . readPackage ( name , async ( err , data ) : Promise < void > => { if ( _ . isNil ( err ) === false ) { if ( err . code === STORAGE . NO_SUCH_FILE_ERROR || err . code === HTTP_STATUS . NOT_FOUND ) { debug ( `error on not found %o with error %o` , name , err . message ) ; return reject ( errorUtils . getNotFound ( ) ) ; } return reject ( err ) ; } data = normalizePackage ( data ) ; try { await this . storagePlugin . remove ( name ) ; const attachments = Object . keys ( data . _attachments ) ; debug ( 'attachments to remove %s' , attachments ?. length ) ; for ( let attachment of attachments ) { debug ( 'remove attachment %s' , attachment ) ; await storage . deletePackage ( attachment ) ; } debug ( 'remove package.json' ) ; await storage . deletePackage ( STORAGE . PACKAGE_FILE_NAME ) ; debug ( 'remove package folder' ) ; await storage . removePackage ( ) ; resolve ( ) ; } catch ( err ) { this . logger . error ( { err } , 'removed package has failed @{err.message}' ) ; throw errorUtils . getBadData ( err . message ) ; } } ) ; } ) ; } param { * } name param { * } packageInfo param { * } callback public updateVersions ( name , packageInfo , callback ) { debug ( `updating versions for package %o` , name ) ; this . _readCreatePackage ( name , ( err , packageLocalJson ) : void => { if ( err ) { return callback ( err ) ; } let change = false ; packageLocalJson . readme = getLatestReadme ( packageInfo ) ; if ( packageInfo . readme !== packageLocalJson . readme ) { change = true ; } debug ( 'update versions' ) ; for ( const versionId in packageInfo . versions ) { if ( _ . isNil ( packageLocalJson . versions [ versionId ] ) ) { let version = packageInfo . versions [ versionId ] ; version = cleanUpReadme ( version ) ; debug ( 'clean up readme for %o' , versionId ) ; version . contributors = normalizeContributors ( version . contributors as Author [ ] ) ; change = true ; packageLocalJson . versions [ versionId ] = version ; if ( version ?. dist ?. tarball ) { const urlObject = UrlNode . parse ( version . dist . tarball ) ; const filename = urlObject . pathname . replace ( /^.*\// , '' ) ; if ( _ . isNil ( packageLocalJson . _distfiles [ filename ] ) ) { const hash = ( packageLocalJson . _distfiles [ filename ] = { url : version . dist . tarball , sha : version . dist . shasum , } ) ; const upLink = version [ Symbol . for ( '__verdaccio_uplink' ) ] ; if ( _ . isNil ( upLink ) === false ) { this . _updateUplinkToRemoteProtocol ( hash , upLink ) ; } } } } } debug ( 'update dist-tags' ) ; for ( const tag in packageInfo [ DIST_TAGS ] ) { if ( ! packageLocalJson [ DIST_TAGS ] [ tag ] || packageLocalJson [ DIST_TAGS ] [ tag ] !== packageInfo [ DIST_TAGS ] [ tag ] ) { change = true ; packageLocalJson [ DIST_TAGS ] [ tag ] = packageInfo [ DIST_TAGS ] [ tag ] ; } } for ( const up in packageInfo . _uplinks ) { if ( Object . prototype . hasOwnProperty . call ( packageInfo . _uplinks , up ) ) { const need_change = ! isObject ( packageLocalJson . _uplinks [ up ] ) || packageInfo . _uplinks [ up ] . etag !== packageLocalJson . _uplinks [ up ] . etag || packageInfo . _uplinks [ up ] . fetched !== packageLocalJson . _uplinks [ up ] . fetched ; if ( need_change ) { change = true ; packageLocalJson . _uplinks [ up ] = packageInfo . _uplinks [ up ] ; } } } debug ( 'update time' ) ; if ( 'time' in packageInfo && ! _ . isEqual ( packageLocalJson . time , packageInfo . time ) ) { packageLocalJson . time = packageInfo . time ; change = true ; } if ( change ) { debug ( 'updating package info %o' , name ) ; this . _writePackage ( name , packageLocalJson , function ( err ) : void { callback ( err , packageLocalJson ) ; } ) ; } else { callback ( null , packageLocalJson ) ; } } ) ; } param { * } name param { * } version param { * } metadata param { * } tag param { * } callback public addVersion ( name , version , metadata , tag , callback ) { debug ( `add version %s package for %s` , version , name ) ; this . _updatePackage ( name , async ( data , cb ) : Promise < void > => { debug ( '%s package is being updated' , name ) ; data . readme = metadata . readme ; debug ( '%s` readme mutated' , name ) ; metadata = cleanUpReadme ( metadata ) ; metadata . contributors = normalizeContributors ( metadata . contributors as Author [ ] ) ; debug ( '%s` contributors normalized' , name ) ; const hasVersion = data . versions [ version ] != null ; if ( hasVersion ) { debug ( '%s version %s already exists' , name , version ) ; return cb ( errorUtils . getConflict ( ) ) ; } if ( validatioUtils . isObject ( metadata . dist ) && _ . isString ( metadata . dist . tarball ) ) { const tarball = metadata . dist . tarball . replace ( /.*\// , '' ) ; if ( validatioUtils . isObject ( data . _attachments [ tarball ] ) ) { if ( _ . isNil ( data . _attachments [ tarball ] . shasum ) === false && _ . isNil ( metadata . dist . shasum ) === false ) { if ( data . _attachments [ tarball ] . shasum != metadata . dist . shasum ) { const errorMessage = `shasum error, ` + `${ data . _attachments [ tarball ] . shasum } != ${ metadata . dist . shasum }` ; return cb ( errorUtils . getBadRequest ( errorMessage ) ) ; } } const currentDate = new Date ( ) . toISOString ( ) ; if ( _ . isNil ( data . time ) ) { data . time = { } ; } data . time [ 'modified' ] = currentDate ; if ( 'created' in data . time === false ) { data . time . created = currentDate ; } data . time [ version ] = currentDate ; data . _attachments [ tarball ] . version = version ; } } data . versions [ version ] = metadata ; tagVersion ( data , version , tag ) ; try { debug ( '%s` add on database' , name ) ; await this . storagePlugin . add ( name ) ; cb ( ) ; } catch ( err ) { cb ( errorUtils . getBadData ( err . message ) ) ; } } , callback ) ; } param { * } pkgName param { * } tags param { * } callback public mergeTags ( pkgName , tags , callback ) { debug ( `merge tags for` , pkgName ) ; this . _updatePackage ( pkgName , ( data , cb ) : void => { for ( const tag in tags ) { if ( _ . isNull ( tags [ tag ] ) ) { delete data [ DIST_TAGS ] [ tag ] ; continue ; } if ( _ . isNil ( data . versions [ tags [ tag ] ] ) ) { return cb ( errorUtils . getNotFound ( API_ERROR . VERSION_NOT_EXIST ) ) ; } const version = tags [ tag ] ; tagVersion ( data , version , tag ) ; } cb ( null ) ; } , callback ) ; } param { * } name param { * } incomingPkg param { * } revision param { * } callback return { Function } public changePackage ( name , incomingPkg , revision , callback ) { debug ( `change package tags for %o revision %s` , name , revision ) ; if ( ! validatioUtils . isObject ( incomingPkg . versions ) || ! validatioUtils . isObject ( incomingPkg [ DIST_TAGS ] ) ) { debug ( `change package bad data for %o` , name ) ; return callback ( errorUtils . getBadData ( ) ) ; } debug ( `change package udapting package for %o` , name ) ; this . _updatePackage ( name , ( localData , cb ) : void => { for ( const version in localData . versions ) { const incomingVersion = incomingPkg . versions [ version ] ; if ( _ . isNil ( incomingVersion ) ) { this . logger . info ( { name : name , version : version } , 'unpublishing @{name}@@{version}' ) ; delete localData . versions [ version ] ; delete localData . time ! [ version ] ; for ( const file in localData . _attachments ) { if ( localData . _attachments [ file ] . version === version ) { delete localData . _attachments [ file ] . version ; } } } else if ( Object . prototype . hasOwnProperty . call ( incomingVersion , 'deprecated' ) ) { const incomingDeprecated = incomingVersion . deprecated ; if ( incomingDeprecated != localData . versions [ version ] . deprecated ) { if ( ! incomingDeprecated ) { this . logger . info ( { name : name , version : version } , 'undeprecating @{name}@@{version}' ) ; delete localData . versions [ version ] . deprecated ; } else { this . logger . info ( { name : name , version : version } , 'deprecating @{name}@@{version}' ) ; localData . versions [ version ] . deprecated = incomingDeprecated ; } localData . time ! . modified = new Date ( ) . toISOString ( ) ; } } } localData [ USERS ] = incomingPkg [ USERS ] ; localData [ DIST_TAGS ] = incomingPkg [ DIST_TAGS ] ; cb ( null ) ; } , function ( err ) : void { if ( err ) { return callback ( err ) ; } callback ( ) ; } ) ; } param { * } name param { * } filename param { * } revision param { * } callback public removeTarball ( name , filename , revision , callback ) { debug ( 'remove tarball %s for %s' , filename , name ) ; assert ( validatioUtils . validateName ( filename ) ) ; this . _updatePackage ( name , ( data , cb ) : void => { if ( data . _attachments [ filename ] ) { delete data . _attachments [ filename ] ; cb ( null ) ; } else { cb ( errorUtils . getNotFound ( 'no such file available' ) ) ; } } , ( err ) => { if ( err ) { this . logger . error ( { err } , 'remove tarball error @{err.message}' ) ; return callback ( err ) ; } const storage = this . _getLocalStorage ( name ) ; if ( storage ) { debug ( 'removing %s from storage' , filename ) ; storage . deletePackage ( filename ) . then ( ( ) : void => { debug ( 'package %s removed' , filename ) ; return callback ( null ) ; } ) . catch ( ( err ) => { this . logger . error ( { err } , 'error removing %s from storage' ) ; return callback ( null ) ; } ) ; } else { callback ( errorUtils . getInternalError ( ) ) ; } } ) ; } param { String } name param { String } filename return { Stream } public addTarball ( name , filename ) { debug ( `add a tarball for %o` , name ) ; assert ( validatioUtils . validateName ( filename ) ) ; let length = 0 ; const shaOneHash = createTarballHash ( ) ; const uploadStream = new UploadTarball ( { } ) ; const _transform = uploadStream . _transform ; const storage = this . _getLocalStorage ( name ) ; uploadStream . abort = function ( ) : void { } ; uploadStream . done = function ( ) : void { } ; uploadStream . _transform = function ( data , ... args ) : void { shaOneHash . update ( data ) ; length += data . length ; const appliedData = [ data , ... args ] ; _transform . apply ( uploadStream , appliedData ) ; } ; if ( name === PROTO_NAME ) { process . nextTick ( ( ) : void => { uploadStream . emit ( 'error' , errorUtils . getForbidden ( ) ) ; } ) ; return uploadStream ; } if ( ! storage ) { process . nextTick ( ( ) : void => { uploadStream . emit ( 'error' , "can't upload this package" ) ; } ) ; return uploadStream ; } const writeStream = storage . writeTarball ( filename ) ; writeStream . on ( 'error' , ( err ) => { if ( err . code === STORAGE . FILE_EXIST_ERROR || err . code === HTTP_STATUS . CONFLICT ) { uploadStream . emit ( 'error' , errorUtils . getConflict ( ) ) ; uploadStream . abort ( ) ; } else if ( err . code === STORAGE . NO_SUCH_FILE_ERROR || err . code === HTTP_STATUS . NOT_FOUND ) { this . getPackageMetadata ( name , function ( _err ) : void { if ( _err ) { uploadStream . emit ( 'error' , _err ) ; } else { uploadStream . emit ( 'error' , err ) ; } } ) ; } else { uploadStream . emit ( 'error' , err ) ; } } ) ; writeStream . on ( 'open' , function ( ) : void { uploadStream . emit ( 'open' ) ; } ) ; writeStream . on ( 'success' , ( ) : void => { this . _updatePackage ( name , function updater ( data , cb ) : void { data . _attachments [ filename ] = { shasum : shaOneHash . digest ( 'hex' ) , } ; cb ( null ) ; } , function ( err ) : void { if ( err ) { uploadStream . emit ( 'error' , err ) ; } else { uploadStream . emit ( 'success' ) ; } } ) ; } ) ; uploadStream . abort = function ( ) : void { writeStream . abort ( ) ; } ; uploadStream . done = function ( ) : void { if ( ! length ) { uploadStream . emit ( 'error' , errorUtils . getBadData ( 'refusing to accept zero-length file' ) ) ; writeStream . abort ( ) ; } else { writeStream . done ( ) ; } } ; uploadStream . pipe ( writeStream ) ; return uploadStream ; } param { * } name param { * } filename return { ReadTarball } public getTarball ( name , filename ) { assert ( validatioUtils . validateName ( filename ) ) ; const storage = this . _getLocalStorage ( name ) ; if ( _ . isNil ( storage ) ) { return this . _createFailureStreamResponse ( ) ; } return this . _streamSuccessReadTarBall ( storage , filename ) ; } private return { ReadTarball } private _createFailureStreamResponse ( ) { const stream = new ReadTarball ( { } ) ; process . nextTick ( ( ) : void => { stream . emit ( 'error' , errorUtils . getNotFound ( 'no such file available' ) ) ; } ) ; return stream ; } param { Object } storage param { String } filename private return { ReadTarball } private _streamSuccessReadTarBall ( storage , filename ) { const stream = new ReadTarball ( { } ) ; const readTarballStream = storage . readTarball ( filename ) ; const e404 = errorUtils . getNotFound ; stream . abort = function ( ) : void { if ( _ . isNil ( readTarballStream ) === false ) { readTarballStream . abort ( ) ; } } ; readTarballStream . on ( 'error' , function ( err ) { if ( err . code === STORAGE . NO_SUCH_FILE_ERROR || err . code === HTTP_STATUS . NOT_FOUND ) { stream . emit ( 'error' , e404 ( 'no such file available' ) ) ; } else { stream . emit ( 'error' , err ) ; } } ) ; readTarballStream . on ( 'content-length' , function ( content ) : void { stream . emit ( 'content-length' , content ) ; } ) ; readTarballStream . on ( 'open' , function ( ) : void { stream . emit ( 'open' ) ; readTarballStream . pipe ( stream ) ; } ) ; return stream ; } public async getPackageMetadataNext ( name ) { const storage = this . _getLocalStorage ( name ) ; debug ( 'get package metadata for %o' , name ) ; if ( typeof storage === 'undefined' ) { throw errorUtils . getNotFound ( ) ; } return await this . _readPackageNext ( name , storage ) ; } param { * } name param { * } callback return { Function } deprecated public getPackageMetadata ( name , callback = ( ) : void => { } ) { const storage = this . _getLocalStorage ( name ) ; debug ( 'get package metadata for %o' , name ) ; if ( typeof storage === 'undefined' ) { return callback ( errorUtils . getNotFound ( ) ) ; } this . _readPackage ( name , storage , callback ) ; } public async search ( searchStream , query ) { debug ( 'search on each package' ) ; this . logger . info ( { t : query . text , q : query . quality , p : query . popularity , m : query . maintenance , s : query . size } , 'search by text @{t}| maintenance @{m}| quality @{q}| popularity @{p}' ) ; const getMetadata = ( searchItem ) => { return new Promise ( ( resolve , reject ) => { this . getPackageMetadata ( searchItem ?. package ?. name , ( err , pkg ) : void => { if ( err ) { this . logger . error ( { err , pkgName : searchItem ?. package ?. name } , 'error on load package @{pkgName} metaadata @{err.message}' ) ; reject ( err ) ; } const searchPackage = normalizeSearchPackage ( pkg , searchItem ) ; const searchPackageItem = { package : searchPackage , score : searchItem . score , flags : searchItem ?. flags , searchScore : 1 , } ; debug ( 'push to stream %o' , searchItem ?. package ?. name ) ; resolve ( searchPackageItem ) ; } ) ; } ) ; } ; if ( typeof this . storagePlugin . search === 'undefined' ) { this . logger . info ( 'plugin search not implemented yet' ) ; searchStream . end ( ) ; } else { debug ( 'search on each package by plugin' ) ; const items = await this . storagePlugin . search ( query ) ; try { for ( const item of items ) { const metadata = await getMetadata ( item ) ; searchStream . write ( metadata ) ; } debug ( 'search local stream end' ) ; searchStream . end ( ) ; } catch ( err ) { this . logger . error ( { err , query } , 'error on search by plugin @{err.message}' ) ; searchStream . emit ( 'error' , err ) ; } } } param { Object } pkgName return { Object } private _getLocalStorage ( pkgName ) { debug ( 'get local storage for %o' , pkgName ) ; return this . storagePlugin . getPackageStorage ( pkgName ) ; } param { Object } storage param { Function } callback deprecated private _readPackage ( name , storage , callback ) { storage . readPackage ( name , ( err , result ) : void => { if ( err ) { if ( err . code === STORAGE . NO_SUCH_FILE_ERROR || err . code === HTTP_STATUS . NOT_FOUND ) { debug ( 'package %s not found' , name ) ; return callback ( errorUtils . getNotFound ( ) ) ; } return callback ( this . _internalError ( err , STORAGE . PACKAGE_FILE_NAME , 'error reading' ) ) ; } callback ( err , normalizePackage ( result ) ) ; } ) ; } private async _readPackageNext ( name , storage ) { try { const result = await storage . readPackageNext ( name ) ; return normalizePackage ( result ) ; } catch ( err ) { if ( err . code === STORAGE . NO_SUCH_FILE_ERROR || err . code === HTTP_STATUS . NOT_FOUND ) { debug ( 'package %s not found' , name ) ; throw errorUtils . getNotFound ( ) ; } this . logger . error ( { err : err , file : STORAGE . PACKAGE_FILE_NAME } , `error reading  @{file}: @{!err.message}` ) ; throw errorUtils . getInternalError ( ) ; } } param { * } pkgName param { * } callback return { Function } private _readCreatePackage ( pkgName , callback ) { const storage = this . _getLocalStorage ( pkgName ) ; if ( _ . isNil ( storage ) ) { this . _createNewPackage ( pkgName , callback ) ; return ; } storage . readPackage ( pkgName , ( err , data ) : void => { if ( _ . isNil ( err ) === false ) { if ( err . code === STORAGE . NO_SUCH_FILE_ERROR || err . code === HTTP_STATUS . NOT_FOUND ) { data = generatePackageTemplate ( pkgName ) ; } else { return callback ( this . _internalError ( err , STORAGE . PACKAGE_FILE_NAME , 'error reading' ) ) ; } } callback ( null , normalizePackage ( data ) ) ; } ) ; } private _createNewPackage ( name , callback ) { return callback ( null , normalizePackage ( generatePackageTemplate ( name ) ) ) ; } param { * } err param { * } file param { * } message return { Object } private _internalError ( err , file , message ) { this . logger . error ( { err : err , file : file } , `${ message }  @{file}: @{!err.message}` ) ; return errorUtils . getInternalError ( ) ; } param { * } name param { * } updateHandler param { * } callback return { Function } private _updatePackage ( name , updateHandler , callback ) { const storage = this . _getLocalStorage ( name ) ; if ( ! storage ) { return callback ( errorUtils . getNotFound ( ) ) ; } storage . updatePackage ( name , updateHandler , this . _writePackage . bind ( this ) , normalizePackage , callback ) ; } param { * } name param { * } json param { * } callback return { Function } private _writePackage ( name , json , callback ) { const storage = this . _getLocalStorage ( name ) ; if ( _ . isNil ( storage ) ) { return callback ( ) ; } storage . savePackage ( name , this . _setDefaultRevision ( json ) , callback ) ; } private _setDefaultRevision ( json ) { if ( _ . isString ( json . _rev ) === false ) { json . _rev = STORAGE . DEFAULT_REVISION ; } if ( _ . isNil ( this . config . _debug ) ) { json . _rev = generateRevision ( json . _rev ) ; } return json ; } param { Object } hash param { String } upLinkKey private private _updateUplinkToRemoteProtocol ( hash , upLinkKey ) { const tarballUrl = UrlNode . parse ( hash . url ) ; const uplinkUrl = UrlNode . parse ( this . config . uplinks [ upLinkKey ] . url ) ; if ( uplinkUrl . host === tarballUrl . host ) { tarballUrl . protocol = uplinkUrl . protocol ; hash . registry = upLinkKey ; hash . url = UrlNode . format ( tarballUrl ) ; } } public async getSecret ( config ) { const secretKey = await this . storagePlugin . getSecret ( ) ; return this . storagePlugin . setSecret ( config . checkSecretKey ( secretKey ) ) ; } private _loadStorage ( config , logger ) { const Storage = this . _loadStorePlugin ( ) ; if ( _ . isNil ( Storage ) ) { assert ( this . config . storage , 'CONFIG: storage path not defined' ) ; return new LocalDatabase ( this . config , logger ) ; } return Storage as IPluginStorage ; } private _loadStorePlugin ( ) { const plugin_params = { config : this . config , logger : this . logger , } ; const plugins = loadPlugin < IPluginStorage > ( this . config , this . config . store , plugin_params , ( plugin ) : IPluginStorage => { return plugin . getPackageStorage ; } ) ; return _ . head ( plugins ) ; } public saveToken ( token ) { if ( _ . isFunction ( this . storagePlugin . saveToken ) === false ) { return Promise . reject ( errorUtils . getCode ( HTTP_STATUS . SERVICE_UNAVAILABLE , SUPPORT_ERRORS . PLUGIN_MISSING_INTERFACE ) ) ; } return this . storagePlugin . saveToken ( token ) ; } public deleteToken ( user , tokenKey ) { if ( _ . isFunction ( this . storagePlugin . deleteToken ) === false ) { return Promise . reject ( errorUtils . getCode ( HTTP_STATUS . SERVICE_UNAVAILABLE , SUPPORT_ERRORS . PLUGIN_MISSING_INTERFACE ) ) ; } return this . storagePlugin . deleteToken ( user , tokenKey ) ; } public readTokens ( filter ) { if ( _ . isFunction ( this . storagePlugin . readTokens ) === false ) { return Promise . reject ( errorUtils . getCode ( HTTP_STATUS . SERVICE_UNAVAILABLE , SUPPORT_ERRORS . PLUGIN_MISSING_INTERFACE ) ) ; } return this . storagePlugin . readTokens ( filter ) ; } } export { LocalStorage } ;