Skip to content

Commit 9dcc9e2

Browse files
committed
feat: experimental captcha support
Open TS viewer window to resolve proxy. This allows us to break out of the extension sandbox.
1 parent e17b253 commit 9dcc9e2

File tree

4 files changed

+141
-132
lines changed

4 files changed

+141
-132
lines changed

apps/extension/components/TokenScriptIframe.tsx

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,36 @@ export function TokenScriptIframe(props: {
1616
}
1717

1818
if (event.data?.source === "TLINK_API_REQUEST") {
19-
iframeRef.current?.contentWindow?.postMessage(
20-
{
19+
try {
20+
iframeRef.current?.contentWindow?.postMessage(
21+
{
22+
type: "TLINK_API_RESPONSE",
23+
source: "TLINK_API_RESPONSE",
24+
data: {
25+
uid: event.data.data.uid,
26+
method: event.data.data.method,
27+
response: await handleTlinkApiRequest(
28+
event.data.data.method,
29+
event.data.data.payload
30+
)
31+
}
32+
},
33+
"*"
34+
)
35+
} catch (e) {
36+
console.error("TLink API request failed: ", e);
37+
iframeRef.current?.contentWindow?.postMessage({
2138
type: "TLINK_API_RESPONSE",
2239
source: "TLINK_API_RESPONSE",
2340
data: {
2441
uid: event.data.data.uid,
2542
method: event.data.data.method,
26-
response: handleTlinkApiRequest(
27-
event.data.data.method,
28-
event.data.data.payload
29-
)
43+
error: e.message
3044
}
31-
},
32-
"*"
33-
)
45+
}, "*");
46+
}
47+
48+
return;
3449
}
3550

3651
if (event.data?.source === "tlink") {

apps/extension/entrypoints/background.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
1+
import {getTwitterHandle} from "@/lib/get-twitter-handle.ts";
2+
13
export default defineBackground(() => {
24
// never mark the function here async
35
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
46
if (!sender.tab || !sender.tab.id) {
57
return null
68
}
79

10+
if (msg.type === "TLINK_API_REQUEST"){
11+
12+
console.log("Handling TLink API request from background: ", msg);
13+
14+
handleTlinkApiRequest(msg.method, msg.payload).then((res) => {
15+
sendResponse(res);
16+
}).catch((err) => {
17+
// TODO: Error handling
18+
console.error("error handling message", err, msg);
19+
})
20+
21+
return true;
22+
}
23+
824
let rpcMethod: string = ""
925
let params: any = []
1026

@@ -37,12 +53,60 @@ export default defineBackground(() => {
3753
}
3854
})
3955
.catch((err) => {
40-
console.error("error handling message", err)
56+
console.error("error handling message", err, msg)
4157
})
4258

4359
return true
4460
})
4561

62+
async function handleTlinkApiRequest(method: string, payload: any) {
63+
switch (method) {
64+
case "getTurnstileToken":
65+
case "getRecaptchaToken":
66+
return handleTlinkApiViaTSViewerWindow(method, payload);
67+
}
68+
}
69+
70+
async function handleTlinkApiViaTSViewerWindow(method: string, payload: any){
71+
72+
return new Promise(async (resolve, reject) => {
73+
74+
let popup: WindowProxy|null;
75+
76+
const requestUrl = `http://localhost:3333/?viewType=tlink-api&method=${method}&payload=${encodeURIComponent(JSON.stringify(payload))}`
77+
78+
function handleMessage(event: MessageEvent){
79+
if (event.source !== popup) {
80+
return
81+
}
82+
83+
popup!.onclose = null;
84+
85+
resolve(event.data);
86+
}
87+
88+
window.addEventListener("message", handleMessage);
89+
90+
91+
//popup = window.open(requestUrl, "", 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=550px,height=800px`');
92+
popup = await chrome.windows.create({
93+
url: requestUrl,
94+
type: 'popup',
95+
focused: true,
96+
setSelfAsOpener: true
97+
// incognito, top, left, ...
98+
}) as WindowProxy;
99+
100+
if (!popup)
101+
reject("Failed to open the popup window");
102+
103+
popup!.onclose = () => {
104+
reject("Popup closed");
105+
}
106+
107+
});
108+
}
109+
46110
async function handleWalletCommunication({
47111
tabId,
48112
rpcMethod,

apps/extension/entrypoints/sandbox/index.html

Lines changed: 0 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@
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>
2520
</head>
2621
<body>
2722
<div id="root">
@@ -33,92 +28,6 @@
3328
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals"></iframe>
3429
</div>
3530
<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-
}
12231

12332
;(function () {
12433
const iframe = document.getElementById("tlink-iframe")
@@ -134,36 +43,6 @@
13443
}
13544

13645
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-
16746
// forward ts viewer API request out
16847
window.parent.postMessage(
16948
{ source: "TLINK_API_REQUEST", data: event.data.data },
Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,65 @@
11
import { getTwitterHandle } from "@/lib/get-twitter-handle"
22

3-
export function handleTlinkApiRequest(method: string, payload: any) {
3+
export async function handleTlinkApiRequest(method: string, payload: any) {
44
switch (method) {
55
case "getTlinkContext":
66
return {
77
handle: getTwitterHandle(),
88
API_KEY:
99
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9qZWN0IjoibXVsdGktY2hhbm5lbC1yZW5kZXJpbmctdGxpbmsiLCJpYXQiOjE3MjcxNzE1MDl9.9864en0XJbVgzACE_gHrQ00mr6fgctNRXWZ0Nex3DcQ"
1010
}
11+
case "getTurnstileToken":
12+
case "getRecaptchaToken":
13+
/*return await chrome.runtime.sendMessage({
14+
type: "TLINK_API_REQUEST",
15+
method,
16+
payload
17+
})*/
18+
return await handleTlinkApiViaTSViewerWindow(method, payload);
1119
}
1220

1321
return null
1422
}
23+
24+
async function handleTlinkApiViaTSViewerWindow(method: string, payload: any){
25+
26+
return new Promise(async (resolve, reject) => {
27+
28+
let popup: WindowProxy|null;
29+
let closedTimer;
30+
31+
const requestUrl = `http://localhost:3333/?viewType=tlink-api&method=${method}&payload=${encodeURIComponent(JSON.stringify(payload))}`
32+
33+
function handleMessage(event: MessageEvent){
34+
35+
if (event.source !== popup || event.data.type !== "TLINK_API_RESPONSE") {
36+
return
37+
}
38+
39+
if (closedTimer)
40+
clearInterval(closedTimer)
41+
42+
if (event.data.response){
43+
resolve(event.data.response);
44+
} else {
45+
reject(new Error(event.data.error));
46+
}
47+
48+
popup?.close();
49+
}
50+
51+
window.addEventListener("message", handleMessage);
52+
53+
popup = window.open(requestUrl, "", 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=250px,height=250px`');
54+
55+
if (!popup)
56+
reject(new Error("Failed to open the popup window"));
57+
58+
closedTimer = setInterval(() => {
59+
if (!popup || popup.closed){
60+
reject(new Error("Popup closed"));
61+
}
62+
}, 1000)
63+
64+
});
65+
}

0 commit comments

Comments
 (0)