@arcjet/analyze 1.0.0-alpha.9 → 1.0.0-beta.10
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 +53 -39
- package/index.d.ts +24 -22
- package/index.js +97 -88
- package/package.json +24 -22
- package/index.ts +0 -165
- package/wasm/arcjet.wasm.d.ts +0 -31
- package/wasm/arcjet.wasm.js +0 -38
- package/wasm/arcjet_analyze_js_req.d.ts +0 -70
- package/wasm/arcjet_analyze_js_req.js +0 -508
- package/wasm/arcjet_analyze_js_req_bg.wasm +0 -0
- package/wasm/arcjet_analyze_js_req_bg.wasm.d.ts +0 -10
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<a href="https://arcjet.com" target="_arcjet-home">
|
|
2
2
|
<picture>
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://arcjet.com/arcjet-
|
|
4
|
-
<img src="https://arcjet.com/arcjet-
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://arcjet.com/logo/arcjet-dark-lockup-voyage-horizontal.svg">
|
|
4
|
+
<img src="https://arcjet.com/logo/arcjet-light-lockup-voyage-horizontal.svg" alt="Arcjet Logo" height="128" width="auto">
|
|
5
5
|
</picture>
|
|
6
6
|
</a>
|
|
7
7
|
|
|
@@ -17,62 +17,76 @@
|
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
[Arcjet][arcjet] helps developers protect their apps in just a few lines of
|
|
20
|
-
code. Implement rate limiting, bot protection, email verification
|
|
20
|
+
code. Implement rate limiting, bot protection, email verification, and defense
|
|
21
21
|
against common attacks.
|
|
22
22
|
|
|
23
23
|
This is the [Arcjet][arcjet] local analysis engine.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
- [npm package (`@arcjet/analyze`)](https://www.npmjs.com/package/@arcjet/analyze)
|
|
26
|
+
- [GitHub source code (`analyze/` in `arcjet/arcjet-js`)](https://github.com/arcjet/arcjet-js/tree/main/analyze)
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
npm install -S @arcjet/analyze
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Example
|
|
32
|
-
|
|
33
|
-
```ts
|
|
34
|
-
import { generateFingerprint, isValidEmail } from "@arcjet/analyze";
|
|
28
|
+
## What is this?
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
This package provides functionality to analyze requests.
|
|
31
|
+
The work is done in WebAssembly but is called here from JavaScript.
|
|
32
|
+
The functionality is wrapped up into rules in our core package
|
|
33
|
+
([`arcjet`][github-arcjet-arcjet]),
|
|
34
|
+
in turn exposed from our adapters (such as `@arcjet/next`).
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
console.log("is email valid?", valid);
|
|
41
|
-
```
|
|
36
|
+
<!-- TODO(@wooorm-arcjet): link `adapters` above when the main repo is up to date. -->
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
The WebAssembly files are in
|
|
39
|
+
[`@arcjet/analyze-wasm`][github-arcjet-analyze-wasm].
|
|
40
|
+
They are separate because we need to change the import structure for each
|
|
41
|
+
runtime that we support in the bindings.
|
|
42
|
+
Separate packages lets us not duplicate code while providing a combined
|
|
43
|
+
higher-level API for calling our core functionality.
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
will run local analysis on request details before calling the Arcjet API.
|
|
45
|
+
## When should I use this?
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
This is an internal Arcjet package not designed for public use.
|
|
48
|
+
See our [_Get started_ guide][arcjet-get-started] for how to use Arcjet in your
|
|
49
|
+
application.
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
Uint8Array, which would take up ~3x the space of the Wasm file. See
|
|
53
|
-
[Better Binary Batter: Mixing Base64 and Uint8Array][wasm-base64-blog] for more
|
|
54
|
-
details.
|
|
51
|
+
## Install
|
|
55
52
|
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
This package is ESM only.
|
|
54
|
+
Install with npm in Node.js:
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
Wasm file if we don't do this.
|
|
56
|
+
```sh
|
|
57
|
+
npm install @arcjet/analyze
|
|
58
|
+
```
|
|
63
59
|
|
|
64
|
-
|
|
65
|
-
properly support consistent asset bundling techniques.
|
|
60
|
+
## Use
|
|
66
61
|
|
|
67
|
-
|
|
62
|
+
```js
|
|
63
|
+
import { generateFingerprint, isValidEmail } from "@arcjet/analyze";
|
|
68
64
|
|
|
69
|
-
|
|
65
|
+
const fingerprint = await generateFingerprint(
|
|
66
|
+
{ characteristics: [] },
|
|
67
|
+
{ ip: "127.0.0.1" },
|
|
68
|
+
);
|
|
69
|
+
console.log(fingerprint);
|
|
70
|
+
// => "fp::2::0d219da6100b99f95cf639b77e088c6df3c096aa5fd61dec5287c5cf94d5e545"
|
|
71
|
+
|
|
72
|
+
const result = await isValidEmail({}, "hello@example.com", {
|
|
73
|
+
tag: "allow-email-validation-config",
|
|
74
|
+
val: {
|
|
75
|
+
allowDomainLiteral: false,
|
|
76
|
+
allow: [],
|
|
77
|
+
requireTopLevelDomain: true,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
console.log(result);
|
|
81
|
+
// => { blocked: [], validity: "valid" }
|
|
82
|
+
```
|
|
70
83
|
|
|
71
84
|
## License
|
|
72
85
|
|
|
73
|
-
|
|
86
|
+
[Apache License, Version 2.0][apache-license] © [Arcjet Labs, Inc.][arcjet]
|
|
74
87
|
|
|
75
88
|
[arcjet]: https://arcjet.com
|
|
76
|
-
[
|
|
77
|
-
[wasm-base64-blog]: https://blobfolio.com/2019/better-binary-batter-mixing-base64-and-uint8array/
|
|
89
|
+
[arcjet-get-started]: https://docs.arcjet.com/get-started
|
|
78
90
|
[apache-license]: http://www.apache.org/licenses/LICENSE-2.0
|
|
91
|
+
[github-arcjet-analyze-wasm]: https://github.com/arcjet/arcjet-js/tree/main/analyze-wasm
|
|
92
|
+
[github-arcjet-arcjet]: https://github.com/arcjet/arcjet-js/tree/main/arcjet
|
package/index.d.ts
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type { BotConfig, BotResult, DetectedSensitiveInfoEntity, DetectSensitiveInfoFunction, EmailValidationConfig, EmailValidationResult, SensitiveInfoEntities, SensitiveInfoEntity, SensitiveInfoResult } from "@arcjet/analyze-wasm";
|
|
2
|
+
import type { ArcjetLogger } from "@arcjet/protocol";
|
|
3
|
+
interface AnalyzeContext {
|
|
4
|
+
log: ArcjetLogger;
|
|
5
|
+
characteristics: string[];
|
|
6
|
+
}
|
|
7
|
+
type AnalyzeRequest = {
|
|
8
|
+
ip?: string;
|
|
9
|
+
method?: string;
|
|
10
|
+
protocol?: string;
|
|
11
|
+
host?: string;
|
|
12
|
+
path?: string;
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
cookies?: string;
|
|
15
|
+
query?: string;
|
|
16
|
+
extra?: Record<string, string>;
|
|
17
|
+
};
|
|
18
|
+
export { type EmailValidationConfig, type BotConfig, type SensitiveInfoEntity, type DetectedSensitiveInfoEntity, };
|
|
3
19
|
/**
|
|
4
20
|
* Generate a fingerprint for the client. This is used to identify the client
|
|
5
21
|
* across multiple requests.
|
|
6
|
-
* @param
|
|
22
|
+
* @param context - The Arcjet Analyze context.
|
|
23
|
+
* @param request - The request to fingerprint.
|
|
7
24
|
* @returns A SHA-256 string fingerprint.
|
|
8
25
|
*/
|
|
9
|
-
export declare function generateFingerprint(
|
|
10
|
-
export declare function isValidEmail(candidate: string, options
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
* @property `bot_type` - What type of bot this is. This will be one of `BotType`.
|
|
15
|
-
* @property `bot_score` - A score ranging from 0 to 99 representing the degree of
|
|
16
|
-
* certainty. The higher the number within the type category, the greater the
|
|
17
|
-
* degree of certainty. E.g. `BotType.Automated` with a score of 1 means we are
|
|
18
|
-
* sure the request was made by an automated bot. `BotType.LikelyNotABot` with a
|
|
19
|
-
* score of 30 means we don't think this request was a bot, but it's lowest
|
|
20
|
-
* confidence level. `BotType.LikelyNotABot` with a score of 99 means we are
|
|
21
|
-
* almost certain this request was not a bot.
|
|
22
|
-
*/
|
|
23
|
-
export interface BotResult {
|
|
24
|
-
bot_type: number;
|
|
25
|
-
bot_score: number;
|
|
26
|
-
}
|
|
27
|
-
export declare function detectBot(headers: string, patterns_add: string, patterns_remove: string): Promise<BotResult>;
|
|
26
|
+
export declare function generateFingerprint(context: AnalyzeContext, request: AnalyzeRequest): Promise<string>;
|
|
27
|
+
export declare function isValidEmail(context: AnalyzeContext, candidate: string, options: EmailValidationConfig): Promise<EmailValidationResult>;
|
|
28
|
+
export declare function detectBot(context: AnalyzeContext, request: AnalyzeRequest, options: BotConfig): Promise<BotResult>;
|
|
29
|
+
export declare function detectSensitiveInfo(context: AnalyzeContext, candidate: string, entities: SensitiveInfoEntities, contextWindowSize: number, detect?: DetectSensitiveInfoFunction): Promise<SensitiveInfoResult>;
|
package/index.js
CHANGED
|
@@ -1,109 +1,118 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { initializeWasm } from '@arcjet/analyze-wasm';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
let wasmModule;
|
|
20
|
-
// We use `NEXT_RUNTIME` env var to DCE the Node/Browser code in the `else` block
|
|
21
|
-
// possible values: "edge" | "nodejs" | undefined
|
|
22
|
-
if (process.env["NEXT_RUNTIME"] === "edge") {
|
|
23
|
-
const mod = await import(
|
|
24
|
-
// @ts-expect-error
|
|
25
|
-
'./wasm/arcjet_analyze_js_req_bg.wasm?module');
|
|
26
|
-
wasmModule = mod.default;
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
const { wasm } = await import('./wasm/arcjet.wasm.js');
|
|
30
|
-
wasmModule = await WebAssembly.compile(await wasm());
|
|
31
|
-
}
|
|
32
|
-
await initWasm(wasmModule);
|
|
33
|
-
state = "initialized";
|
|
34
|
-
}
|
|
35
|
-
catch (err) {
|
|
36
|
-
state = "errored";
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
3
|
+
const FREE_EMAIL_PROVIDERS = [
|
|
4
|
+
"gmail.com",
|
|
5
|
+
"yahoo.com",
|
|
6
|
+
"hotmail.com",
|
|
7
|
+
"aol.com",
|
|
8
|
+
"hotmail.co.uk",
|
|
9
|
+
];
|
|
10
|
+
function noOpSensitiveInfoDetect() {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
function noOpBotsDetect() {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
function createCoreImports(detect) {
|
|
17
|
+
if (typeof detect !== "function") {
|
|
18
|
+
detect = noOpSensitiveInfoDetect;
|
|
39
19
|
}
|
|
40
20
|
return {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
21
|
+
"arcjet:js-req/bot-identifier": {
|
|
22
|
+
detect: noOpBotsDetect,
|
|
23
|
+
},
|
|
24
|
+
"arcjet:js-req/email-validator-overrides": {
|
|
25
|
+
isFreeEmail(domain) {
|
|
26
|
+
if (FREE_EMAIL_PROVIDERS.includes(domain)) {
|
|
27
|
+
return "yes";
|
|
28
|
+
}
|
|
29
|
+
return "unknown";
|
|
30
|
+
},
|
|
31
|
+
isDisposableEmail() {
|
|
32
|
+
return "unknown";
|
|
33
|
+
},
|
|
34
|
+
hasMxRecords() {
|
|
35
|
+
return "unknown";
|
|
36
|
+
},
|
|
37
|
+
hasGravatar() {
|
|
38
|
+
return "unknown";
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
// TODO(@wooorm-arcjet): figure out a test case for this with the default `detect`.
|
|
42
|
+
"arcjet:js-req/sensitive-information-identifier": {
|
|
43
|
+
detect,
|
|
44
|
+
},
|
|
45
|
+
// TODO(@wooorm-arcjet): figure out a test case for this that calls `verify`.
|
|
46
|
+
"arcjet:js-req/verify-bot": {
|
|
47
|
+
verify() {
|
|
48
|
+
return "unverifiable";
|
|
49
|
+
},
|
|
50
|
+
},
|
|
44
51
|
};
|
|
45
52
|
}
|
|
53
|
+
// TODO(@wooorm-arcjet): document what is used to fingerprint.
|
|
46
54
|
/**
|
|
47
55
|
* Generate a fingerprint for the client. This is used to identify the client
|
|
48
56
|
* across multiple requests.
|
|
49
|
-
* @param
|
|
57
|
+
* @param context - The Arcjet Analyze context.
|
|
58
|
+
* @param request - The request to fingerprint.
|
|
50
59
|
* @returns A SHA-256 string fingerprint.
|
|
51
60
|
*/
|
|
52
|
-
async function generateFingerprint(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// We HAVE to have the WasmAPI in Edge
|
|
61
|
-
const fingerprint = analyze.fingerprint(ip);
|
|
62
|
-
return fingerprint;
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
const analyze = await init();
|
|
66
|
-
if (typeof analyze !== "undefined") {
|
|
67
|
-
const fingerprint = analyze.fingerprint(ip);
|
|
68
|
-
return fingerprint;
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
// Conditional import because it's not available in some runtimes, we know
|
|
72
|
-
// it is when running on Vercel serverless functions.
|
|
73
|
-
// TODO(#180): Avoid nodejs-specific import
|
|
74
|
-
const createHash = await import('crypto');
|
|
75
|
-
// Fingerprint v1 is just the IP address
|
|
76
|
-
const fingerprintRaw = `fp_1_${ip}`;
|
|
77
|
-
const fingerprint = createHash
|
|
78
|
-
.createHash("sha256")
|
|
79
|
-
.update(fingerprintRaw)
|
|
80
|
-
.digest("hex");
|
|
81
|
-
return fingerprint;
|
|
82
|
-
}
|
|
61
|
+
async function generateFingerprint(context, request) {
|
|
62
|
+
const { log } = context;
|
|
63
|
+
const coreImports = createCoreImports();
|
|
64
|
+
const analyze = await initializeWasm(coreImports);
|
|
65
|
+
if (typeof analyze !== "undefined") {
|
|
66
|
+
return analyze.generateFingerprint(JSON.stringify(request), context.characteristics);
|
|
67
|
+
// Ignore the `else` branch as we test in places that have WebAssembly.
|
|
68
|
+
/* node:coverage ignore next 4 */
|
|
83
69
|
}
|
|
70
|
+
log.debug("WebAssembly is not supported in this runtime");
|
|
71
|
+
return "";
|
|
84
72
|
}
|
|
85
|
-
|
|
86
|
-
|
|
73
|
+
// TODO(@wooorm-arcjet): docs.
|
|
74
|
+
async function isValidEmail(context, candidate, options) {
|
|
75
|
+
const { log } = context;
|
|
76
|
+
const coreImports = createCoreImports();
|
|
77
|
+
const analyze = await initializeWasm(coreImports);
|
|
87
78
|
if (typeof analyze !== "undefined") {
|
|
88
79
|
return analyze.isValidEmail(candidate, options);
|
|
80
|
+
// Ignore the `else` branch as we test in places that have WebAssembly.
|
|
81
|
+
/* node:coverage ignore next 4 */
|
|
89
82
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
83
|
+
log.debug("WebAssembly is not supported in this runtime");
|
|
84
|
+
return { blocked: [], validity: "valid" };
|
|
94
85
|
}
|
|
95
|
-
|
|
96
|
-
|
|
86
|
+
// TODO(@wooorm-arcjet): docs.
|
|
87
|
+
async function detectBot(context, request, options) {
|
|
88
|
+
const { log } = context;
|
|
89
|
+
const coreImports = createCoreImports();
|
|
90
|
+
const analyze = await initializeWasm(coreImports);
|
|
97
91
|
if (typeof analyze !== "undefined") {
|
|
98
|
-
return analyze.detectBot(
|
|
92
|
+
return analyze.detectBot(JSON.stringify(request), options);
|
|
93
|
+
// Ignore the `else` branch as we test in places that have WebAssembly.
|
|
94
|
+
/* node:coverage ignore next 4 */
|
|
99
95
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
log.debug("WebAssembly is not supported in this runtime");
|
|
97
|
+
return { allowed: [], denied: [], spoofed: false, verified: false };
|
|
98
|
+
}
|
|
99
|
+
// TODO(@wooorm-arcjet): docs.
|
|
100
|
+
async function detectSensitiveInfo(context, candidate, entities, contextWindowSize, detect) {
|
|
101
|
+
const { log } = context;
|
|
102
|
+
const coreImports = createCoreImports(detect);
|
|
103
|
+
const analyze = await initializeWasm(coreImports);
|
|
104
|
+
if (typeof analyze !== "undefined") {
|
|
105
|
+
const skipCustomDetect = typeof detect !== "function";
|
|
106
|
+
return analyze.detectSensitiveInfo(candidate, {
|
|
107
|
+
entities,
|
|
108
|
+
contextWindowSize,
|
|
109
|
+
skipCustomDetect,
|
|
110
|
+
});
|
|
111
|
+
// Ignore the `else` branch as we test in places that have WebAssembly.
|
|
112
|
+
/* node:coverage ignore next 4 */
|
|
106
113
|
}
|
|
114
|
+
log.debug("WebAssembly is not supported in this runtime");
|
|
115
|
+
throw new Error("SENSITIVE_INFO rule failed to run because Wasm is not supported in this environment.");
|
|
107
116
|
}
|
|
108
117
|
|
|
109
|
-
export { detectBot, generateFingerprint, isValidEmail };
|
|
118
|
+
export { detectBot, detectSensitiveInfo, generateFingerprint, isValidEmail };
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcjet/analyze",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-beta.10",
|
|
4
4
|
"description": "Arcjet local analysis engine",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"analyze",
|
|
7
|
+
"arcjet",
|
|
8
|
+
"attack",
|
|
9
|
+
"limit",
|
|
10
|
+
"protect",
|
|
11
|
+
"verify"
|
|
12
|
+
],
|
|
5
13
|
"license": "Apache-2.0",
|
|
6
14
|
"homepage": "https://arcjet.com",
|
|
7
15
|
"repository": {
|
|
@@ -25,36 +33,30 @@
|
|
|
25
33
|
"main": "./index.js",
|
|
26
34
|
"types": "./index.d.ts",
|
|
27
35
|
"files": [
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"wasm/",
|
|
31
|
-
"*.js",
|
|
32
|
-
"*.d.ts",
|
|
33
|
-
"*.ts",
|
|
34
|
-
"!*.config.js"
|
|
36
|
+
"index.d.ts",
|
|
37
|
+
"index.js"
|
|
35
38
|
],
|
|
36
39
|
"scripts": {
|
|
37
|
-
"prepublishOnly": "npm run build",
|
|
38
40
|
"build": "rollup --config rollup.config.js",
|
|
39
41
|
"lint": "eslint .",
|
|
40
|
-
"
|
|
41
|
-
"test": "
|
|
42
|
+
"prepublishOnly": "npm run build",
|
|
43
|
+
"test-api": "node --test",
|
|
44
|
+
"test-coverage": "node --experimental-test-coverage --test",
|
|
45
|
+
"test": "npm run build && npm run lint && npm run test-coverage"
|
|
42
46
|
},
|
|
43
|
-
"sideEffects": [
|
|
44
|
-
"./wasm/arcjet_analyze_js_req_bg.js"
|
|
45
|
-
],
|
|
46
47
|
"dependencies": {
|
|
47
|
-
"@arcjet/
|
|
48
|
+
"@arcjet/analyze-wasm": "1.0.0-beta.10",
|
|
49
|
+
"@arcjet/protocol": "1.0.0-beta.10"
|
|
48
50
|
},
|
|
49
51
|
"devDependencies": {
|
|
50
|
-
"@arcjet/eslint-config": "1.0.0-
|
|
51
|
-
"@arcjet/rollup-config": "1.0.0-
|
|
52
|
-
"@arcjet/tsconfig": "1.0.0-
|
|
53
|
-
"@
|
|
54
|
-
"@rollup/wasm-node": "4.
|
|
52
|
+
"@arcjet/eslint-config": "1.0.0-beta.10",
|
|
53
|
+
"@arcjet/rollup-config": "1.0.0-beta.10",
|
|
54
|
+
"@arcjet/tsconfig": "1.0.0-beta.10",
|
|
55
|
+
"@bytecodealliance/jco": "1.5.0",
|
|
56
|
+
"@rollup/wasm-node": "4.46.2",
|
|
55
57
|
"@types/node": "18.18.0",
|
|
56
|
-
"
|
|
57
|
-
"typescript": "5.
|
|
58
|
+
"eslint": "9.32.0",
|
|
59
|
+
"typescript": "5.9.2"
|
|
58
60
|
},
|
|
59
61
|
"publishConfig": {
|
|
60
62
|
"access": "public",
|
package/index.ts
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import initWasm, {
|
|
2
|
-
detect_bot,
|
|
3
|
-
generate_fingerprint,
|
|
4
|
-
is_valid_email,
|
|
5
|
-
type EmailValidationConfig,
|
|
6
|
-
} from "./wasm/arcjet_analyze_js_req.js";
|
|
7
|
-
|
|
8
|
-
export { type EmailValidationConfig };
|
|
9
|
-
|
|
10
|
-
type WasmAPI = {
|
|
11
|
-
/**
|
|
12
|
-
* The WASM detect_bot function. Initialized by calling `init()`. Defined at a
|
|
13
|
-
* class level to avoid having to load the WASM module multiple times.
|
|
14
|
-
*/
|
|
15
|
-
detectBot: typeof detect_bot;
|
|
16
|
-
/**
|
|
17
|
-
* The WASM fingerprint function. Initialized by calling `init()`. Defined at
|
|
18
|
-
* a class level to avoid having to load the WASM module multiple times.
|
|
19
|
-
*/
|
|
20
|
-
fingerprint: typeof generate_fingerprint;
|
|
21
|
-
/**
|
|
22
|
-
* The WASM email validation function. Initialized by calling `init()`. Defined at
|
|
23
|
-
* a class level to avoid having to load the WASM module multiple times.
|
|
24
|
-
*/
|
|
25
|
-
isValidEmail: typeof is_valid_email;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
type WasmState = "initialized" | "uninitialized" | "unsupported" | "errored";
|
|
29
|
-
|
|
30
|
-
let state: WasmState = "uninitialized";
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Initialize the WASM module. This can be explicitly called after creating
|
|
34
|
-
* the client, but it will be called automatically if it has not been called
|
|
35
|
-
* when the first request is made. This uses a factory-style pattern because
|
|
36
|
-
* the call must be async and the constructor cannot be async.
|
|
37
|
-
*/
|
|
38
|
-
async function init(): Promise<WasmAPI | undefined> {
|
|
39
|
-
if (state === "errored" || state === "unsupported") return;
|
|
40
|
-
|
|
41
|
-
if (typeof WebAssembly === "undefined") {
|
|
42
|
-
state = "unsupported";
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (state === "uninitialized") {
|
|
47
|
-
try {
|
|
48
|
-
let wasmModule: WebAssembly.Module;
|
|
49
|
-
// We use `NEXT_RUNTIME` env var to DCE the Node/Browser code in the `else` block
|
|
50
|
-
// possible values: "edge" | "nodejs" | undefined
|
|
51
|
-
if (process.env["NEXT_RUNTIME"] === "edge") {
|
|
52
|
-
const mod = await import(
|
|
53
|
-
// @ts-expect-error
|
|
54
|
-
"./wasm/arcjet_analyze_js_req_bg.wasm?module"
|
|
55
|
-
);
|
|
56
|
-
wasmModule = mod.default;
|
|
57
|
-
} else {
|
|
58
|
-
const { wasm } = await import("./wasm/arcjet.wasm.js");
|
|
59
|
-
wasmModule = await WebAssembly.compile(await wasm());
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
await initWasm(wasmModule);
|
|
63
|
-
state = "initialized";
|
|
64
|
-
} catch (err) {
|
|
65
|
-
state = "errored";
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
detectBot: detect_bot,
|
|
72
|
-
fingerprint: generate_fingerprint,
|
|
73
|
-
isValidEmail: is_valid_email,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Generate a fingerprint for the client. This is used to identify the client
|
|
79
|
-
* across multiple requests.
|
|
80
|
-
* @param ip - The IP address of the client.
|
|
81
|
-
* @returns A SHA-256 string fingerprint.
|
|
82
|
-
*/
|
|
83
|
-
export async function generateFingerprint(ip: string): Promise<string> {
|
|
84
|
-
if (ip == "") {
|
|
85
|
-
return "";
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// We use `NEXT_RUNTIME` env var to DCE the JS fallback code in the `else` block
|
|
89
|
-
// possible values: "edge" | "nodejs" | undefined
|
|
90
|
-
if (process.env["NEXT_RUNTIME"] === "edge") {
|
|
91
|
-
const analyze = await init();
|
|
92
|
-
// We HAVE to have the WasmAPI in Edge
|
|
93
|
-
const fingerprint = analyze!.fingerprint(ip);
|
|
94
|
-
return fingerprint;
|
|
95
|
-
} else {
|
|
96
|
-
const analyze = await init();
|
|
97
|
-
if (typeof analyze !== "undefined") {
|
|
98
|
-
const fingerprint = analyze.fingerprint(ip);
|
|
99
|
-
return fingerprint;
|
|
100
|
-
} else {
|
|
101
|
-
// Conditional import because it's not available in some runtimes, we know
|
|
102
|
-
// it is when running on Vercel serverless functions.
|
|
103
|
-
// TODO(#180): Avoid nodejs-specific import
|
|
104
|
-
const createHash = await import("crypto");
|
|
105
|
-
|
|
106
|
-
// Fingerprint v1 is just the IP address
|
|
107
|
-
const fingerprintRaw = `fp_1_${ip}`;
|
|
108
|
-
|
|
109
|
-
const fingerprint = createHash
|
|
110
|
-
.createHash("sha256")
|
|
111
|
-
.update(fingerprintRaw)
|
|
112
|
-
.digest("hex");
|
|
113
|
-
return fingerprint;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export async function isValidEmail(
|
|
119
|
-
candidate: string,
|
|
120
|
-
options?: EmailValidationConfig,
|
|
121
|
-
) {
|
|
122
|
-
const analyze = await init();
|
|
123
|
-
|
|
124
|
-
if (typeof analyze !== "undefined") {
|
|
125
|
-
return analyze.isValidEmail(candidate, options);
|
|
126
|
-
} else {
|
|
127
|
-
// TODO: Fallback to JS if we don't have WASM?
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Represents the result of the bot detection.
|
|
134
|
-
*
|
|
135
|
-
* @property `bot_type` - What type of bot this is. This will be one of `BotType`.
|
|
136
|
-
* @property `bot_score` - A score ranging from 0 to 99 representing the degree of
|
|
137
|
-
* certainty. The higher the number within the type category, the greater the
|
|
138
|
-
* degree of certainty. E.g. `BotType.Automated` with a score of 1 means we are
|
|
139
|
-
* sure the request was made by an automated bot. `BotType.LikelyNotABot` with a
|
|
140
|
-
* score of 30 means we don't think this request was a bot, but it's lowest
|
|
141
|
-
* confidence level. `BotType.LikelyNotABot` with a score of 99 means we are
|
|
142
|
-
* almost certain this request was not a bot.
|
|
143
|
-
*/
|
|
144
|
-
export interface BotResult {
|
|
145
|
-
bot_type: number;
|
|
146
|
-
bot_score: number;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export async function detectBot(
|
|
150
|
-
headers: string,
|
|
151
|
-
patterns_add: string,
|
|
152
|
-
patterns_remove: string,
|
|
153
|
-
): Promise<BotResult> {
|
|
154
|
-
const analyze = await init();
|
|
155
|
-
|
|
156
|
-
if (typeof analyze !== "undefined") {
|
|
157
|
-
return analyze.detectBot(headers, patterns_add, patterns_remove);
|
|
158
|
-
} else {
|
|
159
|
-
// TODO: Fallback to JS if we don't have WASM?
|
|
160
|
-
return {
|
|
161
|
-
bot_type: 1, // NOT_ANALYZED
|
|
162
|
-
bot_score: 0,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
}
|
package/wasm/arcjet.wasm.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// @generated by wasm2module - DO NOT EDIT
|
|
2
|
-
/* tslint:disable */
|
|
3
|
-
/* eslint-disable */
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* This file contains the Arcjet Wasm binary inlined as a base64
|
|
7
|
-
* [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)
|
|
8
|
-
* with the application/wasm MIME type.
|
|
9
|
-
*
|
|
10
|
-
* This was chosen to save on storage space over inlining the file directly as
|
|
11
|
-
* a Uint8Array, which would take up ~3x the space of the Wasm file. See
|
|
12
|
-
* https://blobfolio.com/2019/better-binary-batter-mixing-base64-and-uint8array/
|
|
13
|
-
* for more details.
|
|
14
|
-
*
|
|
15
|
-
* It is then decoded into an ArrayBuffer to be used directly via WebAssembly's
|
|
16
|
-
* `compile()` function in our entry point file.
|
|
17
|
-
*
|
|
18
|
-
* This is all done to avoid trying to read or bundle the Wasm asset in various
|
|
19
|
-
* ways based on the platform or bundler a user is targeting. One example being
|
|
20
|
-
* that Next.js requires special `asyncWebAssembly` webpack config to load our
|
|
21
|
-
* Wasm file if we don't do this.
|
|
22
|
-
*
|
|
23
|
-
* In the future, we hope to do away with this workaround when all bundlers
|
|
24
|
-
* properly support consistent asset bundling techniques.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Returns an ArrayBuffer for the Arcjet Wasm binary, decoded from a base64 Data
|
|
29
|
-
* URL.
|
|
30
|
-
*/
|
|
31
|
-
export function wasm(): Promise<ArrayBuffer>;
|