Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google reCaptcha reset in TypeScript

I'm developing Angular 2 + TypeScript app. I need to reset reCaptcha if form is invalid. But in my typescript code when I write grecaptcha.reset();, I get error: Can not resolve symbol 'grecaptcha'

How can I reset reCaptcha or resolve/declare grecaptcha variable and where?

like image 873
A. Gladkiy Avatar asked Dec 15 '16 13:12

A. Gladkiy


1 Answers

For updated answer see Correct way to integrate reCAPTCHA with Angular 4

Original Answer

For Typescript, declare grecaptcha:

declare var grecaptcha: any;

This presumes that the reCAPTCHA api has been included,

<script src='https://www.google.com/recaptcha/api.js?render=explicit'></script>

Alternatively, grecaptcha can be typed as follows:

declare var grecaptcha: ReCAPTCHA;

ReCAPTCHA Interface

import { ElementRef } from '@angular/core';

/**
 * Interface for Google's reCAPTCHA JavaScript API. 
 * 
 * Display API
 * @see {@link https://developers.google.com/recaptcha/docs/display}
 * 
 * Invisible API
 * @see {@link https://developers.google.com/recaptcha/docs/invisible}
 */
export interface ReCAPTCHA {
  
  /**
   * Programatically invoke the reCAPTCHA check. Used if the invisible reCAPTCHA is on a div 
   * instead of a button.
   * 
   * @param {string} opt_widget_id Optional widget ID, defaults to the first widget created if 
   *     unspecified.
   */
  execute(opt_widget_id?: string): void;

  /** 
   * Renders the container as a reCAPTCHA widget and returns the ID of the newly created widget.
   * 
   * @param {ElementRef|string} container The HTML element to render the reCAPTCHA widget.  Specify 
   *    either the ID of the container (string) or the DOM element itself. 
   * @param {Object} parameters An object containing parameters as key=value pairs, for example,
   *    {"sitekey": "your_site_key", "theme": "light"}.
   */
  render(container: ElementRef|string, parameters: {[key: string]: string}): void;

  /** 
   * Resets the reCAPTCHA widget.
   * 
   * @param {string} opt_widget_id Optional widget ID, defaults to the first widget created if 
   *     unspecified.
   */
  reset(opt_widget_id?: string): void;

  /** 
   * Gets the response for the reCAPTCHA widget. Returns a null if reCaptcha is not validated. 
   * 
   * @param {string} opt_widget_id Optional widget ID, defaults to the first widget created if 
   *     unspecified.
   */
  getResponse(opt_widget_id?: string): string;
}

Example

The following code is an outline and does not show the server side ReCAPTCHA verification.

ReCAPTCHA Service

import { Injectable, OnInit } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs';

export interface ReCAPTCHAResponse {
  success: boolean; 
  status_code: number,
  error_codes?: Array<string>;
}

@Injectable()
export class ReCAPTCHAService {
  public recaptchaResponse$: Observable<ReCAPTCHAResponse>;

  public constructor(private http: Http) {}

  public verifyUserResponse(userResponseToken: string): Observable<ReCAPTCHAResponse> {
    let headers = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: headers });

    return this.http.post('/auth/captcha', {'g-recaptcha-response': userResponseToken}, options)
        .map( (res: Response) => this.extractData(res))
        .catch( (error: Response | any) => this.handleError(error));
  }


  private extractData(res: Response): ReCAPTCHAResponse {
    const recaptchaResponse: ReCAPTCHAResponse = res.json();
    return recaptchaResponse;
  }


  private handleError (error: Response | any): Observable<ReCAPTCHAResponse> {
    let errMsg: string;
    if (error instanceof Response) {
      let body = error.json() || '';
      let err = body.error || JSON.stringify(body);
      errMsg = error.status + ' - ' + (error.statusText || '') + ': ' + err;
    } else {
      errMsg = error.message ? error.message : error.toString();
    }

    return Observable.throw({ success: false, status_code: 0, error_codes: [errMsg]});
  }
}

Component

import { Component, OnDestroy, OnInit } from '@angular/core';
import { ReCAPTCHA } from './recaptcha';
import { ReCAPTCHAResponse, ReCAPTCHAService } from './recaptcha.service';

declare var grecaptcha: ReCAPTCHA;
declare var window: any;

@Component ({
  moduleId: module.id,
  selector: 'create-account',
  templateUrl: 'create-account.component.html'
})
export class CreateAccountComponent implements OnDestroy, OnInit {

  public constructor(private recaptcha: ReCAPTCHAService, private formBuilder: FormBuilder) {}

  public ngOnInit(): void {
    this.buildForms();

    // Export reCAPTCHACallback to global scope.
    window['reCAPTCHACallback'] = this.reCAPTCHACallback.bind(this);

    grecaptcha.render('create-account-captcha', {
      'sitekey': 'your-site-key',
      'size': 'invisible',        // Optional (see docs)
      'callback': 'reCAPTCHACallback'
    });
  }

  /**
   * Verify reCAPTCHA response on server. 
   */
  public reCAPTCHACallback(token: string) {

    if (token == null /* checks for undefined or null */ || token.length === 0) {
      grecaptcha.reset();
      // TODO: report that captcha was invalid
    } else {
      let response$: Observable<ReCAPTCHAResponse>; 
      response$ = this.recaptcha.verifyUserResponse(token);
      response$.subscribe( r => {
        if (r.success) {
          // TODO: create the new user account
        } else {
          grecaptcha.reset();
          // TODO: report that captcha was invalid
        }
      },
      (error: any) => {
        // TODO: handle server error
      });
    }
  }

  private buildForms(): void {
    // TODO
  }
}
like image 122
Christopher Peisert Avatar answered Oct 23 '22 06:10

Christopher Peisert