<commit_before>/**
 * Authenticator
 */
/* eslint-disable no-underscore-dangle */
/* eslint-disable import/no-unresolved, import/extensions */
import ffi from 'ffi';
import ref from 'ref';
/* eslint-enable import/no-unresolved, import/extensions */
import crypto from 'crypto';
import lodash from 'lodash';
import i18n from 'i18n';
import logger from 'logger';

import SafeLib from './safe_lib';
import Listener from './listeners';
import config from '../config';
import * as types from './refs/types';
import * as typeParser from './refs/parsers';
import * as typeConstructor from './refs/constructors';
import CONSTANTS from '../auth-constants';

// private variables
const _registeredClientHandle = Symbol( 'registeredClientHandle' );
const _nwState = Symbol( 'nwState' );
const _reAuthoriseState = Symbol( 'reAuthoriseState' );
const _appListUpdateListener = Symbol( 'appListUpdate' );
const _authReqListener = Symbol( 'authReq' );
const _containerReqListener = Symbol( 'containerReq' );
const _mDataReqListener = Symbol( 'mDataReq' );
const _nwStateChangeListener = Symbol( 'nwStateChangeListener' );
const _reqErrListener = Symbol( 'reqErrListener' );
const _cbRegistry = Symbol( 'cbRegistry' );
const _netDisconnectCb = Symbol( 'netDisconnectCb' );
const _decodeReqPool = Symbol( 'decodeReqPool' );

/**
* @private
* Generates the app's URI converting the string into a base64 format, removing
* characters or symbols which are not valid for a URL like '=' sign,
* and making it lower case.
*/
const genAppUri = ( str ) =>
{
    const urlSafeBase64 = ( new Buffer( str ) )
        .toString( 'base64' )
        .replace( /\+/g, '-' ) // Convert '+' to '-'
        .replace( /\//g, '_' ) // Convert '/' to '_'
        .replace( /=+$/, '' ) // Remove ending '='
        .toLowerCase();
    return `safe-${urlSafeBase64}`;
};

class Authenticator extends SafeLib
{
    constructor()
    {
        super();

        config.i18n();
        this[_registeredClientHandle] = null;
        this[_nwState] = CONSTANTS.NETWORK_STATUS.DISCONNECTED;
        this[_reAuthoriseState] = null;
        this[_appListUpdateListener] = new Listener();
        this[_authReqListener] = new Listener();
        this[_containerReqListener] = new Listener();
        this[_mDataReqListener] = new Listener();
        this[_nwStateChangeListener] = new Listener();
        this[_reqErrListener] = new Listener();
        this[_cbRegistry] = {};
        this[_decodeReqPool] = {};
        this[_netDisconnectCb] = ffi.Callback( types.Void,
            [types.voidPointer, types.int32, types.int32], () =>
            {
                this._pushNetworkState( CONSTANTS.NETWORK_STATUS.DISCONNECTED );
            } );
    }

    get registeredClientHandle()
    {
        return this[_registeredClientHandle];
    }

    set registeredClientHandle( handle )
    {
        this[_registeredClientHandle] = handle;
    }

    get networkState()
    {
        return this[_nwState];
    }

    set networkState( state )
    {
        if ( typeof state === 'undefined' )
        {
            return;
        }
        this[_nwState] = state;
    }

    get networkDisconnectCb()
    {
        return this[_netDisconnectCb];
    }

    getLibStatus()
    {
        return this.isLibLoaded;
    }

    fnsToRegister()
    {
        return {
            create_acc                       : [types.Void, [types.CString, types.CString, types.CString, types.voidPointer, 'pointer', 'pointer']],
            login                            : [types.Void, [types.CString, types.CString, types.voidPointer, 'pointer', 'pointer']],
            auth_decode_ipc_msg              : [types.Void, [types.voidPointer, types.CString, types.voidPointer, 'pointer', 'pointer', 'pointer', 'pointer', 'pointer']],
            encode_auth_resp                 : [types.Void, [types.voidPointer, types.AuthReqPointer, types.u32, types.bool, types.voidPointer, 'pointer']],
            encode_containers_resp           : [types.Void, [types.voidPointer, types.ContainersReqPointer, types.u32, types.bool, types.voidPointer, 'pointer']],
            auth_unregistered_decode_ipc_msg : [types.Void, [types.CString, types.voidPointer, 'pointer', 'pointer']],
            encode_share_mdata_resp          : [types.Void, [types.voidPointer, types.ShareMDataReqPointer, types.u32, types.bool, 'pointer', 'pointer']],
            encode_unregistered_resp         : [types.Void, [types.u32, types.bool, types.voidPointer, 'pointer']],
            auth_registered_apps             : [types.Void, [types.voidPointer, types.voidPointer, 'pointer']],
            auth_revoke_app                  : [types.Void, [types.voidPointer, types.CString, types.voidPointer, 'pointer']],
            auth_apps_accessing_mutable_data : [types.Void, [types.voidPointer, 'pointer', types.u64, 'pointer', 'pointer']],
            auth_free                        : [types.Void, [types.voidPointer]],
            auth_init_logging                : [types.Void, [types.CString, types.voidPointer, 'pointer']],
            auth_set_additional_search_path  : [types.Void, [types.CString, types.voidPointer, 'pointer']],
            auth_reconnect                   : [types.Void, [types.voidPointer, types.voidPointer, 'pointer']],
            auth_account_info                : [types.Void, [types.voidPointer, 'pointer', 'pointer']]
        };
    }

    setListener( type, cb )
    {
    // FIXME check .key required
        switch ( type.key )
        {
            case CONSTANTS.LISTENER_TYPES.APP_LIST_UPDATE.key: {
                return this[_appListUpdateListener].add( cb );
            }
            case CONSTANTS.LISTENER_TYPES.AUTH_REQ.key: {
                return this[_authReqListener].add( cb );
            }
            case CONSTANTS.LISTENER_TYPES.CONTAINER_REQ.key: {
                return this[_containerReqListener].add( cb );
            }
            case CONSTANTS.LISTENER_TYPES.MDATA_REQ.key: {
                return this[_mDataReqListener].add( cb );
            }
            case CONSTANTS.LISTENER_TYPES.NW_STATE_CHANGE.key: {
                return this[_nwStateChangeListener].add( cb );
            }
            case CONSTANTS.LISTENER_TYPES.REQUEST_ERR.key: {
                return this[_reqErrListener].add( cb );
            }
            default: {
                throw new Error( 'Invalid listener type' );
            }
        }
    }

    removeListener( type, id )
    {
        switch ( type.key )
        {
            case CONSTANTS.LISTENER_TYPES.APP_LIST_UPDATE.key: {
                return this[_appListUpdateListener].remove( id );
            }
            case CONSTANTS.LISTENER_TYPES.AUTH_REQ.key: {
                return this[_authReqListener].remove( id );
            }
            case CONSTANTS.LISTENER_TYPES.CONTAINER_REQ.key: {
                return this[_containerReqListener].remove( id );
            }
            case CONSTANTS.LISTENER_TYPES.NW_STATE_CHANGE.key: {
                return this[_nwStateChangeListener].remove( id );
            }
            case CONSTANTS.LISTENER_TYPES.REQUEST_ERR.key: {
                return this[_reqErrListener].remove( id );
            }
            default: {
                throw new Error( 'Invalid listener type' );
            }
        }
    }

    setReAuthoriseState( state )
    {
        this[_reAuthoriseState] = state;
    }

    reconnect()
    {
        return new Promise( ( resolve, reject ) =>
        {
            if ( !this.registeredClientHandle )
            {
                return reject( new Error( i18n.__( 'messages.unauthorised' ) ) );
            }
            try
            {
                const cb = this._pushCb( ffi.Callback( types.Void,
                    [types.voidPointer, types.FfiResultPointer],
                    ( userData, resultPtr ) =>
                    {
                        const result = resultPtr.deref();
                        if ( result.error_code !== 0 )
                        {
                            return reject( JSON.stringify( result ) );
                        }
                        this._pushNetworkState( CONSTANTS.NETWORK_STATUS.CONNECTED );
                        resolve();
                    } ) );

                this.safeLib.auth_reconnect(
                    this.registeredClientHandle,
                    types.Null,
                    this._getCb( cb )
                );
            }
            catch ( e )
            {
                reject( e );
            }
        } );
    }

    createAccount( locator, secret, invitation )
    {
        return new Promise( ( resolve, reject ) =>
        {
            const validationErr = this._isUserCredentialsValid( locator, secret );
            if ( validationErr )
            {
                return reject( validationErr );
            }

            if ( !( invitation && ( typeof invitation === 'string' ) && invitation.trim() ) )
            {
                return Promise.reject( new Error( i18n.__( 'messages.invalid_invite_code' ) ) );
            }

            try
            {
                const createAccCb = this._pushCb( ffi.Callback( types.Void,
                    [types.voidPointer, types.FfiResultPointer, types.ClientHandlePointer],
                    ( userData, resultPtr, clientHandle ) =>
                    {
                        const result = resultPtr.deref();
                        if ( result.error_code !== 0 && clientHandle.length === 0 )
                        {
                            return reject( JSON.stringify( result ) );
                        }
                        this.registeredClientHandle = clientHandle;
                        this._pushNetworkState( CONSTANTS.NETWORK_STATUS.CONNECTED );
                        resolve();
                    } ) );

                const onResult = ( err, res ) =>
                {
                    if ( err || res !== 0 )
                    {
                        return reject( err );
                    }
                };

                this.safeLib.create_acc.async(
                    types.allocCString( locator ),
                    types.allocCString( secret ),
                    types.allocCString( invitation ),
                    types.Null,
                    this.networkDisconnectCb,
                    this._getCb( createAccCb ),
                    onResult );
            }
            catch ( e )
            {
                reject( e );
            }
        } );
    }

    login( locator, secret )
    {
        return new Promise( ( resolve, reject ) =>
        {
            const validationErr = this._isUserCredentialsValid( locator, secret );
            if ( validationErr )
            {
                return reject( validationErr );
            }

            try
            {
                const loginCb = this._pushCb( ffi.Callback( types.Void,
                    [types.voidPointer, types.FfiResultPointer, types.ClientHandlePointer],
                    ( userData, resultPtr, clientHandle ) =>
                    {
                        const result = resultPtr.deref();
                        if ( result.error_code !== 0 && clientHandle.length === 0 )
                        {
                            return reject( JSON.stringify( result ) );
                        }
                        this.registeredClientHandle = clientHandle;
                        this._pushNetworkState( CONSTANTS.NETWORK_STATUS.CONNECTED );
                        resolve();
                    } ) );

                const onResult = ( err, res ) =>
                {
                    if ( err || res !== 0 )
                    {
                        return reject( err );
                    }
                };

                this.safeLib.login.async(
                    types.allocCString( locator ),
                    types.allocCString( secret ),
                    types.Null,
                    this.networkDisconnectCb,
                    this._getCb( loginCb ),
                    onResult );
            }
            catch ( e )
            {
                reject( e );
            }
        } );
    }

    logout()
    {
        this._pushNetworkState( CONSTANTS.NETWORK_STATUS.DISCONNECTED );
        this.safeLib.auth_free( this.registeredClientHandle );
        this.registeredClientHandle = null;
    }

    decodeRequest( uri )
    {
        return new Promise( ( resolve, reject ) =>
        {
            if ( !uri )
            {
                return reject( new Error( 'Invalid URI' ) );
            }
            const parsedURI = uri.replace( 'safe-auth://', '' ).replace( 'safe-auth:', '' ).replace( '/', '' );

            if ( !this.registeredClientHandle )
            {
                return this._decodeUnRegisteredRequest( parsedURI, resolve, reject );
            }
            const decodeReqAuthCb = this._pushCb( ffi.Callback( types.Void,
                [types.voidPointer, types.u32, types.AuthReqPointer], ( userData, reqId, req ) =>
                {
                    if ( !( this[_authReqListener] && this[_authReqListener].len() !== 0 ) )
                    {
                        return;
                    }
                    const authReq = typeParser.parseAuthReq( req.deref() );
                    this[_decodeReqPool][reqId] = authReq;
                    const result = {
                        reqId,
                        authReq
                    };
                    return this._isAlreadyAuthorised( authReq )
                        .then( ( isAuthorised ) =>
                        {
                            if ( isAuthorised )
                            {
                                // re authorise the app
                                if ( this[_reAuthoriseState] !== CONSTANTS.RE_AUTHORISE.STATE.UNLOCK )
                                {
                                    result.isAuthorized = true;
                                    this[_authReqListener].broadcast( null, result );
                                    return resolve();
                                }
                                return this.encodeAuthResp( result, true )
                                    .then( resolve );
                            }
                            this[_authReqListener].broadcast( null, result );
                            resolve();
                        } );
                } ) );

            const decodeReqContainerCb = this._pushCb( ffi.Callback( types.Void,
                [types.voidPointer, types.u32, types.ContainersReqPointer], ( userData, reqId, req ) =>
                {
                    if ( !( this[_containerReqListener] && this[_containerReqListener].len() !== 0 ) )
                    {
                        return;
                    }
                    const contReq = typeParser.parseContainerReq( req.deref() );
                    this[_decodeReqPool][reqId] = contReq;
                    const result = {
                        reqId,
                        contReq
                    };
                    if ( this[_reAuthoriseState] !== CONSTANTS.RE_AUTHORISE.STATE.UNLOCK )
                    {
                        this[_containerReqListener].broadcast( null, result );
                        return resolve();
                    }
                    return this._isAlreadyAuthorisedContainer( contReq )
                        .then( ( isAuthorised ) =>
                        {
                            if ( isAuthorised )
                            {
                                return this.encodeContainersResp( result, true )
                                    .then( resolve );
                            }
                            this[_containerReqListener].broadcast( null, result );
                            resolve();
                        } );
                } ) );

            const shareMdataCb = this._pushCb( ffi.Callback( types.Void,
                [types.voidPointer, types.u32, types.ShareMDataReqPointer, 'pointer'],
                ( userData, reqId, req, meta ) =>
                {
                    const mDataReq = typeParser.parseShareMDataReq( req.deref() );
                    const metaData = typeParser.parseUserMetaDataArray( meta, mDataReq.mdata_len );
                    this[_decodeReqPool][reqId] = mDataReq;
                    const result = {
                        reqId,
                        mDataReq,
                        metaData
                    };
                    const appAccess = [];
                    const tempArr = [];
                    for ( let i = 0; i < mDataReq.mdata_len; i++ )
                    {
                        tempArr[i] = i;
                    }

                    return Promise.all( tempArr.map( ( i ) =>
                    {
                        const mdata = mDataReq.mdata[i];
                        return this._appsAccessingMData( mdata.name, mdata.type_tag )
                            .then( ( res ) =>
                            {
                                appAccess[i] = res;
                            } );
                    } ) )
                        .then( () =>
                        {
                            result.appAccess = appAccess;
                            this[_mDataReqListener].broadcast( null, result );
                        } );
                } ) );

            const unregisteredCb = this._getUnregisteredClientCb( resolve, reject );

            const decodeReqErrorCb = this._pushCb( ffi.Callback( types.Void,
                [types.voidPointer, types.FfiResultPointer, types.CString], ( userData, resultPtr ) =>
                {
                    const result = resultPtr.deref();
                    if ( !( this[_reqErrListener] && this[_reqErrListener].len() !== 0 ) )
                    {
                        return;
                    }
                    this[_reqErrListener].broadcast( JSON.stringify( result ) );
                } ) );
            try
            {
                this.safeLib.auth_decode_ipc_msg(
                    this.registeredClientHandle,
                    types.allocCString( parsedURI ),
                    types.Null,
                    this._getCb( decodeReqAuthCb ),
                    this._getCb( decodeReqContainerCb ),
                    this._getCb( unregisteredCb ),
                    this._getCb( shareMdataCb ),
                    this._getCb( decodeReqErrorCb ) );
            }
            catch ( e )
            {
                reject( e );
            }
        } );
    }

    encodeAuthResp( req, isAllowed )
    {
        return new Promise( ( resolve, reject ) =>
        {
            if ( !this.registeredClientHandle )
            {
                return reject( new Error( i18n.__( 'messages.unauthorised' ) ) );
            }

            if ( !req || typeof isAllowed !== 'boolean' )
            {
                return reject( new Error( i18n.__( 'messages.invalid_params' ) ) );
            }

            if ( !req.reqId || !this[_decodeReqPool][req.reqId] )
            {
                return reject( new Error( i18n.__( 'messages.invalid_req' ) ) );
            }

            const authReq = types.allocAuthReq( typeConstructor.constructAuthReq(
                this[_decodeReqPool][req.reqId] ) );

            delete this[_decodeReqPool][req.reqId];

            try
            {
                const authDecisionCb = this._pushCb( ffi.Callback( types.Void,
                    [types.voidPointer, types.FfiResultPointer, types.CString],
                    ( userData, resultPtr, res ) =>
                    {
                        const result = resultPtr.deref();
                        if ( result.error_code !== 0 && !res )
                        {
                            return reject( JSON.stringify( result ) );
                        }
                        if ( isAllowed )
                        {
                            this._updateAppList();
                        }
                        const appUri = genAppUri( req.authReq.app.id );
                        resolve( `${appUri}:${res}` );
                    } ) );
                this.safeLib.encode_auth_resp(
                    this.registeredClientHandle,
                    authReq,
                    req.reqId,
                    isAllowed,
                    types.Null,
                    this._getCb( authDecisionCb )
                );
            }
            catch ( e )
            {
                reject( e );
            }<commit_msg>fix/error: handles condition for auth error<commit_after>/**
 * Authenticator
 */
/* eslint-disable no-underscore-dangle */
/* eslint-disable import/no-unresolved, import/extensions */
import ffi from 'ffi';
import ref from 'ref';
/* eslint-enable import/no-unresolved, import/extensions */
import crypto from 'crypto';
import lodash from 'lodash';
import i18n from 'i18n';
import logger from 'logger';

import SafeLib from './safe_lib';
import Listener from './listeners';
import config from '../config';
import * as types from './refs/types';
import * as typeParser from './refs/parsers';
import * as typeConstructor from './refs/constructors';
import CONSTANTS from '../auth-constants';

// private variables
const _registeredClientHandle = Symbol( 'registeredClientHandle' );
const _nwState = Symbol( 'nwState' );
const _reAuthoriseState = Symbol( 'reAuthoriseState' );
const _appListUpdateListener = Symbol( 'appListUpdate' );
const _authReqListener = Symbol( 'authReq' );
const _containerReqListener = Symbol( 'containerReq' );
const _mDataReqListener = Symbol( 'mDataReq' );
const _nwStateChangeListener = Symbol( 'nwStateChangeListener' );
const _reqErrListener = Symbol( 'reqErrListener' );
const _cbRegistry = Symbol( 'cbRegistry' );
const _netDisconnectCb = Symbol( 'netDisconnectCb' );
const _decodeReqPool = Symbol( 'decodeReqPool' );

/**
* @private
* Generates the app's URI converting the string into a base64 format, removing
* characters or symbols which are not valid for a URL like '=' sign,
* and making it lower case.
*/
const genAppUri = ( str ) =>
{
    const urlSafeBase64 = ( new Buffer( str ) )
        .toString( 'base64' )
        .replace( /\+/g, '-' ) // Convert '+' to '-'
        .replace( /\//g, '_' ) // Convert '/' to '_'
        .replace( /=+$/, '' ) // Remove ending '='
        .toLowerCase();
    return `safe-${urlSafeBase64}`;
};

class Authenticator extends SafeLib
{
    constructor()
    {
        super();

        config.i18n();
        this[_registeredClientHandle] = null;
        this[_nwState] = CONSTANTS.NETWORK_STATUS.DISCONNECTED;
        this[_reAuthoriseState] = null;
        this[_appListUpdateListener] = new Listener();
        this[_authReqListener] = new Listener();
        this[_containerReqListener] = new Listener();
        this[_mDataReqListener] = new Listener();
        this[_nwStateChangeListener] = new Listener();
        this[_reqErrListener] = new Listener();
        this[_cbRegistry] = {};
        this[_decodeReqPool] = {};
        this[_netDisconnectCb] = ffi.Callback( types.Void,
            [types.voidPointer, types.int32, types.int32], () =>
            {
                this._pushNetworkState( CONSTANTS.NETWORK_STATUS.DISCONNECTED );
            } );
    }

    get registeredClientHandle()
    {
        return this[_registeredClientHandle];
    }

    set registeredClientHandle( handle )
    {
        this[_registeredClientHandle] = handle;
    }

    get networkState()
    {
        return this[_nwState];
    }

    set networkState( state )
    {
        if ( typeof state === 'undefined' )
        {
            return;
        }
        this[_nwState] = state;
    }

    get networkDisconnectCb()
    {
        return this[_netDisconnectCb];
    }

    getLibStatus()
    {
        return this.isLibLoaded;
    }

    fnsToRegister()
    {
        return {
            create_acc                       : [types.Void, [types.CString, types.CString, types.CString, types.voidPointer, 'pointer', 'pointer']],
            login                            : [types.Void, [types.CString, types.CString, types.voidPointer, 'pointer', 'pointer']],
            auth_decode_ipc_msg              : [types.Void, [types.voidPointer, types.CString, types.voidPointer, 'pointer', 'pointer', 'pointer', 'pointer', 'pointer']],
            encode_auth_resp                 : [types.Void, [types.voidPointer, types.AuthReqPointer, types.u32, types.bool, types.voidPointer, 'pointer']],
            encode_containers_resp           : [types.Void, [types.voidPointer, types.ContainersReqPointer, types.u32, types.bool, types.voidPointer, 'pointer']],
            auth_unregistered_decode_ipc_msg : [types.Void, [types.CString, types.voidPointer, 'pointer', 'pointer']],
            encode_share_mdata_resp          : [types.Void, [types.voidPointer, types.ShareMDataReqPointer, types.u32, types.bool, 'pointer', 'pointer']],
            encode_unregistered_resp         : [types.Void, [types.u32, types.bool, types.voidPointer, 'pointer']],
            auth_registered_apps             : [types.Void, [types.voidPointer, types.voidPointer, 'pointer']],
            auth_revoke_app                  : [types.Void, [types.voidPointer, types.CString, types.voidPointer, 'pointer']],
            auth_apps_accessing_mutable_data : [types.Void, [types.voidPointer, 'pointer', types.u64, 'pointer', 'pointer']],
            auth_free                        : [types.Void, [types.voidPointer]],
            auth_init_logging                : [types.Void, [types.CString, types.voidPointer, 'pointer']],
            auth_set_additional_search_path  : [types.Void, [types.CString, types.voidPointer, 'pointer']],
            auth_reconnect                   : [types.Void, [types.voidPointer, types.voidPointer, 'pointer']],
            auth_account_info                : [types.Void, [types.voidPointer, 'pointer', 'pointer']]
        };
    }

    setListener( type, cb )
    {
    // FIXME check .key required
        switch ( type.key )
        {
            case CONSTANTS.LISTENER_TYPES.APP_LIST_UPDATE.key: {
                return this[_appListUpdateListener].add( cb );
            }
            case CONSTANTS.LISTENER_TYPES.AUTH_REQ.key: {
                return this[_authReqListener].add( cb );
            }
            case CONSTANTS.LISTENER_TYPES.CONTAINER_REQ.key: {
                return this[_containerReqListener].add( cb );
            }
            case CONSTANTS.LISTENER_TYPES.MDATA_REQ.key: {
                return this[_mDataReqListener].add( cb );
            }
            case CONSTANTS.LISTENER_TYPES.NW_STATE_CHANGE.key: {
                return this[_nwStateChangeListener].add( cb );
            }
            case CONSTANTS.LISTENER_TYPES.REQUEST_ERR.key: {
                return this[_reqErrListener].add( cb );
            }
            default: {
                throw new Error( 'Invalid listener type' );
            }
        }
    }

    removeListener( type, id )
    {
        switch ( type.key )
        {
            case CONSTANTS.LISTENER_TYPES.APP_LIST_UPDATE.key: {
                return this[_appListUpdateListener].remove( id );
            }
            case CONSTANTS.LISTENER_TYPES.AUTH_REQ.key: {
                return this[_authReqListener].remove( id );
            }
            case CONSTANTS.LISTENER_TYPES.CONTAINER_REQ.key: {
                return this[_containerReqListener].remove( id );
            }
            case CONSTANTS.LISTENER_TYPES.NW_STATE_CHANGE.key: {
                return this[_nwStateChangeListener].remove( id );
            }
            case CONSTANTS.LISTENER_TYPES.REQUEST_ERR.key: {
                return this[_reqErrListener].remove( id );
            }
            default: {
                throw new Error( 'Invalid listener type' );
            }
        }
    }

    setReAuthoriseState( state )
    {
        this[_reAuthoriseState] = state;
    }

    reconnect()
    {
        return new Promise( ( resolve, reject ) =>
        {
            if ( !this.registeredClientHandle )
            {
                return reject( new Error( i18n.__( 'messages.unauthorised' ) ) );
            }
            try
            {
                const cb = this._pushCb( ffi.Callback( types.Void,
                    [types.voidPointer, types.FfiResultPointer],
                    ( userData, resultPtr ) =>
                    {
                        const result = resultPtr.deref();
                        if ( result.error_code !== 0 )
                        {
                            return reject( JSON.stringify( result ) );
                        }
                        this._pushNetworkState( CONSTANTS.NETWORK_STATUS.CONNECTED );
                        resolve();
                    } ) );

                this.safeLib.auth_reconnect(
                    this.registeredClientHandle,
                    types.Null,
                    this._getCb( cb )
                );
            }
            catch ( e )
            {
                reject( e );
            }
        } );
    }

    createAccount( locator, secret, invitation )
    {
        return new Promise( ( resolve, reject ) =>
        {
            const validationErr = this._isUserCredentialsValid( locator, secret );
            if ( validationErr )
            {
                return reject( validationErr );
            }

            if ( !( invitation && ( typeof invitation === 'string' ) && invitation.trim() ) )
            {
                return Promise.reject( new Error( i18n.__( 'messages.invalid_invite_code' ) ) );
            }

            try
            {
                const createAccCb = this._pushCb( ffi.Callback( types.Void,
                    [types.voidPointer, types.FfiResultPointer, types.ClientHandlePointer],
                    ( userData, resultPtr, clientHandle ) =>
                    {
                        const result = resultPtr.deref();
                        if ( result.error_code !== 0 && clientHandle.length === 0 )
                        {
                            return reject( JSON.stringify( result ) );
                        }
                        this.registeredClientHandle = clientHandle;
                        this._pushNetworkState( CONSTANTS.NETWORK_STATUS.CONNECTED );
                        resolve();
                    } ) );

                const onResult = ( err, res ) =>
                {
                    if ( err || res !== 0 )
                    {
                        return reject( err );
                    }
                };

                this.safeLib.create_acc.async(
                    types.allocCString( locator ),
                    types.allocCString( secret ),
                    types.allocCString( invitation ),
                    types.Null,
                    this.networkDisconnectCb,
                    this._getCb( createAccCb ),
                    onResult );
            }
            catch ( e )
            {
                reject( e );
            }
        } );
    }

    login( locator, secret )
    {
        return new Promise( ( resolve, reject ) =>
        {
            const validationErr = this._isUserCredentialsValid( locator, secret );
            if ( validationErr )
            {
                return reject( validationErr );
            }

            try
            {
                const loginCb = this._pushCb( ffi.Callback( types.Void,
                    [types.voidPointer, types.FfiResultPointer, types.ClientHandlePointer],
                    ( userData, resultPtr, clientHandle ) =>
                    {
                        const result = resultPtr.deref();
                        if ( result.error_code !== 0 && clientHandle.length === 0 )
                        {
                            return reject( JSON.stringify( result ) );
                        }
                        this.registeredClientHandle = clientHandle;
                        this._pushNetworkState( CONSTANTS.NETWORK_STATUS.CONNECTED );
                        resolve();
                    } ) );

                const onResult = ( err, res ) =>
                {
                    if ( err || res !== 0 )
                    {
                        return reject( err );
                    }
                };

                this.safeLib.login.async(
                    types.allocCString( locator ),
                    types.allocCString( secret ),
                    types.Null,
                    this.networkDisconnectCb,
                    this._getCb( loginCb ),
                    onResult );
            }
            catch ( e )
            {
                reject( e );
            }
        } );
    }

    logout()
    {
        this._pushNetworkState( CONSTANTS.NETWORK_STATUS.DISCONNECTED );
        this.safeLib.auth_free( this.registeredClientHandle );
        this.registeredClientHandle = null;
    }

    decodeRequest( uri )
    {
        return new Promise( ( resolve, reject ) =>
        {
            if ( !uri )
            {
                return reject( new Error( 'Invalid URI' ) );
            }
            const parsedURI = uri.replace( 'safe-auth://', '' ).replace( 'safe-auth:', '' ).replace( '/', '' );

            if ( !this.registeredClientHandle )
            {
                return this._decodeUnRegisteredRequest( parsedURI, resolve, reject );
            }
            const decodeReqAuthCb = this._pushCb( ffi.Callback( types.Void,
                [types.voidPointer, types.u32, types.AuthReqPointer], ( userData, reqId, req ) =>
                {
                    if ( !( this[_authReqListener] && this[_authReqListener].len() !== 0 ) )
                    {
                        return;
                    }
                    const authReq = typeParser.parseAuthReq( req.deref() );
                    this[_decodeReqPool][reqId] = authReq;
                    const result = {
                        reqId,
                        authReq
                    };
                    return this._isAlreadyAuthorised( authReq )
                        .then( ( isAuthorised ) =>
                        {
                            if ( isAuthorised )
                            {
                                // re authorise the app
                                if ( this[_reAuthoriseState] !== CONSTANTS.RE_AUTHORISE.STATE.UNLOCK )
                                {
                                    result.isAuthorized = true;
                                    this[_authReqListener].broadcast( null, result );
                                    return resolve();
                                }
                                return this.encodeAuthResp( result, true )
                                    .then( resolve );
                            }
                            this[_authReqListener].broadcast( null, result );
                            resolve();
                        } );
                } ) );

            const decodeReqContainerCb = this._pushCb( ffi.Callback( types.Void,
                [types.voidPointer, types.u32, types.ContainersReqPointer], ( userData, reqId, req ) =>
                {
                    if ( !( this[_containerReqListener] && this[_containerReqListener].len() !== 0 ) )
                    {
                        return;
                    }
                    const contReq = typeParser.parseContainerReq( req.deref() );
                    this[_decodeReqPool][reqId] = contReq;
                    const result = {
                        reqId,
                        contReq
                    };
                    if ( this[_reAuthoriseState] !== CONSTANTS.RE_AUTHORISE.STATE.UNLOCK )
                    {
                        this[_containerReqListener].broadcast( null, result );
                        return resolve();
                    }
                    return this._isAlreadyAuthorisedContainer( contReq )
                        .then( ( isAuthorised ) =>
                        {
                            if ( isAuthorised )
                            {
                                return this.encodeContainersResp( result, true )
                                    .then( resolve );
                            }
                            this[_containerReqListener].broadcast( null, result );
                            resolve();
                        } );
                } ) );

            const shareMdataCb = this._pushCb( ffi.Callback( types.Void,
                [types.voidPointer, types.u32, types.ShareMDataReqPointer, 'pointer'],
                ( userData, reqId, req, meta ) =>
                {
                    const mDataReq = typeParser.parseShareMDataReq( req.deref() );
                    const metaData = typeParser.parseUserMetaDataArray( meta, mDataReq.mdata_len );
                    this[_decodeReqPool][reqId] = mDataReq;
                    const result = {
                        reqId,
                        mDataReq,
                        metaData
                    };
                    const appAccess = [];
                    const tempArr = [];
                    for ( let i = 0; i < mDataReq.mdata_len; i++ )
                    {
                        tempArr[i] = i;
                    }

                    return Promise.all( tempArr.map( ( i ) =>
                    {
                        const mdata = mDataReq.mdata[i];
                        return this._appsAccessingMData( mdata.name, mdata.type_tag )
                            .then( ( res ) =>
                            {
                                appAccess[i] = res;
                            } );
                    } ) )
                        .then( () =>
                        {
                            result.appAccess = appAccess;
                            this[_mDataReqListener].broadcast( null, result );
                        } );
                } ) );

            const unregisteredCb = this._getUnregisteredClientCb( resolve, reject );

            const decodeReqErrorCb = this._pushCb( ffi.Callback( types.Void,
                [types.voidPointer, types.FfiResultPointer, types.CString], ( userData, resultPtr ) =>
                {
                    const result = resultPtr.deref();
                    if ( !( this[_reqErrListener] && this[_reqErrListener].len() !== 0 ) )
                    {
                        return;
                    }
                    this[_reqErrListener].broadcast( JSON.stringify( result ) );
                } ) );
            try
            {
                this.safeLib.auth_decode_ipc_msg(
                    this.registeredClientHandle,
                    types.allocCString( parsedURI ),
                    types.Null,
                    this._getCb( decodeReqAuthCb ),
                    this._getCb( decodeReqContainerCb ),
                    this._getCb( unregisteredCb ),
                    this._getCb( shareMdataCb ),
                    this._getCb( decodeReqErrorCb ) );
            }
            catch ( e )
            {
                reject( e );
            }
        } );
    }

    encodeAuthResp( req, isAllowed )
    {
        return new Promise( ( resolve, reject ) =>
        {
            if ( !this.registeredClientHandle )
            {
                return reject( new Error( i18n.__( 'messages.unauthorised' ) ) );
            }

            if ( !req || typeof isAllowed !== 'boolean' )
            {
                return reject( new Error( i18n.__( 'messages.invalid_params' ) ) );
            }

            if ( !req.reqId || !this[_decodeReqPool][req.reqId] )
            {
                return reject( new Error( i18n.__( 'messages.invalid_req' ) ) );
            }

            const authReq = types.allocAuthReq( typeConstructor.constructAuthReq(
                this[_decodeReqPool][req.reqId] ) );

            delete this[_decodeReqPool][req.reqId];

            try
            {
                const authDecisionCb = this._pushCb( ffi.Callback( types.Void,
                    [types.voidPointer, types.FfiResultPointer, types.CString],
                    ( userData, resultPtr, res ) =>
                    {
                        const result = resultPtr.deref();
                        if ( result.error_code !== 0 )
                        {
                            return reject( JSON.stringify( result ) );
                        }
                        if ( isAllowed )
                        {
                            this._updateAppList();
                        }
                        const appUri = genAppUri( req.authReq.app.id );
                        resolve( `${appUri}:${res}` );
                    } ) );
                this.safeLib.encode_auth_resp(
                    this.registeredClientHandle,
                    authReq,
                    req.reqId,
                    isAllowed,
                    types.Null,
                    this._getCb( authDecisionCb )
                );
            }
            catch ( e )
            {
                reject( e );
            }
{'fraction_non_alphanumeric': 0.10099033816425121, 'fraction_numerical': 0.002705314009661836, 'mean_word_length': 1.2360788549824466, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 47, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2636229', 'n_tokens_mistral': 10760, 'n_tokens_neox': 10180, 'n_words': 1956}