@arcjet/node 1.0.0-alpha.21 → 1.0.0-alpha.22

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.
Files changed (4) hide show
  1. package/index.d.ts +5 -0
  2. package/index.js +60 -2
  3. package/index.ts +78 -1
  4. package/package.json +13 -12
package/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export type RemoteClientOptions = {
15
15
  timeout?: number;
16
16
  };
17
17
  export declare function createRemoteClient(options?: RemoteClientOptions): import("@arcjet/protocol/client.js").Client;
18
+ type EventHandlerLike = (event: string, listener: (...args: any[]) => void) => void;
18
19
  export interface ArcjetNodeRequest {
19
20
  headers?: Record<string, string | string[] | undefined>;
20
21
  socket?: Partial<{
@@ -24,6 +25,10 @@ export interface ArcjetNodeRequest {
24
25
  method?: string;
25
26
  httpVersion?: string;
26
27
  url?: string;
28
+ body?: unknown;
29
+ on?: EventHandlerLike;
30
+ removeListener?: EventHandlerLike;
31
+ readable?: boolean;
27
32
  }
28
33
  /**
29
34
  * The ArcjetNode client provides a public `protect()` method to
package/index.js CHANGED
@@ -6,7 +6,22 @@ import { logLevel, baseUrl, isDevelopment, platform } from '@arcjet/env';
6
6
  import { Logger } from '@arcjet/logger';
7
7
  import { createClient } from '@arcjet/protocol/client.js';
8
8
  import { createTransport } from '@arcjet/transport';
9
+ import { readBody } from '@arcjet/body';
9
10
 
11
+ // TODO: Deduplicate with other packages
12
+ function errorMessage(err) {
13
+ if (err) {
14
+ if (typeof err === "string") {
15
+ return err;
16
+ }
17
+ if (typeof err === "object" &&
18
+ "message" in err &&
19
+ typeof err.message === "string") {
20
+ return err.message;
21
+ }
22
+ }
23
+ return "Unknown problem";
24
+ }
10
25
  function createRemoteClient(options) {
11
26
  // The base URL for the Arcjet API. Will default to the standard production
12
27
  // API unless environment variable `ARCJET_BASE_URL` is set.
@@ -17,7 +32,7 @@ function createRemoteClient(options) {
17
32
  // Transport is the HTTP client that the client uses to make requests.
18
33
  const transport = createTransport(url);
19
34
  const sdkStack = "NODEJS";
20
- const sdkVersion = "1.0.0-alpha.21";
35
+ const sdkVersion = "1.0.0-alpha.22";
21
36
  return createClient({
22
37
  transport,
23
38
  baseUrl: url,
@@ -122,7 +137,50 @@ function arcjet(options) {
122
137
  // Further investigation makes it seem like it has something to do with
123
138
  // the definition of `props` in the signature but it's hard to track down
124
139
  const req = toArcjetRequest(request, props ?? {});
125
- return aj.protect({}, req);
140
+ const getBody = async () => {
141
+ try {
142
+ // If request.body is present then the body was likely read by a package like express' `body-parser`.
143
+ // If it's not present then we attempt to read the bytes from the IncomingMessage ourselves.
144
+ if (typeof request.body === "string") {
145
+ return request.body;
146
+ }
147
+ else if (typeof request.body !== "undefined" &&
148
+ // BigInt cannot be serialized with JSON.stringify
149
+ typeof request.body !== "bigint") {
150
+ return JSON.stringify(request.body);
151
+ }
152
+ if (typeof request.on === "function" &&
153
+ typeof request.removeListener === "function") {
154
+ let expectedLength;
155
+ // TODO: This shouldn't need to build headers again but the type
156
+ // for `req` above is overly relaxed
157
+ const headers = new ArcjetHeaders(request.headers);
158
+ const expectedLengthStr = headers.get("content-length");
159
+ if (typeof expectedLengthStr === "string") {
160
+ try {
161
+ expectedLength = parseInt(expectedLengthStr, 10);
162
+ }
163
+ catch {
164
+ // If the expected length couldn't be parsed we'll just not set one.
165
+ }
166
+ }
167
+ // Awaited to throw if it rejects and we'll just return undefined
168
+ const body = await readBody(request, {
169
+ // We will process 1mb bodies
170
+ limit: 1048576,
171
+ expectedLength,
172
+ });
173
+ return body;
174
+ }
175
+ log.warn("no body available");
176
+ return;
177
+ }
178
+ catch (e) {
179
+ log.error("failed to get request body: %s", errorMessage(e));
180
+ return;
181
+ }
182
+ };
183
+ return aj.protect({ getBody }, req);
126
184
  },
127
185
  });
128
186
  }
package/index.ts CHANGED
@@ -15,10 +15,30 @@ import { baseUrl, isDevelopment, logLevel, platform } from "@arcjet/env";
15
15
  import { Logger } from "@arcjet/logger";
16
16
  import { createClient } from "@arcjet/protocol/client.js";
17
17
  import { createTransport } from "@arcjet/transport";
18
+ import { readBody } from "@arcjet/body";
18
19
 
19
20
  // Re-export all named exports from the generic SDK
20
21
  export * from "arcjet";
21
22
 
23
+ // TODO: Deduplicate with other packages
24
+ function errorMessage(err: unknown): string {
25
+ if (err) {
26
+ if (typeof err === "string") {
27
+ return err;
28
+ }
29
+
30
+ if (
31
+ typeof err === "object" &&
32
+ "message" in err &&
33
+ typeof err.message === "string"
34
+ ) {
35
+ return err.message;
36
+ }
37
+ }
38
+
39
+ return "Unknown problem";
40
+ }
41
+
22
42
  // Type helpers from https://github.com/sindresorhus/type-fest but adjusted for
23
43
  // our use.
24
44
  //
@@ -85,6 +105,11 @@ export function createRemoteClient(options?: RemoteClientOptions) {
85
105
  });
86
106
  }
87
107
 
108
+ type EventHandlerLike = (
109
+ event: string,
110
+ listener: (...args: any[]) => void,
111
+ ) => void;
112
+
88
113
  // Interface of fields that the Arcjet Node.js SDK expects on `IncomingMessage`
89
114
  // objects.
90
115
  export interface ArcjetNodeRequest {
@@ -93,6 +118,11 @@ export interface ArcjetNodeRequest {
93
118
  method?: string;
94
119
  httpVersion?: string;
95
120
  url?: string;
121
+ // Things needed for getting a body
122
+ body?: unknown;
123
+ on?: EventHandlerLike;
124
+ removeListener?: EventHandlerLike;
125
+ readable?: boolean;
96
126
  }
97
127
 
98
128
  function cookiesToString(cookies: string | string[] | undefined): string {
@@ -255,7 +285,54 @@ export default function arcjet<
255
285
  ExtraProps<Rules>
256
286
  >;
257
287
 
258
- return aj.protect({}, req);
288
+ const getBody = async () => {
289
+ try {
290
+ // If request.body is present then the body was likely read by a package like express' `body-parser`.
291
+ // If it's not present then we attempt to read the bytes from the IncomingMessage ourselves.
292
+ if (typeof request.body === "string") {
293
+ return request.body;
294
+ } else if (
295
+ typeof request.body !== "undefined" &&
296
+ // BigInt cannot be serialized with JSON.stringify
297
+ typeof request.body !== "bigint"
298
+ ) {
299
+ return JSON.stringify(request.body);
300
+ }
301
+
302
+ if (
303
+ typeof request.on === "function" &&
304
+ typeof request.removeListener === "function"
305
+ ) {
306
+ let expectedLength: number | undefined;
307
+ // TODO: This shouldn't need to build headers again but the type
308
+ // for `req` above is overly relaxed
309
+ const headers = new ArcjetHeaders(request.headers);
310
+ const expectedLengthStr = headers.get("content-length");
311
+ if (typeof expectedLengthStr === "string") {
312
+ try {
313
+ expectedLength = parseInt(expectedLengthStr, 10);
314
+ } catch {
315
+ // If the expected length couldn't be parsed we'll just not set one.
316
+ }
317
+ }
318
+ // Awaited to throw if it rejects and we'll just return undefined
319
+ const body = await readBody(request, {
320
+ // We will process 1mb bodies
321
+ limit: 1048576,
322
+ expectedLength,
323
+ });
324
+ return body;
325
+ }
326
+
327
+ log.warn("no body available");
328
+ return;
329
+ } catch (e) {
330
+ log.error("failed to get request body: %s", errorMessage(e));
331
+ return;
332
+ }
333
+ };
334
+
335
+ return aj.protect({ getBody }, req);
259
336
  },
260
337
  });
261
338
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcjet/node",
3
- "version": "1.0.0-alpha.21",
3
+ "version": "1.0.0-alpha.22",
4
4
  "description": "Arcjet SDK for Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://arcjet.com",
@@ -40,21 +40,22 @@
40
40
  "test": "NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests"
41
41
  },
42
42
  "dependencies": {
43
- "@arcjet/env": "1.0.0-alpha.21",
44
- "@arcjet/headers": "1.0.0-alpha.21",
45
- "@arcjet/ip": "1.0.0-alpha.21",
46
- "@arcjet/logger": "1.0.0-alpha.21",
47
- "@arcjet/protocol": "1.0.0-alpha.21",
48
- "@arcjet/transport": "1.0.0-alpha.21",
49
- "arcjet": "1.0.0-alpha.21"
43
+ "@arcjet/env": "1.0.0-alpha.22",
44
+ "@arcjet/headers": "1.0.0-alpha.22",
45
+ "@arcjet/ip": "1.0.0-alpha.22",
46
+ "@arcjet/logger": "1.0.0-alpha.22",
47
+ "@arcjet/protocol": "1.0.0-alpha.22",
48
+ "@arcjet/transport": "1.0.0-alpha.22",
49
+ "@arcjet/body": "1.0.0-alpha.22",
50
+ "arcjet": "1.0.0-alpha.22"
50
51
  },
51
52
  "devDependencies": {
52
- "@arcjet/eslint-config": "1.0.0-alpha.21",
53
- "@arcjet/rollup-config": "1.0.0-alpha.21",
54
- "@arcjet/tsconfig": "1.0.0-alpha.21",
53
+ "@arcjet/eslint-config": "1.0.0-alpha.22",
54
+ "@arcjet/rollup-config": "1.0.0-alpha.22",
55
+ "@arcjet/tsconfig": "1.0.0-alpha.22",
55
56
  "@jest/globals": "29.7.0",
56
57
  "@types/node": "18.18.0",
57
- "@rollup/wasm-node": "4.20.0",
58
+ "@rollup/wasm-node": "4.21.0",
58
59
  "jest": "29.7.0",
59
60
  "typescript": "5.5.4"
60
61
  },