@appwarden/middleware 3.3.0 → 3.4.1

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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![GitHub](https://img.shields.io/badge/GitHub-appwarden%2Fmiddleware-181717?logo=github&logoColor=white)](https://github.com/appwarden/middleware)
5
5
  [![npm version](https://img.shields.io/npm/v/@appwarden/middleware.svg)](https://www.npmjs.com/package/@appwarden/middleware)
6
6
  [![npm provenance](https://img.shields.io/badge/npm-provenance-green)](https://docs.npmjs.com/generating-provenance-statements)
7
- ![Test Coverage](https://img.shields.io/badge/coverage-95.72%25-brightgreen)
7
+ ![Test Coverage](https://img.shields.io/badge/coverage-91.52%25-brightgreen)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
9
9
 
10
10
  ## Core Features
@@ -3,10 +3,8 @@ import {
3
3
  isHTMLResponse
4
4
  } from "./chunk-HCGLR3Z3.js";
5
5
  import {
6
- debug,
7
- makeCSPHeader,
8
- printMessage
9
- } from "./chunk-ZBYVJ3HA.js";
6
+ makeCSPHeader
7
+ } from "./chunk-GK6JL5NZ.js";
10
8
 
11
9
  // src/middlewares/use-content-security-policy.ts
12
10
  var AppendAttribute = (attribute, nonce) => ({
@@ -27,7 +25,7 @@ var useContentSecurityPolicy = (input) => {
27
25
  // if the csp is disabled
28
26
  !["enforced", "report-only"].includes(config.mode)
29
27
  ) {
30
- debug(printMessage("csp is disabled"));
28
+ context.debug("CSP is disabled");
31
29
  return;
32
30
  }
33
31
  if (response.headers.has("Content-Type") && !isHTMLResponse(response)) {
@@ -39,6 +37,10 @@ var useContentSecurityPolicy = (input) => {
39
37
  config.directives,
40
38
  config.mode
41
39
  );
40
+ context.debug(
41
+ `Applying CSP in ${config.mode} mode`,
42
+ `Directives: ${config.directives ? Object.keys(config.directives).join(", ") : "none"}`
43
+ );
42
44
  const nextResponse = new Response(response.body, response);
43
45
  nextResponse.headers.set(cspHeaderName, cspHeaderValue);
44
46
  nextResponse.headers.set("content-type", "text/html; charset=utf-8");
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  LOCKDOWN_TEST_EXPIRY_MS
3
3
  } from "./chunk-HCGLR3Z3.js";
4
+ import {
5
+ printMessage
6
+ } from "./chunk-GK6JL5NZ.js";
4
7
 
5
8
  // src/utils/build-lock-page-url.ts
6
9
  function normalizeLockPageSlug(lockPageSlug) {
@@ -34,6 +37,32 @@ var createRedirect = (url) => {
34
37
  });
35
38
  };
36
39
 
40
+ // src/utils/debug.ts
41
+ var debug = (isDebug) => (...msg) => {
42
+ if (!isDebug) return;
43
+ const parts = msg.map((m) => {
44
+ let content;
45
+ if (m instanceof Error) {
46
+ content = m.stack ?? m.message;
47
+ } else if (typeof m === "object" && m !== null) {
48
+ try {
49
+ content = JSON.stringify(m);
50
+ } catch {
51
+ try {
52
+ content = String(m);
53
+ } catch {
54
+ content = "[Unserializable value]";
55
+ }
56
+ }
57
+ } else {
58
+ content = String(m);
59
+ }
60
+ return content;
61
+ });
62
+ const message = parts.join(" ");
63
+ console.log(printMessage(message));
64
+ };
65
+
37
66
  // src/utils/memory-cache.ts
38
67
  var MemoryCache = class {
39
68
  cache = /* @__PURE__ */ new Map();
@@ -86,5 +115,6 @@ export {
86
115
  isOnLockPage,
87
116
  TEMPORARY_REDIRECT_STATUS,
88
117
  createRedirect,
118
+ debug,
89
119
  MemoryCache
90
120
  };
@@ -1,6 +1,7 @@
1
1
  import {
2
- MemoryCache
3
- } from "./chunk-SUZPTFWY.js";
2
+ MemoryCache,
3
+ debug
4
+ } from "./chunk-EPJ4ZVO6.js";
4
5
  import {
5
6
  APPWARDEN_CACHE_KEY,
6
7
  APPWARDEN_TEST_ROUTE
@@ -10,17 +11,19 @@ import {
10
11
  getLockValue,
11
12
  store,
12
13
  syncEdgeValue
13
- } from "./chunk-ZBYVJ3HA.js";
14
+ } from "./chunk-GK6JL5NZ.js";
14
15
 
15
16
  // src/core/check-lock-status.ts
16
17
  var createContext = async (config) => {
17
18
  const requestUrl = new URL(config.request.url);
18
19
  const keyName = APPWARDEN_CACHE_KEY;
19
20
  const provider = "cloudflare-cache";
21
+ const debugFn = debug(config.debug ?? false);
20
22
  const edgeCache = store.json(
21
23
  {
22
24
  serviceOrigin: requestUrl.origin,
23
- cache: await caches.open("appwarden:lock")
25
+ cache: await caches.open("appwarden:lock"),
26
+ debug: debugFn
24
27
  },
25
28
  keyName
26
29
  );
@@ -30,7 +33,7 @@ var createContext = async (config) => {
30
33
  edgeCache,
31
34
  requestUrl,
32
35
  provider,
33
- debug: config.debug ?? false,
36
+ debug: debugFn,
34
37
  lockPageSlug: config.lockPageSlug,
35
38
  appwardenApiToken: config.appwardenApiToken,
36
39
  appwardenApiHostname: config.appwardenApiHostname,
@@ -40,6 +43,7 @@ var createContext = async (config) => {
40
43
  var resolveLockStatus = async (context) => {
41
44
  const { lockValue, shouldDeleteEdgeValue } = await getLockValue(context);
42
45
  if (shouldDeleteEdgeValue) {
46
+ context.debug("Deleting corrupted cache value");
43
47
  await deleteEdgeValue(context);
44
48
  }
45
49
  const isTestRoute = context.requestUrl.pathname === APPWARDEN_TEST_ROUTE;
@@ -54,11 +58,21 @@ var resolveLockStatus = async (context) => {
54
58
  var checkLockStatus = async (config) => {
55
59
  const context = await createContext(config);
56
60
  let { isLocked, isTestLock, lockValue, wasDeleted } = await resolveLockStatus(context);
57
- if (MemoryCache.isExpired(lockValue) || wasDeleted) {
61
+ const isExpired = MemoryCache.isExpired(lockValue);
62
+ if (!isExpired && !wasDeleted && lockValue) {
63
+ context.debug("Lock value resolved from cache");
64
+ }
65
+ if (isExpired || wasDeleted) {
58
66
  if (!lockValue || wasDeleted || lockValue.isLocked) {
67
+ context.debug(
68
+ "No fresh cached lock status available - syncing with API synchronously"
69
+ );
59
70
  await syncEdgeValue(context);
60
71
  ({ isLocked, isTestLock } = await resolveLockStatus(context));
61
72
  } else {
73
+ context.debug(
74
+ "Cached lock status expired but last known state unlocked - syncing with API in background"
75
+ );
62
76
  config.waitUntil(syncEdgeValue(context));
63
77
  }
64
78
  }
@@ -1,27 +1,3 @@
1
- // src/utils/debug.ts
2
- var debug = (...msg) => {
3
- if (true) {
4
- const formatted = msg.map((m) => {
5
- if (typeof m === "object" && m !== null) {
6
- if (m instanceof Error) {
7
- return m.stack ?? m.message;
8
- }
9
- try {
10
- return JSON.stringify(m);
11
- } catch {
12
- try {
13
- return String(m);
14
- } catch {
15
- return "[Unserializable value]";
16
- }
17
- }
18
- }
19
- return m;
20
- });
21
- console.log(...formatted);
22
- }
23
- };
24
-
25
1
  // src/utils/cloudflare/cloudflare-cache.ts
26
2
  var store = {
27
3
  json: (context, cacheKey, options) => {
@@ -35,15 +11,10 @@ var store = {
35
11
  };
36
12
  var getCacheValue = async (context, cacheKey) => {
37
13
  const match = await context.cache.match(cacheKey);
38
- if (!match) {
39
- debug(`[${cacheKey.pathname}] Cache MISS!`);
40
- return void 0;
41
- }
42
- debug(`[${cacheKey.pathname}] Cache MATCH!`);
43
- return match;
14
+ return match ?? void 0;
44
15
  };
45
16
  var updateCacheValue = async (context, cacheKey, value, ttl) => {
46
- debug(
17
+ context.debug(
47
18
  "updating cache...",
48
19
  cacheKey.href,
49
20
  value,
@@ -285,9 +256,9 @@ var APIError = class extends Error {
285
256
  };
286
257
  var DEFAULT_API_HOSTNAME = "https://api.appwarden.io";
287
258
  var syncEdgeValue = async (context) => {
288
- debug(`syncing with api`);
259
+ const apiHostname = context.appwardenApiHostname ?? DEFAULT_API_HOSTNAME;
260
+ context.debug(`GET ${apiHostname}`);
289
261
  try {
290
- const apiHostname = context.appwardenApiHostname ?? DEFAULT_API_HOSTNAME;
291
262
  const response = await fetch(new URL("/v1/status/check", apiHostname), {
292
263
  method: "POST",
293
264
  headers: { "content-type": "application/json" },
@@ -313,7 +284,7 @@ var syncEdgeValue = async (context) => {
313
284
  const parsedValue = LockValue.omit({ lastCheck: true }).parse(
314
285
  result.content
315
286
  );
316
- debug(`syncing with api...DONE ${JSON.stringify(parsedValue, null, 2)}`);
287
+ context.debug(`GET ${apiHostname} succeeded`);
317
288
  await context.edgeCache.updateValue({
318
289
  ...parsedValue,
319
290
  lastCheck: Date.now()
@@ -323,19 +294,18 @@ var syncEdgeValue = async (context) => {
323
294
  }
324
295
  }
325
296
  } catch (e) {
326
- const message = "Failed to fetch from check endpoint";
297
+ const message = `GET ${apiHostname} failed`;
327
298
  console.error(
328
299
  printMessage(
329
- e instanceof APIError ? e.message : e instanceof Error ? `${message} - ${e.message}` : message
300
+ e instanceof APIError ? e.message : e instanceof Error ? `${message}: ${e.message}` : message
330
301
  )
331
302
  );
332
303
  }
333
304
  };
334
305
 
335
306
  export {
336
- debug,
337
- getErrors,
338
307
  printMessage,
308
+ getErrors,
339
309
  BooleanSchema,
340
310
  AppwardenApiTokenSchema,
341
311
  LockValue,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getErrors,
3
3
  printMessage
4
- } from "./chunk-ZBYVJ3HA.js";
4
+ } from "./chunk-GK6JL5NZ.js";
5
5
 
6
6
  // src/utils/validate-config.ts
7
7
  function validateConfig(config, schema) {
@@ -1,18 +1,19 @@
1
1
  import {
2
2
  useContentSecurityPolicy
3
- } from "../chunk-BYRGGUK7.js";
3
+ } from "../chunk-AXWJZE7U.js";
4
4
  import {
5
5
  validateConfig
6
- } from "../chunk-7AVYENM2.js";
6
+ } from "../chunk-MNGMTDH3.js";
7
7
  import {
8
8
  checkLockStatus
9
- } from "../chunk-PH77FI6C.js";
9
+ } from "../chunk-G5FWKV2Q.js";
10
10
  import {
11
11
  TEMPORARY_REDIRECT_STATUS,
12
12
  buildLockPageUrl,
13
13
  createRedirect,
14
+ debug,
14
15
  isOnLockPage
15
- } from "../chunk-SUZPTFWY.js";
16
+ } from "../chunk-EPJ4ZVO6.js";
16
17
  import {
17
18
  UseCSPInputSchema,
18
19
  isHTMLRequest
@@ -21,7 +22,7 @@ import {
21
22
  AppwardenApiTokenSchema,
22
23
  BooleanSchema,
23
24
  printMessage
24
- } from "../chunk-ZBYVJ3HA.js";
25
+ } from "../chunk-GK6JL5NZ.js";
25
26
 
26
27
  // src/schemas/astro-cloudflare.ts
27
28
  import { z } from "zod";
@@ -39,8 +40,10 @@ var AstroCloudflareConfigSchema = z.object({
39
40
  });
40
41
 
41
42
  // src/adapters/astro-cloudflare.ts
43
+ var getNowMs = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
42
44
  function createAppwardenMiddleware(configFn) {
43
45
  return async (context, next) => {
46
+ const startTime = getNowMs();
44
47
  const { request } = context;
45
48
  const locals = context.locals;
46
49
  try {
@@ -53,15 +56,23 @@ function createAppwardenMiddleware(configFn) {
53
56
  );
54
57
  return next();
55
58
  }
56
- if (!isHTMLRequest(request)) {
59
+ const config = configFn(runtime);
60
+ const debugFn = debug(config.debug ?? false);
61
+ const requestUrl = new URL(request.url);
62
+ const isHTML = isHTMLRequest(request);
63
+ debugFn(
64
+ `Appwarden middleware invoked for ${requestUrl.pathname}`,
65
+ `isHTML: ${isHTML}`
66
+ );
67
+ if (!isHTML) {
57
68
  return next();
58
69
  }
59
- const config = configFn(runtime);
60
70
  const hasError = validateConfig(config, AstroCloudflareConfigSchema);
61
71
  if (hasError) {
62
72
  return next();
63
73
  }
64
74
  if (isOnLockPage(config.lockPageSlug, request.url)) {
75
+ debugFn("Already on lock page - skipping");
65
76
  return next();
66
77
  }
67
78
  const result = await checkLockStatus({
@@ -74,6 +85,7 @@ function createAppwardenMiddleware(configFn) {
74
85
  });
75
86
  if (result.isLocked) {
76
87
  const lockPageUrl = buildLockPageUrl(config.lockPageSlug, request.url);
88
+ debugFn(`Website is locked - redirecting to ${lockPageUrl.pathname}`);
77
89
  if (context.redirect) {
78
90
  return context.redirect(
79
91
  lockPageUrl.toString(),
@@ -82,13 +94,16 @@ function createAppwardenMiddleware(configFn) {
82
94
  }
83
95
  return createRedirect(lockPageUrl);
84
96
  }
97
+ debugFn("Website is unlocked");
85
98
  const response = await next();
86
99
  if (config.contentSecurityPolicy) {
100
+ debugFn("Applying CSP middleware");
87
101
  const cspContext = {
88
102
  request,
89
103
  response,
90
- hostname: new URL(request.url).hostname,
91
- waitUntil: (fn) => runtime.ctx.waitUntil(fn)
104
+ hostname: requestUrl.hostname,
105
+ waitUntil: (fn) => runtime.ctx.waitUntil(fn),
106
+ debug: debugFn
92
107
  };
93
108
  await useContentSecurityPolicy(config.contentSecurityPolicy)(
94
109
  cspContext,
@@ -96,8 +111,12 @@ function createAppwardenMiddleware(configFn) {
96
111
  }
97
112
  // no-op next
98
113
  );
114
+ const elapsed2 = Math.round(getNowMs() - startTime);
115
+ debugFn(`Middleware executed in ${elapsed2}ms`);
99
116
  return cspContext.response;
100
117
  }
118
+ const elapsed = Math.round(getNowMs() - startTime);
119
+ debugFn(`Middleware executed in ${elapsed}ms`);
101
120
  return response;
102
121
  } catch (error) {
103
122
  if (error instanceof Response) {
@@ -1,14 +1,15 @@
1
1
  import {
2
2
  validateConfig
3
- } from "../chunk-7AVYENM2.js";
3
+ } from "../chunk-MNGMTDH3.js";
4
4
  import {
5
5
  checkLockStatus
6
- } from "../chunk-PH77FI6C.js";
6
+ } from "../chunk-G5FWKV2Q.js";
7
7
  import {
8
8
  TEMPORARY_REDIRECT_STATUS,
9
9
  buildLockPageUrl,
10
+ debug,
10
11
  isOnLockPage
11
- } from "../chunk-SUZPTFWY.js";
12
+ } from "../chunk-EPJ4ZVO6.js";
12
13
  import {
13
14
  UseCSPInputSchema,
14
15
  isHTMLRequest
@@ -17,7 +18,7 @@ import {
17
18
  AppwardenApiTokenSchema,
18
19
  BooleanSchema,
19
20
  printMessage
20
- } from "../chunk-ZBYVJ3HA.js";
21
+ } from "../chunk-GK6JL5NZ.js";
21
22
 
22
23
  // src/adapters/nextjs-cloudflare.ts
23
24
  import {
@@ -51,20 +52,30 @@ var NextJsCloudflareConfigSchema = z.object({
51
52
  });
52
53
 
53
54
  // src/adapters/nextjs-cloudflare.ts
55
+ var getNowMs = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
54
56
  function createAppwardenMiddleware(configFn) {
55
57
  return async (request, _event) => {
58
+ const startTime = getNowMs();
56
59
  try {
57
60
  const { getCloudflareContext } = await import("@opennextjs/cloudflare");
58
61
  const { env, ctx } = await getCloudflareContext();
59
- if (!isHTMLRequest(request)) {
62
+ const config = configFn({ env, ctx });
63
+ const debugFn = debug(config.debug ?? false);
64
+ const requestUrl = new URL(request.url);
65
+ const isHTML = isHTMLRequest(request);
66
+ debugFn(
67
+ `Appwarden middleware invoked for ${requestUrl.pathname}`,
68
+ `isHTML: ${isHTML}`
69
+ );
70
+ if (!isHTML) {
60
71
  return NextResponse.next();
61
72
  }
62
- const config = configFn({ env, ctx });
63
73
  const hasError = validateConfig(config, NextJsCloudflareConfigSchema);
64
74
  if (hasError) {
65
75
  return NextResponse.next();
66
76
  }
67
77
  if (isOnLockPage(config.lockPageSlug, request.url)) {
78
+ debugFn("Already on lock page - skipping");
68
79
  return NextResponse.next();
69
80
  }
70
81
  const result = await checkLockStatus({
@@ -77,10 +88,15 @@ function createAppwardenMiddleware(configFn) {
77
88
  });
78
89
  if (result.isLocked) {
79
90
  const lockPageUrl = buildLockPageUrl(config.lockPageSlug, request.url);
91
+ debugFn(`Website is locked - redirecting to ${lockPageUrl.pathname}`);
80
92
  return NextResponse.redirect(lockPageUrl, TEMPORARY_REDIRECT_STATUS);
81
93
  }
94
+ debugFn("Site is unlocked");
82
95
  if (config.contentSecurityPolicy && config.contentSecurityPolicy.mode !== "disabled") {
83
- const { makeCSPHeader } = await import("../cloudflare-LUT5TVEV.js");
96
+ debugFn(
97
+ `Applying CSP headers in ${config.contentSecurityPolicy.mode} mode`
98
+ );
99
+ const { makeCSPHeader } = await import("../cloudflare-K5EFFMNI.js");
84
100
  const [headerName, headerValue] = makeCSPHeader(
85
101
  "",
86
102
  config.contentSecurityPolicy.directives,
@@ -88,8 +104,12 @@ function createAppwardenMiddleware(configFn) {
88
104
  );
89
105
  const response = NextResponse.next();
90
106
  response.headers.set(headerName, headerValue);
107
+ const elapsed2 = Math.round(getNowMs() - startTime);
108
+ debugFn(`Middleware executed in ${elapsed2}ms`);
91
109
  return response;
92
110
  }
111
+ const elapsed = Math.round(getNowMs() - startTime);
112
+ debugFn(`Middleware executed in ${elapsed}ms`);
93
113
  return NextResponse.next();
94
114
  } catch (error) {
95
115
  console.error(
@@ -1,17 +1,18 @@
1
1
  import {
2
2
  useContentSecurityPolicy
3
- } from "../chunk-BYRGGUK7.js";
3
+ } from "../chunk-AXWJZE7U.js";
4
4
  import {
5
5
  validateConfig
6
- } from "../chunk-7AVYENM2.js";
6
+ } from "../chunk-MNGMTDH3.js";
7
7
  import {
8
8
  checkLockStatus
9
- } from "../chunk-PH77FI6C.js";
9
+ } from "../chunk-G5FWKV2Q.js";
10
10
  import {
11
11
  buildLockPageUrl,
12
12
  createRedirect,
13
+ debug,
13
14
  isOnLockPage
14
- } from "../chunk-SUZPTFWY.js";
15
+ } from "../chunk-EPJ4ZVO6.js";
15
16
  import {
16
17
  UseCSPInputSchema,
17
18
  isHTMLRequest
@@ -20,7 +21,7 @@ import {
20
21
  AppwardenApiTokenSchema,
21
22
  BooleanSchema,
22
23
  printMessage
23
- } from "../chunk-ZBYVJ3HA.js";
24
+ } from "../chunk-GK6JL5NZ.js";
24
25
 
25
26
  // src/schemas/react-router-cloudflare.ts
26
27
  import { z } from "zod";
@@ -41,6 +42,7 @@ var ReactRouterCloudflareConfigSchema = z.object({
41
42
  var cloudflareContextSymbol = /* @__PURE__ */ Symbol.for(
42
43
  "@appwarden/middleware:cloudflare"
43
44
  );
45
+ var getNowMs = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
44
46
  function getCloudflareContext(context) {
45
47
  if (context?.cloudflare) {
46
48
  return context.cloudflare;
@@ -58,6 +60,7 @@ function getCloudflareContext(context) {
58
60
  }
59
61
  function createAppwardenMiddleware(configFn) {
60
62
  return async (args, next) => {
63
+ const startTime = getNowMs();
61
64
  const { request, context } = args;
62
65
  try {
63
66
  const cloudflare = getCloudflareContext(context);
@@ -69,15 +72,23 @@ function createAppwardenMiddleware(configFn) {
69
72
  );
70
73
  return next();
71
74
  }
72
- if (!isHTMLRequest(request)) {
75
+ const config = configFn(cloudflare);
76
+ const debugFn = debug(config.debug ?? false);
77
+ const requestUrl = new URL(request.url);
78
+ const isHTML = isHTMLRequest(request);
79
+ debugFn(
80
+ `Appwarden middleware invoked for ${requestUrl.pathname}`,
81
+ `isHTML: ${isHTML}`
82
+ );
83
+ if (!isHTML) {
73
84
  return next();
74
85
  }
75
- const config = configFn(cloudflare);
76
86
  const hasError = validateConfig(config, ReactRouterCloudflareConfigSchema);
77
87
  if (hasError) {
78
88
  return next();
79
89
  }
80
90
  if (isOnLockPage(config.lockPageSlug, request.url)) {
91
+ debugFn("Already on lock page - skipping");
81
92
  return next();
82
93
  }
83
94
  const result = await checkLockStatus({
@@ -90,15 +101,19 @@ function createAppwardenMiddleware(configFn) {
90
101
  });
91
102
  if (result.isLocked) {
92
103
  const lockPageUrl = buildLockPageUrl(config.lockPageSlug, request.url);
104
+ debugFn(`Website is locked - redirecting to ${lockPageUrl.pathname}`);
93
105
  throw createRedirect(lockPageUrl);
94
106
  }
107
+ debugFn("Website is unlocked");
95
108
  const response = await next();
96
109
  if (config.contentSecurityPolicy && response instanceof Response) {
110
+ debugFn("Applying CSP middleware");
97
111
  const cspContext = {
98
112
  request,
99
113
  response,
100
- hostname: new URL(request.url).hostname,
101
- waitUntil: (fn) => cloudflare.ctx.waitUntil(fn)
114
+ hostname: requestUrl.hostname,
115
+ waitUntil: (fn) => cloudflare.ctx.waitUntil(fn),
116
+ debug: debugFn
102
117
  };
103
118
  await useContentSecurityPolicy(config.contentSecurityPolicy)(
104
119
  cspContext,
@@ -106,8 +121,12 @@ function createAppwardenMiddleware(configFn) {
106
121
  }
107
122
  // no-op next
108
123
  );
124
+ const elapsed2 = Math.round(getNowMs() - startTime);
125
+ debugFn(`Middleware executed in ${elapsed2}ms`);
109
126
  return cspContext.response;
110
127
  }
128
+ const elapsed = Math.round(getNowMs() - startTime);
129
+ debugFn(`Middleware executed in ${elapsed}ms`);
111
130
  return response;
112
131
  } catch (error) {
113
132
  if (error instanceof Response) {
@@ -1,17 +1,18 @@
1
1
  import {
2
2
  useContentSecurityPolicy
3
- } from "../chunk-BYRGGUK7.js";
3
+ } from "../chunk-AXWJZE7U.js";
4
4
  import {
5
5
  validateConfig
6
- } from "../chunk-7AVYENM2.js";
6
+ } from "../chunk-MNGMTDH3.js";
7
7
  import {
8
8
  checkLockStatus
9
- } from "../chunk-PH77FI6C.js";
9
+ } from "../chunk-G5FWKV2Q.js";
10
10
  import {
11
11
  buildLockPageUrl,
12
12
  createRedirect,
13
+ debug,
13
14
  isOnLockPage
14
- } from "../chunk-SUZPTFWY.js";
15
+ } from "../chunk-EPJ4ZVO6.js";
15
16
  import {
16
17
  UseCSPInputSchema,
17
18
  isHTMLRequest
@@ -20,7 +21,7 @@ import {
20
21
  AppwardenApiTokenSchema,
21
22
  BooleanSchema,
22
23
  printMessage
23
- } from "../chunk-ZBYVJ3HA.js";
24
+ } from "../chunk-GK6JL5NZ.js";
24
25
 
25
26
  // src/schemas/tanstack-start-cloudflare.ts
26
27
  import { z } from "zod";
@@ -38,8 +39,10 @@ var TanStackStartCloudflareConfigSchema = z.object({
38
39
  });
39
40
 
40
41
  // src/adapters/tanstack-start-cloudflare.ts
42
+ var getNowMs = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
41
43
  function createAppwardenMiddleware(configFn) {
42
44
  return async (args) => {
45
+ const startTime = getNowMs();
43
46
  const { request, next, context } = args;
44
47
  try {
45
48
  const cloudflare = context.cloudflare;
@@ -51,10 +54,17 @@ function createAppwardenMiddleware(configFn) {
51
54
  );
52
55
  return next();
53
56
  }
54
- if (!isHTMLRequest(request)) {
57
+ const config = configFn(cloudflare);
58
+ const debugFn = debug(config.debug ?? false);
59
+ const requestUrl = new URL(request.url);
60
+ const isHTML = isHTMLRequest(request);
61
+ debugFn(
62
+ `Appwarden middleware invoked for ${requestUrl.pathname}`,
63
+ `isHTML: ${isHTML}`
64
+ );
65
+ if (!isHTML) {
55
66
  return next();
56
67
  }
57
- const config = configFn(cloudflare);
58
68
  const hasError = validateConfig(
59
69
  config,
60
70
  TanStackStartCloudflareConfigSchema
@@ -63,6 +73,7 @@ function createAppwardenMiddleware(configFn) {
63
73
  return next();
64
74
  }
65
75
  if (isOnLockPage(config.lockPageSlug, request.url)) {
76
+ debugFn("Already on lock page - skipping");
66
77
  return next();
67
78
  }
68
79
  const result = await checkLockStatus({
@@ -75,15 +86,19 @@ function createAppwardenMiddleware(configFn) {
75
86
  });
76
87
  if (result.isLocked) {
77
88
  const lockPageUrl = buildLockPageUrl(config.lockPageSlug, request.url);
89
+ debugFn(`Website is locked - redirecting to ${lockPageUrl.pathname}`);
78
90
  throw createRedirect(lockPageUrl);
79
91
  }
92
+ debugFn("Website is unlocked");
80
93
  const response = await next();
81
94
  if (config.contentSecurityPolicy && response instanceof Response) {
95
+ debugFn("Applying CSP middleware");
82
96
  const cspContext = {
83
97
  request,
84
98
  response,
85
- hostname: new URL(request.url).hostname,
86
- waitUntil: (fn) => cloudflare.ctx.waitUntil(fn)
99
+ hostname: requestUrl.hostname,
100
+ waitUntil: (fn) => cloudflare.ctx.waitUntil(fn),
101
+ debug: debugFn
87
102
  };
88
103
  await useContentSecurityPolicy(config.contentSecurityPolicy)(
89
104
  cspContext,
@@ -91,8 +106,12 @@ function createAppwardenMiddleware(configFn) {
91
106
  }
92
107
  // no-op next
93
108
  );
109
+ const elapsed2 = Math.round(getNowMs() - startTime);
110
+ debugFn(`Middleware executed in ${elapsed2}ms`);
94
111
  return cspContext.response;
95
112
  }
113
+ const elapsed = Math.round(getNowMs() - startTime);
114
+ debugFn(`Middleware executed in ${elapsed}ms`);
96
115
  return response;
97
116
  } catch (error) {
98
117
  if (error instanceof Response) {
@@ -11,7 +11,7 @@ import {
11
11
  makeCSPHeader,
12
12
  store,
13
13
  syncEdgeValue
14
- } from "./chunk-ZBYVJ3HA.js";
14
+ } from "./chunk-GK6JL5NZ.js";
15
15
  export {
16
16
  CSP_KEYWORDS,
17
17
  autoQuoteCSPDirectiveArray,
package/cloudflare.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { B as Bindings } from './use-content-security-policy-DUYpyUPy.js';
2
2
  import { z } from 'zod';
3
- export { u as useContentSecurityPolicy } from './use-content-security-policy-CjlLe4yU.js';
3
+ export { u as useContentSecurityPolicy } from './use-content-security-policy-Dvc-oObb.js';
4
4
 
5
5
  declare const UseAppwardenInputSchema: z.ZodObject<{
6
6
  debug: z.ZodDefault<z.ZodEffects<z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodBoolean]>>, boolean, string | boolean | undefined>>;
package/cloudflare.js CHANGED
@@ -1,14 +1,15 @@
1
1
  import {
2
2
  useContentSecurityPolicy
3
- } from "./chunk-BYRGGUK7.js";
3
+ } from "./chunk-AXWJZE7U.js";
4
4
  import {
5
5
  checkLockStatus
6
- } from "./chunk-PH77FI6C.js";
6
+ } from "./chunk-G5FWKV2Q.js";
7
7
  import {
8
8
  buildLockPageUrl,
9
9
  createRedirect,
10
+ debug,
10
11
  isOnLockPage
11
- } from "./chunk-SUZPTFWY.js";
12
+ } from "./chunk-EPJ4ZVO6.js";
12
13
  import {
13
14
  APPWARDEN_CACHE_KEY,
14
15
  UseCSPInputSchema,
@@ -21,7 +22,7 @@ import {
21
22
  insertErrorLogs,
22
23
  printMessage,
23
24
  store
24
- } from "./chunk-ZBYVJ3HA.js";
25
+ } from "./chunk-GK6JL5NZ.js";
25
26
 
26
27
  // src/runners/appwarden-on-cloudflare.ts
27
28
  import { ZodError } from "zod";
@@ -106,7 +107,8 @@ var useAppwarden = (input) => async (context, next) => {
106
107
  const edgeCache = store.json(
107
108
  {
108
109
  serviceOrigin: requestUrl.origin,
109
- cache: await caches.open("appwarden:lock")
110
+ cache: await caches.open("appwarden:lock"),
111
+ debug: context.debug
110
112
  },
111
113
  keyName
112
114
  );
@@ -166,19 +168,29 @@ var useFetchOrigin = () => async (context, next) => {
166
168
  var appwardenOnCloudflare = (inputFn) => async (request, env, ctx) => {
167
169
  ctx.passThroughOnException();
168
170
  const requestUrl = new URL(request.url);
171
+ const parsedInput = ConfigFnInputSchema.safeParse(inputFn);
172
+ if (!parsedInput.success) {
173
+ const tempContext = {
174
+ request,
175
+ hostname: requestUrl.hostname,
176
+ response: new Response("Unhandled response"),
177
+ waitUntil: (fn) => ctx.waitUntil(fn),
178
+ debug: () => {
179
+ }
180
+ // no-op debug for error case
181
+ };
182
+ return insertErrorLogs(tempContext, parsedInput.error);
183
+ }
184
+ const input = parsedInput.data({ env, ctx, cf: {} });
169
185
  const context = {
170
186
  request,
171
187
  hostname: requestUrl.hostname,
172
188
  response: new Response("Unhandled response"),
173
189
  // https://developers.cloudflare.com/workers/observability/errors/#illegal-invocation-errors
174
- waitUntil: (fn) => ctx.waitUntil(fn)
190
+ waitUntil: (fn) => ctx.waitUntil(fn),
191
+ debug: debug(input.debug ?? false)
175
192
  };
176
- const parsedInput = ConfigFnInputSchema.safeParse(inputFn);
177
- if (!parsedInput.success) {
178
- return insertErrorLogs(context, parsedInput.error);
179
- }
180
193
  try {
181
- const input = parsedInput.data({ env, ctx, cf: {} });
182
194
  const pipeline = [useAppwarden(input), useFetchOrigin()];
183
195
  const cspConfig = input.multidomainConfig?.[requestUrl.hostname]?.contentSecurityPolicy;
184
196
  if (cspConfig) {
package/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { B as Bindings, C as CSPDirectivesSchema, a as CSPModeSchema } from './use-content-security-policy-DUYpyUPy.js';
2
- export { M as Middleware, u as useContentSecurityPolicy } from './use-content-security-policy-CjlLe4yU.js';
2
+ export { M as Middleware, u as useContentSecurityPolicy } from './use-content-security-policy-Dvc-oObb.js';
3
3
  import { z } from 'zod';
4
4
 
5
5
  declare const LOCKDOWN_TEST_EXPIRY_MS: number;
package/index.js CHANGED
@@ -5,14 +5,14 @@ import {
5
5
  } from "./chunk-QEFORWCW.js";
6
6
  import {
7
7
  useContentSecurityPolicy
8
- } from "./chunk-BYRGGUK7.js";
8
+ } from "./chunk-AXWJZE7U.js";
9
9
  import {
10
10
  APPWARDEN_CACHE_KEY,
11
11
  CSPDirectivesSchema,
12
12
  CSPModeSchema,
13
13
  LOCKDOWN_TEST_EXPIRY_MS
14
14
  } from "./chunk-HCGLR3Z3.js";
15
- import "./chunk-ZBYVJ3HA.js";
15
+ import "./chunk-GK6JL5NZ.js";
16
16
  export {
17
17
  APPWARDEN_CACHE_KEY,
18
18
  CSPDirectivesSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appwarden/middleware",
3
- "version": "3.3.0",
3
+ "version": "3.4.1",
4
4
  "description": "Instantly shut off access your app deployed on Cloudflare or Vercel",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -108,7 +108,8 @@
108
108
  "devalue@<=5.6.2": ">=5.6.3",
109
109
  "minimatch@<10.2.1": ">=10.2.1",
110
110
  "tar@<7.5.8": ">=7.5.8",
111
- "ajv@>=7.0.0-alpha.0 <8.18.0": ">=8.18.0"
111
+ "ajv@>=7.0.0-alpha.0 <8.18.0": ">=8.18.0",
112
+ "rollup@>=4.0.0 <4.59.0": ">=4.59.0"
112
113
  }
113
114
  }
114
115
  }
@@ -6,6 +6,7 @@ interface MiddlewareContext {
6
6
  request: Request;
7
7
  response: Response;
8
8
  waitUntil: ExecutionContext["waitUntil"];
9
+ debug: (...msg: any[]) => void;
9
10
  }
10
11
  type Middleware = (context: MiddlewareContext, next: () => MiddlewareNextSchemaType) => MiddlewareNextSchemaType;
11
12
  declare const MiddlewareNextSchema: z.ZodUnion<[z.ZodVoid, z.ZodNull, z.ZodPromise<z.ZodUnion<[z.ZodVoid, z.ZodNull]>>]>;
package/vercel.d.ts CHANGED
@@ -4,6 +4,7 @@ declare const AppwardenConfigSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Zo
4
4
  cacheUrl: z.ZodString;
5
5
  appwardenApiToken: z.ZodString;
6
6
  vercelApiToken: z.ZodOptional<z.ZodString>;
7
+ debug: z.ZodOptional<z.ZodBoolean>;
7
8
  lockPageSlug: z.ZodEffects<z.ZodDefault<z.ZodString>, string, string | undefined>;
8
9
  contentSecurityPolicy: z.ZodOptional<z.ZodEffects<z.ZodObject<{
9
10
  mode: z.ZodDefault<z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"disabled">, z.ZodLiteral<"report-only">, z.ZodLiteral<"enforced">]>>>;
@@ -376,6 +377,7 @@ declare const AppwardenConfigSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Zo
376
377
  lockPageSlug: string;
377
378
  appwardenApiToken: string;
378
379
  cacheUrl: string;
380
+ debug?: boolean | undefined;
379
381
  contentSecurityPolicy?: {
380
382
  mode: "disabled" | "report-only" | "enforced";
381
383
  directives?: {
@@ -411,6 +413,7 @@ declare const AppwardenConfigSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Zo
411
413
  }, {
412
414
  appwardenApiToken: string;
413
415
  cacheUrl: string;
416
+ debug?: boolean | undefined;
414
417
  lockPageSlug?: string | undefined;
415
418
  contentSecurityPolicy?: {
416
419
  mode?: "disabled" | "report-only" | "enforced" | undefined;
@@ -448,6 +451,7 @@ declare const AppwardenConfigSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Zo
448
451
  lockPageSlug: string;
449
452
  appwardenApiToken: string;
450
453
  cacheUrl: string;
454
+ debug?: boolean | undefined;
451
455
  contentSecurityPolicy?: {
452
456
  mode: "disabled" | "report-only" | "enforced";
453
457
  directives?: {
@@ -483,6 +487,7 @@ declare const AppwardenConfigSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Zo
483
487
  }, {
484
488
  appwardenApiToken: string;
485
489
  cacheUrl: string;
490
+ debug?: boolean | undefined;
486
491
  lockPageSlug?: string | undefined;
487
492
  contentSecurityPolicy?: {
488
493
  mode?: "disabled" | "report-only" | "enforced" | undefined;
@@ -520,6 +525,7 @@ declare const AppwardenConfigSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Zo
520
525
  lockPageSlug: string;
521
526
  appwardenApiToken: string;
522
527
  cacheUrl: string;
528
+ debug?: boolean | undefined;
523
529
  contentSecurityPolicy?: {
524
530
  mode: "disabled" | "report-only" | "enforced";
525
531
  directives?: {
@@ -555,6 +561,7 @@ declare const AppwardenConfigSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Zo
555
561
  }, {
556
562
  appwardenApiToken: string;
557
563
  cacheUrl: string;
564
+ debug?: boolean | undefined;
558
565
  lockPageSlug?: string | undefined;
559
566
  contentSecurityPolicy?: {
560
567
  mode?: "disabled" | "report-only" | "enforced" | undefined;
@@ -592,6 +599,7 @@ declare const AppwardenConfigSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Zo
592
599
  lockPageSlug: string;
593
600
  appwardenApiToken: string;
594
601
  cacheUrl: string;
602
+ debug?: boolean | undefined;
595
603
  contentSecurityPolicy?: {
596
604
  mode: "disabled" | "report-only" | "enforced";
597
605
  directives?: {
@@ -627,6 +635,7 @@ declare const AppwardenConfigSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Zo
627
635
  }, {
628
636
  appwardenApiToken: string;
629
637
  cacheUrl: string;
638
+ debug?: boolean | undefined;
630
639
  lockPageSlug?: string | undefined;
631
640
  contentSecurityPolicy?: {
632
641
  mode?: "disabled" | "report-only" | "enforced" | undefined;
@@ -664,6 +673,7 @@ declare const AppwardenConfigSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Zo
664
673
  lockPageSlug: string;
665
674
  appwardenApiToken: string;
666
675
  cacheUrl: string;
676
+ debug?: boolean | undefined;
667
677
  contentSecurityPolicy?: {
668
678
  mode: "disabled" | "report-only" | "enforced";
669
679
  directives?: {
@@ -699,6 +709,7 @@ declare const AppwardenConfigSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Zo
699
709
  }, {
700
710
  appwardenApiToken: string;
701
711
  cacheUrl: string;
712
+ debug?: boolean | undefined;
702
713
  lockPageSlug?: string | undefined;
703
714
  contentSecurityPolicy?: {
704
715
  mode?: "disabled" | "report-only" | "enforced" | undefined;
package/vercel.js CHANGED
@@ -4,13 +4,14 @@ import {
4
4
  } from "./chunk-QEFORWCW.js";
5
5
  import {
6
6
  validateConfig
7
- } from "./chunk-7AVYENM2.js";
7
+ } from "./chunk-MNGMTDH3.js";
8
8
  import {
9
9
  MemoryCache,
10
10
  TEMPORARY_REDIRECT_STATUS,
11
11
  buildLockPageUrl,
12
+ debug,
12
13
  isOnLockPage
13
- } from "./chunk-SUZPTFWY.js";
14
+ } from "./chunk-EPJ4ZVO6.js";
14
15
  import {
15
16
  APPWARDEN_CACHE_KEY,
16
17
  CSPDirectivesSchema,
@@ -21,10 +22,9 @@ import {
21
22
  } from "./chunk-HCGLR3Z3.js";
22
23
  import {
23
24
  LockValue,
24
- debug,
25
25
  makeCSPHeader,
26
26
  printMessage
27
- } from "./chunk-ZBYVJ3HA.js";
27
+ } from "./chunk-GK6JL5NZ.js";
28
28
 
29
29
  // src/runners/appwarden-on-vercel.ts
30
30
  import { waitUntil } from "@vercel/functions";
@@ -106,7 +106,7 @@ var APIError = class extends Error {
106
106
  }
107
107
  };
108
108
  var syncEdgeValue = async (context) => {
109
- debug(`syncing with api`);
109
+ context.debug("syncing with api");
110
110
  try {
111
111
  const response = await fetch(new URL("/v1/status/check", "https://api.appwarden.io"), {
112
112
  method: "POST",
@@ -173,6 +173,7 @@ var BaseNextJsConfigSchema = z.object({
173
173
  cacheUrl: z.string(),
174
174
  appwardenApiToken: z.string(),
175
175
  vercelApiToken: z.string().optional(),
176
+ debug: z.boolean().optional(),
176
177
  lockPageSlug: z.string().default("").transform((val) => val.replace(/^\/?/, "/")),
177
178
  contentSecurityPolicy: VercelCSPSchema.optional()
178
179
  });
@@ -224,7 +225,6 @@ var AppwardenConfigSchema = BaseNextJsConfigSchema.refine(
224
225
  });
225
226
 
226
227
  // src/runners/appwarden-on-vercel.ts
227
- debug("Instantiating isolate");
228
228
  var memoryCache = new MemoryCache({ maxSize: 1 });
229
229
  function safeWaitUntil(promise) {
230
230
  try {
@@ -239,6 +239,7 @@ function createAppwardenMiddleware(config) {
239
239
  return NextResponse.next();
240
240
  }
241
241
  const parsedConfig = AppwardenConfigSchema.parse(config);
242
+ const debugFn = debug(parsedConfig.debug ?? false);
242
243
  const applyCspHeaders = (response) => {
243
244
  const cspConfig = parsedConfig.contentSecurityPolicy;
244
245
  if (cspConfig && ["enforced", "report-only"].includes(cspConfig.mode)) {
@@ -254,26 +255,38 @@ function createAppwardenMiddleware(config) {
254
255
  try {
255
256
  const requestUrl = new URL(request.url);
256
257
  const isHTML = isHTMLRequest(request);
257
- debug({ isHTMLRequest: isHTML, url: requestUrl.pathname });
258
+ debugFn(
259
+ `Appwarden middleware invoked for ${requestUrl.pathname}`,
260
+ `isHTML: ${isHTML}`
261
+ );
258
262
  if (!isHTML) {
263
+ debugFn("Non-HTML request detected - passing through");
259
264
  return NextResponse.next();
260
265
  }
261
266
  if (!parsedConfig.lockPageSlug) {
267
+ debugFn("No lockPageSlug configured - passing through");
262
268
  return NextResponse.next();
263
269
  }
264
270
  if (isOnLockPage(parsedConfig.lockPageSlug, request.url)) {
271
+ debugFn("Already on lock page - passing through");
265
272
  return NextResponse.next();
266
273
  }
267
274
  const provider = isCacheUrl.edgeConfig(parsedConfig.cacheUrl) ? "edge-config" : "upstash";
275
+ debugFn(`Using provider: ${provider}`);
268
276
  const cacheValue = memoryCache.get(APPWARDEN_CACHE_KEY);
269
277
  const shouldRecheck = MemoryCache.isExpired(cacheValue);
270
278
  if (!cacheValue || shouldRecheck) {
279
+ debugFn(
280
+ "Memory cache miss or expired - syncing edge value in background",
281
+ `shouldRecheck=${shouldRecheck}`
282
+ );
271
283
  safeWaitUntil(
272
284
  syncEdgeValue({
273
285
  requestUrl,
274
286
  cacheUrl: parsedConfig.cacheUrl,
275
287
  appwardenApiToken: parsedConfig.appwardenApiToken,
276
- vercelApiToken: parsedConfig.vercelApiToken
288
+ vercelApiToken: parsedConfig.vercelApiToken,
289
+ debug: debugFn
277
290
  })
278
291
  );
279
292
  }
@@ -283,6 +296,9 @@ function createAppwardenMiddleware(config) {
283
296
  provider
284
297
  })).lockValue;
285
298
  if (lockValue?.isLocked) {
299
+ debugFn(
300
+ `Website is locked - redirecting to ${parsedConfig.lockPageSlug}`
301
+ );
286
302
  const lockPageUrl = buildLockPageUrl(
287
303
  parsedConfig.lockPageSlug,
288
304
  request.url
@@ -294,8 +310,13 @@ function createAppwardenMiddleware(config) {
294
310
  return applyCspHeaders(redirectResponse);
295
311
  }
296
312
  const response = NextResponse.next();
313
+ debugFn("Site is not locked - passing through");
297
314
  return applyCspHeaders(response);
298
315
  } catch (e) {
316
+ debugFn(
317
+ "Error in Appwarden Vercel middleware",
318
+ e instanceof Error ? e.message : String(e)
319
+ );
299
320
  const message = "Appwarden encountered an unknown error. Please contact Appwarden support at https://appwarden.io/join-community.";
300
321
  if (e instanceof Error) {
301
322
  if (!globalErrors.includes(e.message)) {