Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 44 additions & 11 deletions public/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,27 @@ const sj = new ScramjetServiceWorker({
}
});

// Flag to track if Scramjet config is loaded
let sjConfigLoaded = false;
let sjConfigPromise = null;

// Load Scramjet config once and cache the promise
async function ensureSjConfigLoaded() {
if (sjConfigLoaded) return Promise.resolve();
if (sjConfigPromise) return sjConfigPromise;

sjConfigPromise = sj.loadConfig().then(() => {
sjConfigLoaded = true;
}).catch((error) => {
console.error("Failed to load Scramjet config:", error);
// Don't reset the promise on error to avoid race conditions
// Instead, mark as loaded anyway so we don't keep retrying
sjConfigLoaded = true;
});

return sjConfigPromise;
}

// Enhanced CAPTCHA and Cloudflare verification support
// List of CAPTCHA and verification domains that need special handling
const CAPTCHA_DOMAINS = [
Expand Down Expand Up @@ -95,25 +116,29 @@ self.addEventListener("fetch", function (event) {
event.respondWith(
(async () => {
try {
await sj.loadConfig();
// Load Scramjet config once (cached)
await ensureSjConfigLoaded();

const url = event.request.url;
const isCaptcha = isCaptchaRequest(url);
const isHeavyCookie = isHeavyCookieSite(url);


// Safely check if this is a proxied request using optional chaining
const uvPrefix =
(typeof __uv$config !== "undefined" && __uv$config?.prefix) || null;
const isUvRequest = uvPrefix ? url.startsWith(location.origin + uvPrefix) : false;
const isSjRequest = sj.route(event);
const isProxiedRequest = isUvRequest || isSjRequest;

// Enhanced handling for CAPTCHA and heavy cookie requests
// Only check for special handling if it's a proxied request
let request = event.request;
if (isCaptcha) {
request = enhanceCaptchaRequest(event.request);
} else if (isHeavyCookie) {
request = enhanceHeavyCookieRequest(event.request);
if (isProxiedRequest) {
const isCaptcha = isCaptchaRequest(url);
const isHeavyCookie = isHeavyCookieSite(url);

if (isCaptcha) {
request = enhanceCaptchaRequest(event.request);
} else if (isHeavyCookie) {
request = enhanceHeavyCookieRequest(event.request);
}
}

let response;
Expand Down Expand Up @@ -268,6 +293,8 @@ self.addEventListener("activate", function (event) {
try {
// Claim all clients to ensure the service worker takes control immediately
await self.clients.claim();
// Pre-load Scramjet config during activation for faster first requests
await ensureSjConfigLoaded();
} catch (error) {
console.error("Service worker activation error:", error);
}
Expand All @@ -277,6 +304,12 @@ self.addEventListener("activate", function (event) {

// Add error handling for service worker installation
self.addEventListener("install", function (event) {
// Skip waiting to activate immediately
self.skipWaiting();
event.waitUntil(
(async () => {
// Skip waiting to activate immediately
self.skipWaiting();
// Pre-load Scramjet config during installation
await ensureSjConfigLoaded();
})()
);
});
154 changes: 107 additions & 47 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,16 @@ const link = Astro.url.searchParams.get("redir");
</Layout>
<script>
import { SW } from "@utils/proxy.ts";
import { Settings } from "@utils/settings.ts";
import { BareClient } from "@mercuryworkshop/bare-mux";
import { setupIframeInterceptor } from "@utils/iframe-interceptor.ts";

const init = async () => {
const input = document.getElementById("input") as HTMLInputElement;
const iframe = document.getElementById("iframe") as HTMLIFrameElement;
const iframeWin = iframe.contentWindow;
const bhl = document.getElementById("bhl") as HTMLDivElement;
const phl = document.getElementById("phl") as HTMLDivElement;
const phlImage = document.getElementById("phlImage") as HTMLImageElement;
const phlTitle = document.getElementById("phlTitle") as HTMLDivElement;
const proxyLeft = document.getElementById("pal") as HTMLButtonElement;
const proxyRight = document.getElementById("par") as HTMLButtonElement;
const proxyReload = document.getElementById("prl") as HTMLButtonElement;
const proxyShortcut = {
button: document.getElementById("psc") as HTMLButtonElement,
noShortcut: document.getElementById("noShortcut") as HTMLElement,
shortcut: document.getElementById("shortcut") as HTMLElement
}
const client = new BareClient();

// Get SW instance and wait for it to be ready
Expand All @@ -66,6 +56,94 @@ const link = Astro.url.searchParams.get("redir");

// Setup iframe interceptor to prevent new tabs/windows
setupIframeInterceptor(iframe, (url: string) => sw.encodeURL(url));

// Helper function to get the current iframe window safely
const getIframeWindow = (): Window | null => {
try {
return iframe.contentWindow;
} catch {
return null;
}
};

const getURL = async (): Promise<string> => {
const iframeWin = getIframeWindow();
if (!iframeWin) return "";

try {
if (iframeWin.__uv) {
return iframeWin.__uv.location.href;
}
else if (iframeWin.$scramjet?.config?.prefix) {
return iframeWin.location.href
.replace(iframeWin.location.origin, '')
.replace(iframeWin.$scramjet.config.prefix, '');
}
else {
// Fallback if neither proxy is available
return iframeWin.location.href;
}
} catch {
// Cross-origin access error - try to extract from iframe src
return "";
}
};

// Setup navigation buttons once (not on every search)
// Use a flag to track if listeners have been added
let buttonsInitialized = false;

const setupNavigationButtons = () => {
if (buttonsInitialized) return;
buttonsInitialized = true;

// Get fresh references to buttons
const leftBtn = document.getElementById("pal") as HTMLButtonElement;
const rightBtn = document.getElementById("par") as HTMLButtonElement;
const reloadBtn = document.getElementById("prl") as HTMLButtonElement;

if (!leftBtn || !rightBtn || !reloadBtn) return;

// Add navigation event listeners
leftBtn.addEventListener("click", () => {
const iframeWin = getIframeWindow();
if (iframeWin) {
try {
iframeWin.history.back();
} catch {
console.log("Navigation back failed - iframe may be cross-origin");
}
}
});

rightBtn.addEventListener("click", () => {
const iframeWin = getIframeWindow();
if (iframeWin) {
try {
iframeWin.history.forward();
} catch {
console.log("Navigation forward failed - iframe may be cross-origin");
}
}
});

reloadBtn.addEventListener("click", () => {
const iframeWin = getIframeWindow();
if (iframeWin) {
try {
iframeWin.location.reload();
} catch {
// Fallback: reload iframe by resetting src
const currentSrc = iframe.src;
iframe.src = "";
iframe.src = currentSrc;
}
}
});
};

// Setup navigation buttons immediately
setupNavigationButtons();

input.addEventListener("keypress", async (event: any) => {
if (event.key === "Enter") {
Expand All @@ -87,50 +165,32 @@ const link = Astro.url.searchParams.get("redir");
}
}

const settings = await Settings.getInstance();
iframe.classList.remove("hidden");
iframe.src = sw.encodeURL(input.value);
buttons();
}
});

const getURL = async (): Promise<string> => {
if (iframeWin!.__uv) {
return iframeWin!.__uv.location.href
}
else if (iframeWin!.$scramjet?.config?.prefix) {
return iframeWin!.location.href
.replace(iframeWin!.location.origin, '')
.replace(iframeWin!.$scramjet.config.prefix, '')
}
else {
// Fallback if neither proxy is available
return iframeWin!.location.href;
}
}

const buttons = () => {
proxyLeft.addEventListener("click", () => {
iframeWin!.history.back();
});
proxyRight.addEventListener("click", () => {
iframeWin!.history.forward();
});
proxyReload.addEventListener("click", () => {
iframeWin!.location.reload();
});
/** proxyShortcut.button.addEventListener("click", () => {
console.log("yet to be implemented");
}); */
}

iframe.addEventListener("load", async () => {
phlTitle.innerHTML = iframeWin!.document.title;
const iframeWin = getIframeWindow();
if (!iframeWin) return;

try {
phlTitle.innerHTML = iframeWin.document.title || "Loading...";
} catch {
phlTitle.innerHTML = "Loading...";
}

const pageURL = await getURL();
const data = await client.fetch(`https://www.google.com/s2/favicons?domain=${pageURL}&sz=64`);
const dataRes = await data.blob();
const object = URL.createObjectURL(dataRes);
phlImage.src = object;
if (pageURL) {
try {
const data = await client.fetch(`https://www.google.com/s2/favicons?domain=${pageURL}&sz=64`);
const dataRes = await data.blob();
const object = URL.createObjectURL(dataRes);
phlImage.src = object;
} catch {
// Favicon fetch failed, use default
}
}
bhl.classList.add("hidden");
phl.classList.remove("hidden");
});
Expand Down
15 changes: 11 additions & 4 deletions src/utils/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,22 @@ class SW {
if (url.hostname.includes(".")) return url.toString();
} catch (_) {}

return template.replace("%s", encodeURIComponent(input));
// Use DuckDuckGo as fallback if no template provided
const searchTemplate = template || "https://duckduckgo.com/?q=%s";
return searchTemplate.replace("%s", encodeURIComponent(input));
}

encodeURL(string: string): string {
const proxy = this.#storageManager.getVal("proxy") as "uv" | "sj";
const input = this.search(string, this.#storageManager.getVal("searchEngine"));
return proxy === "uv"
? `${__uv$config.prefix}${__uv$config.encodeUrl!(input)}`
: this.#scramjetController!.encodeUrl(input);

// If Scramjet is selected but controller isn't ready, fall back to UV
if (proxy === "sj" && this.#scramjetController) {
return this.#scramjetController.encodeUrl(input);
}

// Default to UV encoding
return `${__uv$config.prefix}${__uv$config.encodeUrl!(input)}`;
}

async setTransport(transport?: "epoxy" | "libcurl", get?: boolean) {
Expand Down
17 changes: 14 additions & 3 deletions src/utils/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,21 @@ class Settings {
}

proxy(prox?: "uv" | "sj") {
this.#storageManager.setVal("proxy", prox || "uv");
// Only set if explicitly provided or if no value exists
if (prox) {
this.#storageManager.setVal("proxy", prox);
} else if (!this.#storageManager.getVal("proxy")) {
this.#storageManager.setVal("proxy", "uv");
}
}

searchEngine(engine?: string) {
this.#storageManager.setVal("searchEngine", engine || SearchEngines.DuckDuckGo);
// Only set if explicitly provided or if no value exists
if (engine) {
this.#storageManager.setVal("searchEngine", engine);
} else if (!this.#storageManager.getVal("searchEngine")) {
this.#storageManager.setVal("searchEngine", SearchEngines.DuckDuckGo);
}
}

cloak(location: string) {
Expand Down Expand Up @@ -125,7 +135,8 @@ class Settings {
adBlock(enabled?: boolean) {
if (enabled === true || enabled === false) {
this.#storageManager.setVal("adBlock", enabled.valueOf().toString());
} else {
} else if (!this.#storageManager.getVal("adBlock")) {
// Only set default if no value exists
this.#storageManager.setVal("adBlock", "true");
}
}
Expand Down