Because you can't. Not even an Extension is able to. Browsers don't want you to bypass their content enforcement. I wish we had at least one hacker friendly browser.
Isolated web apps a chrome feature for developing apps that run in chromium based on HTML (but tbh only really used in Chromebooks) do support raw TCP sockets so if this was ported to an IWA you could have Firefox on a Chromebook without an external server needed.
I for one am happy that browsers dont let any random web page i visit port scan my internal network.
Extensions can inject headers, such as Access-Control-Allow-Origin: *, to unblock cross-origin requests. In the Manifest V3 context, however, that might require patching window.fetch and window.XMLHttpRequest.
For example,
// content.js
window.fetch = async (...args) => {
const request = args[0] instanceof Request ? args[0].url : args[0]
const config = args[1] || {}
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({ action: "proxyFetch", request, config }, response => {
if (response.error) {
const err = new Error(response.error.message)
err.name = response.error.name
err.stack = response.error.stack
if (response.error.cause) err.cause = response.error.cause
reject(err)
} else {
const base64Data = response.dataUrl.split(",")[1]
const bytes = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0))
const contentType = response.headers["content-type"] || "application/octet-stream"
const blob = new Blob([bytes], { type: contentType })
const status = response.status
const statusText = response.statusText
const headers = new Headers(response.headers)
const body = status === 204 || status === 205 || status === 304 ? null : blob
resolve(new Response(body, { status, statusText, headers }))
}
})
})
}
// Background.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "proxyFetch") {
fetch(message.request, message.config)
.then(async res => {
const headers = Object.fromEntries(res.headers.entries())
const blob = await res.blob()
const reader = new FileReader()
reader.onloadend = () =>
sendResponse({ status: res.status, statusText: res.statusText, headers, dataUrl: reader.result })
reader.readAsDataURL(blob)
})
.catch(err => {
const { name, message, code, stack } = err
sendResponse({ error: { name, message, code, stack } })
})
// Keeps the message channel open for the async fetch
return true
}
})> "the connection is tunneled over a single WebSocket to a Puter relay"
Come on, it's both a server and a proxy, and it doesn't stop being those things just because you're calling it a relay.
no servers is referring to you not needing to host servers in the same as the term "serverless". Such is the ways of modern tech terms I fear
[0]: https://news.ycombinator.com/item?id=48895945
[1]: https://news.ycombinator.com/user?id=coolelectronics
[2]: https://news.ycombinator.com/item?id=45522061
[3]: https://news.ycombinator.com/item?id=44193514
[4]: https://news.ycombinator.com/item?id=42675696
[5]: https://news.ycombinator.com/item?id=41849494
[6]: https://news.ycombinator.com/item?id=41682779
[7]: https://news.ycombinator.com/item?id=41360683
[8]: https://news.ycombinator.com/item?id=41040761
[9]: https://news.ycombinator.com/item?id=40802253
[A]: https://news.ycombinator.com/item?id=39829463
[B]: https://news.ycombinator.com/item?id=39672886
[C]: https://news.ycombinator.com/item?id=39597030
[D]: https://news.ycombinator.com/item?id=39036897
also, talk about posting too much? look at your own submissions page
i never did some wasm but seems it runs quite fast on my macmini m1