@@ -17,8 +17,12 @@ import {
1717import { WINDOW } from '../window'
1818import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms'
1919import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service'
20+ import { TogglzService } from 'src/app/core/togglz/togglz.service'
21+ import { ConfigMessageKey } from 'src/app/types/config.endpoint'
22+ import { take } from 'rxjs/operators'
2023
2124export interface ReCaptchaConfig {
25+ sitekey : string
2226 theme ?: 'dark' | 'light'
2327 type ?: 'audio' | 'image'
2428 size ?: 'compact' | 'normal'
@@ -28,6 +32,7 @@ export interface ReCaptchaConfig {
2832interface WindowWithCaptcha extends Window {
2933 grecaptcha : {
3034 render : ( ElementRef , ReCaptchaConfig ) => number
35+ reset : ( opt_widget_id ?: number ) => void
3136 }
3237 orcidReCaptchaOnLoad : ( ) => void
3338}
@@ -46,15 +51,19 @@ interface WindowWithCaptcha extends Window {
4651export class RecaptchaDirective implements OnInit , ControlValueAccessor {
4752 @Output ( ) captchaFail = new EventEmitter < boolean > ( )
4853 @Output ( ) captchaLoaded = new EventEmitter < number > ( )
49- private onChange : ( value : string ) => void
50- private onTouched : ( value : string ) => void
54+ private onChange : ( value : string | null ) => void
55+ private onTouched : ( value : string | null ) => void
56+ private renderId : number | null = null
57+ private lastConfig : ReCaptchaConfig | null = null
58+ private renderRetried = false
5159
5260 constructor (
5361 @Inject ( WINDOW ) private window : WindowWithCaptcha ,
5462 @Inject ( LOCALE_ID ) public locale : string ,
5563 private ngZone : NgZone ,
5664 private element : ElementRef ,
57- private _errorHandler : ErrorHandlerService
65+ private _errorHandler : ErrorHandlerService ,
66+ private _togglzService : TogglzService
5867 ) { }
5968
6069 ngOnInit ( ) {
@@ -64,28 +73,86 @@ export class RecaptchaDirective implements OnInit, ControlValueAccessor {
6473
6574 registerReCaptchaCallback ( ) {
6675 this . window . orcidReCaptchaOnLoad = ( ) => {
67- const config = {
68- sitekey : runtimeEnvironment . GOOGLE_RECAPTCHA ,
69- callback : ( response : string ) => {
70- this . ngZone . run ( ( ) => this . onSuccess ( response ) )
71- } ,
72-
73- 'expired-callback' : ( response : string ) => {
74- this . ngZone . run ( ( ) => this . onExpired ( ) )
75- } ,
76- 'error-callback' : ( ) => {
77- this . ngZone . run ( ( ) => this . onCaptchaFail ( ) )
78- } ,
79- }
76+ this . _togglzService
77+ . getConfigurationOf ( ConfigMessageKey . RECAPTCHA_WEB_KEY )
78+ . pipe ( take ( 1 ) )
79+ . subscribe ( {
80+ next : ( siteKey : string ) => {
81+ if ( ! siteKey ) {
82+ this . ngZone . run ( ( ) => this . onCaptchaFail ( 'missing_sitekey' ) )
83+ return
84+ }
85+ if ( ! this . window . grecaptcha || ! this . window . grecaptcha . render ) {
86+ this . ngZone . run ( ( ) => this . onCaptchaFail ( 'grecaptcha_not_ready' ) )
87+ return
88+ }
89+
90+ if ( this . renderId !== null ) {
91+ this . window . grecaptcha . reset ( this . renderId )
92+ this . captchaLoaded . emit ( this . renderId )
93+ return
94+ }
95+
96+ const config : ReCaptchaConfig & {
97+ callback : ( response : string ) => void
98+ 'expired-callback' : ( response : string ) => void
99+ 'error-callback' : ( ) => void
100+ } = {
101+ sitekey : siteKey ,
102+ callback : ( response : string ) => {
103+ this . ngZone . run ( ( ) => this . onSuccess ( response ) )
104+ } ,
105+ 'expired-callback' : ( response : string ) => {
106+ this . ngZone . run ( ( ) => this . onExpired ( ) )
107+ } ,
108+ 'error-callback' : ( ) => {
109+ this . handleRenderError ( 'recaptcha_error_callback' )
110+ } ,
111+ }
112+
113+ this . lastConfig = config
114+ this . renderRetried = false
115+ this . renderCaptcha ( config )
116+ } ,
117+ error : ( ) => {
118+ this . ngZone . run ( ( ) => this . onCaptchaFail ( 'config_fetch_error' ) )
119+ } ,
120+ } )
121+ }
122+ }
123+
124+ private renderCaptcha ( config : ReCaptchaConfig ) {
125+ try {
80126 const id = this . render ( this . element . nativeElement , config )
127+ this . renderId = id
81128 this . captchaLoaded . emit ( id )
129+ } catch ( err ) {
130+ this . handleRenderError ( 'render_exception' )
131+ }
132+ }
133+
134+ private handleRenderError ( reason : string ) {
135+ if ( ! this . renderRetried && this . window . grecaptcha && this . lastConfig ) {
136+ this . renderRetried = true
137+ try {
138+ const id = this . render ( this . element . nativeElement , this . lastConfig )
139+ this . renderId = id
140+ this . captchaLoaded . emit ( id )
141+ return
142+ } catch ( _ ) {
143+ // fall through
144+ }
82145 }
146+ this . ngZone . run ( ( ) => this . onCaptchaFail ( reason ) )
83147 }
84148
85149 onExpired ( ) {
86150 this . ngZone . run ( ( ) => {
87151 this . onChange ( null )
88152 this . onTouched ( null )
153+ if ( this . renderId !== null && this . window . grecaptcha ) {
154+ this . window . grecaptcha . reset ( this . renderId )
155+ }
89156 } )
90157 }
91158
@@ -94,9 +161,9 @@ export class RecaptchaDirective implements OnInit, ControlValueAccessor {
94161 this . onTouched ( token )
95162 }
96163
97- onCaptchaFail ( ) {
164+ onCaptchaFail ( reason : string = 'captchaFail' ) {
98165 this . captchaFail . emit ( true )
99- this . _errorHandler . handleError ( new Error ( 'captchaFail' ) ) . subscribe ( )
166+ this . _errorHandler . handleError ( new Error ( reason ) ) . subscribe ( )
100167 }
101168
102169 private render ( element : HTMLElement , config ) : number {
0 commit comments