@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/README.md +5 -0
- package/dist/index.d.ts +225 -0
- package/dist/index.js +887 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +880 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +29 -0
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
|