ngx-captcha-kit
Version:
[](https://www.npmjs.com/package/ngx-captcha-kit) [](https://www.npmjs.com/package/ngx-captcha-kit) [ • 12.5 kB
JavaScript
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