diff --git a/public/sw.js b/public/sw.js
index 3266d21..a8c6f03 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -20,16 +20,27 @@ const sj = new ScramjetServiceWorker({
// Enhanced CAPTCHA and Cloudflare verification support
// List of CAPTCHA and verification domains that need special handling
const CAPTCHA_DOMAINS = [
+ // reCAPTCHA domains
"google.com/recaptcha",
"www.google.com/recaptcha",
"recaptcha.net",
"www.recaptcha.net",
"gstatic.com/recaptcha",
+ "www.gstatic.com/recaptcha",
+ // hCaptcha domains
"hcaptcha.com",
+ "www.hcaptcha.com",
"newassets.hcaptcha.com",
+ "assets.hcaptcha.com",
+ "imgs.hcaptcha.com",
+ "js.hcaptcha.com",
+ // Cloudflare Turnstile domains
"challenges.cloudflare.com",
"cloudflare.com/cdn-cgi/challenge",
- "turnstile.cloudflare.com"
+ "turnstile.cloudflare.com",
+ // Additional verification APIs
+ "api.hcaptcha.com",
+ "api2.hcaptcha.com"
];
// Domains that use heavy cookies and complex browser services
@@ -144,20 +155,48 @@ self.addEventListener("fetch", function (event) {
);
});
-// Script to inject into proxied pages to intercept new tab/window attempts
+// Script to inject into proxied pages for CAPTCHA support and new tab interception
const INTERCEPTOR_SCRIPT = `
`;
diff --git a/src/utils/captcha-handler.ts b/src/utils/captcha-handler.ts
index 9ddf6ac..98c4ba7 100644
--- a/src/utils/captcha-handler.ts
+++ b/src/utils/captcha-handler.ts
@@ -9,12 +9,26 @@
* List of CAPTCHA and verification-related domains
*/
const CAPTCHA_DOMAINS = [
+ // reCAPTCHA domains
"google.com",
+ "www.google.com",
"recaptcha.net",
+ "www.recaptcha.net",
"gstatic.com",
+ "www.gstatic.com",
+ // hCaptcha domains
"hcaptcha.com",
+ "www.hcaptcha.com",
+ "newassets.hcaptcha.com",
+ "assets.hcaptcha.com",
+ "imgs.hcaptcha.com",
+ "js.hcaptcha.com",
+ "api.hcaptcha.com",
+ "api2.hcaptcha.com",
+ // Cloudflare Turnstile domains
"cloudflare.com",
- "challenges.cloudflare.com"
+ "challenges.cloudflare.com",
+ "turnstile.cloudflare.com"
];
/**
@@ -49,31 +63,65 @@ export function initializeCaptchaHandlers() {
window.___grecaptcha_cfg = { clients: {} };
}
+ // Initialize hCaptcha global object
+ if (!window.hcaptcha) {
+ window.hcaptcha = {};
+ }
+
+ // Initialize Turnstile global object
+ if (!window.turnstile) {
+ window.turnstile = {};
+ }
+
+ // Check if URL is CAPTCHA-related
+ const isCaptchaUrl = (url: string): boolean => {
+ const urlLower = url.toLowerCase();
+ return CAPTCHA_DOMAINS.some((domain) => urlLower.includes(domain));
+ };
+
+ // Setup CAPTCHA iframe with proper permissions
+ const setupCaptchaIframe = (iframe: HTMLIFrameElement) => {
+ const src = iframe.src || iframe.getAttribute("src") || "";
+ if (
+ src.includes("recaptcha") ||
+ src.includes("hcaptcha") ||
+ src.includes("challenges.cloudflare.com") ||
+ src.includes("turnstile") ||
+ isCaptchaUrl(src)
+ ) {
+ // Ensure the iframe has proper sandbox permissions
+ if (iframe.sandbox && iframe.sandbox.length > 0) {
+ iframe.sandbox.add("allow-same-origin");
+ iframe.sandbox.add("allow-scripts");
+ iframe.sandbox.add("allow-forms");
+ iframe.sandbox.add("allow-popups");
+ iframe.sandbox.add("allow-popups-to-escape-sandbox");
+ }
+
+ // Ensure credentials are included for CAPTCHA cookies
+ if (iframe.getAttribute("credentialless") !== null) {
+ iframe.removeAttribute("credentialless");
+ }
+
+ // Add proper allow attribute for permissions policy
+ const allow = iframe.getAttribute("allow") || "";
+ if (!allow.includes("cross-origin-isolated")) {
+ iframe.setAttribute("allow", allow + (allow ? "; " : "") + "cross-origin-isolated");
+ }
+ }
+ };
+
// Monitor for CAPTCHA iframe creation and ensure proper setup
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node instanceof HTMLIFrameElement) {
- const src = node.src || "";
- // Check if this is a CAPTCHA iframe
- if (
- src.includes("recaptcha") ||
- src.includes("hcaptcha") ||
- src.includes("challenges.cloudflare.com") ||
- src.includes("turnstile")
- ) {
- // Ensure the iframe has proper sandbox permissions
- if (node.sandbox && node.sandbox.length > 0) {
- node.sandbox.add("allow-same-origin");
- node.sandbox.add("allow-scripts");
- node.sandbox.add("allow-forms");
- }
-
- // Ensure credentials are included for CAPTCHA cookies
- if (node.getAttribute("credentialless") !== null) {
- node.removeAttribute("credentialless");
- }
- }
+ setupCaptchaIframe(node);
+ } else if (node instanceof HTMLElement) {
+ // Also check for iframes within added nodes
+ node.querySelectorAll("iframe").forEach((iframe) => {
+ setupCaptchaIframe(iframe as HTMLIFrameElement);
+ });
}
});
});
@@ -85,6 +133,11 @@ export function initializeCaptchaHandlers() {
subtree: true
});
+ // Setup existing iframes
+ document.querySelectorAll("iframe").forEach((iframe) => {
+ setupCaptchaIframe(iframe as HTMLIFrameElement);
+ });
+
// Ensure cookies are properly handled for CAPTCHA tokens and heavy cookie sites
enhanceCookieHandling();
@@ -93,6 +146,9 @@ export function initializeCaptchaHandlers() {
// Add storage persistence for better cookie support
enhanceStoragePersistence();
+
+ // Setup postMessage handler for CAPTCHA communication
+ setupPostMessageHandler();
}
/**
@@ -219,13 +275,32 @@ function enhanceStoragePersistence() {
}
/**
- * Global declaration for reCAPTCHA config
+ * Setup message handler placeholder for CAPTCHA-related messages
+ * This function exists to be called as part of initialization but no longer
+ * modifies postMessage behavior since overriding it broke MessagePort transfers
+ */
+function setupPostMessageHandler() {
+ // Note: We intentionally don't override postMessage as it can break
+ // transferable objects like MessagePort. The proxy handles cross-origin
+ // issues at the service worker level instead.
+ // CAPTCHA widgets communicate via postMessage with their own origins,
+ // which works natively without intervention.
+}
+
+/**
+ * Global declaration for CAPTCHA configs
*/
declare global {
interface Window {
___grecaptcha_cfg?: {
- clients: Record;
- [key: string]: any;
+ clients: Record;
+ [key: string]: unknown;
+ };
+ hcaptcha?: {
+ [key: string]: unknown;
+ };
+ turnstile?: {
+ [key: string]: unknown;
};
}
}