DEV Community

Cover image for Developing your own Chrome Extension - Fetch with a Proxy and Cloudflare Workers (Part 5)
JoLo
JoLo

Posted on

Developing your own Chrome Extension - Fetch with a Proxy and Cloudflare Workers (Part 5)

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).

The idea with Cloudflare Workers

Let's get started:

bun install wrangler @cloudflare/workers-types
Enter fullscreen mode Exit fullscreen mode

Change the package.json

{
...
  "scripts": {
    "server": "wrangler dev scripts/server.ts",
    "deploy": "wrangler deploy --minify src/index.ts"
  }
...
}

Enter fullscreen mode Exit fullscreen mode

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,
    });
  },
}
Enter fullscreen mode Exit fullscreen mode

In comparison, the Bun HTTP server looks similar.

export const server = Bun.serve({
  async fetch(req) {
    return new Response(null, { status: 200 });
  },
});
Enter fullscreen mode Exit fullscreen mode

Let's run the server.

bun server
Enter fullscreen mode Exit fullscreen mode

Now, the server is running on http://localhost:8787 and we need to adjust the scraper.ts.

const response = await fetch('http://localhost:8787', {
  method: 'POST',
  body: JSON.stringify({ link }),
  headers: {
    'Content-Type': 'application/json',
  },
  mode: 'no-cors'
});
Enter fullscreen mode Exit fullscreen mode

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;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
    },
  });
});
Enter fullscreen mode Exit fullscreen mode

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. But Hono has an in-built authentication middleware.

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
    },
  });
});
Enter fullscreen mode Exit fullscreen mode

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',
        },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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)