Skip to content

Commit a9796f4

Browse files
committed
PD-3822
1 parent 064c692 commit a9796f4

16 files changed

+159
-37
lines changed

src/app/cdk/recaptcha/recaptcha.directive.ts

Lines changed: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ import {
1717
import { WINDOW } from '../window'
1818
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
1919
import { 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

2124
export interface ReCaptchaConfig {
25+
sitekey: string
2226
theme?: 'dark' | 'light'
2327
type?: 'audio' | 'image'
2428
size?: 'compact' | 'normal'
@@ -28,6 +32,7 @@ export interface ReCaptchaConfig {
2832
interface 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 {
4651
export 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 {

src/app/core/record-works/record-works.service.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
import { TogglzService } from '../togglz/togglz.service'
2222
import { of, ReplaySubject } from 'rxjs'
2323
import { UserService } from '..'
24-
import { Config } from '../../types/togglz.endpoint'
24+
import { Config } from '../../types/config.endpoint'
2525

2626
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
2727

src/app/core/togglz/togglz.service.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import { HttpClient } from '@angular/common/http'
22
import { Injectable } from '@angular/core'
33
import { Observable, ReplaySubject } from 'rxjs'
44
import { map, switchMapTo } from 'rxjs/operators'
5-
import { Config } from 'src/app/types/togglz.endpoint'
5+
import {
6+
Config,
7+
ConfigMessageKey,
8+
TogglzFlag,
9+
} from 'src/app/types/config.endpoint'
610
import { MaintenanceMessage } from 'src/app/types/togglz.local'
711

8-
import { TogglzFlag } from './togglz-flags.enum'
9-
1012
@Injectable({
1113
providedIn: 'root',
1214
})
@@ -52,6 +54,10 @@ export class TogglzService {
5254
)
5355
}
5456

57+
getConfigurationOf(configKey: ConfigMessageKey): Observable<string> {
58+
return this.getTogglz().pipe(map((data) => data.messages[configKey]))
59+
}
60+
5561
getMaintenanceMessages(): Observable<MaintenanceMessage> {
5662
return this.getMessageOf(TogglzFlag.MAINTENANCE_MESSAGE).pipe(
5763
map((value) => {
@@ -70,7 +76,7 @@ export class TogglzService {
7076
}
7177

7278
nodelistToArray(nodes: NodeListOf<Element>): Element[] {
73-
const list = []
79+
const list: Element[] = []
7480
nodes.forEach((element) => {
7581
list.push(element)
7682
})

src/app/layout/header/header.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
ApplicationMenuItemBasic,
2222
MenuItemRequirement,
2323
} from 'src/app/types/menu.local'
24-
import { Config } from 'src/app/types/togglz.endpoint'
24+
import { Config } from 'src/app/types/config.endpoint'
2525

2626
import { ApplicationRoutes } from '../../constants'
2727
import { menu } from './menu'

src/app/layout/statistics/statistics.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component, OnInit } from '@angular/core'
2-
import { Config } from 'src/app/types/togglz.endpoint'
2+
import { Config } from 'src/app/types/config.endpoint'
33
import { TogglzService } from 'src/app/core/togglz/togglz.service'
44

55
@Component({

src/app/layout/user-menu/user-menu.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { UserSession } from '../../types/session.local'
2222
import { UserService } from '../../core'
2323
import { TogglzService } from '../../core/togglz/togglz.service'
2424
import { of, ReplaySubject } from 'rxjs'
25-
import { Config } from '../../types/togglz.endpoint'
25+
import { Config } from '../../types/config.endpoint'
2626
import { MatIconModule } from '@angular/material/icon'
2727
import { MatIconHarness } from '@angular/material/icon/testing'
2828
import {

src/app/record/pages/my-orcid/my-orcid.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { RecordModule } from '../../record.module'
1616
import { UserService } from '../../../core'
1717
import { of, ReplaySubject } from 'rxjs'
1818
import { TogglzService } from '../../../core/togglz/togglz.service'
19-
import { Config } from '../../../types/togglz.endpoint'
19+
import { Config } from '../../../types/config.endpoint'
2020
import { RecordService } from '../../../core/record/record.service'
2121
import { NoopAnimationsModule } from '@angular/platform-browser/animations'
2222
import { getUserRecord } from '../../../core/record/record.service.spec'

src/app/types/config.endpoint.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
export const TogglzFlag = {
2+
WORDPRESS_HOME_PAGE: 'WORDPRESS_HOME_PAGE',
3+
HEADER_COMPACT: 'HEADER_COMPACT',
4+
OAUTH_AUTHORIZATION: 'OAUTH_AUTHORIZATION',
5+
OAUTH2_AUTHORIZATION: 'OAUTH2_AUTHORIZATION',
6+
OAUTH_SIGNIN: 'OAUTH_SIGNIN',
7+
EMAIL_DOMAINS_UI: 'EMAIL_DOMAINS_UI',
8+
FEATURED_WORKS_UI: 'FEATURED_WORKS_UI',
9+
NEW_RELIC_BROWSER_MONITORING: 'NEW_RELIC_BROWSER_MONITORING',
10+
LOGIN_DOMAINS_INTERSTITIAL: 'LOGIN_DOMAINS_INTERSTITIAL',
11+
OAUTH_DOMAINS_INTERSTITIAL: 'OAUTH_DOMAINS_INTERSTITIAL',
12+
LOGIN_AFFILIATION_INTERSTITIAL: 'LOGIN_AFFILIATION_INTERSTITIAL',
13+
OAUTH_AFFILIATION_INTERSTITIAL: 'OAUTH_AFFILIATION_INTERSTITIAL',
14+
MAINTENANCE_MESSAGE: 'MAINTENANCE_MESSAGE',
15+
FEATURED_AFFILIATIONS: 'FEATURED_AFFILIATIONS',
16+
} as const
17+
18+
export type TogglzFlag = (typeof TogglzFlag)[keyof typeof TogglzFlag]
19+
20+
// Keys returned by config.json that are not Togglz flags.
21+
export const ConfigMessageKey = {
22+
ACCOUNT_LOCKOUT_SIMULATION: 'ACCOUNT_LOCKOUT_SIMULATION',
23+
EVENTS: 'EVENTS',
24+
READ_BULK_WORKS_DIRECTLY_FROM_DB: 'READ_BULK_WORKS_DIRECTLY_FROM_DB',
25+
SEND_EMAIL_ON_EMAIL_LIST_CHANGE: 'SEND_EMAIL_ON_EMAIL_LIST_CHANGE',
26+
STATIC_PATH: 'STATIC_PATH',
27+
RECAPTCHA_WEB_KEY: 'RECAPTCHA_WEB_KEY',
28+
PAPI_EVENTS: 'PAPI_EVENTS',
29+
OAUTH_AUTHORIZATION_CODE_EXCHANGE: 'OAUTH_AUTHORIZATION_CODE_EXCHANGE',
30+
BASE_DOMAIN_RM_PROTOCALL: 'BASE_DOMAIN_RM_PROTOCALL',
31+
MAPI_SUMMARY_ENDPOINT: 'MAPI_SUMMARY_ENDPOINT',
32+
SEND_EMAIL_ON_DEACTIVATION: 'SEND_EMAIL_ON_DEACTIVATION',
33+
OAUTH_TOKEN_VALIDATION: 'OAUTH_TOKEN_VALIDATION',
34+
SEARCH_BASE: 'SEARCH_BASE',
35+
ORCID_ANGULAR_HELP_HERO: 'ORCID_ANGULAR_HELP_HERO',
36+
PUB_BASE_URI: 'PUB_BASE_URI',
37+
ENABLE_ACCOUNT_LOCKOUT: 'ENABLE_ACCOUNT_LOCKOUT',
38+
ABOUT_URI: 'ABOUT_URI',
39+
CRAZY_EGG: 'CRAZY_EGG',
40+
SEND_ALL_VERIFICATION_EMAILS: 'SEND_ALL_VERIFICATION_EMAILS',
41+
ENABLE_PAPI_RATE_LIMITING: 'ENABLE_PAPI_RATE_LIMITING',
42+
REDIRECT_PUT_TOKEN_ENDPOINT: 'REDIRECT_PUT_TOKEN_ENDPOINT',
43+
SHIBBOLETH_ENABLED: 'SHIBBOLETH_ENABLED',
44+
EMAIL_DOMAINS: 'EMAIL_DOMAINS',
45+
GA_TRACKING_ID: 'GA_TRACKING_ID',
46+
LIVE_IDS: 'LIVE_IDS',
47+
SEND_ADD_WORKS_EMAILS: 'SEND_ADD_WORKS_EMAILS',
48+
DELETE_EVENTS: 'DELETE_EVENTS',
49+
} as const
50+
51+
export type ConfigMessageKey =
52+
(typeof ConfigMessageKey)[keyof typeof ConfigMessageKey]
53+
54+
export type ConfigMessages = Record<TogglzFlag | ConfigMessageKey, string>
55+
56+
export interface Config {
57+
messages: ConfigMessages
58+
}
59+

src/app/types/togglz.endpoint.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/environments/environment.int.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ export const environment: EnvironmentInterface = {
1111
INFO_SITE: 'https://info.qa.orcid.org/',
1212
GOOGLE_ANALYTICS_TESTING_MODE: true,
1313
GOOGLE_TAG_MANAGER: 'GTM-0000000',
14-
GOOGLE_RECAPTCHA: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI',
1514
ZENDESK: 'b8313acd-6439-4894-b431-8c5a2ae9e7cb',
1615
HELP_HERO_ID: 'oYFQMrzFHA',
1716
SHOW_TEST_WARNING_BANNER: true,

0 commit comments

Comments
 (0)