import { AUTHENTICATION_METHODS , OpenYoloCredential , OpenYoloCredentialHintOptions , OpenYoloCredentialRequestOptions , OpenYoloProxyLoginResponse } from '../protocol/data' ; import { OpenYoloInternalError } from '../protocol/errors' ; import { isSecureOrigin } from '../protocol/utils' ; import { OpenYoloApi } from './api' ; /**
 * Converts Open Yolo request options to the navigator.credentials options.
 */ function convertRequestOptions ( options ? ) { if ( ! options ) return ; let convertedOptions = { password : false , federated : { providers : [ ] , protocols : [ ] } , unmediated : false } ; let authMethods = options . supportedAuthMethods . slice ( ) ; let passwordIndex = authMethods . indexOf ( AUTHENTICATION_METHODS . ID_AND_PASSWORD ) ; if ( passwordIndex !== - 1 ) { convertedOptions . password = true ; authMethods . splice ( passwordIndex , 1 ) ; } convertedOptions . federated ! . providers = authMethods ; return convertedOptions ; } /**
 * Convert navigator.credentials Credential to Open Yolo Credential.
 */ function convertCredentialToOpenYolo ( credential ) { const authMethod = credential . type === 'federated' ? ( credential as FederatedCredential ) . provider : AUTHENTICATION_METHODS . ID_AND_PASSWORD ; const convertedCredential = { id : credential . id , authMethod : authMethod , displayName : credential . name || undefined , profilePicture : credential . iconURL || undefined , proxiedAuthRequired : false } ; if ( credential instanceof PasswordCredential ) { convertedCredential . password = credential . password ; } return convertedCredential ; } /**
 * Convert Open Yolo Credential to navigator.credentials Credential.
 */ function convertCredentialFromOpenYolo ( credential ) { if ( credential . authMethod === AUTHENTICATION_METHODS . ID_AND_PASSWORD ) { const convertedCredential = new PasswordCredential ( { id : credential . id , type : 'password' , name : credential . displayName , iconURL : credential . profilePicture , password : credential . password } as PasswordCredentialData ) ; return convertedCredential ; } else { const convertedCredential = new FederatedCredential ( { id : credential . id , name : credential . displayName , iconURL : credential . profilePicture , provider : credential . authMethod } ) ; return convertedCredential ; } } /**
 * Keeps a map of unique key to instances of navigator credentials. This allows
 * to use the proxyLogin method, as the original instance of Credential should
 * be used.
 */ class CredentialsMap { private map : { [ key ] : Credential ; } = { } ; /**
     * Inserts a Credential in the map. Uses `id::type` as uniqueness key.
     * The only case where this uniqueness key could cause issues, is if the user
     * has several PasswordCredentials for the same id (email address), and
     * retrieves several of them before using the proxyLogin with not the last
     * PasswordCredential retrieved.
     */ insert ( credential ) { let key = `${ credential . id }::${ credential . type }` ; this . map [ key ] = credential ; } /**
     * Retrieves the credential in the map corresponding to the key given.
     */ retrieve ( key ) { return this . map [ key ] ; } /**
     * Creates the uniqueness key corresponding to the OpenYolo credential given.
     */ static getKeyFromOpenYoloCredential ( credential ) { let type = credential . authMethod === AUTHENTICATION_METHODS . ID_AND_PASSWORD ? 'password' : 'federated' ; return `${ credential . id }::${ type }` ; } } /**
 * A No-Op navigator.credentials wrapper to be used in-lieu of the real one in
 * browsers that don't implement navigator.credentials.
 */ export class NoOpNavigatorCredentials implements OpenYoloApi { disableAutoSignIn ( ) { return Promise . resolve ( ) ; } async retrieve ( options ? ) { throw OpenYoloInternalError . noCredentialsAvailable ( ) . toExposedError ( ) ; } async save ( credential ) { } async hint ( options ? ) { throw OpenYoloInternalError . noCredentialsAvailable ( ) . toExposedError ( ) ; } async hintsAvailable ( ) { return false ; } async cancelLastOperation ( ) { } async proxyLogin ( credential ) { throw OpenYoloInternalError . requestFailed ( 'Cannot proxy login through the browser.' ) . toExposedError ( ) ; } } /**
 * Wrapper of navigator.credentials exposing the same API than OpenYolo.
 *
 * TODO(tch): wrap navigator.credentials errors in OpenYolo errors.
 */ export class NavigatorCredentials implements OpenYoloApi { private credentialsMap : CredentialsMap = new CredentialsMap ( ) ; constructor ( private cmApi ) { } async disableAutoSignIn ( ) { try { return await this . cmApi . preventSilentAccess ( ) ; } catch ( e ) { return ; } } async retrieve ( options ? ) { const convertedOptions = convertRequestOptions ( options ) ; let credential ; try { credential = await this . cmApi . get ( convertedOptions ) ; } catch ( e ) { const message = `navigator.credentials error: (${ e . message })` ; throw OpenYoloInternalError . requestFailed ( message ) . toExposedError ( ) ; } if ( ! credential ) { throw OpenYoloInternalError . noCredentialsAvailable ( ) . toExposedError ( ) ; } this . credentialsMap . insert ( credential ) ; return convertCredentialToOpenYolo ( credential as FederatedCredential | PasswordCredential ) ; } async save ( credential ) { const convertedCredential = convertCredentialFromOpenYolo ( credential ) ; try { await this . cmApi . store ( convertedCredential ) ; } catch ( e ) { const message = `navigator.credentials error: (${ e . message })` ; throw OpenYoloInternalError . requestFailed ( message ) . toExposedError ( ) ; } } async hint ( options ? ) { throw OpenYoloInternalError . noCredentialsAvailable ( ) . toExposedError ( ) ; } async hintsAvailable ( ) { return false ; } async cancelLastOperation ( ) { } async proxyLogin ( credential ) { const url = `${ window . location . protocol }//${ window . location . host }/signin` ; const cred = this . credentialsMap . retrieve ( CredentialsMap . getKeyFromOpenYoloCredential ( credential ) ) ; if ( ! cred || cred . type !== 'password' ) { throw OpenYoloInternalError . requestFailed ( 'Invalid credential.' ) . toExposedError ( ) ; } let resp ; try { resp = await fetch ( url , { method : 'POST' , credentials : cred } ) ; } catch ( e ) { throw OpenYoloInternalError . requestFailed ( e . message ) . toExposedError ( ) ; } if ( resp . status !== 200 ) { throw OpenYoloInternalError . requestFailed ( `Status code ${ resp . status }` ) . toExposedError ( ) ; } const responseText = await resp . text ( ) ; return { statusCode : resp . status , responseText : responseText } ; } } /**
 * Returns the navigator.credentials wrapper according to the environment. If
 * navigator.credentials is not defined, or the current app not on https,
 * it will create a no-op version of the API.
 */ export function createNavigatorCredentialsApi ( ) { if ( typeof navigator . credentials !== 'undefined' && typeof navigator . credentials . preventSilentAccess === 'function' && isSecureOrigin ( ) ) { return new NavigatorCredentials ( navigator . credentials ) ; } return new NoOpNavigatorCredentials ( ) ; }