@kaito-http/core 4.0.0-beta.6 → 4.0.0-beta.7

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.
@@ -28,13 +28,20 @@ function experimental_createOriginMatcher(origins) {
28
28
  if (origins.length === 0) {
29
29
  return () => false;
30
30
  }
31
+ const escapedCharsRegex = /[.+?^${}()|[\]\\]/g;
31
32
  const source = origins.map((origin) => {
32
- if (origin.startsWith("*.")) {
33
- const escapedDomain = origin.slice(2).replace(/[.+?^${}()|[\]\\]/g, "\\$&");
34
- return `^(?:https?://)[^.]+\\.${escapedDomain}$`;
33
+ if (origin.includes("://*.")) {
34
+ const parts = origin.split("://");
35
+ if (parts.length !== 2) {
36
+ throw new Error(`Invalid origin pattern: ${origin}. Must include protocol (e.g., https://*.example.com)`);
37
+ }
38
+ const [protocol, rest] = parts;
39
+ const domain = rest.slice(2).replace(escapedCharsRegex, "\\$&");
40
+ const pattern = `^${protocol.replace(escapedCharsRegex, "\\$&")}:\\/\\/[^.]+\\.${domain}$`;
41
+ return pattern;
35
42
  } else {
36
- const escapedOrigin = origin.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
37
- return `^${escapedOrigin}$`;
43
+ const pattern = `^${origin.replace(escapedCharsRegex, "\\$&")}$`;
44
+ return pattern;
38
45
  }
39
46
  }).join("|");
40
47
  const regex = new RegExp(source);
@@ -46,7 +53,7 @@ function experimental_createCORSTransform(origins) {
46
53
  const origin = request.headers.get("Origin");
47
54
  if (origin && matcher(origin)) {
48
55
  response.headers.set("Access-Control-Allow-Origin", origin);
49
- response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
56
+ response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
50
57
  response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
51
58
  response.headers.set("Access-Control-Max-Age", "86400");
52
59
  response.headers.set("Access-Control-Allow-Credentials", "true");
@@ -1,31 +1,56 @@
1
1
  /**
2
2
  * Creates a function that matches origins against a predefined set of patterns, supporting wildcards.
3
- * The matcher handles both exact matches and wildcard subdomain patterns (e.g., '*.example.com').
3
+ * The matcher handles both exact matches and wildcard subdomain patterns.
4
4
  *
5
5
  * **⚠️ This API is experimental and may change or even be removed in the future. ⚠️**
6
6
  *
7
7
  * @param origins Array of origin patterns to match against.
8
- * Patterns can be exact origins (e.g., 'https://example.com') or wildcard patterns (e.g., '*.example.com') that match subdomains.
8
+ * Each pattern MUST include the protocol (http:// or https://).
9
+ * Two types of patterns are supported:
10
+ * 1. Exact matches (e.g., 'https://example.com') - matches only the exact domain with exact protocol
11
+ * 2. Wildcard subdomain patterns (e.g., 'https://*.example.com') - matches ONLY subdomains with exact protocol
12
+ *
13
+ * Important matching rules:
14
+ * - Protocol is always matched exactly - if you need both HTTP and HTTPS, include both patterns
15
+ * - Wildcard patterns (e.g., 'https://*.example.com') will ONLY match subdomains, NOT the root domain
16
+ * - To match both subdomains AND the root domain, include both patterns:
17
+ * ['https://*.example.com', 'https://example.com']
18
+ *
9
19
  * @returns A function that tests if an origin matches any of the patterns
10
20
  *
11
21
  * @example
12
22
  * ```typescript
13
23
  * const allowedOrigins = [
14
- * 'https://example.com',
15
- * '*.trusted-domain.com' // Won't match https://evil-domain.com, only subdomains
24
+ * // Exact matches - protocol required
25
+ * 'https://example.com', // matches only https://example.com
26
+ * 'http://example.com', // matches only http://example.com
27
+ *
28
+ * // Wildcard subdomain matches - protocol required
29
+ * 'https://*.example.com', // matches https://app.example.com, https://api.example.com
30
+ * // does NOT match https://example.com
31
+ *
32
+ * // To match both HTTP and HTTPS, include both
33
+ * 'https://*.staging.com', // matches https://app.staging.com
34
+ * 'http://*.staging.com', // matches http://app.staging.com
35
+ *
36
+ * // To match both subdomains and root domain, include both
37
+ * 'https://*.production.com', // matches https://app.production.com
38
+ * 'https://production.com', // matches https://production.com
16
39
  * ];
17
40
  *
18
41
  * const matcher = createOriginMatcher(allowedOrigins);
19
42
  *
20
- * // Exact match
21
- * console.log(matcher('https://example.com')); // true
22
- * console.log(matcher('http://example.com')); // false
43
+ * // Exact matches
44
+ * matcher('https://example.com'); // true
45
+ * matcher('http://example.com'); // true
23
46
  *
24
- * // Wildcard subdomain matches
25
- * console.log(matcher('https://app.trusted-domain.com')); // true
26
- * console.log(matcher('https://staging.trusted-domain.com')); // true
27
- * console.log(matcher('https://trusted-domain.com')); // false, because it's not a subdomain
28
- * console.log(matcher('https://evil-domain.com')); // false
47
+ * // Subdomain matches (protocol specific)
48
+ * matcher('https://app.example.com'); // true
49
+ * matcher('http://app.example.com'); // false - wrong protocol
50
+ *
51
+ * // Root domain with wildcard pattern
52
+ * matcher('https://example.com'); // false - wildcards don't match root
53
+ * matcher('https://production.com'); // true - matched by exact pattern
29
54
  * ```
30
55
  */
31
56
  declare function experimental_createOriginMatcher(origins: string[]): (origin: string) => boolean;
@@ -38,15 +63,31 @@ declare function experimental_createOriginMatcher(origins: string[]): (origin: s
38
63
  * @returns A function that will mutate the Response object by applying the CORS headers
39
64
  * @example
40
65
  * ```ts
41
- * const cors = createCORSHandler({
42
- * origins: ['https://example.com', "*.allows-subdomains.com", "http://localhost:3000"],
43
- * });
66
+ * const cors = experimental_createCORSTransform([
67
+ * // Exact matches
68
+ * 'https://example.com',
69
+ * 'http://localhost:3000',
70
+ *
71
+ * // Wildcard subdomain matches
72
+ * 'https://*.myapp.com', // matches https://dashboard.myapp.com
73
+ * 'http://*.myapp.com', // matches http://dashboard.myapp.com
74
+ *
75
+ * // Match both subdomain and root domain
76
+ * 'https://*.staging.com', // matches https://app.staging.com
77
+ * 'https://staging.com' // matches https://staging.com
78
+ * ]);
44
79
  *
45
- * const handler = createKaitoHandler({
46
- * // ...
47
- * transform: async (request, response) => {
48
- * cors(request, response);
49
- * }
80
+ * const router = create({
81
+ * before: async req => {
82
+ * if (req.method === 'OPTIONS') {
83
+ * // Return early to skip the router. This response still gets passed to `.transform()`
84
+ * // So our CORS headers will still be applied
85
+ * return new Response(null, {status: 204});
86
+ * }
87
+ * },
88
+ * transform: async (request, response) => {
89
+ * cors(request, response);
90
+ * }
50
91
  * });
51
92
  * ```
52
93
  */
@@ -1,31 +1,56 @@
1
1
  /**
2
2
  * Creates a function that matches origins against a predefined set of patterns, supporting wildcards.
3
- * The matcher handles both exact matches and wildcard subdomain patterns (e.g., '*.example.com').
3
+ * The matcher handles both exact matches and wildcard subdomain patterns.
4
4
  *
5
5
  * **⚠️ This API is experimental and may change or even be removed in the future. ⚠️**
6
6
  *
7
7
  * @param origins Array of origin patterns to match against.
8
- * Patterns can be exact origins (e.g., 'https://example.com') or wildcard patterns (e.g., '*.example.com') that match subdomains.
8
+ * Each pattern MUST include the protocol (http:// or https://).
9
+ * Two types of patterns are supported:
10
+ * 1. Exact matches (e.g., 'https://example.com') - matches only the exact domain with exact protocol
11
+ * 2. Wildcard subdomain patterns (e.g., 'https://*.example.com') - matches ONLY subdomains with exact protocol
12
+ *
13
+ * Important matching rules:
14
+ * - Protocol is always matched exactly - if you need both HTTP and HTTPS, include both patterns
15
+ * - Wildcard patterns (e.g., 'https://*.example.com') will ONLY match subdomains, NOT the root domain
16
+ * - To match both subdomains AND the root domain, include both patterns:
17
+ * ['https://*.example.com', 'https://example.com']
18
+ *
9
19
  * @returns A function that tests if an origin matches any of the patterns
10
20
  *
11
21
  * @example
12
22
  * ```typescript
13
23
  * const allowedOrigins = [
14
- * 'https://example.com',
15
- * '*.trusted-domain.com' // Won't match https://evil-domain.com, only subdomains
24
+ * // Exact matches - protocol required
25
+ * 'https://example.com', // matches only https://example.com
26
+ * 'http://example.com', // matches only http://example.com
27
+ *
28
+ * // Wildcard subdomain matches - protocol required
29
+ * 'https://*.example.com', // matches https://app.example.com, https://api.example.com
30
+ * // does NOT match https://example.com
31
+ *
32
+ * // To match both HTTP and HTTPS, include both
33
+ * 'https://*.staging.com', // matches https://app.staging.com
34
+ * 'http://*.staging.com', // matches http://app.staging.com
35
+ *
36
+ * // To match both subdomains and root domain, include both
37
+ * 'https://*.production.com', // matches https://app.production.com
38
+ * 'https://production.com', // matches https://production.com
16
39
  * ];
17
40
  *
18
41
  * const matcher = createOriginMatcher(allowedOrigins);
19
42
  *
20
- * // Exact match
21
- * console.log(matcher('https://example.com')); // true
22
- * console.log(matcher('http://example.com')); // false
43
+ * // Exact matches
44
+ * matcher('https://example.com'); // true
45
+ * matcher('http://example.com'); // true
23
46
  *
24
- * // Wildcard subdomain matches
25
- * console.log(matcher('https://app.trusted-domain.com')); // true
26
- * console.log(matcher('https://staging.trusted-domain.com')); // true
27
- * console.log(matcher('https://trusted-domain.com')); // false, because it's not a subdomain
28
- * console.log(matcher('https://evil-domain.com')); // false
47
+ * // Subdomain matches (protocol specific)
48
+ * matcher('https://app.example.com'); // true
49
+ * matcher('http://app.example.com'); // false - wrong protocol
50
+ *
51
+ * // Root domain with wildcard pattern
52
+ * matcher('https://example.com'); // false - wildcards don't match root
53
+ * matcher('https://production.com'); // true - matched by exact pattern
29
54
  * ```
30
55
  */
31
56
  declare function experimental_createOriginMatcher(origins: string[]): (origin: string) => boolean;
@@ -38,15 +63,31 @@ declare function experimental_createOriginMatcher(origins: string[]): (origin: s
38
63
  * @returns A function that will mutate the Response object by applying the CORS headers
39
64
  * @example
40
65
  * ```ts
41
- * const cors = createCORSHandler({
42
- * origins: ['https://example.com', "*.allows-subdomains.com", "http://localhost:3000"],
43
- * });
66
+ * const cors = experimental_createCORSTransform([
67
+ * // Exact matches
68
+ * 'https://example.com',
69
+ * 'http://localhost:3000',
70
+ *
71
+ * // Wildcard subdomain matches
72
+ * 'https://*.myapp.com', // matches https://dashboard.myapp.com
73
+ * 'http://*.myapp.com', // matches http://dashboard.myapp.com
74
+ *
75
+ * // Match both subdomain and root domain
76
+ * 'https://*.staging.com', // matches https://app.staging.com
77
+ * 'https://staging.com' // matches https://staging.com
78
+ * ]);
44
79
  *
45
- * const handler = createKaitoHandler({
46
- * // ...
47
- * transform: async (request, response) => {
48
- * cors(request, response);
49
- * }
80
+ * const router = create({
81
+ * before: async req => {
82
+ * if (req.method === 'OPTIONS') {
83
+ * // Return early to skip the router. This response still gets passed to `.transform()`
84
+ * // So our CORS headers will still be applied
85
+ * return new Response(null, {status: 204});
86
+ * }
87
+ * },
88
+ * transform: async (request, response) => {
89
+ * cors(request, response);
90
+ * }
50
91
  * });
51
92
  * ```
52
93
  */
package/dist/cors/cors.js CHANGED
@@ -3,13 +3,20 @@ function experimental_createOriginMatcher(origins) {
3
3
  if (origins.length === 0) {
4
4
  return () => false;
5
5
  }
6
+ const escapedCharsRegex = /[.+?^${}()|[\]\\]/g;
6
7
  const source = origins.map((origin) => {
7
- if (origin.startsWith("*.")) {
8
- const escapedDomain = origin.slice(2).replace(/[.+?^${}()|[\]\\]/g, "\\$&");
9
- return `^(?:https?://)[^.]+\\.${escapedDomain}$`;
8
+ if (origin.includes("://*.")) {
9
+ const parts = origin.split("://");
10
+ if (parts.length !== 2) {
11
+ throw new Error(`Invalid origin pattern: ${origin}. Must include protocol (e.g., https://*.example.com)`);
12
+ }
13
+ const [protocol, rest] = parts;
14
+ const domain = rest.slice(2).replace(escapedCharsRegex, "\\$&");
15
+ const pattern = `^${protocol.replace(escapedCharsRegex, "\\$&")}:\\/\\/[^.]+\\.${domain}$`;
16
+ return pattern;
10
17
  } else {
11
- const escapedOrigin = origin.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
12
- return `^${escapedOrigin}$`;
18
+ const pattern = `^${origin.replace(escapedCharsRegex, "\\$&")}$`;
19
+ return pattern;
13
20
  }
14
21
  }).join("|");
15
22
  const regex = new RegExp(source);
@@ -21,7 +28,7 @@ function experimental_createCORSTransform(origins) {
21
28
  const origin = request.headers.get("Origin");
22
29
  if (origin && matcher(origin)) {
23
30
  response.headers.set("Access-Control-Allow-Origin", origin);
24
- response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
31
+ response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
25
32
  response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
26
33
  response.headers.set("Access-Control-Max-Age", "86400");
27
34
  response.headers.set("Access-Control-Allow-Credentials", "true");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaito-http/core",
3
- "version": "4.0.0-beta.6",
3
+ "version": "4.0.0-beta.7",
4
4
  "author": "Alistair Smith <hi@alistair.sh>",
5
5
  "repository": "https://github.com/kaito-http/kaito",
6
6
  "devDependencies": {