import { OpenYoloCredential , OpenYoloCredentialHintOptions , OpenYoloCredentialRequestOptions , OpenYoloProxyLoginResponse } from '../protocol/data' ; import { RenderMode } from '../protocol/data' ; import { OpenYoloErrorType , OpenYoloInternalError } from '../protocol/errors' ; import { FeatureConfig } from '../protocol/feature_config' ; import { PreloadRequest , PreloadRequestType } from '../protocol/preload_request' ; import { SecureChannel } from '../protocol/secure_channel' ; import { generateId , sha256 , startTimeoutRacer , TimeoutRacer } from '../protocol/utils' ; import { CancelLastOperationRequest } from './cancel_last_operation_request' ; import { CredentialRequest } from './credential_request' ; import { CredentialSave } from './credential_save' ; import { DisableAutoSignIn } from './disable_auto_sign_in' ; import { HintAvailableRequest } from './hint_available_request' ; import { HintRequest } from './hint_request' ; import { createNavigatorCredentialsApi } from './navigator_credentials' ; import { ProviderFrameElement } from './provider_frame_elem' ; import { ProxyLogin } from './proxy_login' ; import { respondToHandshake } from './verify' ; const MOBILE_USER_AGENT_REGEX = /android|iphone|ipod|iemobile/i ; /**
 * Provides a mechanism for credential exchange between the current origin and
 * the user's credential provider (e.g. Smart Lock for Passwords).
 */ export interface OpenYoloApi { param options return hintsAvailable ( options ) : Promise < boolean > ; param options return hint ( options ) : Promise < OpenYoloCredential > ; param options return retrieve ( options ) : Promise < OpenYoloCredential > ; param credential return save ( credential ) : Promise < void > ; return disableAutoSignIn ( ) : Promise < void > ; param credential return proxyLogin ( credential ) : Promise < OpenYoloProxyLoginResponse > ; /**
     * Cancels the last pending OpenYOLO request.
     */ cancelLastOperation ( ) : Promise < void > ; } /**
 * A variant of the OpenYoloApi interface, with support for operation timeouts.
 */ export interface OpenYoloWithTimeoutApi { hintsAvailable ( options , timeoutRacer ) : Promise < boolean > ; hint ( options , timeoutRacer ) : Promise < OpenYoloCredential > ; retrieve ( options , timeoutRacer ) : Promise < OpenYoloCredential > ; save ( credential , timeoutRacer ) : Promise < void > ; disableAutoSignIn ( timeoutRacer ) : Promise < void > ; proxyLogin ( credential , timeoutRacer ) : Promise < OpenYoloProxyLoginResponse > ; cancelLastOperation ( timeoutRacer ) : Promise < void > ; dispose ( ) : Promise < void > ; } /**
 * Defines the different timeouts for every request.
 */ const DEFAULT_TIMEOUTS = { retrieve : 3000 , save : 3000 , disableAutoSignIn : 3000 , hintsAvailable : 3000 , hint : 3000 , proxyLogin : 10000 , cancelLastOperation : 3000 } ; const RENDER_MODES = [ RenderMode . bottomSheet , RenderMode . navPopout , RenderMode . fullScreen ] ; /**
 * Sanitzes the input for renderMode, selecting the default one if invalid.
 */ function verifyOrDetectRenderMode ( renderMode ) { if ( renderMode && RENDER_MODES . indexOf ( renderMode ) !== - 1 ) { return renderMode ; } const isNested = window . parent !== window ; if ( isNested ) { return RenderMode . fullScreen ; } const isMobile = navigator . userAgent . match ( MOBILE_USER_AGENT_REGEX ) ; if ( isMobile ) { return RenderMode . bottomSheet ; } else { return RenderMode . navPopout ; } } /**
 * Provides access to the user's preferred credential provider, in order to
 * retrieve credentials.
 */ export class OpenYoloApiImpl implements OpenYoloWithTimeoutApi { private disposed : boolean = false ; /** The fallback when navigator.credentials should be used. */ private navigatorCredentials : OpenYoloApi ; constructor ( private frameManager , private channel , fallbackApi ? ) { this . navigatorCredentials = fallbackApi || createNavigatorCredentialsApi ( ) ; } async hintsAvailable ( options , timeoutRacer ) { this . checkNotDisposed ( ) ; const request = new HintAvailableRequest ( this . frameManager , this . channel ) ; try { return await request . dispatch ( options , timeoutRacer ) ; } catch ( e ) { if ( timeoutRacer . hasTimedOut ( ) ) { this . cancelLastOperationWithoutTimeout ( ) ; } else if ( e [ 'type' ] === OpenYoloErrorType . browserWrappingRequired ) { return this . navigatorCredentials . hintsAvailable ( options ) ; } throw e ; } } async hint ( options , timeoutRacer ) { this . checkNotDisposed ( ) ; const request = new HintRequest ( this . frameManager , this . channel ) ; try { return await request . dispatch ( options , timeoutRacer ) ; } catch ( e ) { if ( timeoutRacer . hasTimedOut ( ) ) { this . cancelLastOperationWithoutTimeout ( ) ; } else if ( e [ 'type' ] === OpenYoloErrorType . browserWrappingRequired ) { return this . navigatorCredentials . hint ( options ) ; } throw e ; } } async retrieve ( options , timeoutRacer ) { this . checkNotDisposed ( ) ; const request = new CredentialRequest ( this . frameManager , this . channel ) ; try { return await request . dispatch ( options , timeoutRacer ) ; } catch ( e ) { if ( timeoutRacer . hasTimedOut ( ) ) { this . cancelLastOperationWithoutTimeout ( ) ; } else if ( e [ 'type' ] === OpenYoloErrorType . browserWrappingRequired ) { return this . navigatorCredentials . retrieve ( options ) ; } throw e ; } } async save ( credential , timeoutRacer ) { this . checkNotDisposed ( ) ; const request = new CredentialSave ( this . frameManager , this . channel ) ; try { return await request . dispatch ( credential , timeoutRacer ) ; } catch ( e ) { if ( timeoutRacer . hasTimedOut ( ) ) { this . cancelLastOperationWithoutTimeout ( ) ; } else if ( e [ 'type' ] === OpenYoloErrorType . browserWrappingRequired ) { return this . navigatorCredentials . save ( credential ) ; } throw e ; } } async proxyLogin ( credential , timeoutRacer ) { this . checkNotDisposed ( ) ; const request = new ProxyLogin ( this . frameManager , this . channel ) ; try { return await request . dispatch ( credential , timeoutRacer ) ; } catch ( e ) { if ( timeoutRacer . hasTimedOut ( ) ) { this . cancelLastOperationWithoutTimeout ( ) ; } else if ( e [ 'type' ] === OpenYoloErrorType . browserWrappingRequired ) { return this . navigatorCredentials . proxyLogin ( credential ) ; } throw e ; } } async disableAutoSignIn ( timeoutRacer ) { this . checkNotDisposed ( ) ; const request = new DisableAutoSignIn ( this . frameManager , this . channel ) ; try { const providerDisableAutoSignIn = request . dispatch ( undefined , timeoutRacer ) ; const browserDisableAutoSignIn = this . navigatorCredentials . disableAutoSignIn ( ) ; await Promise . all ( [ providerDisableAutoSignIn , browserDisableAutoSignIn ] ) ; } catch ( e ) { if ( timeoutRacer . hasTimedOut ( ) ) { this . cancelLastOperationWithoutTimeout ( ) ; } throw e ; } } async cancelLastOperation ( timeoutRacer ) { this . checkNotDisposed ( ) ; const request = new CancelLastOperationRequest ( this . frameManager , this . channel ) ; try { return await request . dispatch ( undefined , timeoutRacer ) ; } catch ( e ) { if ( e [ 'type' ] === OpenYoloErrorType . browserWrappingRequired ) { return this . navigatorCredentials . cancelLastOperation ( ) ; } throw e ; } } private async cancelLastOperationWithoutTimeout ( ) { this . checkNotDisposed ( ) ; const request = new CancelLastOperationRequest ( this . frameManager , this . channel ) ; try { return await request . dispatch ( undefined , undefined ) ; } catch ( e ) { if ( e [ 'type' ] === OpenYoloErrorType . browserWrappingRequired ) { return this . navigatorCredentials . cancelLastOperation ( ) ; } throw e ; } } dispose ( ) { if ( this . disposed ) { return Promise . resolve ( ) ; } this . channel . dispose ( ) ; this . frameManager . dispose ( ) ; return Promise . resolve ( ) ; } private checkNotDisposed ( ) { if ( this . disposed ) { throw OpenYoloInternalError . clientDisposed ( ) . toExposedError ( ) ; } } } export interface OnDemandOpenYoloApi extends OpenYoloApi { /**
     * Sets the provider URL.
     */ setProviderUrlBase ( providerUrlBase ) : void ; /**
     * Sets the features to enable for the current session.
     */ setFeatureConfig ( featuresToEnable ) : void ; /**
     * Sets the render mode, or null if the default one should be used.
     */ setRenderMode ( renderMode ) : void ; /**
     * Sets a custom timeouts, or 0 to disable timeouts.
     */ setTimeouts ( timeoutMs ) : void ; /**
     * Resets the current instantiation of the API.
     */ reset ( ) : void ; } /**
 * Wraps a promise for a CredentialsApiImpl, which is initialized on the first
 * usage of the API. This allows an implementation of CredentialsApi to be
 * directly exported from the module as a constant, without triggering
 * immediate instantiation when the module is loaded.
 */ export class InitializeOnDemandApi implements OnDemandOpenYoloApi { private providerUrlBase : string = 'https://provider.openyolo.org' ; private featuresToEnable : string [ ] = [ ] ; private implPromise : Promise < OpenYoloWithTimeoutApi > | null = null ; private renderMode : RenderMode | null = null ; /**
     * Custom timeouts defined by the client. When null, the predefined timeouts
     * are used.
     */ private customTimeoutsMs : number | null = null ; constructor ( ) { respondToHandshake ( window ) ; } /**
     * Create the open yolo API based on the parameters given. It will always open
     * the provider page to check the user's configuration, and initialize the
     * correct implementation of OpenYolo based on the result.
     */ static createOpenYoloApi ( timeoutRacer , providerUrlBase , featuresToEnable , renderMode , preloadRequest ? ) { let frameManager = null ; const renderModeSanitized = verifyOrDetectRenderMode ( renderMode ) ; const instanceId = generateId ( ) ; return Promise . resolve ( ) . then ( ( ) => { return timeoutRacer . race ( sha256 ( instanceId ) ) ; } ) . then ( ( instanceIdHash ) => { let featureConfig = undefined ; if ( featuresToEnable . length > 0 ) { featureConfig = { 'feature' : featuresToEnable } ; } frameManager = new ProviderFrameElement ( document , instanceIdHash , window . location . origin , renderModeSanitized , providerUrlBase , featureConfig , preloadRequest ) ; return timeoutRacer . race ( SecureChannel . clientConnect ( window , frameManager . getContentWindow ( ) , instanceId , instanceIdHash ) ) ; } ) . then ( ( channel ) => { return new OpenYoloApiImpl ( frameManager ! , channel ) ; } ) . catch ( ( e ) => { if ( frameManager !== null ) { frameManager . dispose ( ) ; } timeoutRacer . rethrowUnlessTimeoutError ( e ) ; throw OpenYoloInternalError . requestTimeoutOnInitialization ( ) . toExposedError ( ) ; } ) ; } setProviderUrlBase ( providerUrlBase ) { this . providerUrlBase = providerUrlBase ; this . reset ( ) ; } setFeatureConfig ( featuresToEnable ) { this . featuresToEnable = featuresToEnable ; this . reset ( ) ; } setRenderMode ( renderMode ) { this . renderMode = renderMode ; this . reset ( ) ; } param timeoutMs setTimeouts ( timeoutMs ) { if ( timeoutMs === null ) { this . customTimeoutsMs = null ; this . reset ( ) ; return ; } if ( typeof timeoutMs !== 'number' || timeoutMs < 0 ) { throw new Error ( 'Invalid timeout. It must be a number greater than or equal to 0. ' + 'Setting it to 0 disable timeouts.' ) ; } const shouldReset = this . customTimeoutsMs !== timeoutMs && timeoutMs === 0 ; this . customTimeoutsMs = timeoutMs ; if ( shouldReset ) { this . reset ( ) ; } } reset ( ) { if ( ! this . implPromise ) { return ; } let promise = this . implPromise ; this . implPromise = null ; promise . then ( ( impl ) => { impl . dispose ( ) ; } ) ; } private init ( timeoutRacer , preloadRequest ? ) { if ( ! this . implPromise ) { this . implPromise = InitializeOnDemandApi . createOpenYoloApi ( timeoutRacer , this . providerUrlBase , this . featuresToEnable , this . renderMode , preloadRequest ) ; } this . implPromise . catch ( ( e ) => { this . reset ( ) ; } ) ; return this . implPromise ; } private startCustomTimeoutRacer ( defaultTimeoutMs ) { return startTimeoutRacer ( this . customTimeoutsMs !== null ? this . customTimeoutsMs : defaultTimeoutMs ) ; } async hintsAvailable ( options ) { const timeoutRacer = this . startCustomTimeoutRacer ( DEFAULT_TIMEOUTS . hintsAvailable ) ; const preloadRequest = { type : PreloadRequestType . hint , options } ; const impl = await this . init ( timeoutRacer , preloadRequest ) ; return await impl . hintsAvailable ( options , timeoutRacer ) ; } async hint ( options ) { const preloadRequest = { type : PreloadRequestType . hint , options } ; const timeoutRacer = this . startCustomTimeoutRacer ( DEFAULT_TIMEOUTS . hint ) ; const impl = await this . init ( timeoutRacer , preloadRequest ) ; return await impl . hint ( options , timeoutRacer ) ; } async retrieve ( options ) { const preloadRequest = { type : PreloadRequestType . retrieve , options } ; const timeoutRacer = this . startCustomTimeoutRacer ( DEFAULT_TIMEOUTS . retrieve ) ; const impl = await this . init ( timeoutRacer , preloadRequest ) ; return impl . retrieve ( options , timeoutRacer ) ; } async save ( credential ) { const timeoutRacer = this . startCustomTimeoutRacer ( DEFAULT_TIMEOUTS . save ) ; const impl = await this . init ( timeoutRacer ) ; return impl . save ( credential , timeoutRacer ) ; } async disableAutoSignIn ( ) { const timeoutRacer = this . startCustomTimeoutRacer ( DEFAULT_TIMEOUTS . disableAutoSignIn ) ; const impl = await this . init ( timeoutRacer ) ; return impl . disableAutoSignIn ( timeoutRacer ) ; } async proxyLogin ( credential ) { const timeoutRacer = this . startCustomTimeoutRacer ( DEFAULT_TIMEOUTS . proxyLogin ) ; const impl = await this . init ( timeoutRacer ) ; return impl . proxyLogin ( credential , timeoutRacer ) ; } async cancelLastOperation ( ) { const timeoutRacer = this . startCustomTimeoutRacer ( DEFAULT_TIMEOUTS . cancelLastOperation ) ; const impl = await this . init ( timeoutRacer ) ; return impl . cancelLastOperation ( timeoutRacer ) ; } } /**
 * API implementation that immediately rejects all requests.
 */ export class FakeOpenYoloApi implements OnDemandOpenYoloApi { private readonly unsupportedBrowserPromise = Promise . reject ( OpenYoloInternalError . unsupportedBrowser ( ) . toExposedError ( ) ) ; setProviderUrlBase ( providerUrlBase ) { } setFeatureConfig ( featuresToEnable ) { } setRenderMode ( renderMode ) { } setTimeouts ( timeout ) { } reset ( ) { } hintsAvailable ( options ) { return this . unsupportedBrowserPromise ; } hint ( options ) { return this . unsupportedBrowserPromise ; } retrieve ( options ) { return this . unsupportedBrowserPromise ; } save ( credential ) { return this . unsupportedBrowserPromise ; } disableAutoSignIn ( ) { return this . unsupportedBrowserPromise ; } cancelLastOperation ( ) { return this . unsupportedBrowserPromise ; } proxyLogin ( credential ) { return this . unsupportedBrowserPromise ; } } /**
 * Checks whether the core browser functionality required for OpenYOLO is
 * supported in the current context.
 */ export function isCompatibleBrowser ( win ) { const hasCryptoRandomImplementation = win . hasOwnProperty ( 'crypto' ) && 'getRandomValues' in win . crypto ; if ( ! hasCryptoRandomImplementation ) { console . warn ( 'The current browser does not provide ' + 'window.crypto.getRandomValues. This is required by the API to work.' + ' This is likely due to an old browser.' ) ; } const hasCryptoSubtleImplementation = win . hasOwnProperty ( 'crypto' ) && ( 'subtle' in win . crypto || 'webkitSubtle' in win . crypto ) ; if ( ! hasCryptoSubtleImplementation ) { console . warn ( 'The current environment does not provide window.crypto.subtle. This ' + 'is required by the API to work. This is likely due to an old ' + 'browser, or running the API in an unsecure origin - only secure ' + 'origins (https: and localhost) provide crypto.subtle.' ) ; } return hasCryptoRandomImplementation && hasCryptoSubtleImplementation ; } export const openyolo = isCompatibleBrowser ( window ) ? new InitializeOnDemandApi ( ) : new FakeOpenYoloApi ( ) ;