354 lines
10 KiB
TypeScript
354 lines
10 KiB
TypeScript
/* eslint-disable no-undef */
|
||
let errorCallbackCount: any = 0;
|
||
|
||
// 常量
|
||
const DEFAULT_VALIDATE =
|
||
'QjGAuvoHrcpuxlbw7cp4WnIbbjzG4rtSlpc7EDovNHQS._ujzPZpeCInSxIT4WunuDDh8dRZYF2GbBGWyHlC6q5uEi9x-TXT9j7J705vSsBXyTar7aqFYyUltKYJ7f4Y2TXm_1Mn6HFkb4M7URQ_rWtpxQ5D6hCgNJYC0HpRE7.2sttqYKLoi7yP1KHzK-PptdHHkVwb77cwS2EJW7Mj_PsOtnPBubTmTZLpnRECJR99dWTVC11xYG0sx8dJNLUxUFxEyzTfX4nSmQz_T5sXATRKHtVAz7nmV0De5unmflfAlUwMGKlCT1khBtewlgN5nHvyxeD8Z1_fPVzi9oznl-sbegj6lKfCWezmLcwft8.4yaVh6SlzXJq-FnSK.euq9OBd5jYc82ge2_hEca1fGU--SkPRzgwkzew4O4qjdS2utdPwFONnhKAIMJRPUmCV4lPHG1OeRDvyNV8sCnuFMw7leasxIhPoycl4pm5bNy70Z1laozEGJgItVNr3'; // 默认validate
|
||
const FALLBACK_LANG: any = {
|
||
'zh-CN': '前方拥堵,已自动跳过验证',
|
||
en: 'captcha error,Verified automatically',
|
||
};
|
||
const CACHE_MIN = 1000 * 60; // 缓存时长单位,1分钟
|
||
const REQUEST_SCRIPT_ERROR = 502;
|
||
|
||
const RESOURCE_CACHE: any = {};
|
||
|
||
// 工具函数
|
||
function loadScript(src: any, cb: any) {
|
||
const head: any = document.head || document.getElementsByTagName('head')[0];
|
||
const script: any = document.createElement('script');
|
||
|
||
cb = cb || function () {};
|
||
|
||
script.type = 'text/javascript';
|
||
script.charset = 'utf8';
|
||
script.async = true;
|
||
script.src = src;
|
||
|
||
if (!('onload' in script)) {
|
||
script.onreadystatechange = function () {
|
||
if (this.readyState !== 'complete' && this.readyState !== 'loaded') {
|
||
return;
|
||
}
|
||
this.onreadystatechange = null;
|
||
cb(null, script); // there is no way to catch loading errors in IE8
|
||
};
|
||
}
|
||
|
||
script.onload = function () {
|
||
this.onerror = this.onload = null;
|
||
cb(null, script);
|
||
};
|
||
script.onerror = function () {
|
||
// because even IE9 works not like others
|
||
this.onerror = this.onload = null;
|
||
cb(new Error('Failed to load ' + this.src), script);
|
||
};
|
||
|
||
head.appendChild(script);
|
||
}
|
||
|
||
function joinUrl(protocol: any, host: any, path: any) {
|
||
protocol = protocol || '';
|
||
host = host || '';
|
||
path = path || '';
|
||
if (protocol) {
|
||
protocol = protocol.replace(/:?\/{0,2}$/, '://');
|
||
}
|
||
if (host) {
|
||
const matched = host.match(/^([-0-9a-zA-Z.:]*)(\/.*)?/);
|
||
host = matched[1];
|
||
path = (matched[2] || '') + '/' + path;
|
||
}
|
||
!host && (protocol = '');
|
||
|
||
return protocol + host + path;
|
||
}
|
||
|
||
function setDomText(el: any, value: any) {
|
||
if (value === undefined) {
|
||
return;
|
||
}
|
||
const nodeType = el.nodeType;
|
||
if (nodeType === 1 || nodeType === 11 || nodeType === 9) {
|
||
if (typeof el.textContent === 'string') {
|
||
el.textContent = value;
|
||
} else {
|
||
el.innerText = value;
|
||
}
|
||
}
|
||
}
|
||
|
||
function queryAllByClassName(selector: any, node: any) {
|
||
node = node || document;
|
||
if (node.querySelectorAll) {
|
||
return node.querySelectorAll(selector);
|
||
}
|
||
if (!/^\.[^.]+$/.test(selector)) {
|
||
return [];
|
||
}
|
||
if (node.getElementsByClassName) {
|
||
return node.getElementsByClassName(selector);
|
||
}
|
||
|
||
const children = node.getElementsByTagName('*');
|
||
let current;
|
||
const result = [];
|
||
const className = selector.slice(1);
|
||
for (let i = 0, l = children.length; i < l; i++) {
|
||
current = children[i];
|
||
if (~(' ' + current.className + ' ').indexOf(' ' + className + ' ')) {
|
||
result.push(current);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
function assert(condition: any, msg: any) {
|
||
if (!condition) {
|
||
throw new Error('[NECaptcha] ' + msg);
|
||
}
|
||
}
|
||
|
||
function isInteger(val: any) {
|
||
if (Number.isInteger) {
|
||
return Number.isInteger(val);
|
||
}
|
||
return typeof val === 'number' && isFinite(val) && Math.floor(val) === val;
|
||
}
|
||
|
||
function isArray(val: any) {
|
||
if (Array.isArray) {
|
||
return Array.isArray(val);
|
||
}
|
||
return Object.prototype.toString.call(val) === '[object Array]';
|
||
}
|
||
|
||
function ObjectAssign(a: any, b: any, c: any) {
|
||
if (Object.assign) {
|
||
// return Object.assign.apply(null, arguments);
|
||
return Object.assign.apply(null, arguments as any);
|
||
}
|
||
|
||
const target: any = {};
|
||
for (let index = 1; index < arguments.length; index++) {
|
||
const source = arguments[index];
|
||
if (source != null) {
|
||
for (const key in source) {
|
||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||
target[key] = source[key];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return target;
|
||
}
|
||
|
||
function getTimestamp(msec: any) {
|
||
msec = !msec && msec !== 0 ? msec : 1;
|
||
return parseInt((new Date().valueOf() / msec).toString(), 10);
|
||
}
|
||
|
||
// 降级方案
|
||
function normalizeFallbackConfig(customConfig: any) {
|
||
const siteProtocol = window.location.protocol.replace(':', '');
|
||
const defaultConf: any = {
|
||
protocol: siteProtocol === 'http' ? 'http' : 'https',
|
||
lang: 'zh-CN',
|
||
errorFallbackCount: 3,
|
||
};
|
||
const config: any = ObjectAssign({}, defaultConf, customConfig);
|
||
|
||
const errorFallbackCount: any = config.errorFallbackCount;
|
||
assert(
|
||
errorFallbackCount === undefined || (isInteger(errorFallbackCount) && errorFallbackCount >= 1),
|
||
"errorFallbackCount must be an integer, and it's value greater than or equal one",
|
||
);
|
||
|
||
return config;
|
||
}
|
||
|
||
function loadResource(config: any, cb: any) {
|
||
if ((window as any).initNECaptcha) {
|
||
return cb(null);
|
||
}
|
||
function genUrl(server: any) {
|
||
const path = 'load.min.js';
|
||
let _urls = [];
|
||
if (isArray(server)) {
|
||
for (let i = 0, len = server.length; i < len; i++) {
|
||
_urls.push(joinUrl(config.protocol, server[i], path));
|
||
}
|
||
} else {
|
||
const url = joinUrl(config.protocol, server, path);
|
||
_urls = [url, url];
|
||
}
|
||
|
||
return _urls;
|
||
}
|
||
const urls = genUrl(config.staticServer || ['cstaticdun.126.net', 'cstaticdun1.126.net', 'cstatic.dun.163yun.com']);
|
||
|
||
function step(i: any) {
|
||
const url = urls[i] + '?v=' + getTimestamp(CACHE_MIN);
|
||
loadScript(url, function (err: any) {
|
||
if (err || !(window as any).initNECaptcha) {
|
||
// loadjs的全局变量
|
||
i = i + 1;
|
||
if (i === urls.length) {
|
||
return cb(new Error('Failed to load script(' + url + ').' + (err ? err.message : 'unreliable script')));
|
||
}
|
||
return step(i);
|
||
}
|
||
return cb(null);
|
||
});
|
||
}
|
||
step(0);
|
||
}
|
||
|
||
/*
|
||
* entry: initNECaptchaWithFallback
|
||
* options:
|
||
* errorFallbackCount: 触发降级的错误次数,默认第三次错误降级
|
||
* defaultFallback: 是否开启默认降级
|
||
* onFallback: 自定义降级方案,参数为默认validate
|
||
*/
|
||
export function initNECaptchaWithFallback(options: any, onload: any, onerror: any) {
|
||
let captchaIns: any = null;
|
||
|
||
const config = normalizeFallbackConfig(options);
|
||
const defaultFallback = config.defaultFallback !== false;
|
||
const langPkg = FALLBACK_LANG[config.lang === 'zh-CN' ? config.lang : 'en'];
|
||
const storeKey = window.location.pathname + '_' + config.captchaId + '_NECAPTCHA_ERROR_COUNTS';
|
||
try {
|
||
errorCallbackCount = parseInt(localStorage.getItem(storeKey)?.toString() || '0', 10);
|
||
} catch (error) {}
|
||
|
||
const fallbackFn = !defaultFallback
|
||
? config.onFallback || function () {}
|
||
: (validate: any) => {
|
||
function setFallbackTip(instance: any) {
|
||
if (!instance) {
|
||
return;
|
||
}
|
||
setFallbackTip(instance._captchaIns);
|
||
if (!instance.$el) {
|
||
return;
|
||
}
|
||
const tipEles = queryAllByClassName('.yidun-fallback__tip', instance.$el);
|
||
if (!tipEles.length) {
|
||
return;
|
||
}
|
||
|
||
// 确保在队列的最后
|
||
setTimeout(() => {
|
||
for (let i = 0, l = tipEles.length; i < l; i++) {
|
||
setDomText(tipEles[i], langPkg);
|
||
}
|
||
}, 0);
|
||
}
|
||
setFallbackTip(captchaIns);
|
||
|
||
config.onVerify && config.onVerify(null, { validate: validate });
|
||
};
|
||
const noFallback = !defaultFallback && !config.onFallback;
|
||
|
||
const proxyOnError = (error: any) => {
|
||
errorCallbackCount++;
|
||
if (errorCallbackCount < config.errorFallbackCount) {
|
||
try {
|
||
localStorage.setItem(storeKey, errorCallbackCount);
|
||
} catch (err) {}
|
||
|
||
onerror(error);
|
||
} else {
|
||
fallbackFn(DEFAULT_VALIDATE);
|
||
proxyRefresh();
|
||
noFallback && onerror(error);
|
||
}
|
||
};
|
||
|
||
const proxyRefresh = () => {
|
||
errorCallbackCount = 0;
|
||
try {
|
||
localStorage.setItem(storeKey, '0');
|
||
} catch (err) {}
|
||
};
|
||
|
||
const triggerInitError = (error: any) => {
|
||
if (initialTimer && initialTimer.isError()) {
|
||
initialTimer.resetError();
|
||
return;
|
||
}
|
||
initialTimer && initialTimer.resetTimer();
|
||
noFallback ? onerror(error) : proxyOnError(error);
|
||
};
|
||
|
||
config.onError = (error: any) => {
|
||
if (initialTimer && initialTimer.isError()) {
|
||
initialTimer.resetError();
|
||
}
|
||
proxyOnError(error);
|
||
};
|
||
config.onDidRefresh = () => {
|
||
if (initialTimer && initialTimer.isError()) {
|
||
initialTimer.resetError();
|
||
}
|
||
proxyRefresh();
|
||
};
|
||
|
||
const initialTimer = options.initTimeoutError ? options.initTimeoutError(proxyOnError) : null; // initialTimer is only for mobile.html
|
||
|
||
const loadResolve = () => {
|
||
(window as any).initNECaptcha(
|
||
config,
|
||
(instance: any) => {
|
||
if (initialTimer && initialTimer.isError()) {
|
||
return;
|
||
}
|
||
initialTimer && initialTimer.resetTimer();
|
||
captchaIns = instance;
|
||
onload && onload(instance);
|
||
},
|
||
triggerInitError,
|
||
);
|
||
};
|
||
const cacheId = 'load-queue';
|
||
if (!RESOURCE_CACHE[cacheId]) {
|
||
RESOURCE_CACHE[cacheId] = {
|
||
rejects: [],
|
||
resolves: [],
|
||
status: 'error',
|
||
};
|
||
}
|
||
if (RESOURCE_CACHE[cacheId].status === 'error') {
|
||
RESOURCE_CACHE[cacheId].status = 'pending';
|
||
loadResource(config, (error: any) => {
|
||
if (error) {
|
||
const err: any = new Error();
|
||
err.code = REQUEST_SCRIPT_ERROR;
|
||
err.message = config.staticServer + '/load.min.js error';
|
||
|
||
const rejects = RESOURCE_CACHE[cacheId].rejects;
|
||
for (let i = 0, iLen = rejects.length; i < iLen; i++) {
|
||
rejects.pop()(err);
|
||
}
|
||
RESOURCE_CACHE[cacheId].status = 'error';
|
||
} else {
|
||
RESOURCE_CACHE[cacheId].status = 'done';
|
||
const resolves = RESOURCE_CACHE[cacheId].resolves;
|
||
for (let j = 0, jLen = resolves.length; j < jLen; j++) {
|
||
resolves.pop()();
|
||
}
|
||
}
|
||
});
|
||
} else if (RESOURCE_CACHE[cacheId].status === 'done') {
|
||
loadResolve();
|
||
}
|
||
if (RESOURCE_CACHE[cacheId].status === 'pending') {
|
||
RESOURCE_CACHE[cacheId].rejects.push(function loadReject(err: any) {
|
||
triggerInitError(err);
|
||
});
|
||
RESOURCE_CACHE[cacheId].resolves.push(loadResolve);
|
||
}
|
||
}
|