@chainpatrol/sdk 0.1.1

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/index.js ADDED
@@ -0,0 +1,887 @@
1
+ 'use strict';
2
+
3
+ var tldts = require('tldts');
4
+ var zod = require('zod');
5
+
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __spreadValues = (a, b) => {
12
+ for (var prop in b || (b = {}))
13
+ if (__hasOwnProp.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ if (__getOwnPropSymbols)
16
+ for (var prop of __getOwnPropSymbols(b)) {
17
+ if (__propIsEnum.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ }
20
+ return a;
21
+ };
22
+ var __export = (target, all) => {
23
+ for (var name in all)
24
+ __defProp(target, name, { get: all[name], enumerable: true });
25
+ };
26
+ var __async = (__this, __arguments, generator) => {
27
+ return new Promise((resolve, reject) => {
28
+ var fulfilled = (value) => {
29
+ try {
30
+ step(generator.next(value));
31
+ } catch (e) {
32
+ reject(e);
33
+ }
34
+ };
35
+ var rejected = (value) => {
36
+ try {
37
+ step(generator.throw(value));
38
+ } catch (e) {
39
+ reject(e);
40
+ }
41
+ };
42
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
43
+ step((generator = generator.apply(__this, __arguments)).next());
44
+ });
45
+ };
46
+
47
+ // src/events.ts
48
+ var ContinueAtOwnRisk = "CHAINPATROL_CONTINUE_AT_OWN_RISK";
49
+ var Events = {
50
+ ContinueAtOwnRisk
51
+ };
52
+
53
+ // src/logger.ts
54
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
55
+ LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
56
+ LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
57
+ LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
58
+ LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
59
+ LogLevel2[LogLevel2["NONE"] = 4] = "NONE";
60
+ return LogLevel2;
61
+ })(LogLevel || {});
62
+ var Logger = class _Logger {
63
+ constructor(meta, minLevel = 4 /* NONE */) {
64
+ this.meta = {};
65
+ if (meta) {
66
+ this.meta = meta;
67
+ }
68
+ this.minLevel = minLevel;
69
+ }
70
+ with(fields) {
71
+ return new _Logger(__spreadValues(__spreadValues({}, this.meta), fields), this.minLevel);
72
+ }
73
+ debug(message, fields) {
74
+ this.log(0 /* DEBUG */, message, fields);
75
+ }
76
+ info(message, fields) {
77
+ this.log(1 /* INFO */, message, fields);
78
+ }
79
+ warn(message, fields) {
80
+ this.log(2 /* WARN */, message, fields);
81
+ }
82
+ error(message, fields) {
83
+ this.log(3 /* ERROR */, message, fields);
84
+ }
85
+ log(level, message, fields) {
86
+ if (level < this.minLevel) {
87
+ return;
88
+ }
89
+ const logObj = __spreadValues({ message, data: __spreadValues({}, fields) }, this.meta);
90
+ const logString = JSON.stringify(logObj, null, 2);
91
+ console.log(`[${LogLevel[level].toUpperCase()}] ${logString}`);
92
+ }
93
+ };
94
+
95
+ // src/relay.ts
96
+ function isExtensionHost() {
97
+ var _a, _b;
98
+ return !!((_a = globalThis.browser) == null ? void 0 : _a.runtime) || !!((_b = globalThis.chrome) == null ? void 0 : _b.runtime);
99
+ }
100
+ function isBrowserHost() {
101
+ return !!globalThis.window;
102
+ }
103
+ function getExtensionHandle() {
104
+ return {
105
+ addListener: (callback) => {
106
+ globalThis.chrome.runtime.onMessage.addListener(callback);
107
+ },
108
+ removeListener: (callback) => {
109
+ globalThis.chrome.runtime.onMessage.removeListener(callback);
110
+ },
111
+ postMessage: (message) => {
112
+ globalThis.chrome.runtime.sendMessage(message);
113
+ }
114
+ };
115
+ }
116
+ function getBrowserHandle() {
117
+ return {
118
+ addListener: (callback) => {
119
+ globalThis.window.addEventListener("message", (event) => {
120
+ if (event.source !== globalThis.window) {
121
+ return;
122
+ }
123
+ callback(event.data);
124
+ });
125
+ },
126
+ removeListener: (callback) => {
127
+ globalThis.window.removeEventListener("message", callback);
128
+ },
129
+ postMessage: (message) => {
130
+ globalThis.window.postMessage(message, "*");
131
+ }
132
+ };
133
+ }
134
+ var _Relay = class _Relay {
135
+ static send(type, data) {
136
+ const message = {
137
+ type,
138
+ data,
139
+ _meta: {
140
+ id: Math.random().toString(36).substring(2, 9),
141
+ origin: globalThis.location.origin
142
+ }
143
+ };
144
+ _Relay.logger.debug("Sending message", message);
145
+ for (const handle of _Relay.handles) {
146
+ handle.postMessage(message);
147
+ }
148
+ }
149
+ static run(events) {
150
+ const listeners = /* @__PURE__ */ new Set();
151
+ for (const handle of _Relay.handles) {
152
+ const listener = _Relay.handleMessage.bind(null, handle, events);
153
+ listeners.add(listener);
154
+ handle.addListener(listener);
155
+ }
156
+ _Relay.logger.debug("Started relay", { events });
157
+ return () => {
158
+ for (const handle of _Relay.handles) {
159
+ for (const listener of listeners) {
160
+ handle.removeListener(listener);
161
+ }
162
+ }
163
+ _Relay.logger.debug("Stopped relay", { events });
164
+ };
165
+ }
166
+ static handleMessage(sourceHandle, events, message) {
167
+ if (!events.includes(message.type)) {
168
+ _Relay.logger.debug("Ignoring message", { message });
169
+ return;
170
+ }
171
+ const destinationHandles = _Relay.handles.filter(
172
+ (handle) => handle !== sourceHandle
173
+ );
174
+ for (const destinationHandle of destinationHandles) {
175
+ destinationHandle.postMessage(message);
176
+ }
177
+ }
178
+ static on(targetEvent, callback) {
179
+ const listeners = /* @__PURE__ */ new Set();
180
+ for (const handle of _Relay.handles) {
181
+ const listener = (message) => {
182
+ if (message.type !== targetEvent) {
183
+ _Relay.logger.debug("Ignoring message", { message });
184
+ return;
185
+ }
186
+ callback(message.data);
187
+ };
188
+ listeners.add(listener);
189
+ handle.addListener(listener);
190
+ }
191
+ return () => {
192
+ for (const handle of _Relay.handles) {
193
+ for (const listener of listeners) {
194
+ handle.removeListener(listener);
195
+ }
196
+ }
197
+ };
198
+ }
199
+ };
200
+ _Relay.handles = [];
201
+ _Relay.logger = new Logger({ component: "Relay" });
202
+ (() => {
203
+ if (isExtensionHost()) {
204
+ _Relay.logger.info("Detected extension host");
205
+ _Relay.handles.push(getExtensionHandle());
206
+ }
207
+ if (isBrowserHost()) {
208
+ _Relay.logger.info("Detected browser host");
209
+ _Relay.handles.push(getBrowserHandle());
210
+ }
211
+ })();
212
+ var Relay = _Relay;
213
+
214
+ // ../../node_modules/normalize-url/index.js
215
+ var DATA_URL_DEFAULT_MIME_TYPE = "text/plain";
216
+ var DATA_URL_DEFAULT_CHARSET = "us-ascii";
217
+ var testParameter = (name, filters) => filters.some((filter) => filter instanceof RegExp ? filter.test(name) : filter === name);
218
+ var supportedProtocols = /* @__PURE__ */ new Set([
219
+ "https:",
220
+ "http:",
221
+ "file:"
222
+ ]);
223
+ var hasCustomProtocol = (urlString) => {
224
+ try {
225
+ const { protocol } = new URL(urlString);
226
+ return protocol.endsWith(":") && !supportedProtocols.has(protocol);
227
+ } catch (e) {
228
+ return false;
229
+ }
230
+ };
231
+ var normalizeDataURL = (urlString, { stripHash }) => {
232
+ var _a, _b;
233
+ const match = new RegExp("^data:(?<type>[^,]*?),(?<data>[^#]*?)(?:#(?<hash>.*))?$").exec(urlString);
234
+ if (!match) {
235
+ throw new Error(`Invalid URL: ${urlString}`);
236
+ }
237
+ let { type, data, hash } = match.groups;
238
+ const mediaType = type.split(";");
239
+ hash = stripHash ? "" : hash;
240
+ let isBase64 = false;
241
+ if (mediaType[mediaType.length - 1] === "base64") {
242
+ mediaType.pop();
243
+ isBase64 = true;
244
+ }
245
+ const mimeType = (_b = (_a = mediaType.shift()) == null ? void 0 : _a.toLowerCase()) != null ? _b : "";
246
+ const attributes = mediaType.map((attribute) => {
247
+ let [key, value = ""] = attribute.split("=").map((string) => string.trim());
248
+ if (key === "charset") {
249
+ value = value.toLowerCase();
250
+ if (value === DATA_URL_DEFAULT_CHARSET) {
251
+ return "";
252
+ }
253
+ }
254
+ return `${key}${value ? `=${value}` : ""}`;
255
+ }).filter(Boolean);
256
+ const normalizedMediaType = [
257
+ ...attributes
258
+ ];
259
+ if (isBase64) {
260
+ normalizedMediaType.push("base64");
261
+ }
262
+ if (normalizedMediaType.length > 0 || mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE) {
263
+ normalizedMediaType.unshift(mimeType);
264
+ }
265
+ return `data:${normalizedMediaType.join(";")},${isBase64 ? data.trim() : data}${hash ? `#${hash}` : ""}`;
266
+ };
267
+ function normalizeUrl(urlString, options) {
268
+ options = __spreadValues({
269
+ defaultProtocol: "http",
270
+ normalizeProtocol: true,
271
+ forceHttp: false,
272
+ forceHttps: false,
273
+ stripAuthentication: true,
274
+ stripHash: false,
275
+ stripTextFragment: true,
276
+ stripWWW: true,
277
+ removeQueryParameters: [/^utm_\w+/i],
278
+ removeTrailingSlash: true,
279
+ removeSingleSlash: true,
280
+ removeDirectoryIndex: false,
281
+ removeExplicitPort: false,
282
+ sortQueryParameters: true
283
+ }, options);
284
+ if (typeof options.defaultProtocol === "string" && !options.defaultProtocol.endsWith(":")) {
285
+ options.defaultProtocol = `${options.defaultProtocol}:`;
286
+ }
287
+ urlString = urlString.trim();
288
+ if (/^data:/i.test(urlString)) {
289
+ return normalizeDataURL(urlString, options);
290
+ }
291
+ if (hasCustomProtocol(urlString)) {
292
+ return urlString;
293
+ }
294
+ const hasRelativeProtocol = urlString.startsWith("//");
295
+ const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString);
296
+ if (!isRelativeUrl) {
297
+ urlString = urlString.replace(/^(?!(?:\w+:)?\/\/)|^\/\//, options.defaultProtocol);
298
+ }
299
+ const urlObject = new URL(urlString);
300
+ if (options.forceHttp && options.forceHttps) {
301
+ throw new Error("The `forceHttp` and `forceHttps` options cannot be used together");
302
+ }
303
+ if (options.forceHttp && urlObject.protocol === "https:") {
304
+ urlObject.protocol = "http:";
305
+ }
306
+ if (options.forceHttps && urlObject.protocol === "http:") {
307
+ urlObject.protocol = "https:";
308
+ }
309
+ if (options.stripAuthentication) {
310
+ urlObject.username = "";
311
+ urlObject.password = "";
312
+ }
313
+ if (options.stripHash) {
314
+ urlObject.hash = "";
315
+ } else if (options.stripTextFragment) {
316
+ urlObject.hash = urlObject.hash.replace(/#?:~:text.*?$/i, "");
317
+ }
318
+ if (urlObject.pathname) {
319
+ const protocolRegex = /\b[a-z][a-z\d+\-.]{1,50}:\/\//g;
320
+ let lastIndex = 0;
321
+ let result = "";
322
+ for (; ; ) {
323
+ const match = protocolRegex.exec(urlObject.pathname);
324
+ if (!match) {
325
+ break;
326
+ }
327
+ const protocol = match[0];
328
+ const protocolAtIndex = match.index;
329
+ const intermediate = urlObject.pathname.slice(lastIndex, protocolAtIndex);
330
+ result += intermediate.replace(/\/{2,}/g, "/");
331
+ result += protocol;
332
+ lastIndex = protocolAtIndex + protocol.length;
333
+ }
334
+ const remnant = urlObject.pathname.slice(lastIndex, urlObject.pathname.length);
335
+ result += remnant.replace(/\/{2,}/g, "/");
336
+ urlObject.pathname = result;
337
+ }
338
+ if (urlObject.pathname) {
339
+ try {
340
+ urlObject.pathname = decodeURI(urlObject.pathname);
341
+ } catch (e) {
342
+ }
343
+ }
344
+ if (options.removeDirectoryIndex === true) {
345
+ options.removeDirectoryIndex = [/^index\.[a-z]+$/];
346
+ }
347
+ if (Array.isArray(options.removeDirectoryIndex) && options.removeDirectoryIndex.length > 0) {
348
+ let pathComponents = urlObject.pathname.split("/");
349
+ const lastComponent = pathComponents[pathComponents.length - 1];
350
+ if (testParameter(lastComponent, options.removeDirectoryIndex)) {
351
+ pathComponents = pathComponents.slice(0, -1);
352
+ urlObject.pathname = pathComponents.slice(1).join("/") + "/";
353
+ }
354
+ }
355
+ if (urlObject.hostname) {
356
+ urlObject.hostname = urlObject.hostname.replace(/\.$/, "");
357
+ if (options.stripWWW && /^www\.(?!www\.)[a-z\-\d]{1,63}\.[a-z.\-\d]{2,63}$/.test(urlObject.hostname)) {
358
+ urlObject.hostname = urlObject.hostname.replace(/^www\./, "");
359
+ }
360
+ }
361
+ if (Array.isArray(options.removeQueryParameters)) {
362
+ for (const key of [...urlObject.searchParams.keys()]) {
363
+ if (testParameter(key, options.removeQueryParameters)) {
364
+ urlObject.searchParams.delete(key);
365
+ }
366
+ }
367
+ }
368
+ if (!Array.isArray(options.keepQueryParameters) && options.removeQueryParameters === true) {
369
+ urlObject.search = "";
370
+ }
371
+ if (Array.isArray(options.keepQueryParameters) && options.keepQueryParameters.length > 0) {
372
+ for (const key of [...urlObject.searchParams.keys()]) {
373
+ if (!testParameter(key, options.keepQueryParameters)) {
374
+ urlObject.searchParams.delete(key);
375
+ }
376
+ }
377
+ }
378
+ if (options.sortQueryParameters) {
379
+ urlObject.searchParams.sort();
380
+ try {
381
+ urlObject.search = decodeURIComponent(urlObject.search);
382
+ } catch (e) {
383
+ }
384
+ }
385
+ if (options.removeTrailingSlash) {
386
+ urlObject.pathname = urlObject.pathname.replace(/\/$/, "");
387
+ }
388
+ if (options.removeExplicitPort && urlObject.port) {
389
+ urlObject.port = "";
390
+ }
391
+ const oldUrlString = urlString;
392
+ urlString = urlObject.toString();
393
+ if (!options.removeSingleSlash && urlObject.pathname === "/" && !oldUrlString.endsWith("/") && urlObject.hash === "") {
394
+ urlString = urlString.replace(/\/$/, "");
395
+ }
396
+ if ((options.removeTrailingSlash || urlObject.pathname === "/") && urlObject.hash === "" && options.removeSingleSlash) {
397
+ urlString = urlString.replace(/\/$/, "");
398
+ }
399
+ if (hasRelativeProtocol && !options.normalizeProtocol) {
400
+ urlString = urlString.replace(/^http:\/\//, "//");
401
+ }
402
+ if (options.stripProtocol) {
403
+ urlString = urlString.replace(/^(?:https?:)?\/\//, "");
404
+ }
405
+ return urlString;
406
+ }
407
+
408
+ // src/client.ts
409
+ function trimTrailingSlashes(url) {
410
+ return url.replace(/\/+$/, "");
411
+ }
412
+ var ChainPatrolClient = class {
413
+ constructor(options) {
414
+ this.logger = new Logger({ component: "ChainPatrolClient" });
415
+ var _a;
416
+ this.baseUrl = (_a = options.baseUrl) != null ? _a : "https://app.chainpatrol.io/api/";
417
+ if (!options.apiKey) {
418
+ throw new Error("ChainPatrol API key is required");
419
+ }
420
+ this.apiKey = options.apiKey;
421
+ }
422
+ fetch(req) {
423
+ return __async(this, null, function* () {
424
+ const url = `${trimTrailingSlashes(this.baseUrl)}/${req.path.join("/")}`;
425
+ this.logger.debug("fetch", { url, req });
426
+ const res = yield fetch(url, {
427
+ method: req.method,
428
+ headers: {
429
+ "Content-Type": "application/json",
430
+ "X-Api-Key": this.apiKey
431
+ },
432
+ body: JSON.stringify(req.body)
433
+ });
434
+ if (!res.ok) {
435
+ throw new Error(yield res.text());
436
+ }
437
+ return res.json();
438
+ });
439
+ }
440
+ get asset() {
441
+ return {
442
+ check: (req) => __async(this, null, function* () {
443
+ return yield this.fetch({
444
+ path: ["v2", "asset", "check"],
445
+ method: "POST",
446
+ body: req
447
+ });
448
+ }),
449
+ list: (req) => __async(this, null, function* () {
450
+ return yield this.fetch({
451
+ path: ["v2", "asset", "list"],
452
+ method: "GET",
453
+ body: req
454
+ });
455
+ })
456
+ };
457
+ }
458
+ };
459
+
460
+ // src/storage/index.ts
461
+ var storage_exports = {};
462
+ __export(storage_exports, {
463
+ Browser: () => Browser,
464
+ Extension: () => Extension,
465
+ Memory: () => Memory,
466
+ defineStorage: () => defineStorage
467
+ });
468
+
469
+ // src/storage/define-storage.ts
470
+ function defineStorage(config) {
471
+ return () => config({
472
+ keys: Object.values(ThreatDetector.StorageKeys)
473
+ });
474
+ }
475
+
476
+ // src/storage/extension.ts
477
+ var Extension = defineStorage(({ keys }) => {
478
+ return {
479
+ get: (key) => __async(void 0, null, function* () {
480
+ const result = yield chrome.storage.local.get(key);
481
+ return result[key];
482
+ }),
483
+ set: (key, value) => __async(void 0, null, function* () {
484
+ yield chrome.storage.local.set({ [key]: value });
485
+ }),
486
+ delete: (key) => __async(void 0, null, function* () {
487
+ yield chrome.storage.local.remove(key);
488
+ }),
489
+ size: () => __async(void 0, null, function* () {
490
+ const usageBytes = yield chrome.storage.local.getBytesInUse(keys);
491
+ return usageBytes;
492
+ })
493
+ };
494
+ });
495
+
496
+ // src/storage/browser.ts
497
+ function isStorageAvailable(type) {
498
+ let storage;
499
+ try {
500
+ storage = window[type];
501
+ const x = "__storage_test__";
502
+ storage.setItem(x, x);
503
+ storage.removeItem(x);
504
+ return true;
505
+ } catch (e) {
506
+ return e instanceof DOMException && // everything except Firefox
507
+ (e.code === 22 || // Firefox
508
+ e.code === 1014 || // test name field too, because code might not be present
509
+ // everything except Firefox
510
+ e.name === "QuotaExceededError" || // Firefox
511
+ e.name === "NS_ERROR_DOM_QUOTA_REACHED") && // acknowledge QuotaExceededError only if there's something already stored
512
+ storage && storage.length !== 0;
513
+ }
514
+ }
515
+ var Browser = defineStorage(({ keys }) => {
516
+ if (!isStorageAvailable("localStorage")) {
517
+ throw new Error("localStorage is not available");
518
+ }
519
+ return {
520
+ get: (key) => __async(void 0, null, function* () {
521
+ return localStorage.getItem(key);
522
+ }),
523
+ set: (key, value) => __async(void 0, null, function* () {
524
+ localStorage.setItem(key, value);
525
+ }),
526
+ delete: (key) => __async(void 0, null, function* () {
527
+ localStorage.removeItem(key);
528
+ }),
529
+ size: () => __async(void 0, null, function* () {
530
+ var _a, _b;
531
+ let total = 0;
532
+ for (let i = 0; i < localStorage.length; i++) {
533
+ const key = localStorage.key(i);
534
+ if (key && keys.includes(key)) {
535
+ total += (_b = (_a = localStorage.getItem(key)) == null ? void 0 : _a.length) != null ? _b : 0;
536
+ }
537
+ }
538
+ return total;
539
+ })
540
+ };
541
+ });
542
+
543
+ // src/storage/memory.ts
544
+ var Memory = defineStorage(() => {
545
+ const storage = /* @__PURE__ */ new Map();
546
+ return {
547
+ get: (key) => __async(void 0, null, function* () {
548
+ return storage.get(key) || null;
549
+ }),
550
+ set: (key, value) => __async(void 0, null, function* () {
551
+ storage.set(key, value);
552
+ }),
553
+ delete: (key) => __async(void 0, null, function* () {
554
+ storage.delete(key);
555
+ }),
556
+ size: () => __async(void 0, null, function* () {
557
+ let total = 0;
558
+ for (const value of storage.values()) {
559
+ total += value.length;
560
+ }
561
+ return total;
562
+ })
563
+ };
564
+ });
565
+
566
+ // src/detector.ts
567
+ var DomainParseError = class extends Error {
568
+ constructor(message) {
569
+ super(message);
570
+ this.name = "DomainParseError";
571
+ }
572
+ };
573
+ var _ThreatDetector = class _ThreatDetector {
574
+ constructor({
575
+ mode = "cloud",
576
+ apiKey = "",
577
+ storage = Memory(),
578
+ proxyUrl,
579
+ redirectUrl
580
+ }) {
581
+ this.logger = new Logger({ component: "ThreatDetector" });
582
+ this.mode = mode;
583
+ this.storage = storage;
584
+ this.redirectUrl = redirectUrl;
585
+ if (mode === "cloud") {
586
+ this.client = new ChainPatrolClient({
587
+ apiKey,
588
+ baseUrl: proxyUrl
589
+ });
590
+ }
591
+ }
592
+ url(url) {
593
+ return __async(this, null, function* () {
594
+ this.logger.debug("Checking URL", { url });
595
+ let domains;
596
+ try {
597
+ domains = this.generateDomains(url);
598
+ } catch (e) {
599
+ this.logger.error("Unable to parse domain", { url, error: e });
600
+ return {
601
+ ok: false,
602
+ url,
603
+ error: e instanceof DomainParseError ? e.message : "Unable to parse domain"
604
+ };
605
+ }
606
+ this.logger.debug("Generated domains", { domains });
607
+ let results = (yield Promise.all(domains.map((domain) => this.urlHelper(domain)))).filter(
608
+ (r) => r.ok
609
+ );
610
+ if (results.length === 0) {
611
+ return {
612
+ ok: false,
613
+ url,
614
+ error: "URL does not have a valid domain"
615
+ };
616
+ }
617
+ this.logger.debug("Results for domains", { results });
618
+ if (results.some((r) => r.status === "IGNORED")) {
619
+ return {
620
+ ok: true,
621
+ status: "IGNORED",
622
+ url
623
+ };
624
+ }
625
+ for (const result of results) {
626
+ if (result.ok && result.status !== "UNKNOWN") {
627
+ return result;
628
+ }
629
+ }
630
+ return results[0];
631
+ });
632
+ }
633
+ generateDomains(_url) {
634
+ var _a, _b;
635
+ const domain = this.parseDomainOrThrow(_url);
636
+ const domains = [domain];
637
+ const parsedDomain = tldts.parse(domain);
638
+ if (!parsedDomain.subdomain) {
639
+ return domains;
640
+ }
641
+ const subdomainParts = (_b = (_a = parsedDomain.subdomain) == null ? void 0 : _a.split(".")) != null ? _b : [];
642
+ for (let i = 0; i < subdomainParts.length; i++) {
643
+ const subdomain = subdomainParts.slice(i).join(".");
644
+ domains.unshift(`${subdomain}.${domain}`);
645
+ }
646
+ return domains;
647
+ }
648
+ urlHelper(domain) {
649
+ return __async(this, null, function* () {
650
+ let status = yield this.getStatusFromCache(domain);
651
+ if (this.mode === "cloud" && this.client && status === "UNKNOWN") {
652
+ try {
653
+ const res = yield this.client.asset.check({
654
+ type: "URL",
655
+ content: domain
656
+ });
657
+ this.logger.debug("Updating cache", { domain, status: res.status });
658
+ if (res.status === "ALLOWED") {
659
+ this.addDomainToCache(domain, _ThreatDetector.StorageKeys.ALLOWLIST);
660
+ } else if (res.status === "BLOCKED") {
661
+ this.addDomainToCache(domain, _ThreatDetector.StorageKeys.BLOCKLIST);
662
+ }
663
+ status = res.status;
664
+ } catch (e) {
665
+ return {
666
+ ok: false,
667
+ url: domain,
668
+ error: "Unable to check URL"
669
+ };
670
+ }
671
+ }
672
+ let redirectUrl;
673
+ if (status === "BLOCKED") {
674
+ if (typeof this.redirectUrl === "function") {
675
+ redirectUrl = this.redirectUrl(domain);
676
+ } else if (typeof this.redirectUrl === "string") {
677
+ const url = new URL(this.redirectUrl);
678
+ url.searchParams.set("originUrl", domain);
679
+ redirectUrl = url.toString();
680
+ }
681
+ }
682
+ return {
683
+ ok: true,
684
+ status,
685
+ url: domain,
686
+ redirectUrl
687
+ };
688
+ });
689
+ }
690
+ allow(url) {
691
+ return __async(this, null, function* () {
692
+ try {
693
+ const domain = this.parseDomainOrThrow(url);
694
+ this.logger.debug("Allowing URL", { url, domain });
695
+ yield this.invalidateDomainInCaches(domain);
696
+ yield this.addDomainToCache(domain, _ThreatDetector.StorageKeys.ALLOWLIST);
697
+ return {
698
+ ok: true,
699
+ url: domain
700
+ };
701
+ } catch (e) {
702
+ this.logger.error("Unable to allow URL", { url, error: e });
703
+ return {
704
+ ok: false,
705
+ url,
706
+ error: "Unable to allow URL"
707
+ };
708
+ }
709
+ });
710
+ }
711
+ block(url) {
712
+ return __async(this, null, function* () {
713
+ try {
714
+ const domain = this.parseDomainOrThrow(url);
715
+ this.logger.debug("Blocking URL", { url, domain });
716
+ yield this.invalidateDomainInCaches(domain);
717
+ yield this.addDomainToCache(domain, _ThreatDetector.StorageKeys.BLOCKLIST);
718
+ return {
719
+ ok: true,
720
+ url: domain
721
+ };
722
+ } catch (e) {
723
+ this.logger.error("Unable to block URL", { url, error: e });
724
+ return {
725
+ ok: false,
726
+ url,
727
+ error: "Unable to block URL"
728
+ };
729
+ }
730
+ });
731
+ }
732
+ ignore(url) {
733
+ return __async(this, null, function* () {
734
+ try {
735
+ const domain = this.parseDomainOrThrow(url);
736
+ this.logger.debug("Ignoring URL", { url, domain });
737
+ yield this.addDomainToCache(
738
+ domain,
739
+ _ThreatDetector.StorageKeys.IGNORELIST
740
+ );
741
+ return {
742
+ ok: true,
743
+ url: domain
744
+ };
745
+ } catch (e) {
746
+ this.logger.error("Unable to ignore URL", { url, error: e });
747
+ return {
748
+ ok: false,
749
+ url,
750
+ error: "Unable to ignore URL"
751
+ };
752
+ }
753
+ });
754
+ }
755
+ parseDomainOrThrow(url) {
756
+ this.logger.debug("Parsing domain", { url });
757
+ try {
758
+ const normalizedUrl = normalizeUrl(url, {
759
+ stripWWW: false,
760
+ removeTrailingSlash: false
761
+ });
762
+ this.logger.debug("Normalized URL", { from: url, to: normalizedUrl });
763
+ const parsedURL = new URL(normalizedUrl);
764
+ this.logger.debug("Extract domain from URL", { url: parsedURL.hostname });
765
+ const parsedSubdomainResult = tldts.parse(parsedURL.hostname);
766
+ this.logger.debug("Parsed domain", { parsedSubdomainResult });
767
+ if (parsedSubdomainResult.hostname === "localhost") {
768
+ throw new DomainParseError("ThreatDetector does not support localhost");
769
+ }
770
+ if (parsedSubdomainResult.isIp) {
771
+ throw new DomainParseError(
772
+ "ThreatDetector does not support IP addresses"
773
+ );
774
+ }
775
+ if (!parsedSubdomainResult.domain) {
776
+ throw new DomainParseError("Unable to parse domain");
777
+ }
778
+ const domain = parsedSubdomainResult.domain;
779
+ return domain;
780
+ } catch (e) {
781
+ if (e instanceof DomainParseError) {
782
+ throw e;
783
+ } else {
784
+ this.logger.error("Unable to parse domain", { url, error: e });
785
+ throw new DomainParseError("Unable to parse domain");
786
+ }
787
+ }
788
+ }
789
+ invalidateDomainInCaches(domain) {
790
+ return __async(this, null, function* () {
791
+ yield this.invalidateDomainInCache(
792
+ domain,
793
+ _ThreatDetector.StorageKeys.ALLOWLIST
794
+ );
795
+ yield this.invalidateDomainInCache(
796
+ domain,
797
+ _ThreatDetector.StorageKeys.BLOCKLIST
798
+ );
799
+ });
800
+ }
801
+ invalidateDomainInCache(domain, key) {
802
+ return __async(this, null, function* () {
803
+ const data = yield this.storage.get(key);
804
+ if (!data) {
805
+ return;
806
+ }
807
+ const list = yield this.getListFromStorage(key);
808
+ if (!list.data.domains.includes(domain)) {
809
+ return;
810
+ }
811
+ list.data.domains = list.data.domains.filter((u) => u !== domain);
812
+ yield this.setListInStorage(key, list);
813
+ });
814
+ }
815
+ addDomainToCache(domain, key) {
816
+ return __async(this, null, function* () {
817
+ const list = yield this.getListFromStorage(key);
818
+ if (list.data.domains.includes(domain)) {
819
+ return;
820
+ }
821
+ list.data.domains.push(domain);
822
+ yield this.setListInStorage(key, list);
823
+ });
824
+ }
825
+ isDomainInList(domain, key) {
826
+ return __async(this, null, function* () {
827
+ const list = yield this.getListFromStorage(key);
828
+ return list.data.domains.includes(domain);
829
+ });
830
+ }
831
+ getListFromStorage(key) {
832
+ return __async(this, null, function* () {
833
+ const data = yield this.storage.get(key);
834
+ if (!data) {
835
+ const list2 = {
836
+ version: 1,
837
+ data: {
838
+ domains: []
839
+ }
840
+ };
841
+ yield this.setListInStorage(key, list2);
842
+ return list2;
843
+ }
844
+ const list = _ThreatDetector.Schema.parse(JSON.parse(data));
845
+ return list;
846
+ });
847
+ }
848
+ setListInStorage(key, list) {
849
+ return __async(this, null, function* () {
850
+ yield this.storage.set(key, JSON.stringify(list));
851
+ });
852
+ }
853
+ getStatusFromCache(domain) {
854
+ return __async(this, null, function* () {
855
+ if (yield this.isDomainInList(domain, _ThreatDetector.StorageKeys.IGNORELIST)) {
856
+ return "IGNORED";
857
+ } else if (yield this.isDomainInList(domain, _ThreatDetector.StorageKeys.BLOCKLIST)) {
858
+ return "BLOCKED";
859
+ } else if (yield this.isDomainInList(domain, _ThreatDetector.StorageKeys.ALLOWLIST)) {
860
+ return "ALLOWED";
861
+ } else {
862
+ return "UNKNOWN";
863
+ }
864
+ });
865
+ }
866
+ };
867
+ _ThreatDetector.StorageKeys = {
868
+ ALLOWLIST: "chainpatrol.allowed",
869
+ BLOCKLIST: "chainpatrol.blocked",
870
+ IGNORELIST: "chainpatrol.ignored"
871
+ };
872
+ _ThreatDetector.Schema = zod.z.object({
873
+ version: zod.z.literal(1),
874
+ data: zod.z.object({
875
+ domains: zod.z.array(zod.z.string())
876
+ })
877
+ });
878
+ var ThreatDetector = _ThreatDetector;
879
+
880
+ exports.ChainPatrolClient = ChainPatrolClient;
881
+ exports.DomainParseError = DomainParseError;
882
+ exports.Events = Events;
883
+ exports.Relay = Relay;
884
+ exports.Storage = storage_exports;
885
+ exports.ThreatDetector = ThreatDetector;
886
+ //# sourceMappingURL=out.js.map
887
+ //# sourceMappingURL=index.js.map