@aryanbansal-launch/edge-utils 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ export function protectWithBasicAuth(request, options) {
2
+ const url = new URL(request.url);
3
+ if (!url.hostname.includes(options.hostnameIncludes)) {
4
+ return null;
5
+ }
6
+ const authHeader = request.headers.get("Authorization");
7
+ if (!authHeader || !authHeader.startsWith("Basic ")) {
8
+ return Promise.resolve(new Response("Authentication Required", {
9
+ status: 401,
10
+ headers: {
11
+ "WWW-Authenticate": `Basic realm="${options.realm ?? "Protected Area"}"`,
12
+ "Content-Type": "text/html"
13
+ }
14
+ }));
15
+ }
16
+ try {
17
+ const base64Credentials = authHeader.split(" ")[1];
18
+ const credentials = atob(base64Credentials);
19
+ const [username, password] = credentials.split(":");
20
+ if (username === options.username &&
21
+ password === options.password) {
22
+ return fetch(request);
23
+ }
24
+ return Promise.resolve(new Response("Unauthorized - Invalid credentials", { status: 401 }));
25
+ }
26
+ catch {
27
+ return Promise.resolve(new Response("Unauthorized - Invalid auth format", { status: 401 }));
28
+ }
29
+ }
@@ -0,0 +1,9 @@
1
+ export function getGeoHeaders(request) {
2
+ return {
3
+ country: request.headers.get("x-country-code"),
4
+ region: request.headers.get("x-region-code"),
5
+ city: request.headers.get("x-city"),
6
+ latitude: request.headers.get("x-latitude"),
7
+ longitude: request.headers.get("x-longitude")
8
+ };
9
+ }
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from "./response/json.js";
2
+ export * from "./response/passthrough.js";
3
+ export * from "./redirect/redirect.js";
4
+ export * from "./auth/basic-auth.js";
5
+ export * from "./security/ip-access.js";
6
+ export * from "./security/block-bots.js";
7
+ export * from "./geo/geo-headers.js";
@@ -0,0 +1,9 @@
1
+ export function redirectIfMatch(request, options) {
2
+ const url = new URL(request.url);
3
+ if (url.pathname === options.path &&
4
+ (!options.method || request.method === options.method)) {
5
+ url.pathname = options.to;
6
+ return Response.redirect(url, options.status ?? 301);
7
+ }
8
+ return null;
9
+ }
@@ -0,0 +1,6 @@
1
+ export function jsonResponse(body, init) {
2
+ return new Response(JSON.stringify(body), {
3
+ headers: { "Content-Type": "application/json" },
4
+ ...init
5
+ });
6
+ }
@@ -0,0 +1,3 @@
1
+ export function passThrough(request) {
2
+ return fetch(request);
3
+ }
@@ -0,0 +1,19 @@
1
+ const DEFAULT_BOTS = [
2
+ "claudebot",
3
+ "gptbot",
4
+ "googlebot",
5
+ "bingbot",
6
+ "ahrefsbot",
7
+ "yandexbot",
8
+ "semrushbot",
9
+ "mj12bot",
10
+ "facebookexternalhit",
11
+ "twitterbot"
12
+ ];
13
+ export function blockAICrawlers(request, bots = DEFAULT_BOTS) {
14
+ const ua = (request.headers.get("user-agent") || "").toLowerCase();
15
+ if (bots.some(bot => ua.includes(bot))) {
16
+ return new Response("Forbidden: AI crawlers are not allowed.", { status: 403 });
17
+ }
18
+ return null;
19
+ }
@@ -0,0 +1,13 @@
1
+ import { getClientIP } from "../utils/ip.js";
2
+ export function ipAccessControl(request, options) {
3
+ const ip = getClientIP(request);
4
+ if (!ip)
5
+ return null;
6
+ if (options.deny?.includes(ip)) {
7
+ return new Response("Forbidden", { status: 403 });
8
+ }
9
+ if (options.allow && !options.allow.includes(ip)) {
10
+ return new Response("Forbidden", { status: 403 });
11
+ }
12
+ return null;
13
+ }
@@ -0,0 +1,6 @@
1
+ export function getClientIP(request) {
2
+ const xff = request.headers.get("x-forwarded-for");
3
+ if (!xff)
4
+ return null;
5
+ return xff.split(",")[0].trim();
6
+ }
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@aryanbansal-launch/edge-utils",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "exports": {
7
+ ".": "./dist/index.js"
8
+ },
9
+ "files": ["dist"],
10
+ "scripts": {
11
+ "build": "tsc"
12
+ }
13
+ }
package/readme.md ADDED
@@ -0,0 +1,109 @@
1
+ # Edge Utils
2
+
3
+ A collection of high-performance utilities designed for Edge Computing environments (like Cloudflare Workers, Vercel Edge Functions, or Contentstack Launch). These utilities help you handle common tasks like authentication, security, redirection, and geo-location directly at the edge.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Usage Example](#usage-example)
9
+ - [API Reference](#api-reference)
10
+ - [Security](#security)
11
+ - [Authentication](#authentication)
12
+ - [Redirection](#redirection)
13
+ - [Geo Location](#geo-location)
14
+ - [Responses](#responses)
15
+
16
+ ## Installation
17
+
18
+ Install the package via npm:
19
+
20
+ ```bash
21
+ npm install @launch/edge-utils
22
+ ```
23
+
24
+ ## Usage Example
25
+
26
+ Here is a comprehensive example of how to use multiple utilities in a single edge handler:
27
+
28
+ ```typescript
29
+ import {
30
+ jsonResponse,
31
+ passThrough,
32
+ redirectIfMatch,
33
+ protectWithBasicAuth,
34
+ ipAccessControl,
35
+ blockAICrawlers,
36
+ getGeoHeaders
37
+ } from "@launch/edge-utils";
38
+
39
+ export default async function handler(request: Request) {
40
+ // 1. Block known AI crawlers and bots
41
+ const botResponse = blockAICrawlers(request);
42
+ if (botResponse) return botResponse;
43
+
44
+ // 2. IP-based access control
45
+ const ipResponse = ipAccessControl(request, { allow: ["203.0.113.10"] });
46
+ if (ipResponse) return ipResponse;
47
+
48
+ // 3. Basic Authentication for specific hostnames
49
+ const authResponse = await protectWithBasicAuth(request, {
50
+ hostnameIncludes: "test-protected-domain.dev",
51
+ username: "admin",
52
+ password: "securepassword"
53
+ });
54
+ if (authResponse && authResponse.status === 401) return authResponse;
55
+
56
+ // 4. Conditional Redirection
57
+ const redirectResponse = redirectIfMatch(request, {
58
+ path: "/old-page",
59
+ method: "GET",
60
+ to: "/new-page"
61
+ });
62
+ if (redirectResponse) return redirectResponse;
63
+
64
+ // 5. Handle specific routes
65
+ if (new URL(request.url).pathname === "/api/status") {
66
+ return jsonResponse({ status: "ok", timestamp: new Date() });
67
+ }
68
+
69
+ // 6. Access Geo-location headers
70
+ const geo = getGeoHeaders(request);
71
+ console.log("Request from country:", geo.country);
72
+
73
+ // 7. Pass through the request if no utility intercepted it
74
+ return passThrough(request);
75
+ }
76
+ ```
77
+
78
+ ## API Reference
79
+
80
+ ### Security
81
+
82
+ #### `blockAICrawlers(request: Request, bots?: string[]): Response | null`
83
+ Blocks common AI crawlers (like GPTBot, ClaudeBot) based on the `User-Agent` header. Returns a `403 Forbidden` response if a bot is detected, otherwise returns `null`.
84
+
85
+ #### `ipAccessControl(request: Request, options: { allow?: string[], deny?: string[] }): Response | null`
86
+ Restricts access based on the client's IP address. You can provide an `allow` list or a `deny` list.
87
+
88
+ ### Authentication
89
+
90
+ #### `protectWithBasicAuth(request: Request, options: AuthOptions): Promise<Response> | null`
91
+ Implements HTTP Basic Authentication. If the hostname matches `hostnameIncludes` and credentials are missing or invalid, it returns a `401 Unauthorized` response with the appropriate headers.
92
+
93
+ ### Redirection
94
+
95
+ #### `redirectIfMatch(request: Request, options: RedirectOptions): Response | null`
96
+ Redirects the request if the URL path and HTTP method match the provided options.
97
+
98
+ ### Geo Location
99
+
100
+ #### `getGeoHeaders(request: Request): Record<string, string | null>`
101
+ Extracts geo-location information (country, city, region, etc.) from the request headers typically provided by edge platforms.
102
+
103
+ ### Responses
104
+
105
+ #### `jsonResponse(data: any, init?: ResponseInit): Response`
106
+ A helper to return a JSON response with the correct `Content-Type` header.
107
+
108
+ #### `passThrough(request: Request): Response`
109
+ Continues the request processing by calling `fetch(request)`. Useful at the end of an edge function.