Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

arcjet

Package Overview
Dependencies
Maintainers
2
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

arcjet - npm Package Compare versions

Comparing version 1.0.0-alpha.13 to 1.0.0-alpha.14

75

index.d.ts

@@ -1,2 +0,2 @@

import { ArcjetContext, ArcjetBotType, ArcjetEmailType, ArcjetMode, ArcjetStack, ArcjetDecision, ArcjetRule, ArcjetRequestDetails } from "@arcjet/protocol";
import { ArcjetContext, ArcjetBotType, ArcjetEmailType, ArcjetMode, ArcjetStack, ArcjetDecision, ArcjetRule, ArcjetRequestDetails, ArcjetLogger } from "@arcjet/protocol";
import { Transport } from "@arcjet/protocol/proto.js";

@@ -17,33 +17,9 @@ export * from "@arcjet/protocol";

export type RemoteClientOptions = {
transport?: Transport;
baseUrl?: string;
timeout?: number;
sdkStack?: ArcjetStack;
sdkVersion?: string;
transport: Transport;
baseUrl: string;
timeout: number;
sdkStack: ArcjetStack;
sdkVersion: string;
};
export declare function defaultBaseUrl(): string;
export declare function createRemoteClient(options?: RemoteClientOptions): RemoteClient;
/**
* Represents the runtime that the client is running in. This is used to bring
* in the appropriate libraries for the runtime e.g. the WASM module.
*/
export declare enum Runtime {
/**
* Running in a Node.js runtime
*/
Node = "node",
/**
* Running in a Node.js runtime without WASM support e.g. Vercel serverless
* functions
* @see
* https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js
*/
Node_NoWASM = "node_nowasm",
/**
* Running in an Edge runtime
* @see https://edge-runtime.vercel.app/
* @see https://vercel.com/docs/concepts/functions/edge-functions/edge-runtime
*/
Edge = "edge"
}
export declare function createRemoteClient(options: RemoteClientOptions): RemoteClient;
type TokenBucketRateLimitOptions<Characteristics extends readonly string[]> = {

@@ -134,24 +110,2 @@ mode?: ArcjetMode;

};
export declare class ArcjetHeaders extends Headers {
constructor(init?: HeadersInit | Record<string, string | string[] | undefined>);
/**
* Append a key and value to the headers, while filtering any key named
* `cookie`.
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/append)
*
* @param key The key to append in the headers
* @param value The value to append for the key in the headers
*/
append(key: string, value: string): void;
/**
* Set a key and value in the headers, but filtering any key named `cookie`.
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/set)
*
* @param key The key to set in the headers
* @param value The value to set for the key in the headers
*/
set(key: string, value: string): void;
}
type PlainObject = {

@@ -166,2 +120,9 @@ [key: string]: unknown;

/**
* Additional context that can be provided by adapters.
*
* Among other things, this could include the Arcjet API Key if it were only
* available in a runtime handler or IP details provided by a platform.
*/
export type ArcjetAdapterContext = Record<string, unknown>;
/**
* @property {string} ip - The IP address of the client.

@@ -225,2 +186,6 @@ * @property {string} method - The HTTP method of the request.

client?: RemoteClient;
/**
* The logger used to emit useful information from the SDK.
*/
log?: ArcjetLogger;
}

@@ -232,3 +197,2 @@ /**

export interface Arcjet<Props extends PlainObject> {
get runtime(): Runtime;
/**

@@ -238,6 +202,7 @@ * Make a decision about how to handle a request. This will analyze the

*
* @param {ArcjetAdapterContext} ctx - Additional context for this function call.
* @param {ArcjetRequest} request - Details about the {@link ArcjetRequest} that Arcjet needs to make a decision.
* @returns An {@link ArcjetDecision} indicating Arcjet's decision about the request.
*/
protect(request: ArcjetRequest<Props>): Promise<ArcjetDecision>;
protect(ctx: ArcjetAdapterContext, request: ArcjetRequest<Props>): Promise<ArcjetDecision>;
/**

@@ -244,0 +209,0 @@ * Augments the client with another rule. Useful for varying rules based on

@@ -7,3 +7,4 @@ import { ArcjetRuleResult, ArcjetEmailReason, ArcjetBotType, ArcjetErrorReason, ArcjetBotReason, ArcjetErrorDecision, ArcjetReason, ArcjetDenyDecision } from '@arcjet/protocol';

import * as duration from '@arcjet/duration';
import logger from '@arcjet/logger';
import ArcjetHeaders from '@arcjet/headers';
import { runtime } from '@arcjet/runtime';

@@ -15,5 +16,2 @@ function assert(condition, msg) {

}
function isIterable(val) {
return typeof val?.[Symbol.iterator] === "function";
}
function nowInSeconds() {

@@ -63,39 +61,2 @@ return Math.floor(Date.now() / 1000);

}
const baseUrlAllowed = [
"https://decide.arcjet.com",
"https://decide.arcjettest.com",
"https://fly.decide.arcjet.com",
"https://fly.decide.arcjettest.com",
"https://decide.arcjet.orb.local:4082",
];
function defaultBaseUrl() {
// TODO(#90): Remove this production conditional before 1.0.0
if (process.env["NODE_ENV"] === "production") {
// Use ARCJET_BASE_URL if it is set and belongs to our allowlist; otherwise
// use the hardcoded default.
if (typeof process.env["ARCJET_BASE_URL"] === "string" &&
baseUrlAllowed.includes(process.env["ARCJET_BASE_URL"])) {
return process.env["ARCJET_BASE_URL"];
}
// If we're running on fly.io, use the Arcjet Decide Service hosted on fly
// Ref: https://fly.io/docs/machines/runtime-environment/#environment-variables
if (typeof process.env["FLY_APP_NAME"] === "string" &&
process.env["FLY_APP_NAME"] !== "") {
return "https://fly.decide.arcjet.com";
}
return "https://decide.arcjet.com";
}
else {
if (process.env["ARCJET_BASE_URL"]) {
return process.env["ARCJET_BASE_URL"];
}
// If we're running on fly.io, use the Arcjet Decide Service hosted on fly
// Ref: https://fly.io/docs/machines/runtime-environment/#environment-variables
if (typeof process.env["FLY_APP_NAME"] === "string" &&
process.env["FLY_APP_NAME"] !== "") {
return "https://fly.decide.arcjet.com";
}
return "https://decide.arcjet.com";
}
}
const knownFields = [

@@ -138,18 +99,8 @@ "ip",

function createRemoteClient(options) {
// TODO(#207): Remove this when we can default the transport
if (typeof options?.transport === "undefined") {
throw new Error("Transport must be defined");
}
// The base URL for the Arcjet API. Will default to the standard production
// API unless environment variable `ARCJET_BASE_URL` is set.
// TODO(#207): This is unused until we can default the transport
const baseUrl = options?.baseUrl ?? defaultBaseUrl();
// The timeout for the Arcjet API in milliseconds. This is set to a low value
// in production so calls fail open.
const timeout = options?.timeout ?? (process.env["NODE_ENV"] === "production" ? 500 : 1000);
const sdkStack = ArcjetStackToProtocol(options?.sdkStack ?? "NODEJS");
const sdkVersion = "1.0.0-alpha.13";
const client = createPromiseClient(DecideService, options.transport);
const { transport, sdkVersion, baseUrl, timeout } = options;
const sdkStack = ArcjetStackToProtocol(options.sdkStack);
const client = createPromiseClient(DecideService, transport);
return Object.freeze({
async decide(context, details, rules) {
const { log } = context;
// Build the request object from the Protobuf generated class.

@@ -175,3 +126,3 @@ const decideRequest = new DecideRequest({

});
logger.debug("Decide request to %s", baseUrl);
log.debug("Decide request to %s", baseUrl);
const response = await client.decide(decideRequest, {

@@ -182,7 +133,7 @@ headers: { Authorization: `Bearer ${context.key}` },

const decision = ArcjetDecisionFromProtocol(response.decision);
logger.debug("Decide response", {
log.debug({
id: decision.id,
fingerprint: context.fingerprint,
path: details.path,
runtime: runtime(),
runtime: context.runtime,
ttl: decision.ttl,

@@ -192,6 +143,7 @@ conclusion: decision.conclusion,

ruleResults: decision.results,
});
}, "Decide response");
return decision;
},
report(context, details, decision, rules) {
const { log } = context;
// Build the request object from the Protobuf generated class.

@@ -217,3 +169,3 @@ const reportRequest = new ReportRequest({

});
logger.debug("Report request to %s", baseUrl);
log.debug("Report request to %s", baseUrl);
// We use the promise API directly to avoid returning a promise from this function so execution can't be paused with `await`

@@ -226,12 +178,12 @@ client

.then((response) => {
logger.debug("Report response", {
log.debug({
id: response.decision?.id,
fingerprint: context.fingerprint,
path: details.path,
runtime: runtime(),
runtime: context.runtime,
ttl: decision.ttl,
});
}, "Report response");
})
.catch((err) => {
logger.log("Encountered problem sending report: %s", errorMessage(err));
log.info("Encountered problem sending report: %s", errorMessage(err));
});

@@ -241,105 +193,2 @@ },

}
/**
* Represents the runtime that the client is running in. This is used to bring
* in the appropriate libraries for the runtime e.g. the WASM module.
*/
var Runtime;
(function (Runtime) {
/**
* Running in a Node.js runtime
*/
Runtime["Node"] = "node";
/**
* Running in a Node.js runtime without WASM support e.g. Vercel serverless
* functions
* @see
* https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js
*/
Runtime["Node_NoWASM"] = "node_nowasm";
/**
* Running in an Edge runtime
* @see https://edge-runtime.vercel.app/
* @see https://vercel.com/docs/concepts/functions/edge-functions/edge-runtime
*/
Runtime["Edge"] = "edge";
})(Runtime || (Runtime = {}));
function runtime() {
if (typeof process.env["ARCJET_RUNTIME"] === "string") {
switch (process.env["ARCJET_RUNTIME"]) {
case "edge":
return Runtime.Edge;
case "node":
return Runtime.Node;
case "node_nowasm":
return Runtime.Node_NoWASM;
default:
throw new Error("Unknown ARCJET_RUNTIME specified!");
}
}
else {
if (process.env["NEXT_RUNTIME"] === "edge") {
return Runtime.Edge;
}
else if (process.env["VERCEL"] === "1") {
return Runtime.Node_NoWASM;
}
else {
return Runtime.Node;
}
}
}
class ArcjetHeaders extends Headers {
constructor(init) {
super();
if (typeof init !== "undefined") {
if (isIterable(init)) {
for (const [key, value] of init) {
this.append(key, value);
}
}
else {
for (const [key, value] of Object.entries(init)) {
if (typeof value === "undefined") {
continue;
}
if (Array.isArray(value)) {
for (const singleValue of value) {
this.append(key, singleValue);
}
}
else {
this.append(key, value);
}
}
}
}
}
/**
* Append a key and value to the headers, while filtering any key named
* `cookie`.
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/append)
*
* @param key The key to append in the headers
* @param value The value to append for the key in the headers
*/
append(key, value) {
if (key.toLowerCase() !== "cookie") {
super.append(key, value);
}
}
/**
* Set a key and value in the headers, but filtering any key named `cookie`.
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/set)
*
* @param key The key to set in the headers
* @param value The value to set for the key in the headers
*/
set(key, value) {
if (key.toLowerCase() !== "cookie") {
super.set(key, value);
}
}
}
const Priority = {

@@ -468,3 +317,3 @@ Shield: 1,

async protect(context, { email }) {
if (await analyze.isValidEmail(email, analyzeOpts)) {
if (await analyze.isValidEmail(context, email, analyzeOpts)) {
return new ArcjetRuleResult({

@@ -555,3 +404,3 @@ ttl: 0,

}
const botResult = await analyze.detectBot(JSON.stringify(headersKV), JSON.stringify(Object.fromEntries(add.map(([key, botType]) => [
const botResult = await analyze.detectBot(context, JSON.stringify(headersKV), JSON.stringify(Object.fromEntries(add.map(([key, botType]) => [
key,

@@ -641,11 +490,16 @@ ArcjetBotTypeToProtocol(botType),

// We destructure here to make the function signature neat when viewed by consumers
const { key, rules, client } = options;
const { key, rules } = options;
const rt = runtime();
// TODO: Separate the ArcjetOptions from the SDK Options
// It is currently optional in the options so users can override it via an SDK
if (typeof options.log === "undefined") {
throw new Error("Log is required");
}
const log = options.log;
// TODO(#207): Remove this when we can default the transport so client is not required
// It is currently optional in the options so the Next SDK can override it for the user
if (typeof client === "undefined") {
if (typeof options.client === "undefined") {
throw new Error("Client is required");
}
// This is reassigned to help TypeScript's type inference, as it loses the
// type narrowing of the above `if` statement when using from inside `protect`
const remoteClient = client;
const client = options.client;
// A local cache of block decisions. Might be emphemeral per request,

@@ -658,3 +512,3 @@ // depending on the way the runtime works, but it's worth a try.

.sort((a, b) => a.priority - b.priority);
async function protect(rules, request) {
async function protect(rules, ctx, request) {
// This goes against the type definition above, but users might call

@@ -679,4 +533,4 @@ // `protect()` with no value and we don't want to crash

});
logger.time("local");
logger.time("fingerprint");
log.time?.("local");
log.time?.("fingerprint");
let ip = "";

@@ -687,14 +541,19 @@ if (typeof details.ip === "string") {

if (details.ip === "") {
logger.warn("generateFingerprint: ip is empty");
log.warn("generateFingerprint: ip is empty");
}
const fingerprint = await analyze.generateFingerprint(ip);
logger.debug("fingerprint (%s): %s", runtime(), fingerprint);
logger.timeEnd("fingerprint");
const context = { key, fingerprint };
const baseContext = {
key,
log,
...ctx,
};
const fingerprint = await analyze.generateFingerprint(baseContext, ip);
log.debug("fingerprint (%s): %s", rt, fingerprint);
log.timeEnd?.("fingerprint");
const context = { ...baseContext, fingerprint, runtime: rt };
if (rules.length < 1) {
// TODO(#607): Error if no rules configured after deprecation period
logger.warn("Calling `protect()` with no rules is deprecated. Did you mean to configure the Shield rule?");
log.warn("Calling `protect()` with no rules is deprecated. Did you mean to configure the Shield rule?");
}
if (rules.length > 10) {
logger.error("Failure running rules. Only 10 rules may be specified.");
log.error("Failure running rules. Only 10 rules may be specified.");
const decision = new ArcjetErrorDecision({

@@ -707,3 +566,3 @@ ttl: 0,

});
remoteClient.report(context, details, decision,
client.report(context, details, decision,
// No rules because we've determined they were too long and we don't

@@ -728,13 +587,13 @@ // want to try to send them to the server

// can take advantage of that.
logger.time("cache");
log.time?.("cache");
const existingBlockReason = blockCache.get(fingerprint);
logger.timeEnd("cache");
log.timeEnd?.("cache");
// If already blocked then we can async log to the API and return the
// decision immediately.
if (existingBlockReason) {
logger.timeEnd("local");
logger.debug("decide: alreadyBlocked", {
log.timeEnd?.("local");
log.debug({
fingerprint,
existingBlockReason,
});
}, "decide: alreadyBlocked");
const decision = new ArcjetDenyDecision({

@@ -746,4 +605,4 @@ ttl: blockCache.ttl(fingerprint),

});
remoteClient.report(context, details, decision, rules);
logger.debug("decide: already blocked", {
client.report(context, details, decision, rules);
log.debug({
id: decision.id,

@@ -753,4 +612,4 @@ conclusion: decision.conclusion,

reason: existingBlockReason,
runtime: runtime(),
});
runtime: rt,
}, "decide: already blocked");
return decision;

@@ -768,7 +627,7 @@ }

}
logger.time(rule.type);
log.time?.(rule.type);
try {
localRule.validate(context, details);
results[idx] = await localRule.protect(context, details);
logger.debug("Local rule result:", {
log.debug({
id: results[idx].ruleId,

@@ -778,10 +637,10 @@ rule: rule.type,

path: details.path,
runtime: runtime(),
runtime: rt,
ttl: results[idx].ttl,
conclusion: results[idx].conclusion,
reason: results[idx].reason,
});
}, "Local rule result:");
}
catch (err) {
logger.error("Failure running rule: %s due to %s", rule.type, errorMessage(err));
log.error("Failure running rule: %s due to %s", rule.type, errorMessage(err));
results[idx] = new ArcjetRuleResult({

@@ -794,5 +653,5 @@ ttl: 0,

}
logger.timeEnd(rule.type);
log.timeEnd?.(rule.type);
if (results[idx].isDenied()) {
logger.timeEnd("local");
log.timeEnd?.("local");
const decision = new ArcjetDenyDecision({

@@ -806,3 +665,3 @@ ttl: results[idx].ttl,

// request.
remoteClient.report(context, details, decision, rules);
client.report(context, details, decision, rules);
// If we're not in DRY_RUN mode, we want to cache non-zero TTL results

@@ -812,7 +671,7 @@ // and return this DENY decision.

if (results[idx].ttl > 0) {
logger.debug("Caching decision for %d seconds", decision.ttl, {
log.debug({
fingerprint,
conclusion: decision.conclusion,
reason: decision.reason,
});
}, "Caching decision for %d seconds", decision.ttl);
blockCache.set(fingerprint, decision.reason, nowInSeconds() + decision.ttl);

@@ -822,17 +681,17 @@ }

}
logger.warn(`Dry run mode is enabled for "%s" rule. Overriding decision. Decision was: %s`, rule.type, decision.conclusion);
log.warn(`Dry run mode is enabled for "%s" rule. Overriding decision. Decision was: %s`, rule.type, decision.conclusion);
}
}
logger.timeEnd("local");
logger.time("remote");
log.timeEnd?.("local");
log.time?.("remote");
// With no cached values, we take a decision remotely. We use a timeout to
// fail open.
try {
logger.time("decideApi");
const decision = await remoteClient.decide(context, details, rules);
logger.timeEnd("decideApi");
log.time?.("decideApi");
const decision = await client.decide(context, details, rules);
log.timeEnd?.("decideApi");
// If the decision is to block and we have a non-zero TTL, we cache the
// block locally
if (decision.isDenied() && decision.ttl > 0) {
logger.debug("decide: Caching block locally for %d seconds", decision.ttl);
log.debug("decide: Caching block locally for %d seconds", decision.ttl);
blockCache.set(fingerprint, decision.reason, nowInSeconds() + decision.ttl);

@@ -843,3 +702,3 @@ }

catch (err) {
logger.error("Encountered problem getting remote decision: %s", errorMessage(err));
log.error("Encountered problem getting remote decision: %s", errorMessage(err));
const decision = new ArcjetErrorDecision({

@@ -850,7 +709,7 @@ ttl: 0,

});
remoteClient.report(context, details, decision, rules);
client.report(context, details, decision, rules);
return decision;
}
finally {
logger.timeEnd("remote");
log.timeEnd?.("remote");
}

@@ -862,10 +721,7 @@ }

return Object.freeze({
get runtime() {
return runtime();
},
withRule(rule) {
return withRule(rule);
},
async protect(request) {
return protect(rules, request);
async protect(ctx, request) {
return protect(rules, ctx, request);
},

@@ -875,10 +731,7 @@ });

return Object.freeze({
get runtime() {
return runtime();
},
withRule(rule) {
return withRule(rule);
},
async protect(request) {
return protect(rootRules, request);
async protect(ctx, request) {
return protect(rootRules, ctx, request);
},

@@ -888,2 +741,2 @@ });

export { ArcjetHeaders, Runtime, createRemoteClient, arcjet as default, defaultBaseUrl, detectBot, fixedWindow, protectSignup, rateLimit, shield, slidingWindow, tokenBucket, validateEmail };
export { createRemoteClient, arcjet as default, detectBot, fixedWindow, protectSignup, rateLimit, shield, slidingWindow, tokenBucket, validateEmail };

@@ -24,2 +24,3 @@ import {

ArcjetShieldRule,
ArcjetLogger,
} from "@arcjet/protocol";

@@ -43,3 +44,4 @@ import {

import * as duration from "@arcjet/duration";
import logger from "@arcjet/logger";
import ArcjetHeaders from "@arcjet/headers";
import { runtime } from "@arcjet/runtime";

@@ -54,6 +56,2 @@ export * from "@arcjet/protocol";

function isIterable(val: any): val is Iterable<any> {
return typeof val?.[Symbol.iterator] === "function";
}
function nowInSeconds(): number {

@@ -200,57 +198,9 @@ return Math.floor(Date.now() / 1000);

export type RemoteClientOptions = {
transport?: Transport;
baseUrl?: string;
timeout?: number;
sdkStack?: ArcjetStack;
sdkVersion?: string;
transport: Transport;
baseUrl: string;
timeout: number;
sdkStack: ArcjetStack;
sdkVersion: string;
};
const baseUrlAllowed = [
"https://decide.arcjet.com",
"https://decide.arcjettest.com",
"https://fly.decide.arcjet.com",
"https://fly.decide.arcjettest.com",
"https://decide.arcjet.orb.local:4082",
];
export function defaultBaseUrl() {
// TODO(#90): Remove this production conditional before 1.0.0
if (process.env["NODE_ENV"] === "production") {
// Use ARCJET_BASE_URL if it is set and belongs to our allowlist; otherwise
// use the hardcoded default.
if (
typeof process.env["ARCJET_BASE_URL"] === "string" &&
baseUrlAllowed.includes(process.env["ARCJET_BASE_URL"])
) {
return process.env["ARCJET_BASE_URL"];
}
// If we're running on fly.io, use the Arcjet Decide Service hosted on fly
// Ref: https://fly.io/docs/machines/runtime-environment/#environment-variables
if (
typeof process.env["FLY_APP_NAME"] === "string" &&
process.env["FLY_APP_NAME"] !== ""
) {
return "https://fly.decide.arcjet.com";
}
return "https://decide.arcjet.com";
} else {
if (process.env["ARCJET_BASE_URL"]) {
return process.env["ARCJET_BASE_URL"];
}
// If we're running on fly.io, use the Arcjet Decide Service hosted on fly
// Ref: https://fly.io/docs/machines/runtime-environment/#environment-variables
if (
typeof process.env["FLY_APP_NAME"] === "string" &&
process.env["FLY_APP_NAME"] !== ""
) {
return "https://fly.decide.arcjet.com";
}
return "https://decide.arcjet.com";
}
}
const knownFields = [

@@ -301,25 +251,9 @@ "ip",

export function createRemoteClient(
options?: RemoteClientOptions,
): RemoteClient {
// TODO(#207): Remove this when we can default the transport
if (typeof options?.transport === "undefined") {
throw new Error("Transport must be defined");
}
export function createRemoteClient(options: RemoteClientOptions): RemoteClient {
const { transport, sdkVersion, baseUrl, timeout } = options;
// The base URL for the Arcjet API. Will default to the standard production
// API unless environment variable `ARCJET_BASE_URL` is set.
// TODO(#207): This is unused until we can default the transport
const baseUrl = options?.baseUrl ?? defaultBaseUrl();
const sdkStack = ArcjetStackToProtocol(options.sdkStack);
// The timeout for the Arcjet API in milliseconds. This is set to a low value
// in production so calls fail open.
const timeout =
options?.timeout ?? (process.env["NODE_ENV"] === "production" ? 500 : 1000);
const client = createPromiseClient(DecideService, transport);
const sdkStack = ArcjetStackToProtocol(options?.sdkStack ?? "NODEJS");
const sdkVersion = "__ARCJET_SDK_VERSION__";
const client = createPromiseClient(DecideService, options.transport);
return Object.freeze({

@@ -331,2 +265,4 @@ async decide(

): Promise<ArcjetDecision> {
const { log } = context;
// Build the request object from the Protobuf generated class.

@@ -353,3 +289,3 @@ const decideRequest = new DecideRequest({

logger.debug("Decide request to %s", baseUrl);
log.debug("Decide request to %s", baseUrl);

@@ -363,12 +299,15 @@ const response = await client.decide(decideRequest, {

logger.debug("Decide response", {
id: decision.id,
fingerprint: context.fingerprint,
path: details.path,
runtime: runtime(),
ttl: decision.ttl,
conclusion: decision.conclusion,
reason: decision.reason,
ruleResults: decision.results,
});
log.debug(
{
id: decision.id,
fingerprint: context.fingerprint,
path: details.path,
runtime: context.runtime,
ttl: decision.ttl,
conclusion: decision.conclusion,
reason: decision.reason,
ruleResults: decision.results,
},
"Decide response",
);

@@ -384,2 +323,4 @@ return decision;

): void {
const { log } = context;
// Build the request object from the Protobuf generated class.

@@ -406,3 +347,3 @@ const reportRequest = new ReportRequest({

logger.debug("Report request to %s", baseUrl);
log.debug("Report request to %s", baseUrl);

@@ -416,15 +357,15 @@ // We use the promise API directly to avoid returning a promise from this function so execution can't be paused with `await`

.then((response) => {
logger.debug("Report response", {
id: response.decision?.id,
fingerprint: context.fingerprint,
path: details.path,
runtime: runtime(),
ttl: decision.ttl,
});
log.debug(
{
id: response.decision?.id,
fingerprint: context.fingerprint,
path: details.path,
runtime: context.runtime,
ttl: decision.ttl,
},
"Report response",
);
})
.catch((err: unknown) => {
logger.log(
"Encountered problem sending report: %s",
errorMessage(err),
);
log.info("Encountered problem sending report: %s", errorMessage(err));
});

@@ -435,49 +376,2 @@ },

/**
* Represents the runtime that the client is running in. This is used to bring
* in the appropriate libraries for the runtime e.g. the WASM module.
*/
export enum Runtime {
/**
* Running in a Node.js runtime
*/
Node = "node",
/**
* Running in a Node.js runtime without WASM support e.g. Vercel serverless
* functions
* @see
* https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js
*/
Node_NoWASM = "node_nowasm",
/**
* Running in an Edge runtime
* @see https://edge-runtime.vercel.app/
* @see https://vercel.com/docs/concepts/functions/edge-functions/edge-runtime
*/
Edge = "edge",
}
function runtime(): Runtime {
if (typeof process.env["ARCJET_RUNTIME"] === "string") {
switch (process.env["ARCJET_RUNTIME"]) {
case "edge":
return Runtime.Edge;
case "node":
return Runtime.Node;
case "node_nowasm":
return Runtime.Node_NoWASM;
default:
throw new Error("Unknown ARCJET_RUNTIME specified!");
}
} else {
if (process.env["NEXT_RUNTIME"] === "edge") {
return Runtime.Edge;
} else if (process.env["VERCEL"] === "1") {
return Runtime.Node_NoWASM;
} else {
return Runtime.Node;
}
}
}
type TokenBucketRateLimitOptions<Characteristics extends readonly string[]> = {

@@ -572,61 +466,2 @@ mode?: ArcjetMode;

export class ArcjetHeaders extends Headers {
constructor(
init?: HeadersInit | Record<string, string | string[] | undefined>,
) {
super();
if (typeof init !== "undefined") {
if (isIterable(init)) {
for (const [key, value] of init) {
this.append(key, value);
}
} else {
for (const [key, value] of Object.entries(
init as Record<string, string | string[] | undefined>,
)) {
if (typeof value === "undefined") {
continue;
}
if (Array.isArray(value)) {
for (const singleValue of value) {
this.append(key, singleValue);
}
} else {
this.append(key, value);
}
}
}
}
}
/**
* Append a key and value to the headers, while filtering any key named
* `cookie`.
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/append)
*
* @param key The key to append in the headers
* @param value The value to append for the key in the headers
*/
append(key: string, value: string): void {
if (key.toLowerCase() !== "cookie") {
super.append(key, value);
}
}
/**
* Set a key and value in the headers, but filtering any key named `cookie`.
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/set)
*
* @param key The key to set in the headers
* @param value The value to set for the key in the headers
*/
set(key: string, value: string): void {
if (key.toLowerCase() !== "cookie") {
super.set(key, value);
}
}
}
const Priority = {

@@ -681,2 +516,10 @@ Shield: 1,

/**
* Additional context that can be provided by adapters.
*
* Among other things, this could include the Arcjet API Key if it were only
* available in a runtime handler or IP details provided by a platform.
*/
export type ArcjetAdapterContext = Record<string, unknown>;
/**
* @property {string} ip - The IP address of the client.

@@ -895,3 +738,3 @@ * @property {string} method - The HTTP method of the request.

): Promise<ArcjetRuleResult> {
if (await analyze.isValidEmail(email, analyzeOpts)) {
if (await analyze.isValidEmail(context, email, analyzeOpts)) {
return new ArcjetRuleResult({

@@ -1003,2 +846,3 @@ ttl: 0,

const botResult = await analyze.detectBot(
context,
JSON.stringify(headersKV),

@@ -1133,2 +977,6 @@ JSON.stringify(

client?: RemoteClient;
/**
* The logger used to emit useful information from the SDK.
*/
log?: ArcjetLogger;
}

@@ -1141,3 +989,2 @@

export interface Arcjet<Props extends PlainObject> {
get runtime(): Runtime;
/**

@@ -1147,6 +994,10 @@ * Make a decision about how to handle a request. This will analyze the

*
* @param {ArcjetAdapterContext} ctx - Additional context for this function call.
* @param {ArcjetRequest} request - Details about the {@link ArcjetRequest} that Arcjet needs to make a decision.
* @returns An {@link ArcjetDecision} indicating Arcjet's decision about the request.
*/
protect(request: ArcjetRequest<Props>): Promise<ArcjetDecision>;
protect(
ctx: ArcjetAdapterContext,
request: ArcjetRequest<Props>,
): Promise<ArcjetDecision>;

@@ -1174,12 +1025,19 @@ /**

// We destructure here to make the function signature neat when viewed by consumers
const { key, rules, client } = options;
const { key, rules } = options;
const rt = runtime();
// TODO: Separate the ArcjetOptions from the SDK Options
// It is currently optional in the options so users can override it via an SDK
if (typeof options.log === "undefined") {
throw new Error("Log is required");
}
const log = options.log;
// TODO(#207): Remove this when we can default the transport so client is not required
// It is currently optional in the options so the Next SDK can override it for the user
if (typeof client === "undefined") {
if (typeof options.client === "undefined") {
throw new Error("Client is required");
}
// This is reassigned to help TypeScript's type inference, as it loses the
// type narrowing of the above `if` statement when using from inside `protect`
const remoteClient = client;
const client = options.client;

@@ -1197,2 +1055,3 @@ // A local cache of block decisions. Might be emphemeral per request,

rules: ArcjetRule[],
ctx: ArcjetAdapterContext,
request: ArcjetRequest<Props>,

@@ -1221,5 +1080,5 @@ ) {

logger.time("local");
log.time?.("local");
logger.time("fingerprint");
log.time?.("fingerprint");
let ip = "";

@@ -1230,13 +1089,20 @@ if (typeof details.ip === "string") {

if (details.ip === "") {
logger.warn("generateFingerprint: ip is empty");
log.warn("generateFingerprint: ip is empty");
}
const fingerprint = await analyze.generateFingerprint(ip);
logger.debug("fingerprint (%s): %s", runtime(), fingerprint);
logger.timeEnd("fingerprint");
const context: ArcjetContext = { key, fingerprint };
const baseContext = {
key,
log,
...ctx,
};
const fingerprint = await analyze.generateFingerprint(baseContext, ip);
log.debug("fingerprint (%s): %s", rt, fingerprint);
log.timeEnd?.("fingerprint");
const context: ArcjetContext = { ...baseContext, fingerprint, runtime: rt };
if (rules.length < 1) {
// TODO(#607): Error if no rules configured after deprecation period
logger.warn(
log.warn(
"Calling `protect()` with no rules is deprecated. Did you mean to configure the Shield rule?",

@@ -1247,3 +1113,3 @@ );

if (rules.length > 10) {
logger.error("Failure running rules. Only 10 rules may be specified.");
log.error("Failure running rules. Only 10 rules may be specified.");

@@ -1258,3 +1124,3 @@ const decision = new ArcjetErrorDecision({

remoteClient.report(
client.report(
context,

@@ -1286,5 +1152,5 @@ details,

// can take advantage of that.
logger.time("cache");
log.time?.("cache");
const existingBlockReason = blockCache.get(fingerprint);
logger.timeEnd("cache");
log.timeEnd?.("cache");

@@ -1294,7 +1160,10 @@ // If already blocked then we can async log to the API and return the

if (existingBlockReason) {
logger.timeEnd("local");
logger.debug("decide: alreadyBlocked", {
fingerprint,
existingBlockReason,
});
log.timeEnd?.("local");
log.debug(
{
fingerprint,
existingBlockReason,
},
"decide: alreadyBlocked",
);
const decision = new ArcjetDenyDecision({

@@ -1307,11 +1176,14 @@ ttl: blockCache.ttl(fingerprint),

remoteClient.report(context, details, decision, rules);
client.report(context, details, decision, rules);
logger.debug("decide: already blocked", {
id: decision.id,
conclusion: decision.conclusion,
fingerprint,
reason: existingBlockReason,
runtime: runtime(),
});
log.debug(
{
id: decision.id,
conclusion: decision.conclusion,
fingerprint,
reason: existingBlockReason,
runtime: rt,
},
"decide: already blocked",
);

@@ -1331,3 +1203,3 @@ return decision;

logger.time(rule.type);
log.time?.(rule.type);

@@ -1338,14 +1210,17 @@ try {

logger.debug("Local rule result:", {
id: results[idx].ruleId,
rule: rule.type,
fingerprint,
path: details.path,
runtime: runtime(),
ttl: results[idx].ttl,
conclusion: results[idx].conclusion,
reason: results[idx].reason,
});
log.debug(
{
id: results[idx].ruleId,
rule: rule.type,
fingerprint,
path: details.path,
runtime: rt,
ttl: results[idx].ttl,
conclusion: results[idx].conclusion,
reason: results[idx].reason,
},
"Local rule result:",
);
} catch (err) {
logger.error(
log.error(
"Failure running rule: %s due to %s",

@@ -1364,6 +1239,6 @@ rule.type,

logger.timeEnd(rule.type);
log.timeEnd?.(rule.type);
if (results[idx].isDenied()) {
logger.timeEnd("local");
log.timeEnd?.("local");

@@ -1379,3 +1254,3 @@ const decision = new ArcjetDenyDecision({

// request.
remoteClient.report(context, details, decision, rules);
client.report(context, details, decision, rules);

@@ -1386,7 +1261,11 @@ // If we're not in DRY_RUN mode, we want to cache non-zero TTL results

if (results[idx].ttl > 0) {
logger.debug("Caching decision for %d seconds", decision.ttl, {
fingerprint,
conclusion: decision.conclusion,
reason: decision.reason,
});
log.debug(
{
fingerprint,
conclusion: decision.conclusion,
reason: decision.reason,
},
"Caching decision for %d seconds",
decision.ttl,
);

@@ -1403,3 +1282,3 @@ blockCache.set(

logger.warn(
log.warn(
`Dry run mode is enabled for "%s" rule. Overriding decision. Decision was: %s`,

@@ -1412,4 +1291,4 @@ rule.type,

logger.timeEnd("local");
logger.time("remote");
log.timeEnd?.("local");
log.time?.("remote");

@@ -1419,5 +1298,5 @@ // With no cached values, we take a decision remotely. We use a timeout to

try {
logger.time("decideApi");
const decision = await remoteClient.decide(context, details, rules);
logger.timeEnd("decideApi");
log.time?.("decideApi");
const decision = await client.decide(context, details, rules);
log.timeEnd?.("decideApi");

@@ -1427,6 +1306,3 @@ // If the decision is to block and we have a non-zero TTL, we cache the

if (decision.isDenied() && decision.ttl > 0) {
logger.debug(
"decide: Caching block locally for %d seconds",
decision.ttl,
);
log.debug("decide: Caching block locally for %d seconds", decision.ttl);

@@ -1442,3 +1318,3 @@ blockCache.set(

} catch (err) {
logger.error(
log.error(
"Encountered problem getting remote decision: %s",

@@ -1453,7 +1329,7 @@ errorMessage(err),

remoteClient.report(context, details, decision, rules);
client.report(context, details, decision, rules);
return decision;
} finally {
logger.timeEnd("remote");
log.timeEnd?.("remote");
}

@@ -1469,5 +1345,2 @@ }

return Object.freeze({
get runtime() {
return runtime();
},
withRule(rule: Primitive | Product) {

@@ -1477,5 +1350,6 @@ return withRule(rule);

async protect(
ctx: ArcjetAdapterContext,
request: ArcjetRequest<ExtraProps<typeof rules>>,
): Promise<ArcjetDecision> {
return protect(rules, request);
return protect(rules, ctx, request);
},

@@ -1486,5 +1360,2 @@ });

return Object.freeze({
get runtime() {
return runtime();
},
withRule(rule: Primitive | Product) {

@@ -1494,7 +1365,8 @@ return withRule(rule);

async protect(
ctx: ArcjetAdapterContext,
request: ArcjetRequest<ExtraProps<typeof rootRules>>,
): Promise<ArcjetDecision> {
return protect(rootRules, request);
return protect(rootRules, ctx, request);
},
});
}
{
"name": "arcjet",
"version": "1.0.0-alpha.13",
"version": "1.0.0-alpha.14",
"description": "Arcjet TypeScript and JavaScript SDK core",

@@ -43,14 +43,16 @@ "license": "Apache-2.0",

"dependencies": {
"@arcjet/analyze": "1.0.0-alpha.13",
"@arcjet/duration": "1.0.0-alpha.13",
"@arcjet/logger": "1.0.0-alpha.13",
"@arcjet/protocol": "1.0.0-alpha.13"
"@arcjet/analyze": "1.0.0-alpha.14",
"@arcjet/duration": "1.0.0-alpha.14",
"@arcjet/headers": "1.0.0-alpha.14",
"@arcjet/logger": "1.0.0-alpha.14",
"@arcjet/protocol": "1.0.0-alpha.14",
"@arcjet/runtime": "1.0.0-alpha.14"
},
"devDependencies": {
"@arcjet/eslint-config": "1.0.0-alpha.13",
"@arcjet/rollup-config": "1.0.0-alpha.13",
"@arcjet/tsconfig": "1.0.0-alpha.13",
"@arcjet/eslint-config": "1.0.0-alpha.14",
"@arcjet/rollup-config": "1.0.0-alpha.14",
"@arcjet/tsconfig": "1.0.0-alpha.14",
"@edge-runtime/jest-environment": "2.3.10",
"@jest/globals": "29.7.0",
"@rollup/wasm-node": "4.17.2",
"@rollup/wasm-node": "4.18.0",
"@types/node": "18.18.0",

@@ -57,0 +59,0 @@ "jest": "29.7.0",

<a href="https://arcjet.com" target="_arcjet-home">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://arcjet.com/arcjet-logo-minimal-dark-mark-all.svg">
<img src="https://arcjet.com/arcjet-logo-minimal-light-mark-all.svg" alt="Arcjet Logo" height="128" width="auto">
<source media="(prefers-color-scheme: dark)" srcset="https://arcjet.com/logo/arcjet-dark-lockup-voyage-horizontal.svg">
<img src="https://arcjet.com/logo/arcjet-light-lockup-voyage-horizontal.svg" alt="Arcjet Logo" height="128" width="auto">
</picture>

@@ -20,3 +20,3 @@ </a>

[Arcjet][arcjet] helps developers protect their apps in just a few lines of
code. Implement rate limiting, bot protection, email verification & defend
code. Implement rate limiting, bot protection, email verification, and defense
against common attacks.

@@ -45,3 +45,4 @@

import http from "http";
import arcjet, { createRemoteClient, defaultBaseUrl } from "arcjet";
import arcjet, { createRemoteClient } from "arcjet";
import { baseUrl } from "@arcjet/env";
import { createConnectTransport } from "@connectrpc/connect-node";

@@ -57,3 +58,3 @@

transport: createConnectTransport({
baseUrl: defaultBaseUrl(),
baseUrl: baseUrl(process.env),
httpVersion: "2",

@@ -68,2 +69,6 @@ }),

) {
// Any sort of additional context that might want to be included for the
// execution of `protect()`. This is mostly only useful for writing adapters.
const ctx = {};
// Construct an object with Arcjet request details

@@ -78,3 +83,3 @@ const path = new URL(req.url || "", `http://${req.headers.host}`);

const decision = await aj.protect(details);
const decision = await aj.protect(ctx, details);
console.log(decision);

@@ -81,0 +86,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc