@patchstack/connect 0.3.20 → 0.3.21
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/dist/protect.cjs +232 -155
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.ts +12 -1
- package/dist/protect.js +207 -130
- package/dist/protect.js.map +1 -1
- package/package.json +1 -1
package/dist/protect.cjs
CHANGED
|
@@ -164,7 +164,7 @@ async function resolveConfig(options) {
|
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
166
|
async function readConfigFile(cwd) {
|
|
167
|
-
const target =
|
|
167
|
+
const target = import_node_path2.default.join(cwd, CONFIG_FILENAME);
|
|
168
168
|
let raw;
|
|
169
169
|
try {
|
|
170
170
|
raw = await (0, import_promises.readFile)(target, "utf8");
|
|
@@ -215,12 +215,12 @@ function isUuid(value) {
|
|
|
215
215
|
function isEnvironment(value) {
|
|
216
216
|
return value === "production" || value === "sandbox";
|
|
217
217
|
}
|
|
218
|
-
var import_promises,
|
|
218
|
+
var import_promises, import_node_path2, CONFIG_FILENAME, DEFAULT_ENVIRONMENT;
|
|
219
219
|
var init_config = __esm({
|
|
220
220
|
"src/config.ts"() {
|
|
221
221
|
"use strict";
|
|
222
222
|
import_promises = require("fs/promises");
|
|
223
|
-
|
|
223
|
+
import_node_path2 = __toESM(require("path"), 1);
|
|
224
224
|
init_types();
|
|
225
225
|
init_client();
|
|
226
226
|
CONFIG_FILENAME = ".patchstackrc.json";
|
|
@@ -232,7 +232,7 @@ var init_config = __esm({
|
|
|
232
232
|
async function readDeclaredDependencyNames(cwd) {
|
|
233
233
|
let raw;
|
|
234
234
|
try {
|
|
235
|
-
raw = await (0, import_promises2.readFile)(
|
|
235
|
+
raw = await (0, import_promises2.readFile)(import_node_path3.default.join(cwd, "package.json"), "utf8");
|
|
236
236
|
} catch {
|
|
237
237
|
return [];
|
|
238
238
|
}
|
|
@@ -264,12 +264,12 @@ function missingDependencies(declared, packages) {
|
|
|
264
264
|
const present = new Set(packages.map((entry) => entry.name));
|
|
265
265
|
return declared.filter((name) => !present.has(name));
|
|
266
266
|
}
|
|
267
|
-
var import_promises2,
|
|
267
|
+
var import_promises2, import_node_path3;
|
|
268
268
|
var init_consistency = __esm({
|
|
269
269
|
"src/parsers/consistency.ts"() {
|
|
270
270
|
"use strict";
|
|
271
271
|
import_promises2 = require("fs/promises");
|
|
272
|
-
|
|
272
|
+
import_node_path3 = __toESM(require("path"), 1);
|
|
273
273
|
}
|
|
274
274
|
});
|
|
275
275
|
|
|
@@ -335,7 +335,7 @@ function extractFromV1(deps, acc = [], depth = 0) {
|
|
|
335
335
|
return acc;
|
|
336
336
|
}
|
|
337
337
|
function extractNameFromPath(pkgPath) {
|
|
338
|
-
const segments = pkgPath.split("node_modules" +
|
|
338
|
+
const segments = pkgPath.split("node_modules" + import_node_path4.default.sep === pkgPath ? import_node_path4.default.sep : "/");
|
|
339
339
|
const parts = pkgPath.split("/");
|
|
340
340
|
const nmIndex = parts.lastIndexOf("node_modules");
|
|
341
341
|
if (nmIndex === -1 || nmIndex >= parts.length - 1) {
|
|
@@ -356,19 +356,19 @@ function isDirectV2(pkgPath) {
|
|
|
356
356
|
const nmCount = parts.filter((p) => p === "node_modules").length;
|
|
357
357
|
return nmCount === 1;
|
|
358
358
|
}
|
|
359
|
-
var import_promises3,
|
|
359
|
+
var import_promises3, import_node_path4;
|
|
360
360
|
var init_npm = __esm({
|
|
361
361
|
"src/parsers/npm.ts"() {
|
|
362
362
|
"use strict";
|
|
363
363
|
import_promises3 = require("fs/promises");
|
|
364
|
-
|
|
364
|
+
import_node_path4 = __toESM(require("path"), 1);
|
|
365
365
|
init_types();
|
|
366
366
|
}
|
|
367
367
|
});
|
|
368
368
|
|
|
369
369
|
// src/parsers/node_modules.ts
|
|
370
370
|
async function walkNodeModules(cwd) {
|
|
371
|
-
const root =
|
|
371
|
+
const root = import_node_path5.default.join(cwd, "node_modules");
|
|
372
372
|
try {
|
|
373
373
|
const info = await (0, import_promises4.stat)(root);
|
|
374
374
|
if (!info.isDirectory()) {
|
|
@@ -402,7 +402,7 @@ async function walk(dir, acc, depth) {
|
|
|
402
402
|
if (name.startsWith(".")) {
|
|
403
403
|
continue;
|
|
404
404
|
}
|
|
405
|
-
const fullPath =
|
|
405
|
+
const fullPath = import_node_path5.default.join(dir, name);
|
|
406
406
|
if (!await isPlainDirectory(fullPath)) {
|
|
407
407
|
continue;
|
|
408
408
|
}
|
|
@@ -417,7 +417,7 @@ async function walk(dir, acc, depth) {
|
|
|
417
417
|
if (sub.startsWith(".")) {
|
|
418
418
|
continue;
|
|
419
419
|
}
|
|
420
|
-
const scopedDir =
|
|
420
|
+
const scopedDir = import_node_path5.default.join(fullPath, sub);
|
|
421
421
|
if (!await isPlainDirectory(scopedDir)) {
|
|
422
422
|
continue;
|
|
423
423
|
}
|
|
@@ -433,7 +433,7 @@ async function walk(dir, acc, depth) {
|
|
|
433
433
|
async function readPackage(pkgDir, depth, acc) {
|
|
434
434
|
let raw;
|
|
435
435
|
try {
|
|
436
|
-
raw = await (0, import_promises4.readFile)(
|
|
436
|
+
raw = await (0, import_promises4.readFile)(import_node_path5.default.join(pkgDir, "package.json"), "utf8");
|
|
437
437
|
} catch {
|
|
438
438
|
return;
|
|
439
439
|
}
|
|
@@ -456,7 +456,7 @@ async function readPackage(pkgDir, depth, acc) {
|
|
|
456
456
|
});
|
|
457
457
|
}
|
|
458
458
|
async function walkNested(pkgDir, acc, depth) {
|
|
459
|
-
const nested =
|
|
459
|
+
const nested = import_node_path5.default.join(pkgDir, "node_modules");
|
|
460
460
|
if (!await isPlainDirectory(nested)) {
|
|
461
461
|
return;
|
|
462
462
|
}
|
|
@@ -470,12 +470,12 @@ async function isPlainDirectory(dir) {
|
|
|
470
470
|
return false;
|
|
471
471
|
}
|
|
472
472
|
}
|
|
473
|
-
var import_promises4,
|
|
473
|
+
var import_promises4, import_node_path5;
|
|
474
474
|
var init_node_modules = __esm({
|
|
475
475
|
"src/parsers/node_modules.ts"() {
|
|
476
476
|
"use strict";
|
|
477
477
|
import_promises4 = require("fs/promises");
|
|
478
|
-
|
|
478
|
+
import_node_path5 = __toESM(require("path"), 1);
|
|
479
479
|
init_types();
|
|
480
480
|
}
|
|
481
481
|
});
|
|
@@ -758,7 +758,7 @@ async function parseYarnLockfile(lockfilePath) {
|
|
|
758
758
|
"LOCKFILE_PARSE_ERROR"
|
|
759
759
|
);
|
|
760
760
|
}
|
|
761
|
-
const directNames = await readDirectDepNames(
|
|
761
|
+
const directNames = await readDirectDepNames(import_node_path6.default.dirname(lockfilePath));
|
|
762
762
|
const entries = [];
|
|
763
763
|
const seen = /* @__PURE__ */ new Set();
|
|
764
764
|
for (const block of blocks) {
|
|
@@ -902,7 +902,7 @@ async function readDirectDepNames(cwd) {
|
|
|
902
902
|
const names = /* @__PURE__ */ new Set();
|
|
903
903
|
let raw;
|
|
904
904
|
try {
|
|
905
|
-
raw = await (0, import_promises6.readFile)(
|
|
905
|
+
raw = await (0, import_promises6.readFile)(import_node_path6.default.join(cwd, "package.json"), "utf8");
|
|
906
906
|
} catch {
|
|
907
907
|
return names;
|
|
908
908
|
}
|
|
@@ -927,12 +927,12 @@ async function readDirectDepNames(cwd) {
|
|
|
927
927
|
}
|
|
928
928
|
return names;
|
|
929
929
|
}
|
|
930
|
-
var import_promises6,
|
|
930
|
+
var import_promises6, import_node_path6;
|
|
931
931
|
var init_yarn = __esm({
|
|
932
932
|
"src/parsers/yarn.ts"() {
|
|
933
933
|
"use strict";
|
|
934
934
|
import_promises6 = require("fs/promises");
|
|
935
|
-
|
|
935
|
+
import_node_path6 = __toESM(require("path"), 1);
|
|
936
936
|
init_types();
|
|
937
937
|
}
|
|
938
938
|
});
|
|
@@ -941,7 +941,7 @@ var init_yarn = __esm({
|
|
|
941
941
|
async function detectLockfile(cwd) {
|
|
942
942
|
const probed = await Promise.all(
|
|
943
943
|
LOCKFILE_CANDIDATES.map(async (candidate) => {
|
|
944
|
-
const filePath =
|
|
944
|
+
const filePath = import_node_path7.default.join(cwd, candidate.filename);
|
|
945
945
|
return { ...candidate, filePath, present: await exists(filePath) };
|
|
946
946
|
})
|
|
947
947
|
);
|
|
@@ -1019,7 +1019,7 @@ function staleWarning(source, missing) {
|
|
|
1019
1019
|
async function presentLockfiles(cwd) {
|
|
1020
1020
|
const probed = await Promise.all(
|
|
1021
1021
|
LOCKFILE_CANDIDATES.map(async (candidate) => {
|
|
1022
|
-
const filePath =
|
|
1022
|
+
const filePath = import_node_path7.default.join(cwd, candidate.filename);
|
|
1023
1023
|
return { ...candidate, filePath, present: await exists(filePath) };
|
|
1024
1024
|
})
|
|
1025
1025
|
);
|
|
@@ -1045,12 +1045,12 @@ async function exists(filePath) {
|
|
|
1045
1045
|
return false;
|
|
1046
1046
|
}
|
|
1047
1047
|
}
|
|
1048
|
-
var import_promises7,
|
|
1048
|
+
var import_promises7, import_node_path7, LOCKFILE_CANDIDATES;
|
|
1049
1049
|
var init_parsers = __esm({
|
|
1050
1050
|
"src/parsers/index.ts"() {
|
|
1051
1051
|
"use strict";
|
|
1052
1052
|
import_promises7 = require("fs/promises");
|
|
1053
|
-
|
|
1053
|
+
import_node_path7 = __toESM(require("path"), 1);
|
|
1054
1054
|
init_types();
|
|
1055
1055
|
init_consistency();
|
|
1056
1056
|
init_npm();
|
|
@@ -2876,9 +2876,189 @@ function renderBlockPage({ url = "", code = "patchstack" } = {}) {
|
|
|
2876
2876
|
</html>`;
|
|
2877
2877
|
}
|
|
2878
2878
|
|
|
2879
|
-
// src/protect/
|
|
2879
|
+
// src/protect/rules/store.js
|
|
2880
2880
|
var import_node_fs = require("fs");
|
|
2881
|
-
var
|
|
2881
|
+
var import_node_path = require("path");
|
|
2882
|
+
function makeStore(options = {}) {
|
|
2883
|
+
let mem = null;
|
|
2884
|
+
const durable = durableTier(options);
|
|
2885
|
+
return {
|
|
2886
|
+
async read() {
|
|
2887
|
+
if (mem) return mem;
|
|
2888
|
+
const env = await durable.read();
|
|
2889
|
+
if (env) mem = env;
|
|
2890
|
+
return env;
|
|
2891
|
+
},
|
|
2892
|
+
async write(env) {
|
|
2893
|
+
if (env) mem = env;
|
|
2894
|
+
await durable.write(env);
|
|
2895
|
+
}
|
|
2896
|
+
};
|
|
2897
|
+
}
|
|
2898
|
+
function durableTier(options) {
|
|
2899
|
+
const adapter = options.ruleCache;
|
|
2900
|
+
if (adapter && typeof adapter.read === "function" && typeof adapter.write === "function") {
|
|
2901
|
+
return {
|
|
2902
|
+
read: async () => {
|
|
2903
|
+
try {
|
|
2904
|
+
return toEnvelope(await adapter.read());
|
|
2905
|
+
} catch {
|
|
2906
|
+
return null;
|
|
2907
|
+
}
|
|
2908
|
+
},
|
|
2909
|
+
write: async (env) => {
|
|
2910
|
+
try {
|
|
2911
|
+
await adapter.write(env);
|
|
2912
|
+
} catch {
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
const dir = options.cacheDir;
|
|
2918
|
+
return {
|
|
2919
|
+
read: async () => cacheRead(dir),
|
|
2920
|
+
write: async (env) => cacheWrite(dir, env)
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
function cachePath(dir) {
|
|
2924
|
+
return (0, import_node_path.join)(dir, "patchstack-rules.json");
|
|
2925
|
+
}
|
|
2926
|
+
function cacheWrite(dir, env) {
|
|
2927
|
+
if (!dir) return;
|
|
2928
|
+
try {
|
|
2929
|
+
(0, import_node_fs.mkdirSync)(dir, { recursive: true });
|
|
2930
|
+
(0, import_node_fs.writeFileSync)(cachePath(dir), JSON.stringify(env));
|
|
2931
|
+
} catch {
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
function cacheRead(dir) {
|
|
2935
|
+
if (!dir) return null;
|
|
2936
|
+
try {
|
|
2937
|
+
return toEnvelope(JSON.parse((0, import_node_fs.readFileSync)(cachePath(dir), "utf8")));
|
|
2938
|
+
} catch {
|
|
2939
|
+
return null;
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
function toEnvelope(value) {
|
|
2943
|
+
if (!value || typeof value !== "object") return null;
|
|
2944
|
+
if (value.bundle && typeof value.bundle === "object") {
|
|
2945
|
+
return { bundle: value.bundle, etag: value.etag ?? null };
|
|
2946
|
+
}
|
|
2947
|
+
if (Array.isArray(value.firewall) || Array.isArray(value.whitelists)) {
|
|
2948
|
+
return { bundle: value, etag: null };
|
|
2949
|
+
}
|
|
2950
|
+
return null;
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
// src/protect/rules/source.js
|
|
2954
|
+
async function resolveRules(options, store) {
|
|
2955
|
+
if (options.siteUuid) {
|
|
2956
|
+
const prior = await store.read();
|
|
2957
|
+
const client = new PulseRuleClient({ siteUuid: options.siteUuid, baseUrl: options.pulseRulesUrl, etag: prior?.etag });
|
|
2958
|
+
const res = await client.getRules();
|
|
2959
|
+
if (res.success && res.notModified && prior?.bundle) return normalizeBundle(prior.bundle);
|
|
2960
|
+
if (res.success && !res.notModified) {
|
|
2961
|
+
const bundle = normalizeBundle(res);
|
|
2962
|
+
await store.write({ bundle, etag: res.etag ?? null });
|
|
2963
|
+
return bundle;
|
|
2964
|
+
}
|
|
2965
|
+
if (prior?.bundle) {
|
|
2966
|
+
options.onError?.(new Error(`pulse rule fetch failed (${res.error ?? "no usable response"}); using cached bundle`));
|
|
2967
|
+
return normalizeBundle(prior.bundle);
|
|
2968
|
+
}
|
|
2969
|
+
if (options.rules) {
|
|
2970
|
+
options.onError?.(new Error(`pulse rule fetch failed (${res.error ?? "no usable response"}); using bundled fallback`));
|
|
2971
|
+
return normalizeBundle(options.rules);
|
|
2972
|
+
}
|
|
2973
|
+
options.onError?.(new Error(`pulse rule fetch failed (${res.error ?? "no usable response"}); no cache \u2014 running with no rules`));
|
|
2974
|
+
return emptyBundle();
|
|
2975
|
+
}
|
|
2976
|
+
if (options.token) {
|
|
2977
|
+
const prior = await store.read();
|
|
2978
|
+
const client = new PatchstackRuleClient({ token: options.token, baseUrl: options.baseUrl, etag: prior?.etag });
|
|
2979
|
+
const res = await client.getRules();
|
|
2980
|
+
if (res.success && res.notModified && prior?.bundle) return normalizeBundle(prior.bundle);
|
|
2981
|
+
if (res.success && !res.notModified) {
|
|
2982
|
+
const bundle = normalizeBundle(res);
|
|
2983
|
+
await store.write({ bundle, etag: res.etag ?? null });
|
|
2984
|
+
return bundle;
|
|
2985
|
+
}
|
|
2986
|
+
if (prior?.bundle) {
|
|
2987
|
+
options.onError?.(new Error(`rule fetch failed (${res.error ?? "no usable response"}); using cached bundle`));
|
|
2988
|
+
return normalizeBundle(prior.bundle);
|
|
2989
|
+
}
|
|
2990
|
+
options.onError?.(new Error(`rule fetch failed (${res.error ?? "no usable response"}); no cache \u2014 running with no rules`));
|
|
2991
|
+
return emptyBundle();
|
|
2992
|
+
}
|
|
2993
|
+
if (options.rules) {
|
|
2994
|
+
return normalizeBundle(options.rules);
|
|
2995
|
+
}
|
|
2996
|
+
return emptyBundle();
|
|
2997
|
+
}
|
|
2998
|
+
function normalizeBundle(b) {
|
|
2999
|
+
return {
|
|
3000
|
+
firewall: Array.isArray(b.firewall) ? b.firewall : [],
|
|
3001
|
+
whitelists: Array.isArray(b.whitelists) ? b.whitelists : [],
|
|
3002
|
+
whitelist_keys: b.whitelist_keys ?? {}
|
|
3003
|
+
};
|
|
3004
|
+
}
|
|
3005
|
+
function emptyBundle() {
|
|
3006
|
+
return { firewall: [], whitelists: [], whitelist_keys: {} };
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
// src/protect/rules/refresh.js
|
|
3010
|
+
var JITTER_FRACTION3 = 0.1;
|
|
3011
|
+
var MAX_BACKOFF_MULTIPLIER = 8;
|
|
3012
|
+
function startRefresh(tick, { refreshMs, onError } = {}) {
|
|
3013
|
+
let stopped = false;
|
|
3014
|
+
let failures = 0;
|
|
3015
|
+
let timer = null;
|
|
3016
|
+
const schedule = () => {
|
|
3017
|
+
if (stopped) return;
|
|
3018
|
+
const backoff = Math.min(2 ** failures, MAX_BACKOFF_MULTIPLIER);
|
|
3019
|
+
const jitter = 1 - Math.random() * JITTER_FRACTION3;
|
|
3020
|
+
const delay = refreshMs * backoff * jitter;
|
|
3021
|
+
timer = setTimeout(run, delay);
|
|
3022
|
+
timer?.unref?.();
|
|
3023
|
+
};
|
|
3024
|
+
const run = async () => {
|
|
3025
|
+
if (stopped) return;
|
|
3026
|
+
try {
|
|
3027
|
+
await tick();
|
|
3028
|
+
failures = 0;
|
|
3029
|
+
} catch (err) {
|
|
3030
|
+
failures++;
|
|
3031
|
+
onError?.(err);
|
|
3032
|
+
}
|
|
3033
|
+
schedule();
|
|
3034
|
+
};
|
|
3035
|
+
schedule();
|
|
3036
|
+
return {
|
|
3037
|
+
stop() {
|
|
3038
|
+
stopped = true;
|
|
3039
|
+
if (timer) clearTimeout(timer);
|
|
3040
|
+
}
|
|
3041
|
+
};
|
|
3042
|
+
}
|
|
3043
|
+
function makeRefreshHandler(tick, secret) {
|
|
3044
|
+
return async (request) => {
|
|
3045
|
+
if (!secret) return new Response("not found", { status: 404 });
|
|
3046
|
+
let provided = null;
|
|
3047
|
+
try {
|
|
3048
|
+
provided = request?.headers?.get?.("x-patchstack-refresh") ?? new URL(request.url).searchParams.get("token");
|
|
3049
|
+
} catch {
|
|
3050
|
+
provided = null;
|
|
3051
|
+
}
|
|
3052
|
+
if (provided !== secret) return new Response("forbidden", { status: 403 });
|
|
3053
|
+
let refreshed = true;
|
|
3054
|
+
try {
|
|
3055
|
+
await tick();
|
|
3056
|
+
} catch {
|
|
3057
|
+
refreshed = false;
|
|
3058
|
+
}
|
|
3059
|
+
return new Response(JSON.stringify({ refreshed }), { status: 200, headers: { "content-type": "application/json" } });
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
2882
3062
|
|
|
2883
3063
|
// src/protect/supabase-guard.js
|
|
2884
3064
|
var GUARD_PATH = "/_patchstack/guard";
|
|
@@ -2954,7 +3134,8 @@ async function createProtection(options = {}) {
|
|
|
2954
3134
|
const mode = options.mode === "block" ? "block" : "dry-run";
|
|
2955
3135
|
const onError = options.onError;
|
|
2956
3136
|
const onDetect = options.onDetect ?? defaultOnDetect;
|
|
2957
|
-
const
|
|
3137
|
+
const store = makeStore(options);
|
|
3138
|
+
const bundle = await resolveRules(options, store);
|
|
2958
3139
|
let requestRules;
|
|
2959
3140
|
let responseRules;
|
|
2960
3141
|
let egressRules;
|
|
@@ -3270,30 +3451,35 @@ async function createProtection(options = {}) {
|
|
|
3270
3451
|
allowHosts: options.allowHosts
|
|
3271
3452
|
});
|
|
3272
3453
|
}
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3454
|
+
const cwd = options.cwd ?? (typeof process !== "undefined" ? process.cwd() : void 0);
|
|
3455
|
+
const live = Boolean(options.siteUuid || options.token);
|
|
3456
|
+
const refreshSecret = options.refreshSecret ?? (typeof process !== "undefined" ? process.env.PATCHSTACK_REFRESH_SECRET : void 0);
|
|
3457
|
+
const refreshable = live && (options.refreshMs > 0 || Boolean(refreshSecret));
|
|
3458
|
+
let reporter = null;
|
|
3459
|
+
if (refreshable && options.siteUuid && options.reportManifest !== false && cwd) {
|
|
3460
|
+
try {
|
|
3461
|
+
({ reportManifest: reporter } = await Promise.resolve().then(() => (init_refresh_manifest(), refresh_manifest_exports)));
|
|
3462
|
+
} catch (err) {
|
|
3463
|
+
onError?.(err);
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
const runRefreshTick = async () => {
|
|
3467
|
+
if (reporter) {
|
|
3277
3468
|
try {
|
|
3278
|
-
|
|
3469
|
+
await reporter(cwd);
|
|
3279
3470
|
} catch (err) {
|
|
3280
3471
|
onError?.(err);
|
|
3281
3472
|
}
|
|
3282
3473
|
}
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
applyBundle(await resolveRules(options));
|
|
3293
|
-
})().catch((err) => onError?.(err));
|
|
3294
|
-
}, options.refreshMs);
|
|
3295
|
-
timer.unref?.();
|
|
3296
|
-
protection.stopRefresh = () => clearInterval(timer);
|
|
3474
|
+
applyBundle(await resolveRules(options, store));
|
|
3475
|
+
};
|
|
3476
|
+
if (live) {
|
|
3477
|
+
protection.refresh = () => runRefreshTick();
|
|
3478
|
+
protection.refreshHandler = () => makeRefreshHandler(runRefreshTick, refreshSecret);
|
|
3479
|
+
}
|
|
3480
|
+
if (options.refreshMs > 0 && live) {
|
|
3481
|
+
const loop = startRefresh(runRefreshTick, { refreshMs: options.refreshMs, onError });
|
|
3482
|
+
protection.stopRefresh = loop.stop;
|
|
3297
3483
|
}
|
|
3298
3484
|
return protection;
|
|
3299
3485
|
}
|
|
@@ -3535,115 +3721,6 @@ function leakResponse() {
|
|
|
3535
3721
|
headers: { "content-type": "application/json" }
|
|
3536
3722
|
});
|
|
3537
3723
|
}
|
|
3538
|
-
async function resolveRules(options) {
|
|
3539
|
-
const cache = makeCache(options);
|
|
3540
|
-
if (options.siteUuid) {
|
|
3541
|
-
const prior = await cache.read();
|
|
3542
|
-
const client = new PulseRuleClient({ siteUuid: options.siteUuid, baseUrl: options.pulseRulesUrl, etag: prior?.etag });
|
|
3543
|
-
const res = await client.getRules();
|
|
3544
|
-
if (res.success && res.notModified && prior?.bundle) return normalizeBundle(prior.bundle);
|
|
3545
|
-
if (res.success && !res.notModified) {
|
|
3546
|
-
const bundle = normalizeBundle(res);
|
|
3547
|
-
await cache.write({ bundle, etag: res.etag ?? null });
|
|
3548
|
-
return bundle;
|
|
3549
|
-
}
|
|
3550
|
-
if (prior?.bundle) {
|
|
3551
|
-
options.onError?.(new Error(`pulse rule fetch failed (${res.error ?? "no usable response"}); using cached bundle`));
|
|
3552
|
-
return normalizeBundle(prior.bundle);
|
|
3553
|
-
}
|
|
3554
|
-
if (options.rules) {
|
|
3555
|
-
options.onError?.(new Error(`pulse rule fetch failed (${res.error ?? "no usable response"}); using bundled fallback`));
|
|
3556
|
-
return normalizeBundle(options.rules);
|
|
3557
|
-
}
|
|
3558
|
-
options.onError?.(new Error(`pulse rule fetch failed (${res.error ?? "no usable response"}); no cache \u2014 running with no rules`));
|
|
3559
|
-
return emptyBundle();
|
|
3560
|
-
}
|
|
3561
|
-
if (options.token) {
|
|
3562
|
-
const prior = await cache.read();
|
|
3563
|
-
const client = new PatchstackRuleClient({ token: options.token, baseUrl: options.baseUrl, etag: prior?.etag });
|
|
3564
|
-
const res = await client.getRules();
|
|
3565
|
-
if (res.success && res.notModified && prior?.bundle) return normalizeBundle(prior.bundle);
|
|
3566
|
-
if (res.success && !res.notModified) {
|
|
3567
|
-
const bundle = normalizeBundle(res);
|
|
3568
|
-
await cache.write({ bundle, etag: res.etag ?? null });
|
|
3569
|
-
return bundle;
|
|
3570
|
-
}
|
|
3571
|
-
if (prior?.bundle) {
|
|
3572
|
-
options.onError?.(new Error(`rule fetch failed (${res.error ?? "no usable response"}); using cached bundle`));
|
|
3573
|
-
return normalizeBundle(prior.bundle);
|
|
3574
|
-
}
|
|
3575
|
-
options.onError?.(new Error(`rule fetch failed (${res.error ?? "no usable response"}); no cache \u2014 running with no rules`));
|
|
3576
|
-
return emptyBundle();
|
|
3577
|
-
}
|
|
3578
|
-
if (options.rules) {
|
|
3579
|
-
return normalizeBundle(options.rules);
|
|
3580
|
-
}
|
|
3581
|
-
return emptyBundle();
|
|
3582
|
-
}
|
|
3583
|
-
function normalizeBundle(b) {
|
|
3584
|
-
return {
|
|
3585
|
-
firewall: Array.isArray(b.firewall) ? b.firewall : [],
|
|
3586
|
-
whitelists: Array.isArray(b.whitelists) ? b.whitelists : [],
|
|
3587
|
-
whitelist_keys: b.whitelist_keys ?? {}
|
|
3588
|
-
};
|
|
3589
|
-
}
|
|
3590
|
-
function emptyBundle() {
|
|
3591
|
-
return { firewall: [], whitelists: [], whitelist_keys: {} };
|
|
3592
|
-
}
|
|
3593
|
-
function makeCache(options) {
|
|
3594
|
-
const adapter = options.ruleCache;
|
|
3595
|
-
if (adapter && typeof adapter.read === "function" && typeof adapter.write === "function") {
|
|
3596
|
-
return {
|
|
3597
|
-
read: async () => {
|
|
3598
|
-
try {
|
|
3599
|
-
return toEnvelope(await adapter.read());
|
|
3600
|
-
} catch {
|
|
3601
|
-
return null;
|
|
3602
|
-
}
|
|
3603
|
-
},
|
|
3604
|
-
write: async (env) => {
|
|
3605
|
-
try {
|
|
3606
|
-
await adapter.write(env);
|
|
3607
|
-
} catch {
|
|
3608
|
-
}
|
|
3609
|
-
}
|
|
3610
|
-
};
|
|
3611
|
-
}
|
|
3612
|
-
const dir = options.cacheDir;
|
|
3613
|
-
return {
|
|
3614
|
-
read: async () => cacheRead(dir),
|
|
3615
|
-
write: async (env) => cacheWrite(dir, env)
|
|
3616
|
-
};
|
|
3617
|
-
}
|
|
3618
|
-
function cachePath(dir) {
|
|
3619
|
-
return (0, import_node_path7.join)(dir, "patchstack-rules.json");
|
|
3620
|
-
}
|
|
3621
|
-
function cacheWrite(dir, env) {
|
|
3622
|
-
if (!dir) return;
|
|
3623
|
-
try {
|
|
3624
|
-
(0, import_node_fs.mkdirSync)(dir, { recursive: true });
|
|
3625
|
-
(0, import_node_fs.writeFileSync)(cachePath(dir), JSON.stringify(env));
|
|
3626
|
-
} catch {
|
|
3627
|
-
}
|
|
3628
|
-
}
|
|
3629
|
-
function cacheRead(dir) {
|
|
3630
|
-
if (!dir) return null;
|
|
3631
|
-
try {
|
|
3632
|
-
return toEnvelope(JSON.parse((0, import_node_fs.readFileSync)(cachePath(dir), "utf8")));
|
|
3633
|
-
} catch {
|
|
3634
|
-
return null;
|
|
3635
|
-
}
|
|
3636
|
-
}
|
|
3637
|
-
function toEnvelope(value) {
|
|
3638
|
-
if (!value || typeof value !== "object") return null;
|
|
3639
|
-
if (value.bundle && typeof value.bundle === "object") {
|
|
3640
|
-
return { bundle: value.bundle, etag: value.etag ?? null };
|
|
3641
|
-
}
|
|
3642
|
-
if (Array.isArray(value.firewall) || Array.isArray(value.whitelists)) {
|
|
3643
|
-
return { bundle: value, etag: null };
|
|
3644
|
-
}
|
|
3645
|
-
return null;
|
|
3646
|
-
}
|
|
3647
3724
|
var BLOCK_MESSAGE = "This request has been blocked by Patchstack.";
|
|
3648
3725
|
function blockBody(result) {
|
|
3649
3726
|
return { error: BLOCK_MESSAGE, message: BLOCK_MESSAGE, rule: result?.rule?.id };
|