先看效果
基本思路
接口进行触发是否进行图像验证,验证后将结果携带到接口里面去,进行人机验证
使用的技术(可惜只有web版本的)
验证码2.0智能人机验证(VAPTCHA)- 安全、易用、完全免费手势验证码VAPTCHA是基于人工智能和大数据的次世代人机验证解决方案。独有的验证策略及风控模型组合可彻底杜绝刷票、灌水、撞库等恶意攻击行为。https://www.vaptcha.com/
本来想使用flutter 实现一个插件 来调用安卓的ios,写的过程发现 vaptcha 的原生使用的是webview实现的,额 还是使用web版本进行封装把
代码如下
webview代码
import 'dart:convert';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class AppWebView extends StatefulWidget {
final String url;
final Function(dynamic)? onMessageReceived;
const AppWebView({
super.key,
required this.url,
this.onMessageReceived,
});
@override
State<AppWebView> createState() => _AppWebViewState();
}
class _AppWebViewState extends State<AppWebView> {
late final WebViewController controller;
int progress = 0;
@override
void initState() {
super.initState();
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel("lh", onMessageReceived: onMessageReceived)
..enableZoom(true)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
// Update loading bar.
this.progress = progress;
setState(() {});
},
onPageStarted: (String url) {},
onPageFinished: (String url) {},
onWebResourceError: (WebResourceError error) {},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse(widget.url));
}
// 接受h5发送来的数据
onMessageReceived(message) async {
widget.onMessageReceived?.call(message);
//接收H5发过来的数据
String sendMesStr = message.message;
print("onMessageReceived sendMesStr:${sendMesStr}");
Map<String, dynamic> msg = json.decode(sendMesStr);
String method = msg["method"] ?? "";
// Map<String, dynamic> data = msg["data"] ?? {};
if (method.isNotEmpty) {
switch (method) {
case "back":
controller.goBack();
break;
}
}
}
@override
Widget build(BuildContext context) {
if (!kIsWeb && (Platform.isIOS || Platform.isAndroid)) {
return Scaffold(
// appBar: AppBar(title: const Text('Flutter Simple Example')),
body: Stack(children: [
WebViewWidget(controller: controller),
if (progress != 100)
const Center(
child: CupertinoActivityIndicator(),
)
]),
);
} else {
return const Center(
child: Text('WebView control is not supported on this platform yet.'),
);
}
}
}
封装的核心
import 'dart:convert';
import 'package:LS/common/api/api.dart';
import 'package:LS/common/utils/toast.dart';
import 'package:LS/common/widgets/app_webview.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class Captcha {
static show() {
AppToast.dialog(
title: "图像验证".tr,
showBtn: false,
SizedBox(
height: 350.h,
child: const AppWebView(
url: "https://genaertest.wbnr.xyz/captcha",
onMessageReceived: onMessageReceived,
),
),
);
}
static onMessageReceived(message) async {
//接收H5发过来的数据
String sendMesStr = message.message;
print("onMessageReceived sendMesStr:${sendMesStr}");
Map<String, dynamic> msg = json.decode(sendMesStr);
String method = msg["method"] ?? "";
Map<String, dynamic> data = msg["data"] ?? {};
String server = data["server"] ?? "";
String token = data["token"] ?? "";
if (method.isNotEmpty) {
switch (method) {
case "getServerToken":
try {
// 调用后端接口
final res = await Api.captchaVerify(server: server, token: token);
final data = res["data"] ?? {};
if (data["success"] == 1) {
AppToast.show("验证通过");
}
// 关闭 SmartDialog 弹窗
SmartDialog.dismiss();
} catch (e) {
SmartDialog.dismiss();
}
break;
}
}
}
}
弹窗使用的是flutter_smart_dialog 可以自己集成一下
flutter_smart_dialog | Flutter PackageAn elegant Flutter Dialog solution, Easily implement Toast, Loading and custom Dialog, Make the use of the dialog easier!https://pub-web.flutter-io.cn/packages/flutter_smart_dialog
AppToast.dialog 其实是下面的封装
SmartDialog.show(
builder: (_) {
return child;
},
keepSingle: true,
clickMaskDismiss: clickMaskDismiss,
usePenetrate: false,
maskWidget: maskWidget,
);
然后是网页代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title></title>
<style>
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
html {
line-height: 1.15;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
}
main {
display: block;
}
h1 {
font-size: 2em;
margin: 0.67em 0;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
pre {
font-family: monospace, monospace;
font-size: 1em;
}
a {
background-color: transparent;
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted;
}
b,
strong {
font-weight: bolder;
}
code,
kbd,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
small {
font-size: 80%;
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
img {
border-style: none;
}
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
font-size: 100%;
line-height: 1.15;
margin: 0;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
[type="button"],
[type="reset"],
[type="submit"],
button {
-webkit-appearance: button;
}
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner,
button::-moz-focus-inner {
border-style: none;
padding: 0;
}
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring,
button:-moz-focusring {
outline: 1px dotted ButtonText;
}
fieldset {
padding: 0.35em 0.75em 0.625em;
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal;
}
progress {
vertical-align: baseline;
}
textarea {
overflow: auto;
}
[type="checkbox"],
[type="radio"] {
box-sizing: border-box;
padding: 0;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
details {
display: block;
}
summary {
display: list-item;
}
template {
display: none;
}
[hidden] {
display: none;
}
/*# sourceMappingURL=normalize.min.css.map */
</style>
<style>
.VAPTCHA-init-main {
display: table;
width: 100%;
height: 100%;
background-color: #eeeeee;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.VAPTCHA-init-loading {
display: table-cell;
vertical-align: middle;
text-align: center;
}
.VAPTCHA-init-loading > a {
display: inline-block;
width: 18px;
height: 18px;
border: none;
}
.VAPTCHA-init-loading .VAPTCHA-text {
font-family: sans-serif;
font-size: 12px;
color: #cccccc;
vertical-align: middle;
}
#VAPTCHAContainer {
width: 240px;
height: 36px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
<!-- <script src="https://v-cn.vaptcha.com/v3.js"></script> -->
<script>
!(function () {
"use strict";
function e(e) {
return e === undefined || null === e;
}
function t(e) {
return e !== undefined && null !== e;
}
function n(e) {
return null !== e && "object" === (void 0 === e ? "undefined" : p(e));
}
function a(e) {
return (
"object" === (void 0 === e ? "undefined" : p(e)) &&
e instanceof HTMLElement
);
}
function r(e) {
var t = e && e.toString().match(/^\s*function (\w+)/);
return t ? t[1] : "";
}
function o(e) {
var t =
arguments.length > 1 && arguments[1] !== undefined
? arguments[1]
: {};
for (var n in t) e[n] = t[n];
return e;
}
function i(e) {
var t = Object.create(null);
return function (n) {
return t[n] || (t[n] = e(n));
};
}
function c(e) {
return h.call(e).slice(8, -1);
}
function u(e) {
throw new Error(e);
}
function l() {
if (navigator.cookieEnabled) {
localStorage.setItem("vaptchatemp1", "1");
var e = localStorage.getItem("vaptchatemp1");
return localStorage.removeItem("vaptchatemp1"), !!e;
}
return !1;
}
function Promise(e) {
var t = this;
(this.state = "pending"),
(this.value = undefined),
(this.reason = undefined),
(this.onResolveAsyncCallbacks = []),
(this.onRejectAsyncCallbacks = []);
var n = function (e) {
"pending" === t.state &&
((t.state = "fulfilled"),
(t.value = e),
t.onResolveAsyncCallbacks.map(function (e) {
return e();
}));
},
a = function (e) {
"pending" === t.state &&
((t.state = "rejected"),
(t.reason = e),
t.onRejectAsyncCallbacks.map(function (t) {
return t(e);
}));
};
try {
e(n, a);
} catch (r) {
a(r);
}
}
function s(e, t) {
if (!(e instanceof t))
throw new TypeError("Cannot call a class as a function");
}
function f() {
var e = navigator.language || navigator.userLanguage;
return "zh-CN" === e
? "zh-CN"
: "zh-TW" === e
? "zh-TW"
: e.includes("en", -1)
? "en"
: e.includes("ja", -1)
? "jp"
: e.includes("vi", -1)
? "vi"
: "zh-CN";
}
(window.HTMLElement = window.HTMLElement || Element),
Array.prototype.map ||
(Array.prototype.map = function (e, t) {
var n, a, r;
if (null == this)
throw new TypeError(" this is null or not defined");
var o = Object(this),
i = o.length >>> 0;
if ("[object Function]" != Object.prototype.toString.call(e))
throw new TypeError(e + " is not a function");
for (t && (n = t), a = new Array(i), r = 0; r < i; ) {
var c, u;
r in o && ((c = o[r]), (u = e.call(n, c, r, o)), (a[r] = u)),
r++;
}
return a;
}),
Array.prototype.includes ||
(Array.prototype.includes = function (e, t) {
if (null == this)
throw new TypeError('"this" is null or not defined');
var n = Object(this),
a = n.length >>> 0;
if (0 === a) return !1;
for (
var r = 0 | t, o = Math.max(r >= 0 ? r : a - Math.abs(r), 0);
o < a;
) {
if (n[o] === e) return !0;
o++;
}
return !1;
}),
Array.prototype.findIndex ||
(Array.prototype.findIndex = function (e) {
if (null == this)
throw new TypeError('"this" is null or not defined');
var t = Object(this),
n = t.length >>> 0;
if ("function" != typeof e)
throw new TypeError("predicate must be a function");
for (var a = arguments[1], r = 0; r < n; ) {
if (e.call(a, t[r], r, t)) return r;
r++;
}
return -1;
}),
Object.create ||
(Object.create = function (e) {
var t = function () {};
return (t.prototype = e), new t();
});
var d = {
vid: null,
scene: 0,
container: null,
mode: "popup",
style: "dark",
lang: "auto",
ai: !0,
https: !0,
guide: !0,
aiAnimation: !1,
protocol: "https://",
css_version: "2.9.12",
cdn_servers: ["statics.vaptcha.com"],
api_server: "api.vaptcha.com/v3",
canvas_path: "/canvas.min.js",
offline_server: "",
},
p =
"function" == typeof Symbol && "symbol" == typeof Symbol.iterator
? function (e) {
return typeof e;
}
: function (e) {
return e &&
"function" == typeof Symbol &&
e.constructor === Symbol &&
e !== Symbol.prototype
? "symbol"
: typeof e;
},
h = Object.prototype.toString,
v =
(i(function (e) {
for (
var t = {},
n =
(e && -1 !== e.indexOf("?") && e.split("?")[1]) ||
window.location.search.substring(1),
a = n.split("&"),
r = 0;
r < a.length;
r++
) {
var o = a[r].split("=");
t[decodeURIComponent(o[0])] = decodeURIComponent(o[1]);
}
return t;
}),
i(function (e) {
return e.charAt(0).toUpperCase() + e.slice(1);
})),
g = function (t) {
(this.data = t),
(this.valiudateFuns = []),
(this.ruleFuns = {
required: function (t, n) {
return e(t) || 0 === t.length ? n : null;
},
});
};
g.prototype = {
constructor: g,
addValidateRules: function (e) {
o(this.ruleFuns, e);
},
add: function (e, t, n) {
var a = this,
r = t.split(":"),
o = r.shift(),
i = this.ruleFuns[o];
r.unshift(this.data[e]),
r.push(n),
i
? this.valiudateFuns.push(function () {
return i.apply(a, r);
})
: console.warn(
"Validator warning: rule " + o + " is not defined"
);
},
validate: function () {
for (var e, t = 0; (e = this.valiudateFuns[t++]); ) {
var n = e();
if (n) return u(n), !1;
}
return !0;
},
};
var m = {
AccessDenied: "0101",
RefreshAgain: "0102",
Success: "0103",
Fail: "0104",
RefreshTooFast: "0105",
RefreshTanto: "0106",
DrawTanto: "0107",
Attack: "0108",
jsonpTimeOut: "0703",
challengeExpire: "1002",
};
(Promise.prototype.then = function (e) {
var t = this;
if ("fulfilled" === this.state) {
var a = e(this.value);
if (n(a) && "Promise" === r(a.constructor)) return a;
}
return "pending" === this.state
? new Promise(function (a) {
t.onResolveAsyncCallbacks.push(function () {
var o = e(t.value);
if (n(o) && "Promise" === r(o.constructor)) return o.then(a);
a(o);
});
})
: this;
}),
(Promise.prototype["catch"] = function (e) {
return (
"rejected" === this.state && e(this.reason),
"pending" === this.state && this.onRejectAsyncCallbacks.push(e),
this
);
}),
(Promise.resolve = function (e) {
return new Promise(function (t) {
t(e);
});
}),
(Promise.reject = function (e) {
return new Promise(function (t, n) {
n(e);
});
});
var y = (function () {
function e(e, t) {
for (var n = 0; n < t.length; n++) {
var a = t[n];
(a.enumerable = a.enumerable || !1),
(a.configurable = !0),
"value" in a && (a.writable = !0),
Object.defineProperty(e, a.key, a);
}
}
return function (t, n, a) {
return n && e(t.prototype, n), a && e(t, a), t;
};
})(),
w = (function () {
function e() {
s(this, e);
}
return (
y(e, [
{
key: "GenerateFP",
value: function () {
return this.extractCRC32FromBase64(
this.getComplexCanvasFingerprint(
arguments.length > 0 && arguments[0] !== undefined
? arguments[0]
: ""
)
);
},
},
{
key: "getComplexCanvasFingerprint",
value: function () {
var e =
arguments.length > 0 && arguments[0] !== undefined
? arguments[0]
: "",
t = "BrowserLeaks,com <canvas> 1.0" + e,
n = document.createElement("canvas");
n.setAttribute("width", "220"),
n.setAttribute("height", "30");
var a = n.getContext("2d");
return (
(a.textBaseline = "top"),
(a.font = "14px 'Arial'"),
(a.textBaseline = "alphabetic"),
(a.fillStyle = "#f60"),
a.fillRect(125, 1, 62, 20),
(a.fillStyle = "#069"),
a.fillText(t, 2, 15),
(a.fillStyle = "rgba(102, 204, 0, 0.7)"),
a.fillText(t, 4, 17),
n.toDataURL()
);
},
},
{
key: "extractCRC32FromBase64",
value: function (e) {
return (
(e = e.replace("data:image/png;base64,", "")),
this.string2Hex(atob(e).slice(-16, -12).toString())
);
},
},
{
key: "string2Hex",
value: function (e) {
for (var t = "", n = 0; n < e.length; n++) {
var a = e.charCodeAt(n);
a <= 15 && (t += "0"),
(t += a.toString(16).toLocaleUpperCase());
}
return t;
},
},
]),
e
);
})(),
T =
(new w(),
(function () {
var e = d.protocol,
t = d.api_server,
n = function (e) {
var t = "";
for (var n in e)
Object.prototype.hasOwnProperty.call(e, n) &&
(t += "&" + n + "=" + encodeURIComponent(e[n]));
return t;
},
a = function (a, r) {
var o = n(r),
i = a.indexOf("http://") > -1 || a.indexOf("https://") > -1;
return (
a.indexOf("?") < 0 && (o = "?" + o.slice(1)),
i ? "" + a + o : "" + e + t + a + o
);
},
r = function (e) {
var t = document.getElementsByTagName("head")[0],
n = document.createElement("script");
return (
(n.charset = "UTF-8"),
(n.src = e),
t.appendChild(n),
{
remove: function () {
t.removeChild(n);
},
}
);
},
i = function (e, t, n, i) {
return (
(t = t || {}),
(n = n || !1),
new Promise(function (c) {
if (n) {
var u = setTimeout(function () {
clearTimeout(u),
l.remove(),
c({ code: "0703", msg: "Time out,Refresh Again!" });
}, i || 2e3);
window["static"] = function () {
clearTimeout(u), c.apply(this, arguments), l.remove();
};
var l = r(e);
} else {
var s = "VaptchaJsonp" + new Date().valueOf();
window[s] && (s += "1"),
o(t, { callback: s }),
(e = a(e, t));
var f = r(e),
d = setTimeout(function () {
clearTimeout(d),
(window[s] = null),
f.remove(),
c({
code: "0703",
msg: "Time out,Refresh Again!",
});
}, 1e4);
window[s] = function () {
clearTimeout(d),
c.apply(this, arguments),
f.remove(),
(window[s] = null);
};
}
})
);
};
return (
(i.setConfig = function (n) {
(e = n.protocol || e), (t = n.api_server || t);
}),
i
);
})()),
S = {
staticConfig: function (e) {
return T(
e.protocol + e.url + e.type + e.id,
{},
!0,
e.waitTime || 2e3
);
},
getConfig: function (e) {
var n = "";
return (
l() &&
t(localStorage.getItem("vaptchanu")) &&
(n = localStorage.getItem("vaptchanu")),
T("/config", {
vi: e.vid,
t: e.mode,
s: e.scene || 0,
z: e.zone,
v: 3,
u: n,
})
);
},
lang: function (e) {
return T("http://localhost:8080/api/v1/lang", {}, !1);
},
},
C = {
en: {
"0201": "id empty",
"0202": "id error",
"0208": "scene error",
"0209": "request used up",
"0906": "params error",
"0702": "VAPTCHA unit does not match the domain name",
"0105": "Request too fast, try again later!",
},
"zh-CN": {
"0702": "验证单元与域名不匹配",
"0105": "刷新过快,请稍后再试。",
},
},
b = 0,
j = 9999999999999,
I = 0,
N = 9999999999999,
A = (function () {
function r() {
var e = navigator.language || navigator.userLanguage;
return "zh-CN" === e
? "zh-CN"
: "zh-TW" === e
? "zh-TW"
: e.includes("en", -1)
? "en"
: e.includes("ja", -1)
? "jp"
: "zh-CN";
}
function i(e) {
if ("cn" === e.area) return "cn";
if ("sea" === e.area) return "sea";
if ("na" === e.area) return (e.area = "sea"), "sea";
if (
t(localStorage.getItem("vaptchaNetway")) &&
"" !== localStorage.getItem("vaptchaNetway")
)
return (
o(e, { area: localStorage.getItem("vaptchaNetway") }),
localStorage.getItem("vaptchaNetway")
);
var n = 0 - new Date().getTimezoneOffset() / 60,
a = navigator.language || window.navigator.userLanguage;
return (
(a = a.toLowerCase()),
8 === n && "zh-cn" === a
? (o(e, { area: "cn" }), "cn")
: 8 === n && "zh-cn" !== a
? (o(e, { area: "sea" }), "sea")
: (n >= 6 && n < 8) || (n > 8 && n <= 10)
? (o(e, { area: "sea" }), "na")
: (o(e, { area: "na" }), "na")
);
}
var l = !1,
s = function (e) {
var n = void 0,
a = void 0;
return (
t(e.area)
? ((n = "channel-" + i(e) + ".vaptcha.net/" + i(e)),
(a = "api-" + i(e) + ".vaptcha.net"))
: (o(e, { area: "cn" }),
(n = "channel-cn.vaptcha.net/cn"),
(a = "api-cn.vaptcha.net")),
(b = new Date().getTime()),
t(localStorage.getItem("vaptchaSpareCh")) &&
new Date().getTime() -
localStorage.getItem("vaptchaSpareCh") <
36e5
? S.staticConfig({
protocol: e.protocol,
id: e.vid,
url: n,
type: "/config/",
}).then(function (a) {
if (
((j = new Date().getTime()), w(i(e), e), t(a.alias))
)
return S.staticConfig({
protocol: e.protocol,
id: a.alias,
url: n,
type: "/alias/",
}).then(function (e) {
return o(e, { state: a.state }), Promise.resolve(e);
});
console.log("channel error");
})
: S.staticConfig({
protocol: e.protocol,
url: a,
type: "/channel/",
id: e.vid,
waitTime: 5e3,
}).then(function (a) {
return (
(j = new Date().getTime()),
w(i(e), e),
t(a.msg)
? (localStorage.setItem(
"vaptchaSpareCh",
new Date().getTime()
),
S.staticConfig({
protocol: e.protocol,
id: e.vid,
url: n,
type: "/config/",
}).then(function (a) {
if (t(a.alias))
return S.staticConfig({
protocol: e.protocol,
id: a.alias,
url: n,
type: "/alias/",
}).then(function (e) {
return (
o(e, { state: a.state }),
Promise.resolve(e)
);
});
console.log("channel error");
}))
: Promise.resolve(a)
);
})
);
},
f = function (e) {
return s(e)
.then(function (n) {
if (t(n.code) && "0703" === n.code)
return (
u("5001: channel interface timeout"),
Promise.reject("5001: channel interface timeout")
);
o(e, { api_server: n.api }), T.setConfig(e);
var a = 0 - new Date().getTimezoneOffset() / 60;
return o(e, { zone: a }), S.getConfig(e);
})
.then(function (n) {
if (t(n.code) && "0703" === n.code)
return (
u("5002: config interface timeout"),
Promise.reject("5002: config interface timeout")
);
if (!n) return Promise.resolve();
if (n.code !== m.Success) {
var a = C.en;
return (
"0702" === n.msg
? alert("" + a[n.msg])
: "0105" === n.code
? alert("" + a[n.code])
: console.log("errorCode:" + n.code + ":" + n.msg),
u(a[n.msg] || n.msg),
Promise.reject(n.code)
);
}
return (
n.data.guideVersion && n.data.guideVersion > "3.2.0"
? ((n.data.v3Update = 0),
console.log(
"Sorry,the version of V3.JS is too low, please upgrade!"
))
: (n.data.v3Update = 1),
o(e, n.data),
Promise.resolve()
);
});
},
d = function (e, t) {
return e.protocol + "static-" + e.area + ".vaptcha.net/" + t;
},
p = function (t) {
var n = document.getElementsByTagName("head")[0],
a = document.getElementById("vaptcha_style");
return new Promise(function (r) {
e(a)
? ((a = document.createElement("link")),
o(a, {
rel: "stylesheet",
type: "text/css",
href: t,
id: "vaptcha_style",
onload: r,
}),
n && n.appendChild(a))
: r();
});
},
h = function O(e) {
var n = document.getElementsByTagName("head")[0],
a = document.querySelector("script[src='" + e + "']");
return new Promise(function (r) {
if (t(a))
return void (a.loaded
? r()
: setTimeout(function () {
return O(e).then(r);
}));
a = document.createElement("script");
var i = function () {
(a.readyState &&
"loaded" !== a.readyState &&
"complete" !== a.readyState) ||
(r(),
(a.loaded = !0),
(a.onload = null),
(a.onreadystatechange = null));
};
o(a, {
async: !0,
charset: "utf-8",
src: e,
onerror: function () {
return u("load sdk timeout");
},
onload: i,
onreadystatechange: i,
}),
n.appendChild(a);
});
},
y = function (e) {
var t = e.sdkName,
n = e.config,
a = d(n, "js/" + n.js_path);
return h(a).then(function () {
var e = v(t);
return Promise.resolve(
new (0, window["_" + e + "Vaptcha"])(n)
);
});
},
w = function (e, n) {
var a = t(localStorage.getItem("vaptchaNetway")),
r = t(localStorage.getItem("vaptchaNetwayTime"))
? localStorage.getItem("vaptchaNetwayTime")
: 0,
o = new Date().getTime() - r,
i =
(t(localStorage.getItem("vaptchaNetwayTime2")) &&
localStorage.getItem("vaptchaNetwayTime2"),
j - b),
c = void 0;
if (
(i < 500 &&
!a &&
(localStorage.setItem("vaptchaNetway", e),
localStorage.setItem(
"vaptchaNetwayTime",
new Date().getTime()
),
localStorage.setItem("vaptchaNetwayTime2", i)),
!a || i >= 500 || o > 864e5)
) {
e = e.toLowerCase();
var u = void 0;
u = "cn" == e ? "sea" : "cn";
var l = "api-" + u + ".vaptcha.net";
(I = new Date().getTime()),
S.staticConfig({
protocol: n.protocol,
url: l,
type: "/channel/",
id: n.vid,
waitTime: 5e3,
}).then(function (t) {
(N = new Date().getTime()),
(c = N - I),
i < c &&
(localStorage.setItem("vaptchaNetway", e),
localStorage.setItem(
"vaptchaNetwayTime",
new Date().getTime()
),
localStorage.setItem("vaptchaNetwayTime2", i)),
c < i &&
(localStorage.setItem("vaptchaNetway", u),
localStorage.setItem(
"vaptchaNetwayTime",
new Date().getTime()
),
localStorage.setItem("vaptchaNetwayTime2", c));
});
}
},
A = function (e) {
if ("auto" === e.lang || "" === e.lang) {
var o = r();
e.lang = o || "zh-CN";
}
(l = !0),
(e.https = !0),
(e.protocol = "https://"),
T.setConfig(e),
t(e.type) && (e.mode = e.type),
"embedded" === e.mode && (e.mode = "embed"),
!["embed", "popup", "invisible"].includes(e.mode) &&
(e.mode = "popup"),
t(e.mode) && (e.type = e.mode);
var i = new g(e);
if (
(i.addValidateRules({
elementOrSelector: function (t, r) {
if (
("String" === c(e.container) &&
(e.container = document.querySelector(e.container)),
n(e.container) &&
a(e.container[0]) &&
(e.container = e.container[0]),
!a(e.container))
)
return r;
},
}),
i.add("vid", "required", "please configure vid"),
"invisible" !== e.mode &&
i.add(
"container",
"elementOrSelector",
"5004: please configure container with element or selector"
),
i.validate())
)
return f(e)
.then(function () {
var t = e.https
? "css/theme_https." + e.css_version + ".css"
: "css/theme." + e.css_version + ".css",
n = d(e, t);
return p(n);
})
.then(function () {
var t = e.mode;
return (l = !1), y({ sdkName: t, config: e });
});
};
return function k(e) {
return new Promise(function (t) {
l
? setTimeout(function () {
k(e).then(t);
}, 1e3)
: A(e).then(t);
})["catch"](function (e) {
return (l = !1), u(e), Promise.reject(e);
});
};
})(),
O = (function () {
var e = function (e) {
var n = e.getAttribute("data-config"),
a = {};
if (t(n))
try {
a = JSON.parse(n);
} catch (r) {
u("dom config format error");
}
return a;
},
n = function (e) {
var n = e.getAttribute("data-vid");
return t(n) ? { vid: n } : {};
},
a = function (e, n) {
var a = Object.create(d);
(a.container = e),
o(a, n),
t(a.vid) &&
A(a).then(function (e) {
e.renderTokenInput(), e.render();
});
};
return function () {
for (
var t = document.querySelectorAll("[data-vid]"),
r = document.querySelectorAll("[data-config]"),
o = 0;
o < r.length;
o++
) {
var i = e(r[o]);
a(r[o], i);
}
for (var c = 0; c < t.length; c++)
if (!Array.prototype.includes.call(r, t[c])) {
var u = n(t[c]);
a(t[c], u);
}
};
})();
(window.onload = O),
(window.vaptcha = function (e) {
var t = Object.create(d);
return (
o(t, e),
("auto" === t.lang || "" === t.lang) && (t.lang = f() || "zh-CN"),
A(t)
);
});
})();
</script>
</head>
<body>
<div id="VAPTCHAContainer">
<!-- 下面代码为预加载动画代码,仅供参考 -->
<div class="VAPTCHA-init-main">
<div class="VAPTCHA-init-loading">
<a href="/" target="_blank">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="48px"
height="60px"
viewBox="0 0 24 30"
style="
enable-background: new 0 0 50 50;
width: 14px;
height: 14px;
vertical-align: middle;
"
xml:space="preserve"
>
<rect x="0" y="9.22656" width="4" height="12.5469" fill="#CCCCCC">
<animate
attributeName="height"
attributeType="XML"
values="5;21;5"
begin="0s"
dur="0.6s"
repeatCount="indefinite"
></animate>
<animate
attributeName="y"
attributeType="XML"
values="13; 5; 13"
begin="0s"
dur="0.6s"
repeatCount="indefinite"
></animate>
</rect>
<rect
x="10"
y="5.22656"
width="4"
height="20.5469"
fill="#CCCCCC"
>
<animate
attributeName="height"
attributeType="XML"
values="5;21;5"
begin="0.15s"
dur="0.6s"
repeatCount="indefinite"
></animate>
<animate
attributeName="y"
attributeType="XML"
values="13; 5; 13"
begin="0.15s"
dur="0.6s"
repeatCount="indefinite"
></animate>
</rect>
<rect
x="20"
y="8.77344"
width="4"
height="13.4531"
fill="#CCCCCC"
>
<animate
attributeName="height"
attributeType="XML"
values="5;21;5"
begin="0.3s"
dur="0.6s"
repeatCount="indefinite"
></animate>
<animate
attributeName="y"
attributeType="XML"
values="13; 5; 13"
begin="0.3s"
dur="0.6s"
repeatCount="indefinite"
></animate>
</rect>
</svg>
</a>
<span class="VAPTCHA-text">Vaptcha Initializing...</span>
</div>
</div>
</div>
<script>
function appCallMethod(key, value) {
window[key] = value;
}
function callAppMethod(method, data = {}) {
try {
lh.postMessage(
JSON.stringify({
method,
data,
})
);
} catch (error) {}
}
</script>
<script>
var serverToken = {};
var data = {};
function getServerToken() {
return serverToken;
}
function getData() {
return data;
}
vaptcha({
vid: "65ddaa64d3784602950e7b12",
mode: "click",
scene: "1",
container: "#VAPTCHAContainer",
area: "auto",
lang: "en",
}).then(function (VAPTCHAObj) {
// 将VAPTCHA验证实例保存到局部变量中
obj = VAPTCHAObj;
// 渲染验证组件
VAPTCHAObj.render();
// 验证成功进行后续操作
VAPTCHAObj.listen("pass", function () {
serverToken = VAPTCHAObj.getServerToken();
data = {
server: serverToken.server,
token: serverToken.token,
scene: (window["scene"] ?? 1) * 1,
};
callAppMethod("getServerToken", data);
});
});
</script>
</body>
</html>
最后只需要在接口拦截器中 判断错误状态码 然后主动弹起人机验证