@patchstack/connect 0.3.21 → 0.3.23
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/AGENT-INSTALL.md +14 -3
- package/dist/cli.js +62 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +12 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/protect.cjs +277 -12
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.ts +27 -2
- package/dist/protect.js +275 -13
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-XBOGN446.js → refresh-manifest-ZIFRZF25.js} +4 -1
- package/dist/refresh-manifest-ZIFRZF25.js.map +1 -0
- package/package.json +1 -1
- package/dist/refresh-manifest-XBOGN446.js.map +0 -1
package/dist/protect.cjs
CHANGED
|
@@ -155,8 +155,10 @@ async function resolveConfig(options) {
|
|
|
155
155
|
"CONFIG_MISSING"
|
|
156
156
|
);
|
|
157
157
|
}
|
|
158
|
+
const apiKeyRaw = fromEnv.apiKey ?? fromFile.apiKey ?? null;
|
|
158
159
|
return {
|
|
159
160
|
siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,
|
|
161
|
+
apiKey: apiKeyRaw === null || apiKeyRaw.length === 0 ? null : apiKeyRaw,
|
|
160
162
|
endpoint,
|
|
161
163
|
timeoutMs,
|
|
162
164
|
environment,
|
|
@@ -204,6 +206,7 @@ function readEnv() {
|
|
|
204
206
|
const environmentRaw = process.env.PATCHSTACK_ENVIRONMENT;
|
|
205
207
|
return {
|
|
206
208
|
siteUuid: process.env.PATCHSTACK_SITE_UUID ?? void 0,
|
|
209
|
+
apiKey: process.env.PATCHSTACK_API_KEY ?? void 0,
|
|
207
210
|
endpoint: process.env.PATCHSTACK_ENDPOINT ?? void 0,
|
|
208
211
|
timeoutMs,
|
|
209
212
|
environment: environmentRaw !== void 0 && environmentRaw.length > 0 ? environmentRaw : void 0
|
|
@@ -2243,7 +2246,8 @@ var PulseRuleClient = class {
|
|
|
2243
2246
|
etag: response.headers?.get?.("etag") ?? null,
|
|
2244
2247
|
firewall: data.firewall,
|
|
2245
2248
|
whitelists: Array.isArray(data.whitelists) ? data.whitelists : [],
|
|
2246
|
-
whitelist_keys: data.whitelist_keys ?? {}
|
|
2249
|
+
whitelist_keys: data.whitelist_keys ?? {},
|
|
2250
|
+
...enforcementField(data)
|
|
2247
2251
|
};
|
|
2248
2252
|
this.#cache = result;
|
|
2249
2253
|
this.#etag = result.etag;
|
|
@@ -2269,6 +2273,11 @@ var PulseRuleClient = class {
|
|
|
2269
2273
|
this.#etag = null;
|
|
2270
2274
|
}
|
|
2271
2275
|
};
|
|
2276
|
+
function enforcementField(data) {
|
|
2277
|
+
if (!data || typeof data !== "object") return {};
|
|
2278
|
+
const v = data.enforcement ?? data.mode;
|
|
2279
|
+
return v === "block" || v === "dry-run" ? { enforcement: v } : {};
|
|
2280
|
+
}
|
|
2272
2281
|
|
|
2273
2282
|
// src/protect/engine/fetch.js
|
|
2274
2283
|
var MAX_BODY_BYTES = 1024 * 1024;
|
|
@@ -2996,10 +3005,12 @@ async function resolveRules(options, store) {
|
|
|
2996
3005
|
return emptyBundle();
|
|
2997
3006
|
}
|
|
2998
3007
|
function normalizeBundle(b) {
|
|
3008
|
+
const enforcement = b?.enforcement ?? b?.mode;
|
|
2999
3009
|
return {
|
|
3000
3010
|
firewall: Array.isArray(b.firewall) ? b.firewall : [],
|
|
3001
3011
|
whitelists: Array.isArray(b.whitelists) ? b.whitelists : [],
|
|
3002
|
-
whitelist_keys: b.whitelist_keys ?? {}
|
|
3012
|
+
whitelist_keys: b.whitelist_keys ?? {},
|
|
3013
|
+
...enforcement === "block" || enforcement === "dry-run" ? { enforcement } : {}
|
|
3003
3014
|
};
|
|
3004
3015
|
}
|
|
3005
3016
|
function emptyBundle() {
|
|
@@ -3060,6 +3071,169 @@ function makeRefreshHandler(tick, secret) {
|
|
|
3060
3071
|
};
|
|
3061
3072
|
}
|
|
3062
3073
|
|
|
3074
|
+
// src/protect/firewall-log.js
|
|
3075
|
+
var DEFAULT_API_BASE = "https://api.patchstack.com";
|
|
3076
|
+
var DEFAULT_FLUSH_MS = 1e3;
|
|
3077
|
+
var MAX_BATCH = 50;
|
|
3078
|
+
var TOKEN_SKEW_MS = 6e4;
|
|
3079
|
+
function parseApiKey(apiKey) {
|
|
3080
|
+
if (typeof apiKey !== "string" || apiKey.length === 0) return null;
|
|
3081
|
+
const idx = apiKey.lastIndexOf("-");
|
|
3082
|
+
if (idx <= 0 || idx === apiKey.length - 1) return null;
|
|
3083
|
+
const clientSecret = apiKey.slice(0, idx);
|
|
3084
|
+
const clientId = apiKey.slice(idx + 1);
|
|
3085
|
+
if (!/^\d+$/.test(clientId) || clientSecret.length === 0) return null;
|
|
3086
|
+
return { clientId, clientSecret };
|
|
3087
|
+
}
|
|
3088
|
+
function resolveApiBase(pulseOrManifestUrl) {
|
|
3089
|
+
const fromEnv = typeof process !== "undefined" ? process.env?.PATCHSTACK_API_BASE : void 0;
|
|
3090
|
+
if (typeof fromEnv === "string" && fromEnv.length > 0) {
|
|
3091
|
+
return fromEnv.replace(/\/$/, "");
|
|
3092
|
+
}
|
|
3093
|
+
if (typeof pulseOrManifestUrl === "string" && pulseOrManifestUrl.length > 0) {
|
|
3094
|
+
try {
|
|
3095
|
+
return new URL(pulseOrManifestUrl).origin;
|
|
3096
|
+
} catch {
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
const endpoint = typeof process !== "undefined" ? process.env?.PATCHSTACK_ENDPOINT : void 0;
|
|
3100
|
+
if (typeof endpoint === "string" && endpoint.length > 0) {
|
|
3101
|
+
try {
|
|
3102
|
+
return new URL(endpoint).origin;
|
|
3103
|
+
} catch {
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
return DEFAULT_API_BASE;
|
|
3107
|
+
}
|
|
3108
|
+
function createFirewallLogReporter(opts) {
|
|
3109
|
+
const creds = parseApiKey(opts.apiKey);
|
|
3110
|
+
if (!creds) {
|
|
3111
|
+
return { record() {
|
|
3112
|
+
}, flush() {
|
|
3113
|
+
}, stop() {
|
|
3114
|
+
} };
|
|
3115
|
+
}
|
|
3116
|
+
const apiBase = (opts.apiBase ?? DEFAULT_API_BASE).replace(/\/$/, "");
|
|
3117
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
3118
|
+
const flushMs = Number.isFinite(opts.flushMs) ? opts.flushMs : DEFAULT_FLUSH_MS;
|
|
3119
|
+
const sourceHost = typeof opts.sourceHost === "string" ? opts.sourceHost : "";
|
|
3120
|
+
let queue = [];
|
|
3121
|
+
let timer = null;
|
|
3122
|
+
let stopped = false;
|
|
3123
|
+
let cachedToken = null;
|
|
3124
|
+
let tokenInflight = null;
|
|
3125
|
+
const fetchAccessToken = async () => {
|
|
3126
|
+
if (cachedToken && Date.now() < cachedToken.expiresAt - TOKEN_SKEW_MS) {
|
|
3127
|
+
return cachedToken.token;
|
|
3128
|
+
}
|
|
3129
|
+
if (tokenInflight) return tokenInflight;
|
|
3130
|
+
tokenInflight = (async () => {
|
|
3131
|
+
try {
|
|
3132
|
+
const res = await fetchImpl(`${apiBase}/oauth/token`, {
|
|
3133
|
+
method: "POST",
|
|
3134
|
+
headers: {
|
|
3135
|
+
"Content-Type": "application/json",
|
|
3136
|
+
Accept: "application/json",
|
|
3137
|
+
"User-Agent": "@patchstack/connect"
|
|
3138
|
+
},
|
|
3139
|
+
body: JSON.stringify({
|
|
3140
|
+
grant_type: "client_credentials",
|
|
3141
|
+
client_id: creds.clientId,
|
|
3142
|
+
client_secret: creds.clientSecret
|
|
3143
|
+
})
|
|
3144
|
+
});
|
|
3145
|
+
if (!res || !res.ok) return null;
|
|
3146
|
+
const body = await res.json();
|
|
3147
|
+
const token = body?.access_token;
|
|
3148
|
+
if (typeof token !== "string" || token.length === 0) return null;
|
|
3149
|
+
const expiresIn = Number(body?.expires_in);
|
|
3150
|
+
const ttlMs = Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn * 1e3 : 36e5;
|
|
3151
|
+
cachedToken = { token, expiresAt: Date.now() + ttlMs };
|
|
3152
|
+
return token;
|
|
3153
|
+
} catch {
|
|
3154
|
+
return null;
|
|
3155
|
+
} finally {
|
|
3156
|
+
tokenInflight = null;
|
|
3157
|
+
}
|
|
3158
|
+
})();
|
|
3159
|
+
return tokenInflight;
|
|
3160
|
+
};
|
|
3161
|
+
const flush = () => {
|
|
3162
|
+
if (timer) {
|
|
3163
|
+
clearTimeout(timer);
|
|
3164
|
+
timer = null;
|
|
3165
|
+
}
|
|
3166
|
+
if (queue.length === 0 || typeof fetchImpl !== "function") return;
|
|
3167
|
+
const batch = queue.splice(0, MAX_BATCH);
|
|
3168
|
+
void (async () => {
|
|
3169
|
+
const token = await fetchAccessToken();
|
|
3170
|
+
if (!token) return;
|
|
3171
|
+
const body = new URLSearchParams();
|
|
3172
|
+
body.set("type", "firewall");
|
|
3173
|
+
body.set("logs", JSON.stringify(batch));
|
|
3174
|
+
try {
|
|
3175
|
+
const p = fetchImpl(`${apiBase}/api/logs/log`, {
|
|
3176
|
+
method: "POST",
|
|
3177
|
+
headers: {
|
|
3178
|
+
Authorization: `Bearer ${token}`,
|
|
3179
|
+
Accept: "application/json",
|
|
3180
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
3181
|
+
"User-Agent": "@patchstack/connect",
|
|
3182
|
+
...sourceHost ? { "Source-Host": sourceHost } : {}
|
|
3183
|
+
},
|
|
3184
|
+
body
|
|
3185
|
+
});
|
|
3186
|
+
if (p && typeof p.then === "function") p.catch(() => {
|
|
3187
|
+
});
|
|
3188
|
+
} catch {
|
|
3189
|
+
}
|
|
3190
|
+
})();
|
|
3191
|
+
};
|
|
3192
|
+
return {
|
|
3193
|
+
/**
|
|
3194
|
+
* @param {{
|
|
3195
|
+
* rule?: { id?: string | number },
|
|
3196
|
+
* method?: string | null,
|
|
3197
|
+
* path?: string | null,
|
|
3198
|
+
* ip?: string | null,
|
|
3199
|
+
* userAgent?: string | null,
|
|
3200
|
+
* }} event
|
|
3201
|
+
*/
|
|
3202
|
+
record(event) {
|
|
3203
|
+
if (stopped) return;
|
|
3204
|
+
const fid = event?.rule?.id;
|
|
3205
|
+
if (fid === void 0 || fid === null || fid === "") return;
|
|
3206
|
+
queue.push({
|
|
3207
|
+
fid,
|
|
3208
|
+
method: event.method ?? null,
|
|
3209
|
+
request_uri: event.path ?? null,
|
|
3210
|
+
ip: event.ip ?? null,
|
|
3211
|
+
user_agent: event.userAgent ?? null,
|
|
3212
|
+
log_date: (/* @__PURE__ */ new Date()).toISOString()
|
|
3213
|
+
});
|
|
3214
|
+
if (queue.length >= MAX_BATCH) {
|
|
3215
|
+
flush();
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
if (!timer) timer = setTimeout(flush, flushMs);
|
|
3219
|
+
},
|
|
3220
|
+
flush,
|
|
3221
|
+
stop() {
|
|
3222
|
+
stopped = true;
|
|
3223
|
+
flush();
|
|
3224
|
+
}
|
|
3225
|
+
};
|
|
3226
|
+
}
|
|
3227
|
+
function telemetryEnabled() {
|
|
3228
|
+
const v = typeof process !== "undefined" ? process.env?.PATCHSTACK_TELEMETRY : void 0;
|
|
3229
|
+
if (v === void 0 || v === "") return true;
|
|
3230
|
+
return !/^(0|false|off|no)$/i.test(String(v));
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
// src/protect/runtime.js
|
|
3234
|
+
var import_node_fs2 = require("fs");
|
|
3235
|
+
var import_node_path8 = require("path");
|
|
3236
|
+
|
|
3063
3237
|
// src/protect/supabase-guard.js
|
|
3064
3238
|
var GUARD_PATH = "/_patchstack/guard";
|
|
3065
3239
|
var HOP_BY_HOP = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding", "connection"]);
|
|
@@ -3131,11 +3305,30 @@ function createServerFnGuard({ protection }) {
|
|
|
3131
3305
|
};
|
|
3132
3306
|
}
|
|
3133
3307
|
async function createProtection(options = {}) {
|
|
3134
|
-
const mode = options.mode === "block" ? "block" : "dry-run";
|
|
3135
3308
|
const onError = options.onError;
|
|
3136
|
-
const
|
|
3309
|
+
const userOnDetect = options.onDetect ?? defaultOnDetect;
|
|
3310
|
+
const apiKey = resolveApiKey(options);
|
|
3311
|
+
const firewallLog = apiKey && telemetryEnabled() && options.reportFirewallLog !== false ? createFirewallLogReporter({
|
|
3312
|
+
apiKey,
|
|
3313
|
+
apiBase: resolveApiBase(options.pulseRulesUrl ?? options.baseUrl),
|
|
3314
|
+
sourceHost: options.sourceHost,
|
|
3315
|
+
fetchImpl: options.fetchImpl
|
|
3316
|
+
}) : null;
|
|
3317
|
+
const onDetect = (detection) => {
|
|
3318
|
+
userOnDetect(detection);
|
|
3319
|
+
if (firewallLog && detection?.mode === "block") {
|
|
3320
|
+
firewallLog.record({
|
|
3321
|
+
rule: detection.rule,
|
|
3322
|
+
method: detection.method,
|
|
3323
|
+
path: detection.path,
|
|
3324
|
+
ip: detection.ip,
|
|
3325
|
+
userAgent: detection.userAgent
|
|
3326
|
+
});
|
|
3327
|
+
}
|
|
3328
|
+
};
|
|
3137
3329
|
const store = makeStore(options);
|
|
3138
3330
|
const bundle = await resolveRules(options, store);
|
|
3331
|
+
let mode = resolveMode(options, bundle);
|
|
3139
3332
|
let requestRules;
|
|
3140
3333
|
let responseRules;
|
|
3141
3334
|
let egressRules;
|
|
@@ -3164,9 +3357,19 @@ async function createProtection(options = {}) {
|
|
|
3164
3357
|
};
|
|
3165
3358
|
applyBundle(bundle);
|
|
3166
3359
|
const maskFn = typeof options.maskWith === "function" ? options.maskWith : () => typeof options.maskWith === "string" ? options.maskWith : "[REDACTED]";
|
|
3167
|
-
const decide = (phase, result, block, allow2) => {
|
|
3360
|
+
const decide = (phase, result, block, allow2, ctx = {}) => {
|
|
3168
3361
|
if (!result || !result.blocked) return allow2();
|
|
3169
|
-
onDetect({
|
|
3362
|
+
onDetect({
|
|
3363
|
+
phase,
|
|
3364
|
+
mode,
|
|
3365
|
+
category: result.rule?.category,
|
|
3366
|
+
rule: result.rule,
|
|
3367
|
+
message: result.message,
|
|
3368
|
+
method: ctx.method,
|
|
3369
|
+
path: ctx.path,
|
|
3370
|
+
ip: ctx.ip,
|
|
3371
|
+
userAgent: ctx.userAgent
|
|
3372
|
+
});
|
|
3170
3373
|
return mode === "block" ? block() : allow2();
|
|
3171
3374
|
};
|
|
3172
3375
|
const isTextCT = (ct) => {
|
|
@@ -3330,7 +3533,9 @@ async function createProtection(options = {}) {
|
|
|
3330
3533
|
return mode === "block";
|
|
3331
3534
|
};
|
|
3332
3535
|
const protection = {
|
|
3333
|
-
mode
|
|
3536
|
+
get mode() {
|
|
3537
|
+
return mode;
|
|
3538
|
+
},
|
|
3334
3539
|
get rules() {
|
|
3335
3540
|
return { request: requestRules, response: responseRules, egress: egressRules };
|
|
3336
3541
|
},
|
|
@@ -3347,7 +3552,7 @@ async function createProtection(options = {}) {
|
|
|
3347
3552
|
onError?.(err);
|
|
3348
3553
|
return null;
|
|
3349
3554
|
}
|
|
3350
|
-
return decide("request", result, () => blockResponse(result, request), () => null);
|
|
3555
|
+
return decide("request", result, () => blockResponse(result, request), () => null, fetchRequestMeta(request));
|
|
3351
3556
|
};
|
|
3352
3557
|
},
|
|
3353
3558
|
// Wrap a fetch handler: screens the request, then the response (redact/block).
|
|
@@ -3385,7 +3590,8 @@ async function createProtection(options = {}) {
|
|
|
3385
3590
|
() => {
|
|
3386
3591
|
if (exprOptions.screenResponses) wrapNodeResponse(res);
|
|
3387
3592
|
next();
|
|
3388
|
-
}
|
|
3593
|
+
},
|
|
3594
|
+
nodeRequestMeta(req)
|
|
3389
3595
|
);
|
|
3390
3596
|
};
|
|
3391
3597
|
},
|
|
@@ -3437,7 +3643,8 @@ async function createProtection(options = {}) {
|
|
|
3437
3643
|
if (req.body === void 0) req.body = shaped.body;
|
|
3438
3644
|
if (nodeOptions.screenResponses) wrapNodeResponse(res);
|
|
3439
3645
|
next();
|
|
3440
|
-
}
|
|
3646
|
+
},
|
|
3647
|
+
nodeRequestMeta(req)
|
|
3441
3648
|
);
|
|
3442
3649
|
});
|
|
3443
3650
|
};
|
|
@@ -3471,7 +3678,9 @@ async function createProtection(options = {}) {
|
|
|
3471
3678
|
onError?.(err);
|
|
3472
3679
|
}
|
|
3473
3680
|
}
|
|
3474
|
-
|
|
3681
|
+
const next = await resolveRules(options, store);
|
|
3682
|
+
mode = resolveMode(options, next);
|
|
3683
|
+
applyBundle(next);
|
|
3475
3684
|
};
|
|
3476
3685
|
if (live) {
|
|
3477
3686
|
protection.refresh = () => runRefreshTick();
|
|
@@ -3479,10 +3688,39 @@ async function createProtection(options = {}) {
|
|
|
3479
3688
|
}
|
|
3480
3689
|
if (options.refreshMs > 0 && live) {
|
|
3481
3690
|
const loop = startRefresh(runRefreshTick, { refreshMs: options.refreshMs, onError });
|
|
3482
|
-
protection.stopRefresh =
|
|
3691
|
+
protection.stopRefresh = () => {
|
|
3692
|
+
loop.stop();
|
|
3693
|
+
firewallLog?.stop();
|
|
3694
|
+
};
|
|
3695
|
+
} else if (firewallLog) {
|
|
3696
|
+
protection.stopRefresh = () => firewallLog.stop();
|
|
3483
3697
|
}
|
|
3484
3698
|
return protection;
|
|
3485
3699
|
}
|
|
3700
|
+
function resolveMode(options, bundle) {
|
|
3701
|
+
const env = typeof process !== "undefined" ? process.env?.PATCHSTACK_MODE : void 0;
|
|
3702
|
+
if (env === "block" || env === "dry-run") return env;
|
|
3703
|
+
if (bundle?.enforcement === "block" || bundle?.enforcement === "dry-run") return bundle.enforcement;
|
|
3704
|
+
if (options?.mode === "block") return "block";
|
|
3705
|
+
if (options?.mode === "dry-run") return "dry-run";
|
|
3706
|
+
return "dry-run";
|
|
3707
|
+
}
|
|
3708
|
+
function resolveApiKey(options) {
|
|
3709
|
+
if (typeof options?.apiKey === "string" && options.apiKey.length > 0) return options.apiKey;
|
|
3710
|
+
if (typeof process !== "undefined") {
|
|
3711
|
+
const fromEnv = process.env?.PATCHSTACK_API_KEY;
|
|
3712
|
+
if (typeof fromEnv === "string" && fromEnv.length > 0) return fromEnv;
|
|
3713
|
+
}
|
|
3714
|
+
try {
|
|
3715
|
+
if (typeof process === "undefined" || typeof process.cwd !== "function") return void 0;
|
|
3716
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
3717
|
+
const raw = (0, import_node_fs2.readFileSync)((0, import_node_path8.join)(cwd, ".patchstackrc.json"), "utf8");
|
|
3718
|
+
const key = JSON.parse(raw)?.apiKey;
|
|
3719
|
+
if (typeof key === "string" && key.length > 0) return key;
|
|
3720
|
+
} catch {
|
|
3721
|
+
}
|
|
3722
|
+
return void 0;
|
|
3723
|
+
}
|
|
3486
3724
|
function byPhase(rules, phase) {
|
|
3487
3725
|
return (rules ?? []).filter((r) => (r.phase ?? "request") === phase);
|
|
3488
3726
|
}
|
|
@@ -3746,6 +3984,33 @@ function defaultOnDetect({ phase, mode, category, rule, message }) {
|
|
|
3746
3984
|
const tag = mode === "block" ? "BLOCK" : "DETECT (dry-run)";
|
|
3747
3985
|
console.warn(`[patchstack] ${tag} phase=${phase ?? "request"} category=${category ?? "?"} rule=${rule?.id ?? "?"} ${message ?? ""}`.trim());
|
|
3748
3986
|
}
|
|
3987
|
+
function fetchRequestMeta(request) {
|
|
3988
|
+
if (!request) return {};
|
|
3989
|
+
let path7 = null;
|
|
3990
|
+
try {
|
|
3991
|
+
path7 = new URL(request.url).pathname;
|
|
3992
|
+
} catch {
|
|
3993
|
+
path7 = typeof request.url === "string" ? request.url : null;
|
|
3994
|
+
}
|
|
3995
|
+
return {
|
|
3996
|
+
method: request.method ?? null,
|
|
3997
|
+
path: path7,
|
|
3998
|
+
ip: request.headers?.get?.("x-forwarded-for") ?? null,
|
|
3999
|
+
userAgent: request.headers?.get?.("user-agent") ?? null
|
|
4000
|
+
};
|
|
4001
|
+
}
|
|
4002
|
+
function nodeRequestMeta(req) {
|
|
4003
|
+
if (!req) return {};
|
|
4004
|
+
const headers = req.headers ?? {};
|
|
4005
|
+
const ua = headers["user-agent"] ?? headers["User-Agent"];
|
|
4006
|
+
const fwd = headers["x-forwarded-for"] ?? headers["X-Forwarded-For"];
|
|
4007
|
+
return {
|
|
4008
|
+
method: req.method ?? null,
|
|
4009
|
+
path: req.originalUrl || req.url || null,
|
|
4010
|
+
ip: req.ip ?? (typeof fwd === "string" ? fwd : null),
|
|
4011
|
+
userAgent: typeof ua === "string" ? ua : Array.isArray(ua) ? ua[0] : null
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
3749
4014
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3750
4015
|
0 && (module.exports = {
|
|
3751
4016
|
GUARD_PATH,
|