Developing your own Chrome Extension - Fetch with a Proxy and Cloudflare Workers (Part 5)
Tags: cloudflare, chrome, ai, webdev
Published: 2024-03-03 11:00:00

In the last [part](https://dev.to/jolodev/developing-your-own-chrome-extension-fixing-errors-with-ts-morph-and-using-buns-api-part-4-3hni), 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).

![The idea with Cloudflare Workers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s4m8xpd0safdoxcmpipm.png)

Let's get started:

```sh
bun install wrangler @cloudflare/workers-types
```
Change the `package.json`
```json
{
...
  "scripts": {
    "server": "wrangler dev scripts/server.ts",
    "deploy": "wrangler deploy --minify src/index.ts"
  }
...
}

```

The [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/), Cloudflare's Developer Platform command-line interface (CLI), allows you to manage Worker projects and has an in-built [Miniflare](https://github.com/cloudflare/workers-sdk/tree/main/packages/miniflare), which runs an HTTP server.

Let's create an HTTP server `server.ts`
```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](https://bun.sh/guides/http/simple) looks similar.
```ts
export const server = Bun.serve({
  async fetch(req) {
    return new Response(null, { status: 200 });
  },
});
```
Let's run the server.
```sh
bun server
```
Now, the server is running on `http://localhost:8787` and we need to adjust the `scraper.ts`.
```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:
```ts
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](https://hono.dev/).
**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:
```ts
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
```ts
// 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](https://developer.mozilla.org/en-US/docs/Web/HTTP/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](https://hono.dev/middleware/builtin/cors).

```ts
// 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](https://github.com/jolo-dev/worth-the-click).

Thanks for reading, and stay tuned!