UNPKG

ngx-captcha-kit

Version:

[![npm version](https://badge.fury.io/js/ngx-captcha-kit.svg)](https://www.npmjs.com/package/ngx-captcha-kit) [![npm downloads](https://img.shields.io/npm/dm/ngx-captcha-kit.svg)](https://www.npmjs.com/package/ngx-captcha-kit) [![License: MIT](https://img

277 lines (271 loc) 12.5 kB
import * as i0 from '@angular/core'; import { PLATFORM_ID, Inject, Injectable, EventEmitter, Output, Input, Component, NgModule } from '@angular/core'; import { isPlatformBrowser, DOCUMENT } from '@angular/common'; class CaptchaService { doc; platformId; scriptLoaded = new Map(); readyPromises = new Map(); constructor(doc, platformId) { this.doc = doc; this.platformId = platformId; } loadScript(url, onloadCallbackName, language, asyncLoad = true) { let modifiedUrl = url; if (language && url.includes('google.com/recaptcha')) { modifiedUrl += (url.includes('?') ? '&' : '?') + `hl=${language}`; } if (this.scriptLoaded.get(modifiedUrl)) return this.readyPromises.get(modifiedUrl); if (!isPlatformBrowser(this.platformId)) return Promise.resolve(); const promise = new Promise((resolve) => { if (onloadCallbackName) { window[onloadCallbackName] = () => resolve(); } else { resolve(); } }); this.readyPromises.set(modifiedUrl, promise); const script = this.doc.createElement('script'); script.src = modifiedUrl; script.async = asyncLoad; script.defer = asyncLoad; script.onload = () => { this.scriptLoaded.set(modifiedUrl, true); if (!onloadCallbackName) promise.then(() => { }); }; this.doc.body.appendChild(script); return promise; } async executeRecaptchaV3(siteKey, action, language) { await this.loadScript(`https://www.google.com/recaptcha/api.js?render=${siteKey}`, undefined, language); await new Promise(resolve => window.grecaptcha.ready(resolve)); return window.grecaptcha.execute(siteKey, { action }); } async executeTurnstile(siteKey, action, cData) { await this.loadScript('https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'); await window.turnstile.ready(); return new Promise((resolve, reject) => { window.turnstile.render('#turnstile-container', { sitekey: siteKey, action: action, cData: cData, callback: (token) => resolve(token), 'error-callback': (error) => reject(error), }); }); } async executeAlibabaCaptcha(sceneId, options) { await this.loadScript('https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js'); window.initAliyunCaptcha({ SceneId: sceneId, mode: options.mode, element: options.element, captchaVerifyCallback: options.captchaVerifyCallback, language: options.language || 'zh', // 其他選項 }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: CaptchaService, deps: [{ token: DOCUMENT }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: CaptchaService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: CaptchaService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [DOCUMENT] }] }, { type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID] }] }] }); class CaptchaComponent { captchaService; el; type; siteKey; sceneId; prefix; // 新增 for Alibaba region = 'cn'; // 新增 for Alibaba, default 'cn' action; theme = 'light'; size = 'normal'; mode = 'embed'; cData; language = 'auto'; resolved = new EventEmitter(); error = new EventEmitter(); containerId = 'captcha-container'; widgetId; isReady = false; // 用於 v3 ready 狀態 constructor(captchaService, el) { this.captchaService = captchaService; this.el = el; } async ngAfterViewInit() { if (!this.type) { console.log('Type is required'); this.error.emit('Type is required'); return; } try { switch (this.type) { case 'recaptcha-v2': await this.captchaService.loadScript('https://www.google.com/recaptcha/api.js?render=explicit&onload=ngRecaptchaOnload', 'ngRecaptchaOnload', this.language); this.widgetId = window.grecaptcha.render(this.el.nativeElement.querySelector('div'), { sitekey: this.siteKey, theme: this.theme, size: this.size, callback: (token) => this.resolved.emit(token), 'error-callback': (err) => this.error.emit(err), }); break; case 'recaptcha-v3': await this.captchaService.loadScript(`https://www.google.com/recaptcha/api.js?render=${this.siteKey}`, undefined, this.language); await new Promise(resolve => window.grecaptcha.ready(resolve)); this.isReady = true; break; case 'turnstile': const renderPromise = new Promise((resolve, reject) => { window.turnstileOnload = () => { try { this.widgetId = window.turnstile.render(this.el.nativeElement.querySelector('div'), { sitekey: this.siteKey, action: this.action, cData: this.cData, theme: this.theme, language: this.language, callback: (token) => this.resolved.emit(token), 'error-callback': (err) => this.error.emit(err), }); resolve(); } catch (err) { reject(err); } }; }); await this.captchaService.loadScript('https://challenges.cloudflare.com/turnstile/v0/api.js?onload=turnstileOnload&render=explicit'); await renderPromise; break; case 'alibaba': if (!this.sceneId || !this.prefix) { this.error.emit('SceneId and Prefix are required for Alibaba Captcha'); return; } window.AliyunCaptchaConfig = { region: this.region || 'cn', prefix: this.prefix, }; await new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = 'https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js'; script.async = true; script.defer = true; script.onload = () => resolve(); script.onerror = (err) => reject(err); document.body.appendChild(script); }); if (typeof window.initAliyunCaptcha !== 'function') { this.error.emit('AliyunCaptcha script loaded but initAliyunCaptcha not defined. Check console for errors.'); return; } window.initAliyunCaptcha({ SceneId: this.sceneId, prefix: this.prefix, region: this.region, mode: this.mode, element: `#${this.containerId}`, captchaVerifyCallback: (param) => this.resolved.emit(param), language: this.language, }); break; default: this.error.emit('Unsupported type'); } } catch (err) { console.log(err); this.error.emit(err); } } // Public 方法給 v3 手動執行 async execute() { if (this.type !== 'recaptcha-v3' || !this.isReady) { throw new Error('reCAPTCHA v3 not ready or invalid type'); } const token = await window.grecaptcha.execute(this.siteKey, { action: this.action }); this.resolved.emit(token); return token; } ngOnDestroy() { if (this.type === 'recaptcha-v2' && this.widgetId !== undefined) { window.grecaptcha.reset(this.widgetId); } else if (this.type === 'turnstile' && this.widgetId !== undefined) { window.turnstile.remove(this.widgetId); } } getClass() { return this.type === 'turnstile' ? 'cf-turnstile' : this.type === 'recaptcha-v2' ? 'g-recaptcha' : ''; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: CaptchaComponent, deps: [{ token: CaptchaService }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.0", type: CaptchaComponent, isStandalone: false, selector: "captcha-kit", inputs: { type: "type", siteKey: "siteKey", sceneId: "sceneId", prefix: "prefix", region: "region", action: "action", theme: "theme", size: "size", mode: "mode", cData: "cData", language: "language" }, outputs: { resolved: "resolved", error: "error" }, ngImport: i0, template: `<div [id]="containerId" [class]="getClass()"></div>`, isInline: true }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: CaptchaComponent, decorators: [{ type: Component, args: [{ standalone: false, selector: 'captcha-kit', template: `<div [id]="containerId" [class]="getClass()"></div>`, }] }], ctorParameters: () => [{ type: CaptchaService }, { type: i0.ElementRef }], propDecorators: { type: [{ type: Input }], siteKey: [{ type: Input }], sceneId: [{ type: Input }], prefix: [{ type: Input }], region: [{ type: Input }], action: [{ type: Input }], theme: [{ type: Input }], size: [{ type: Input }], mode: [{ type: Input }], cData: [{ type: Input }], language: [{ type: Input }], resolved: [{ type: Output }], error: [{ type: Output }] } }); class NgxCaptchaKitModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: NgxCaptchaKitModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.1.0", ngImport: i0, type: NgxCaptchaKitModule, declarations: [CaptchaComponent], exports: [CaptchaComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: NgxCaptchaKitModule, providers: [CaptchaService] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: NgxCaptchaKitModule, decorators: [{ type: NgModule, args: [{ declarations: [CaptchaComponent], exports: [CaptchaComponent], providers: [CaptchaService] }] }] }); /** * Generated bundle index. Do not edit. */ export { CaptchaComponent, CaptchaService, NgxCaptchaKitModule }; //# sourceMappingURL=ngx-captcha-kit.mjs.map