In the last part, we encountered a problem. The content_script
cannot skip the CORS and fetch the content outside the host where the script got injected.
That is why I came up with the idea of my server, a.k.a. a proxy.
However, choosing an HTTP Server can be a difficult task. While Bun has an in-built HTTP Server, deploying it can be challenging. Although you can wrap it into a Docker file, there are multiple ways to deploy it, which can be confusing.
In this part, I chose Cloudflare Workers because of its Serverless Edge functions and ease of deployment. Furthermore, the DX is excellent.
Cloudflare Workers as Proxy
The idea got extended. The content_script
will call the Cloudflare Worker and fetch the entire content (see below).
Let's get started:
bun install wrangler @cloudflare/workers-types
Change the package.json
{
...
"scripts": {
"server": "wrangler dev scripts/server.ts",
"deploy": "wrangler deploy --minify src/index.ts"
}
...
}
The Wrangler, Cloudflare's Developer Platform command-line interface (CLI), allows you to manage Worker projects and has an in-built Miniflare, which runs an HTTP server.
Let's create an HTTP server server.ts
// in server.ts
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
return new Response(null, {
status: 200,
});
},
}
In comparison, the Bun HTTP server looks similar.
export const server = Bun.serve({ async fetch(req) { return new Response(null, { status: 200 }); }, });
Let's run the server.
bun server
Now, the server is running on
http://localhost:8787
and we need to adjust thescraper.ts
.const response = await fetch('http://localhost:8787', { method: 'POST', body: JSON.stringify({ link }), headers: { 'Content-Type': 'application/json', }, mode: 'no-cors' });
Here we do a POST request to our Worker and post a
{"link": "link"}
. With that, we adjust the Worker:export default { async fetch(req: Request) { if (req.method === 'POST') { const link: { link: string } = await req.json(); const foo = await fetch(link.link); const bar = await foo.text(); const res = new Response(bar, { status: 200, headers: { 'Access-Control-Allow-Origin': '*', // Allow requests from all origins 'Access-Control-Allow-Methods': 'POST', // Specify allowed methods 'Access-Control-Allow-Headers': 'Content-Type', // Specify allowed headers }, }); console.log('In Server', res); return res; } } }
Now, we are using a Cloudflare Worker as a proxy to obtain the content of any website, effectively avoiding CORS. However, we need to ensure its security since the URL is public.
Adding Hono
This is the default version of Cloudflare Workers, which adopts the HTTP fetch standard. But honestly, I don't like this syntax, and we can do better. Here, I would like you to introduce Hono.
Hono is a fast, lightweight framework optimized for the edge, which runs on any JavaScript runtime, embraces web standards, and offers a better Developer Experience. For example, it offers a Router. Let's see what it looks like with our code:import { Hono } from 'hono';
const app = new Hono();
app.post('/', async (c) => {
const link: { link: string } = await c.req.json();
const foo = await fetch(link.link);
const bar = await foo.text();
return new Response(bar, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*', // Allow requests from all origins
'Access-Control-Allow-Methods': 'POST', // Specify allowed methods
'Access-Control-Allow-Headers': 'Content-Type', // Specify allowed headers
},
});
});
With that, we can easily add new APIs, and it is more readable.
Hono also comes with more features. One feature we will use in the next section is **authentication**.
### Adding a Bearer Token
Alternatively, we could set up [Lucia](https://lucia-auth.com/). But Hono has an in-built [authentication middleware](https://hono.dev/middleware/builtin/bearer-auth).
```ts
import { Hono } from 'hono';
import { bearerAuth } from 'hono/bearer-auth';
const app = new Hono();
const token = 'honoiscool';
app.post('/', bearerAuth({ token }), async (c) => {
const link: { link: string } = await c.req.json();
const foo = await fetch(link.link);
const bar = await foo.text();
return new Response(bar, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*', // Allow requests from all origins
'Access-Control-Allow-Methods': 'POST', // Specify allowed methods
'Access-Control-Allow-Headers': 'Content-Type', // Specify allowed headers
},
});
});
That's it. Now, we can only access this API endpoint when there is a valid Bearer Token
// in scraper.ts
export async function getPage(link: string) {
try {
const response = await fetch('http://localhost:8787', {
method: 'POST',
body: JSON.stringify({ link }),
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer honoiscool',
},
});
}
}
Adding CORS
Please note that CORS restricts any other domain outside of the origin (in this case, localhost
) from loading resources. Since the Chrome extension is injected into a page, it obtains the website's origin. For instance, if the extension is used on medium.com
, the origin is medium.com, outside of where the server is running (localhost). To enable an external origin to call this server, we can utilize Hono's CORS middleware.
// in server.ts
import { cors } from 'hono/cors';
const app = new Hono();
app.use('/', cors());
Conclusion
As part of the series, we have created our own server to avoid CORS. To achieve this, we combined Cloudflare Workers with Hono, a framework that provides useful middleware like cors and authentication, enhancing the developer experience. We plan to replace the currently hardcoded Bearer Token with a Google OAuth module in the upcoming part.
Find the repository here.
Thanks for reading, and stay tuned!
Top comments (0)