Skip to content

Commit e17b253

Browse files
committed
feat: experimental captcha support
1 parent c1cef04 commit e17b253

File tree

2 files changed

+124
-2
lines changed

2 files changed

+124
-2
lines changed

apps/extension/entrypoints/sandbox/index.html

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
height: 100%;
1818
}
1919
</style>
20+
<script async src="https://www.google.com/recaptcha/api.js?render=explicit"></script>
21+
<script
22+
src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
23+
defer
24+
></script>
2025
</head>
2126
<body>
2227
<div id="root">
@@ -28,10 +33,97 @@
2833
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals"></iframe>
2934
</div>
3035
<script>
36+
const DEFAULT_RC_SITE_KEY = "6LeXSIIqAAAAAJlp04ct42518YoNJeWiUtUTItTb";
37+
38+
const widgetIds = {};
39+
40+
async function getRecaptchaToken(sitekey, action){
41+
42+
return new Promise((resolve, reject) => {
43+
44+
if (!sitekey)
45+
sitekey = DEFAULT_RC_SITE_KEY;
46+
47+
grecaptcha.ready(function() {
48+
const elemId = `recaptcha-${sitekey}`;
49+
let widgetId;
50+
if (widgetIds[sitekey] === undefined){
51+
const elem = document.createElement("div");
52+
elem.id = elemId;
53+
document.body.append(elem);
54+
widgetId = grecaptcha.render(elemId, {
55+
sitekey,
56+
size: 'invisible'
57+
});
58+
widgetIds[sitekey] = widgetId;
59+
} else {
60+
widgetId = widgetIds[sitekey];
61+
const elem = document.getElementById(elemId).getElementsByClassName("grecaptcha-badge")[0];
62+
if (elem) elem.style.visibility = "visible";
63+
}
64+
65+
grecaptcha.execute(widgetId, {action: action ?? "recaptcha"}).then(function(token) {
66+
resolve(token);
67+
setTimeout(() => {
68+
const elem = document.getElementById(elemId).getElementsByClassName("grecaptcha-badge")[0];
69+
if (elem) elem.style.visibility = "hidden";
70+
}, 5000);
71+
}).catch(e => {
72+
reject(e);
73+
});
74+
});
75+
})
76+
}
77+
78+
const DEFAULT_TS_SITE_KEY = "0x4AAAAAAA0Rmw6kZyekmiSB";
79+
80+
async function getTurnstileToken(sitekey){
81+
82+
return new Promise((resolve, reject) => {
83+
84+
if (!sitekey)
85+
sitekey = DEFAULT_TS_SITE_KEY;
86+
87+
const elemId = `turnstile-${sitekey}`;
88+
let elem;
89+
90+
try {
91+
let widgetId;
92+
93+
if (!document.getElementById(elemId)) {
94+
elem = document.createElement("div");
95+
elem.id = elemId;
96+
elem.classList.add("turnstile-widget");
97+
document.body.append(elem);
98+
}
99+
100+
widgetId = turnstile.render("#" + elemId, {
101+
sitekey,
102+
callback: function (token) {
103+
console.log(`Challenge Success ${token}`);
104+
resolve(token);
105+
turnstile.remove(widgetId);
106+
elem.remove();
107+
},
108+
'error-callback': function (error){
109+
console.error("Turnstile error: ", error);
110+
turnstile.remove(widgetId);
111+
elem.remove();
112+
reject(error);
113+
}
114+
});
115+
} catch (e){
116+
reject(e);
117+
if (elem)
118+
elem.remove();
119+
}
120+
})
121+
}
122+
31123
;(function () {
32124
const iframe = document.getElementById("tlink-iframe")
33125

34-
function handleMessage(event) {
126+
async function handleMessage(event) {
35127
// We only proxy messages that originate from the child iframe or parent window
36128
// This prevents postMessage listeners of other tlink iframes on the same page causing duplicate requests to the background service
37129
if (
@@ -42,6 +134,36 @@
42134
}
43135

44136
if (event.data.type === "TLINK_API_REQUEST") {
137+
138+
// grecaptcha must be loaded in the extension context
139+
const req = event.data.data;
140+
141+
if (req.method === "getRecaptchaToken") {
142+
event.data.source = "TLINK_API_RESPONSE";
143+
try {
144+
const response = await getRecaptchaToken(req.payload?.sitekey, req.payload.action);
145+
event.data.data.response = response;
146+
} catch (e){
147+
console.error(e);
148+
event.data.data.error = e.message;
149+
}
150+
iframe?.contentWindow?.postMessage(event.data, "*");
151+
return;
152+
}
153+
154+
if (req.method === "getTurnstileToken") {
155+
event.data.source = "TLINK_API_RESPONSE";
156+
try {
157+
const response = await getTurnstileToken(req.payload?.sitekey);
158+
event.data.data.response = response;
159+
} catch (e){
160+
console.error(e);
161+
event.data.data.error = e.message;
162+
}
163+
iframe?.contentWindow?.postMessage(event.data, "*");
164+
return;
165+
}
166+
45167
// forward ts viewer API request out
46168
window.parent.postMessage(
47169
{ source: "TLINK_API_REQUEST", data: event.data.data },

apps/extension/wxt.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default defineConfig({
2626
content_security_policy: {
2727
extension_pages: "script-src 'self'; object-src 'self';",
2828
sandbox:
29-
"sandbox allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self' https://viewer.tokenscript.org/ https://viewer-staging.tokenscript.org/ http://localhost:3333/;"
29+
"sandbox allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/ https://www.gstatic.com/ https://recaptcha.google.com/ https://challenges.cloudflare.com/; child-src 'self' https://viewer.tokenscript.org/ https://viewer-staging.tokenscript.org/ http://localhost:3333/ https://www.google.com/ https://challenges.cloudflare.com/; worker-src 'self' https://www.google.com/;"
3030
}
3131
},
3232
modules: ["@wxt-dev/module-react"]

0 commit comments

Comments
 (0)