@appwarden/middleware 3.12.0 → 3.13.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.
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.28%25-brightgreen)
7
+ ![Test Coverage](https://img.shields.io/badge/coverage-94.3%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
@@ -1,13 +1,16 @@
1
1
  import {
2
2
  APPWARDEN_HEARTBEAT_ROUTE,
3
+ HEARTBEAT_CONFIG_ERRORS_MAX_SERIALIZED_BYTES,
3
4
  HEARTBEAT_CONFIG_ERROR_MAX_CODE_LENGTH,
4
5
  HEARTBEAT_CONFIG_ERROR_MAX_COUNT,
5
6
  HEARTBEAT_CONFIG_ERROR_MAX_MESSAGE_LENGTH,
6
7
  HEARTBEAT_CONFIG_ERROR_MAX_PATH_DEPTH,
7
8
  HEARTBEAT_CONFIG_ERROR_MAX_PATH_SEGMENT_LENGTH,
8
9
  HEARTBEAT_CONTRACT_VERSION,
9
- LOCKDOWN_TEST_EXPIRY_MS
10
- } from "./chunk-Z7P4QVEY.js";
10
+ HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES,
11
+ LOCKDOWN_TEST_EXPIRY_MS,
12
+ validateHeartbeatResponseBody
13
+ } from "./chunk-SREQAAZC.js";
11
14
 
12
15
  // src/utils/build-lock-page-url.ts
13
16
  function normalizeLockPageSlug(lockPageSlug) {
@@ -119,9 +122,14 @@ var MemoryCache = class {
119
122
  };
120
123
 
121
124
  // src/version.ts
122
- var MIDDLEWARE_VERSION = "3.11.6";
125
+ var MIDDLEWARE_VERSION = "3.12.0";
123
126
 
124
127
  // src/utils/heartbeat.ts
128
+ var DEFAULT_HEARTBEAT_CONFIG_ERROR_CODE = "custom";
129
+ var DEFAULT_HEARTBEAT_CONFIG_ERROR_MESSAGE = "Appwarden configuration validation failed";
130
+ var HEARTBEAT_CONSTRUCTION_FAILURE_BODY = JSON.stringify({
131
+ error: "appwarden_heartbeat_construction_failed"
132
+ });
125
133
  function createSanitizedMessage(code, path) {
126
134
  const fieldName = path.length > 0 ? path[path.length - 1] : "field";
127
135
  switch (code) {
@@ -159,39 +167,80 @@ function createSanitizedMessage(code, path) {
159
167
  return `Validation error for ${fieldName}`;
160
168
  }
161
169
  }
170
+ function truncateWithEllipsis(value, maxLength) {
171
+ if (value.length <= maxLength) {
172
+ return value;
173
+ }
174
+ if (maxLength <= 3) {
175
+ return value.substring(0, maxLength);
176
+ }
177
+ return value.substring(0, maxLength - 3) + "...";
178
+ }
179
+ function sanitizePathSegment(segment) {
180
+ if (typeof segment === "number" && Number.isFinite(segment)) {
181
+ return Number.isSafeInteger(segment) ? Math.max(0, segment) : Math.max(0, Math.trunc(segment));
182
+ }
183
+ return truncateWithEllipsis(
184
+ typeof segment === "string" ? segment : String(segment),
185
+ HEARTBEAT_CONFIG_ERROR_MAX_PATH_SEGMENT_LENGTH
186
+ );
187
+ }
162
188
  function sanitizePath(path) {
163
189
  const truncatedPath = path.slice(0, HEARTBEAT_CONFIG_ERROR_MAX_PATH_DEPTH);
164
- return truncatedPath.map((segment) => {
165
- if (typeof segment === "string" && segment.length > HEARTBEAT_CONFIG_ERROR_MAX_PATH_SEGMENT_LENGTH) {
166
- return segment.substring(
167
- 0,
168
- HEARTBEAT_CONFIG_ERROR_MAX_PATH_SEGMENT_LENGTH - 3
169
- ) + "...";
170
- }
171
- return segment;
172
- });
190
+ return truncatedPath.map(sanitizePathSegment);
173
191
  }
174
192
  function truncateMessage(message) {
175
- if (message.length <= HEARTBEAT_CONFIG_ERROR_MAX_MESSAGE_LENGTH) {
176
- return message;
193
+ return truncateWithEllipsis(
194
+ message,
195
+ HEARTBEAT_CONFIG_ERROR_MAX_MESSAGE_LENGTH
196
+ );
197
+ }
198
+ function truncateCode(code) {
199
+ return truncateWithEllipsis(code, HEARTBEAT_CONFIG_ERROR_MAX_CODE_LENGTH);
200
+ }
201
+ function normalizeNonEmptyString(value, fallback, truncate) {
202
+ const normalizedValue = truncate(value.trim());
203
+ if (normalizedValue.length > 0) {
204
+ return normalizedValue;
177
205
  }
178
- return message.substring(0, HEARTBEAT_CONFIG_ERROR_MAX_MESSAGE_LENGTH - 3) + "...";
206
+ return truncate(fallback);
207
+ }
208
+ function getSerializedJsonByteLength(value) {
209
+ return new TextEncoder().encode(JSON.stringify(value)).length;
210
+ }
211
+ function isConfigErrorsWithinByteBudget(configErrors) {
212
+ return getSerializedJsonByteLength(configErrors) <= HEARTBEAT_CONFIG_ERRORS_MAX_SERIALIZED_BYTES;
213
+ }
214
+ function isResponseBodyWithinByteBudget(body) {
215
+ return getSerializedJsonByteLength(body) <= HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES;
179
216
  }
180
217
  function createHeartbeatConfigError(path, code, message) {
181
218
  return {
182
219
  path: sanitizePath(path),
183
- code: code.substring(0, HEARTBEAT_CONFIG_ERROR_MAX_CODE_LENGTH),
184
- message: truncateMessage(message)
220
+ code: normalizeNonEmptyString(
221
+ code,
222
+ DEFAULT_HEARTBEAT_CONFIG_ERROR_CODE,
223
+ truncateCode
224
+ ),
225
+ message: normalizeNonEmptyString(
226
+ message,
227
+ DEFAULT_HEARTBEAT_CONFIG_ERROR_MESSAGE,
228
+ truncateMessage
229
+ )
185
230
  };
186
231
  }
187
232
  function normalizeHeartbeatConfigErrors(configErrors) {
188
- return configErrors.slice(0, HEARTBEAT_CONFIG_ERROR_MAX_COUNT).map(
233
+ const normalizedConfigErrors = configErrors.slice(0, HEARTBEAT_CONFIG_ERROR_MAX_COUNT).map(
189
234
  (configError) => createHeartbeatConfigError(
190
235
  configError.path,
191
236
  configError.code,
192
237
  configError.message
193
238
  )
194
239
  );
240
+ while (normalizedConfigErrors.length > 0 && !isConfigErrorsWithinByteBudget(normalizedConfigErrors)) {
241
+ normalizedConfigErrors.pop();
242
+ }
243
+ return normalizedConfigErrors;
195
244
  }
196
245
  function sanitizeConfigErrors(error) {
197
246
  if (!error) {
@@ -213,30 +262,48 @@ function sanitizeConfigErrors(error) {
213
262
  return errors;
214
263
  }
215
264
  function createHeartbeatResponseBody(service, configErrors = []) {
216
- return {
265
+ const normalizedConfigErrors = normalizeHeartbeatConfigErrors(configErrors);
266
+ const body = {
217
267
  app: "appwarden",
218
268
  kind: "heartbeat",
219
269
  status: "ok",
220
270
  contractVersion: HEARTBEAT_CONTRACT_VERSION,
221
271
  service,
222
272
  version: MIDDLEWARE_VERSION,
223
- configErrors: normalizeHeartbeatConfigErrors(configErrors)
273
+ configErrors: normalizedConfigErrors
224
274
  };
275
+ while (body.configErrors.length > 0 && !isResponseBodyWithinByteBudget(body)) {
276
+ body.configErrors.pop();
277
+ }
278
+ return validateHeartbeatResponseBody(body);
225
279
  }
226
- function createHeartbeatResponse(service, configErrors = []) {
227
- const body = createHeartbeatResponseBody(service, configErrors);
228
- return new Response(JSON.stringify(body), {
229
- status: 200,
280
+ function createHeartbeatConstructionFailureResponse() {
281
+ return new Response(HEARTBEAT_CONSTRUCTION_FAILURE_BODY, {
282
+ status: 500,
230
283
  headers: {
231
284
  "content-type": "application/json",
232
- "cache-control": "no-store",
233
- "x-appwarden-heartbeat": "1",
234
- "x-appwarden-contract-version": String(HEARTBEAT_CONTRACT_VERSION),
235
- "x-appwarden-service": service,
236
- "x-appwarden-version": MIDDLEWARE_VERSION
285
+ "cache-control": "no-store"
237
286
  }
238
287
  });
239
288
  }
289
+ function createHeartbeatResponse(service, configErrors = []) {
290
+ try {
291
+ const body = createHeartbeatResponseBody(service, configErrors);
292
+ return new Response(JSON.stringify(body), {
293
+ status: 200,
294
+ headers: {
295
+ "content-type": "application/json",
296
+ "cache-control": "no-store",
297
+ "x-appwarden-heartbeat": "1",
298
+ "x-appwarden-contract-version": String(HEARTBEAT_CONTRACT_VERSION),
299
+ "x-appwarden-service": service,
300
+ "x-appwarden-version": MIDDLEWARE_VERSION
301
+ }
302
+ });
303
+ } catch {
304
+ return createHeartbeatConstructionFailureResponse();
305
+ }
306
+ }
240
307
  function isHeartbeatRoute(url) {
241
308
  return url.pathname === APPWARDEN_HEARTBEAT_ROUTE;
242
309
  }
@@ -3,7 +3,7 @@ import {
3
3
  AppwardenApiTokenSchema,
4
4
  BooleanSchema,
5
5
  UseCSPInputSchema
6
- } from "./chunk-Z7P4QVEY.js";
6
+ } from "./chunk-SREQAAZC.js";
7
7
 
8
8
  // src/schemas/use-appwarden.ts
9
9
  import { z } from "zod";
@@ -2,12 +2,12 @@ import {
2
2
  MemoryCache,
3
3
  debug,
4
4
  printMessage
5
- } from "./chunk-HTSD4WPC.js";
5
+ } from "./chunk-2ZSJUEHK.js";
6
6
  import {
7
7
  APPWARDEN_CACHE_KEY,
8
8
  APPWARDEN_TEST_ROUTE,
9
9
  LockValue
10
- } from "./chunk-Z7P4QVEY.js";
10
+ } from "./chunk-SREQAAZC.js";
11
11
 
12
12
  // src/utils/cloudflare/cloudflare-cache.ts
13
13
  var store = {
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  isHTMLResponse,
3
3
  makeCSPHeader
4
- } from "./chunk-HTSD4WPC.js";
4
+ } from "./chunk-2ZSJUEHK.js";
5
5
  import {
6
6
  UseCSPInputSchema
7
- } from "./chunk-Z7P4QVEY.js";
7
+ } from "./chunk-SREQAAZC.js";
8
8
 
9
9
  // src/middlewares/use-content-security-policy.ts
10
10
  var AppendAttribute = (attribute, nonce) => ({
@@ -5,10 +5,13 @@ var globalErrors = [errors.badCacheConnection];
5
5
  var APPWARDEN_TEST_ROUTE = "/_appwarden/test";
6
6
  var APPWARDEN_HEARTBEAT_ROUTE = "/_appwarden/heartbeat";
7
7
  var HEARTBEAT_CONTRACT_VERSION = 1;
8
+ var HEARTBEAT_VERSION_MAX_LENGTH = 128;
8
9
  var HEARTBEAT_CONFIG_ERROR_MAX_COUNT = 10;
9
10
  var HEARTBEAT_CONFIG_ERROR_MAX_PATH_DEPTH = 10;
10
11
  var HEARTBEAT_CONFIG_ERROR_MAX_CODE_LENGTH = 100;
11
12
  var HEARTBEAT_CONFIG_ERROR_MAX_MESSAGE_LENGTH = 500;
13
+ var HEARTBEAT_CONFIG_ERRORS_MAX_SERIALIZED_BYTES = 12 * 1024;
14
+ var HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES = 32 * 1024;
12
15
  var HEARTBEAT_CONFIG_ERROR_MAX_PATH_SEGMENT_LENGTH = 100;
13
16
  var APPWARDEN_CACHE_KEY = "appwarden-lock";
14
17
  var HEARTBEAT_SERVICE_VALUES = [
@@ -36,13 +39,78 @@ var HEARTBEAT_SERVICES = {
36
39
  VERCEL
37
40
  };
38
41
 
42
+ // src/types/heartbeat.ts
43
+ import { z } from "zod";
44
+ function getSerializedJsonByteLength(value) {
45
+ return new TextEncoder().encode(JSON.stringify(value)).length;
46
+ }
47
+ function getHeartbeatResponseBodyByteBudgetIssue() {
48
+ return {
49
+ code: z.ZodIssueCode.custom,
50
+ message: `Serialized heartbeat response body must be at most ${HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES} bytes`,
51
+ path: []
52
+ };
53
+ }
54
+ function getHeartbeatResponseBodySerializationIssue() {
55
+ return {
56
+ code: z.ZodIssueCode.custom,
57
+ message: "Heartbeat response body must be JSON-serializable",
58
+ path: []
59
+ };
60
+ }
61
+ var HeartbeatConfigErrorPathSegmentSchema = z.union([
62
+ z.string().max(HEARTBEAT_CONFIG_ERROR_MAX_PATH_SEGMENT_LENGTH),
63
+ z.number().int().nonnegative()
64
+ ]);
65
+ var HeartbeatConfigErrorSchema = z.object({
66
+ path: z.array(HeartbeatConfigErrorPathSegmentSchema).max(HEARTBEAT_CONFIG_ERROR_MAX_PATH_DEPTH),
67
+ code: z.string().min(1).max(HEARTBEAT_CONFIG_ERROR_MAX_CODE_LENGTH),
68
+ message: z.string().min(1).max(HEARTBEAT_CONFIG_ERROR_MAX_MESSAGE_LENGTH)
69
+ }).strict();
70
+ var HeartbeatConfigErrorsSchema = z.array(HeartbeatConfigErrorSchema).max(HEARTBEAT_CONFIG_ERROR_MAX_COUNT).superRefine((configErrors, ctx) => {
71
+ if (getSerializedJsonByteLength(configErrors) > HEARTBEAT_CONFIG_ERRORS_MAX_SERIALIZED_BYTES) {
72
+ ctx.addIssue({
73
+ code: z.ZodIssueCode.custom,
74
+ message: `Serialized configErrors payload must be at most ${HEARTBEAT_CONFIG_ERRORS_MAX_SERIALIZED_BYTES} bytes`
75
+ });
76
+ }
77
+ });
78
+ var HeartbeatResponseBodySchema = z.object({
79
+ app: z.literal("appwarden"),
80
+ kind: z.literal("heartbeat"),
81
+ status: z.literal("ok"),
82
+ contractVersion: z.literal(HEARTBEAT_CONTRACT_VERSION),
83
+ service: z.enum(HEARTBEAT_SERVICE_VALUES),
84
+ version: z.string().min(1).max(HEARTBEAT_VERSION_MAX_LENGTH),
85
+ configErrors: HeartbeatConfigErrorsSchema
86
+ }).strict().superRefine((body, ctx) => {
87
+ if (getSerializedJsonByteLength(body) > HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES) {
88
+ ctx.addIssue({
89
+ code: z.ZodIssueCode.custom,
90
+ message: `Serialized heartbeat response body must be at most ${HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES} bytes`
91
+ });
92
+ }
93
+ });
94
+ function validateHeartbeatResponseBody(value) {
95
+ let serializedJsonByteLength;
96
+ try {
97
+ serializedJsonByteLength = getSerializedJsonByteLength(value);
98
+ } catch {
99
+ throw new z.ZodError([getHeartbeatResponseBodySerializationIssue()]);
100
+ }
101
+ if (serializedJsonByteLength > HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES) {
102
+ throw new z.ZodError([getHeartbeatResponseBodyByteBudgetIssue()]);
103
+ }
104
+ return HeartbeatResponseBodySchema.parse(value);
105
+ }
106
+
39
107
  // src/schemas/use-content-security-policy.ts
40
- import { z as z3 } from "zod";
108
+ import { z as z4 } from "zod";
41
109
 
42
110
  // src/types/csp.ts
43
- import { z } from "zod";
44
- var stringySchema = z.union([z.array(z.string()), z.string(), z.boolean()]);
45
- var ContentSecurityPolicySchema = z.object({
111
+ import { z as z2 } from "zod";
112
+ var stringySchema = z2.union([z2.array(z2.string()), z2.string(), z2.boolean()]);
113
+ var ContentSecurityPolicySchema = z2.object({
46
114
  "default-src": stringySchema.optional(),
47
115
  "script-src": stringySchema.optional(),
48
116
  "style-src": stringySchema.optional(),
@@ -72,8 +140,8 @@ var ContentSecurityPolicySchema = z.object({
72
140
  });
73
141
 
74
142
  // src/schemas/helpers.ts
75
- import { z as z2 } from "zod";
76
- var BoolOrStringSchema = z2.union([z2.string(), z2.boolean()]).optional();
143
+ import { z as z3 } from "zod";
144
+ var BoolOrStringSchema = z3.union([z3.string(), z3.boolean()]).optional();
77
145
  var BooleanSchema = BoolOrStringSchema.transform((val) => {
78
146
  if (val === "true" || val === true) {
79
147
  return true;
@@ -82,29 +150,29 @@ var BooleanSchema = BoolOrStringSchema.transform((val) => {
82
150
  }
83
151
  throw new Error("Invalid value");
84
152
  });
85
- var AppwardenApiTokenSchema = z2.string().refine((val) => !!val, { message: "appwardenApiToken is required" });
86
- var AppwardenApiHostnameSchema = z2.string().url({
153
+ var AppwardenApiTokenSchema = z3.string().refine((val) => !!val, { message: "appwardenApiToken is required" });
154
+ var AppwardenApiHostnameSchema = z3.string().url({
87
155
  message: "Invalid `appwardenApiHostname`. Please provide an absolute URL (e.g. https://api.appwarden.io)."
88
156
  }).refine((value) => value.startsWith("https://"), {
89
157
  message: "`appwardenApiHostname` must use the https:// scheme (e.g. https://api.appwarden.io)."
90
158
  });
91
- var LockValue = z2.object({
92
- isLocked: z2.number(),
93
- isLockedTest: z2.number(),
94
- lastCheck: z2.number()
159
+ var LockValue = z3.object({
160
+ isLocked: z3.number(),
161
+ isLockedTest: z3.number(),
162
+ lastCheck: z3.number()
95
163
  });
96
164
 
97
165
  // src/schemas/use-content-security-policy.ts
98
- var CSPDirectivesSchema = z3.union([
99
- z3.string(),
166
+ var CSPDirectivesSchema = z4.union([
167
+ z4.string(),
100
168
  ContentSecurityPolicySchema
101
169
  ]);
102
- var CSPModeSchema = z3.union([
103
- z3.literal("disabled"),
104
- z3.literal("report-only"),
105
- z3.literal("enforced")
170
+ var CSPModeSchema = z4.union([
171
+ z4.literal("disabled"),
172
+ z4.literal("report-only"),
173
+ z4.literal("enforced")
106
174
  ]);
107
- var UseCSPInputSchema = z3.object({
175
+ var UseCSPInputSchema = z4.object({
108
176
  mode: CSPModeSchema,
109
177
  directives: CSPDirectivesSchema.refine(
110
178
  (val) => {
@@ -134,6 +202,8 @@ export {
134
202
  HEARTBEAT_CONFIG_ERROR_MAX_PATH_DEPTH,
135
203
  HEARTBEAT_CONFIG_ERROR_MAX_CODE_LENGTH,
136
204
  HEARTBEAT_CONFIG_ERROR_MAX_MESSAGE_LENGTH,
205
+ HEARTBEAT_CONFIG_ERRORS_MAX_SERIALIZED_BYTES,
206
+ HEARTBEAT_RESPONSE_BODY_MAX_SERIALIZED_BYTES,
137
207
  HEARTBEAT_CONFIG_ERROR_MAX_PATH_SEGMENT_LENGTH,
138
208
  APPWARDEN_CACHE_KEY,
139
209
  HEARTBEAT_SERVICES,
@@ -141,6 +211,9 @@ export {
141
211
  AppwardenApiTokenSchema,
142
212
  AppwardenApiHostnameSchema,
143
213
  LockValue,
214
+ HeartbeatConfigErrorSchema,
215
+ HeartbeatResponseBodySchema,
216
+ validateHeartbeatResponseBody,
144
217
  CSPDirectivesSchema,
145
218
  CSPModeSchema,
146
219
  UseCSPInputSchema
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useContentSecurityPolicy
3
- } from "./chunk-ILIYP3TG.js";
3
+ } from "./chunk-J2TA6BEU.js";
4
4
 
5
5
  // src/utils/apply-content-security-policy-to-response.ts
6
6
  var applyContentSecurityPolicyToResponse = async ({
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  applyContentSecurityPolicyToResponse,
3
3
  isResponseLike
4
- } from "../chunk-M2YVPCTG.js";
5
- import "../chunk-ILIYP3TG.js";
4
+ } from "../chunk-TBSMAMWC.js";
5
+ import "../chunk-J2TA6BEU.js";
6
6
  import {
7
7
  getNowMs,
8
8
  logElapsed
9
9
  } from "../chunk-G6BMPIYD.js";
10
10
  import {
11
11
  checkLockStatus
12
- } from "../chunk-EXGUJ5XK.js";
12
+ } from "../chunk-HP5GMFH7.js";
13
13
  import {
14
14
  TEMPORARY_REDIRECT_STATUS,
15
15
  buildLockPageUrl,
@@ -22,14 +22,14 @@ import {
22
22
  isOnLockPage,
23
23
  printMessage,
24
24
  sanitizeConfigErrors
25
- } from "../chunk-HTSD4WPC.js";
25
+ } from "../chunk-2ZSJUEHK.js";
26
26
  import {
27
27
  AppwardenApiHostnameSchema,
28
28
  AppwardenApiTokenSchema,
29
29
  BooleanSchema,
30
30
  HEARTBEAT_SERVICES,
31
31
  UseCSPInputSchema
32
- } from "../chunk-Z7P4QVEY.js";
32
+ } from "../chunk-SREQAAZC.js";
33
33
 
34
34
  // src/adapters/astro-cloudflare.ts
35
35
  import { waitUntil } from "cloudflare:workers";
@@ -7,7 +7,7 @@ import {
7
7
  } from "../chunk-G6BMPIYD.js";
8
8
  import {
9
9
  checkLockStatus
10
- } from "../chunk-EXGUJ5XK.js";
10
+ } from "../chunk-HP5GMFH7.js";
11
11
  import {
12
12
  TEMPORARY_REDIRECT_STATUS,
13
13
  buildLockPageUrl,
@@ -20,14 +20,14 @@ import {
20
20
  makeCSPHeader,
21
21
  printMessage,
22
22
  sanitizeConfigErrors
23
- } from "../chunk-HTSD4WPC.js";
23
+ } from "../chunk-2ZSJUEHK.js";
24
24
  import {
25
25
  AppwardenApiHostnameSchema,
26
26
  AppwardenApiTokenSchema,
27
27
  BooleanSchema,
28
28
  HEARTBEAT_SERVICES,
29
29
  UseCSPInputSchema
30
- } from "../chunk-Z7P4QVEY.js";
30
+ } from "../chunk-SREQAAZC.js";
31
31
 
32
32
  // src/adapters/nextjs-cloudflare.ts
33
33
  import { getCloudflareContext } from "@opennextjs/cloudflare";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  applyContentSecurityPolicyToResponse,
3
3
  isResponseLike
4
- } from "../chunk-M2YVPCTG.js";
5
- import "../chunk-ILIYP3TG.js";
4
+ } from "../chunk-TBSMAMWC.js";
5
+ import "../chunk-J2TA6BEU.js";
6
6
  import {
7
7
  getNowMs,
8
8
  logElapsed
9
9
  } from "../chunk-G6BMPIYD.js";
10
10
  import {
11
11
  checkLockStatus
12
- } from "../chunk-EXGUJ5XK.js";
12
+ } from "../chunk-HP5GMFH7.js";
13
13
  import {
14
14
  buildLockPageUrl,
15
15
  createHeartbeatConfigError,
@@ -21,14 +21,14 @@ import {
21
21
  isOnLockPage,
22
22
  printMessage,
23
23
  sanitizeConfigErrors
24
- } from "../chunk-HTSD4WPC.js";
24
+ } from "../chunk-2ZSJUEHK.js";
25
25
  import {
26
26
  AppwardenApiHostnameSchema,
27
27
  AppwardenApiTokenSchema,
28
28
  BooleanSchema,
29
29
  HEARTBEAT_SERVICES,
30
30
  UseCSPInputSchema
31
- } from "../chunk-Z7P4QVEY.js";
31
+ } from "../chunk-SREQAAZC.js";
32
32
 
33
33
  // src/adapters/react-router-cloudflare.ts
34
34
  import { waitUntil } from "cloudflare:workers";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  applyContentSecurityPolicyToResponse,
3
3
  isResponseLike
4
- } from "../chunk-M2YVPCTG.js";
5
- import "../chunk-ILIYP3TG.js";
4
+ } from "../chunk-TBSMAMWC.js";
5
+ import "../chunk-J2TA6BEU.js";
6
6
  import {
7
7
  getNowMs,
8
8
  logElapsed
9
9
  } from "../chunk-G6BMPIYD.js";
10
10
  import {
11
11
  checkLockStatus
12
- } from "../chunk-EXGUJ5XK.js";
12
+ } from "../chunk-HP5GMFH7.js";
13
13
  import {
14
14
  buildLockPageUrl,
15
15
  createHeartbeatConfigError,
@@ -21,14 +21,14 @@ import {
21
21
  isOnLockPage,
22
22
  printMessage,
23
23
  sanitizeConfigErrors
24
- } from "../chunk-HTSD4WPC.js";
24
+ } from "../chunk-2ZSJUEHK.js";
25
25
  import {
26
26
  AppwardenApiHostnameSchema,
27
27
  AppwardenApiTokenSchema,
28
28
  BooleanSchema,
29
29
  HEARTBEAT_SERVICES,
30
30
  UseCSPInputSchema
31
- } from "../chunk-Z7P4QVEY.js";
31
+ } from "../chunk-SREQAAZC.js";
32
32
 
33
33
  // src/adapters/tanstack-start-cloudflare.ts
34
34
  import { waitUntil } from "cloudflare:workers";
package/cloudflare.js CHANGED
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  UseAppwardenInputSchema,
3
3
  lockPageSlugRefinement
4
- } from "./chunk-6YCNCR22.js";
4
+ } from "./chunk-GNDWHKJ5.js";
5
5
  import {
6
6
  getErrors
7
7
  } from "./chunk-NV7K5PRA.js";
8
8
  import {
9
9
  useContentSecurityPolicy
10
- } from "./chunk-ILIYP3TG.js";
10
+ } from "./chunk-J2TA6BEU.js";
11
11
  import {
12
12
  checkLockStatus
13
- } from "./chunk-EXGUJ5XK.js";
13
+ } from "./chunk-HP5GMFH7.js";
14
14
  import {
15
15
  buildLockPageUrl,
16
16
  createHeartbeatConfigError,
@@ -22,10 +22,10 @@ import {
22
22
  isOnLockPage,
23
23
  printMessage,
24
24
  sanitizeConfigErrors
25
- } from "./chunk-HTSD4WPC.js";
25
+ } from "./chunk-2ZSJUEHK.js";
26
26
  import {
27
27
  HEARTBEAT_SERVICES
28
- } from "./chunk-Z7P4QVEY.js";
28
+ } from "./chunk-SREQAAZC.js";
29
29
 
30
30
  // src/runners/appwarden-on-cloudflare.ts
31
31
  import { ZodError } from "zod";
package/index.d.ts CHANGED
@@ -3,6 +3,113 @@ import { z } from 'zod';
3
3
 
4
4
  declare const LOCKDOWN_TEST_EXPIRY_MS: number;
5
5
  declare const APPWARDEN_CACHE_KEY: "appwarden-lock";
6
+ declare const HEARTBEAT_SERVICE_VALUES: readonly ["cloudflare", "cloudflare-astro", "cloudflare-react-router", "cloudflare-tanstack-start", "cloudflare-nextjs", "vercel"];
7
+
8
+ /**
9
+ * Service identifiers for different middleware adapters.
10
+ * These are hardcoded per adapter bundle.
11
+ */
12
+ type HeartbeatService = (typeof HEARTBEAT_SERVICE_VALUES)[number];
13
+ /**
14
+ * Schema for validating heartbeat config errors.
15
+ * Ensures errors are bounded and sanitized.
16
+ */
17
+ declare const HeartbeatConfigErrorSchema: z.ZodObject<{
18
+ path: z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">;
19
+ code: z.ZodString;
20
+ message: z.ZodString;
21
+ }, "strict", z.ZodTypeAny, {
22
+ message: string;
23
+ code: string;
24
+ path: (string | number)[];
25
+ }, {
26
+ message: string;
27
+ code: string;
28
+ path: (string | number)[];
29
+ }>;
30
+ type HeartbeatConfigError = z.infer<typeof HeartbeatConfigErrorSchema>;
31
+ /**
32
+ * Schema for validating the heartbeat response body.
33
+ */
34
+ declare const HeartbeatResponseBodySchema: z.ZodEffects<z.ZodObject<{
35
+ app: z.ZodLiteral<"appwarden">;
36
+ kind: z.ZodLiteral<"heartbeat">;
37
+ status: z.ZodLiteral<"ok">;
38
+ contractVersion: z.ZodLiteral<1>;
39
+ service: z.ZodEnum<["cloudflare", "cloudflare-astro", "cloudflare-react-router", "cloudflare-tanstack-start", "cloudflare-nextjs", "vercel"]>;
40
+ version: z.ZodString;
41
+ configErrors: z.ZodEffects<z.ZodArray<z.ZodObject<{
42
+ path: z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">;
43
+ code: z.ZodString;
44
+ message: z.ZodString;
45
+ }, "strict", z.ZodTypeAny, {
46
+ message: string;
47
+ code: string;
48
+ path: (string | number)[];
49
+ }, {
50
+ message: string;
51
+ code: string;
52
+ path: (string | number)[];
53
+ }>, "many">, {
54
+ message: string;
55
+ code: string;
56
+ path: (string | number)[];
57
+ }[], {
58
+ message: string;
59
+ code: string;
60
+ path: (string | number)[];
61
+ }[]>;
62
+ }, "strict", z.ZodTypeAny, {
63
+ status: "ok";
64
+ app: "appwarden";
65
+ kind: "heartbeat";
66
+ contractVersion: 1;
67
+ service: "cloudflare" | "cloudflare-astro" | "cloudflare-react-router" | "cloudflare-tanstack-start" | "cloudflare-nextjs" | "vercel";
68
+ version: string;
69
+ configErrors: {
70
+ message: string;
71
+ code: string;
72
+ path: (string | number)[];
73
+ }[];
74
+ }, {
75
+ status: "ok";
76
+ app: "appwarden";
77
+ kind: "heartbeat";
78
+ contractVersion: 1;
79
+ service: "cloudflare" | "cloudflare-astro" | "cloudflare-react-router" | "cloudflare-tanstack-start" | "cloudflare-nextjs" | "vercel";
80
+ version: string;
81
+ configErrors: {
82
+ message: string;
83
+ code: string;
84
+ path: (string | number)[];
85
+ }[];
86
+ }>, {
87
+ status: "ok";
88
+ app: "appwarden";
89
+ kind: "heartbeat";
90
+ contractVersion: 1;
91
+ service: "cloudflare" | "cloudflare-astro" | "cloudflare-react-router" | "cloudflare-tanstack-start" | "cloudflare-nextjs" | "vercel";
92
+ version: string;
93
+ configErrors: {
94
+ message: string;
95
+ code: string;
96
+ path: (string | number)[];
97
+ }[];
98
+ }, {
99
+ status: "ok";
100
+ app: "appwarden";
101
+ kind: "heartbeat";
102
+ contractVersion: 1;
103
+ service: "cloudflare" | "cloudflare-astro" | "cloudflare-react-router" | "cloudflare-tanstack-start" | "cloudflare-nextjs" | "vercel";
104
+ version: string;
105
+ configErrors: {
106
+ message: string;
107
+ code: string;
108
+ path: (string | number)[];
109
+ }[];
110
+ }>;
111
+ type HeartbeatResponseBody = z.infer<typeof HeartbeatResponseBodySchema>;
112
+ declare function validateHeartbeatResponseBody(value: unknown): HeartbeatResponseBody;
6
113
 
7
114
  /**
8
115
  * Extracts the Edge Config ID from a valid Edge Config URL
@@ -787,4 +894,4 @@ declare const LockValue: z.ZodObject<{
787
894
  }>;
788
895
  type LockValueType = z.infer<typeof LockValue>;
789
896
 
790
- export { APPWARDEN_CACHE_KEY, LOCKDOWN_TEST_EXPIRY_MS, type LockValueType, UseAppwardenInputSchema, getEdgeConfigId, isCacheUrl, isValidCacheUrl };
897
+ export { APPWARDEN_CACHE_KEY, type HeartbeatConfigError, HeartbeatConfigErrorSchema, type HeartbeatResponseBody, HeartbeatResponseBodySchema, type HeartbeatService, LOCKDOWN_TEST_EXPIRY_MS, type LockValueType, UseAppwardenInputSchema, getEdgeConfigId, isCacheUrl, isValidCacheUrl, validateHeartbeatResponseBody };
package/index.js CHANGED
@@ -5,20 +5,26 @@ import {
5
5
  } from "./chunk-QEFORWCW.js";
6
6
  import {
7
7
  UseAppwardenInputSchema
8
- } from "./chunk-6YCNCR22.js";
8
+ } from "./chunk-GNDWHKJ5.js";
9
9
  import {
10
10
  APPWARDEN_CACHE_KEY,
11
11
  CSPDirectivesSchema,
12
12
  CSPModeSchema,
13
- LOCKDOWN_TEST_EXPIRY_MS
14
- } from "./chunk-Z7P4QVEY.js";
13
+ HeartbeatConfigErrorSchema,
14
+ HeartbeatResponseBodySchema,
15
+ LOCKDOWN_TEST_EXPIRY_MS,
16
+ validateHeartbeatResponseBody
17
+ } from "./chunk-SREQAAZC.js";
15
18
  export {
16
19
  APPWARDEN_CACHE_KEY,
17
20
  CSPDirectivesSchema,
18
21
  CSPModeSchema,
22
+ HeartbeatConfigErrorSchema,
23
+ HeartbeatResponseBodySchema,
19
24
  LOCKDOWN_TEST_EXPIRY_MS,
20
25
  UseAppwardenInputSchema,
21
26
  getEdgeConfigId,
22
27
  isCacheUrl,
23
- isValidCacheUrl
28
+ isValidCacheUrl,
29
+ validateHeartbeatResponseBody
24
30
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appwarden/middleware",
3
- "version": "3.12.0",
3
+ "version": "3.13.0",
4
4
  "description": "Instantly disable all user interaction with your app deployed on Cloudflare or Vercel",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/vercel.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  makeCSPHeader,
21
21
  printMessage,
22
22
  sanitizeConfigErrors
23
- } from "./chunk-HTSD4WPC.js";
23
+ } from "./chunk-2ZSJUEHK.js";
24
24
  import {
25
25
  APPWARDEN_CACHE_KEY,
26
26
  AppwardenApiHostnameSchema,
@@ -30,7 +30,7 @@ import {
30
30
  LockValue,
31
31
  errors,
32
32
  globalErrors
33
- } from "./chunk-Z7P4QVEY.js";
33
+ } from "./chunk-SREQAAZC.js";
34
34
 
35
35
  // src/runners/appwarden-on-vercel.ts
36
36
  import { waitUntil } from "@vercel/functions";