voluptasmollitia
Version:
Monorepo for the Firebase JavaScript SDK
140 lines (120 loc) • 3.85 kB
text/typescript
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FirebaseApp } from '@firebase/app-types';
import { getState, setState } from './state';
import { Deferred } from '@firebase/util';
import { getRecaptcha, ensureActivated } from './util';
export const RECAPTCHA_URL = 'https://www.google.com/recaptcha/api.js';
export function initialize(
app: FirebaseApp,
siteKey: string
): Promise<GreCAPTCHA> {
const state = getState(app);
const initialized = new Deferred<GreCAPTCHA>();
setState(app, { ...state, reCAPTCHAState: { initialized } });
const divId = `fire_app_check_${app.name}`;
const invisibleDiv = document.createElement('div');
invisibleDiv.id = divId;
invisibleDiv.style.display = 'none';
document.body.appendChild(invisibleDiv);
const grecaptcha = getRecaptcha();
if (!grecaptcha) {
loadReCAPTCHAScript(() => {
const grecaptcha = getRecaptcha();
if (!grecaptcha) {
// it shouldn't happen.
throw new Error('no recaptcha');
}
grecaptcha.ready(() => {
// Invisible widgets allow us to set a different siteKey for each widget, so we use them to support multiple apps
renderInvisibleWidget(app, siteKey, grecaptcha, divId);
initialized.resolve(grecaptcha);
});
});
} else {
grecaptcha.ready(() => {
renderInvisibleWidget(app, siteKey, grecaptcha, divId);
initialized.resolve(grecaptcha);
});
}
return initialized.promise;
}
export async function getToken(app: FirebaseApp): Promise<string> {
ensureActivated(app);
// ensureActivated() guarantees that reCAPTCHAState is set
const reCAPTCHAState = getState(app).reCAPTCHAState!;
const recaptcha = await reCAPTCHAState.initialized.promise;
return new Promise((resolve, _reject) => {
// Updated after initialization is complete.
const reCAPTCHAState = getState(app).reCAPTCHAState!;
recaptcha.ready(() => {
resolve(
// widgetId is guaranteed to be available if reCAPTCHAState.initialized.promise resolved.
recaptcha.execute(reCAPTCHAState.widgetId!, {
action: 'fire_app_check'
})
);
});
});
}
/**
*
* @param app
* @param container - Id of a HTML element.
*/
function renderInvisibleWidget(
app: FirebaseApp,
siteKey: string,
grecaptcha: GreCAPTCHA,
container: string
): void {
const widgetId = grecaptcha.render(container, {
sitekey: siteKey,
size: 'invisible'
});
const state = getState(app);
setState(app, {
...state,
reCAPTCHAState: {
...state.reCAPTCHAState!, // state.reCAPTCHAState is set in the initialize()
widgetId
}
});
}
function loadReCAPTCHAScript(onload: () => void): void {
const script = document.createElement('script');
script.src = `${RECAPTCHA_URL}`;
script.onload = onload;
document.head.appendChild(script);
}
declare global {
interface Window {
grecaptcha: GreCAPTCHA | undefined;
}
}
export interface GreCAPTCHA {
ready: (callback: () => void) => void;
execute: (siteKey: string, options: { action: string }) => Promise<string>;
render: (
container: string | HTMLElement,
parameters: GreCAPTCHARenderOption
) => string;
}
export interface GreCAPTCHARenderOption {
sitekey: string;
size: 'invisible';
}