@bgord/bun 1.6.5 → 1.6.7
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/bots.vo.d.ts +1 -1
- package/dist/bots.vo.d.ts.map +1 -1
- package/dist/bots.vo.js +132 -3
- package/dist/bots.vo.js.map +1 -1
- package/dist/client.vo.d.ts +5 -0
- package/dist/client.vo.d.ts.map +1 -1
- package/dist/client.vo.js +16 -1
- package/dist/client.vo.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/logger-winston-production.adapter.d.ts +0 -1
- package/dist/logger-winston-production.adapter.d.ts.map +1 -1
- package/dist/logger-winston-production.adapter.js +1 -7
- package/dist/logger-winston-production.adapter.js.map +1 -1
- package/dist/modules/system/events/MINUTE_HAS_PASSED_EVENT.d.ts +16 -0
- package/dist/modules/system/events/MINUTE_HAS_PASSED_EVENT.d.ts.map +1 -0
- package/dist/modules/system/events/MINUTE_HAS_PASSED_EVENT.js +10 -0
- package/dist/modules/system/events/MINUTE_HAS_PASSED_EVENT.js.map +1 -0
- package/dist/modules/system/events/index.d.ts +1 -0
- package/dist/modules/system/events/index.d.ts.map +1 -1
- package/dist/modules/system/events/index.js +1 -0
- package/dist/modules/system/events/index.js.map +1 -1
- package/dist/modules/system/index.d.ts +1 -0
- package/dist/modules/system/index.d.ts.map +1 -1
- package/dist/modules/system/index.js +1 -0
- package/dist/modules/system/index.js.map +1 -1
- package/dist/modules/system/services/index.d.ts +3 -0
- package/dist/modules/system/services/index.d.ts.map +1 -0
- package/dist/modules/system/services/index.js +3 -0
- package/dist/modules/system/services/index.js.map +1 -0
- package/dist/modules/system/services/passage-of-time-hourly.service.d.ts +19 -0
- package/dist/modules/system/services/passage-of-time-hourly.service.d.ts.map +1 -0
- package/dist/modules/system/services/passage-of-time-hourly.service.js +21 -0
- package/dist/modules/system/services/passage-of-time-hourly.service.js.map +1 -0
- package/dist/modules/system/services/passage-of-time-minute.service.d.ts +19 -0
- package/dist/modules/system/services/passage-of-time-minute.service.d.ts.map +1 -0
- package/dist/modules/system/services/passage-of-time-minute.service.js +21 -0
- package/dist/modules/system/services/passage-of-time-minute.service.js.map +1 -0
- package/dist/setup.service.d.ts.map +1 -1
- package/dist/setup.service.js +2 -3
- package/dist/setup.service.js.map +1 -1
- package/dist/shield-user-agent-blocker.adapter.d.ts +7 -0
- package/dist/shield-user-agent-blocker.adapter.d.ts.map +1 -0
- package/dist/shield-user-agent-blocker.adapter.js +15 -0
- package/dist/shield-user-agent-blocker.adapter.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -5
- package/readme.md +5 -0
- package/src/bots.vo.ts +132 -3
- package/src/client.vo.ts +21 -1
- package/src/index.ts +1 -0
- package/src/logger-winston-production.adapter.ts +1 -8
- package/src/modules/system/events/MINUTE_HAS_PASSED_EVENT.ts +13 -0
- package/src/modules/system/events/index.ts +1 -0
- package/src/modules/system/index.ts +1 -0
- package/src/modules/system/services/index.ts +2 -0
- package/src/modules/system/services/passage-of-time-hourly.service.ts +37 -0
- package/src/modules/system/services/passage-of-time-minute.service.ts +37 -0
- package/src/setup.service.ts +2 -3
- package/src/shield-user-agent-blocker.adapter.ts +18 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bgord/bun",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Bartosz Gordon",
|
|
@@ -38,18 +38,14 @@
|
|
|
38
38
|
"zod": "4.2.1"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@axiomhq/winston": "1.3.1",
|
|
42
41
|
"@bgord/tools": "1.2.3",
|
|
43
|
-
"@hono/ua-blocker": "0.1.22",
|
|
44
42
|
"better-auth": "1.4.7",
|
|
45
43
|
"croner": "9.1.0",
|
|
46
44
|
"csv": "6.4.1",
|
|
47
45
|
"dotenv": "17.2.3",
|
|
48
46
|
"hcaptcha": "0.2.0",
|
|
49
47
|
"hono": "4.11.1",
|
|
50
|
-
"isomorphic-dompurify": "2.34.0",
|
|
51
48
|
"lodash": "4.17.21",
|
|
52
|
-
"marked": "17.0.1",
|
|
53
49
|
"node-cache": "5.1.2",
|
|
54
50
|
"nodemailer": "7.0.11",
|
|
55
51
|
"winston": "3.19.0",
|
package/readme.md
CHANGED
|
@@ -215,6 +215,10 @@ src/
|
|
|
215
215
|
│ └── system
|
|
216
216
|
│ ├── events
|
|
217
217
|
│ │ ├── HOUR_HAS_PASSED_EVENT.ts
|
|
218
|
+
│ │ ├── MINUTE_HAS_PASSED_EVENT.ts
|
|
219
|
+
│ └── services
|
|
220
|
+
│ ├── passage-of-time-hourly.service.ts
|
|
221
|
+
│ └── passage-of-time-minute.service.ts
|
|
218
222
|
├── node-env.vo.ts
|
|
219
223
|
├── pdf-generator-noop.adapter.ts
|
|
220
224
|
├── pdf-generator.port.ts
|
|
@@ -269,6 +273,7 @@ src/
|
|
|
269
273
|
├── shield-noop.adapter.ts
|
|
270
274
|
├── shield-rate-limit.adapter.ts
|
|
271
275
|
├── shield-timeout.adapter.ts
|
|
276
|
+
├── shield-user-agent-blocker.adapter.ts
|
|
272
277
|
├── shield.port.ts
|
|
273
278
|
├── simulated-error.middleware.ts
|
|
274
279
|
├── slower.middleware.ts
|
package/src/bots.vo.ts
CHANGED
|
@@ -1,3 +1,132 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// Source: https://github.com/honojs/middleware/blob/main/packages/ua-blocker/src/generated.ts
|
|
2
|
+
export const ALL_BOTS = [
|
|
3
|
+
"AddSearchBot",
|
|
4
|
+
"AI2Bot",
|
|
5
|
+
"AI2Bot-DeepResearchEval",
|
|
6
|
+
"Ai2Bot-Dolma",
|
|
7
|
+
"aiHitBot",
|
|
8
|
+
"amazon-kendra",
|
|
9
|
+
"Amazonbot",
|
|
10
|
+
"AmazonBuyForMe",
|
|
11
|
+
"Andibot",
|
|
12
|
+
"Anomura",
|
|
13
|
+
"anthropic-ai",
|
|
14
|
+
"Applebot",
|
|
15
|
+
"Applebot-Extended",
|
|
16
|
+
"atlassian-bot",
|
|
17
|
+
"Awario",
|
|
18
|
+
"bedrockbot",
|
|
19
|
+
"bigsur.ai",
|
|
20
|
+
"Bravebot",
|
|
21
|
+
"Brightbot 1.0",
|
|
22
|
+
"BuddyBot",
|
|
23
|
+
"Bytespider",
|
|
24
|
+
"CCBot",
|
|
25
|
+
"Channel3Bot",
|
|
26
|
+
"ChatGLM-Spider",
|
|
27
|
+
"ChatGPT Agent",
|
|
28
|
+
"ChatGPT-User",
|
|
29
|
+
"Claude-SearchBot",
|
|
30
|
+
"Claude-User",
|
|
31
|
+
"Claude-Web",
|
|
32
|
+
"ClaudeBot",
|
|
33
|
+
"Cloudflare-AutoRAG",
|
|
34
|
+
"CloudVertexBot",
|
|
35
|
+
"cohere-ai",
|
|
36
|
+
"cohere-training-data-crawler",
|
|
37
|
+
"Cotoyogi",
|
|
38
|
+
"Crawl4AI",
|
|
39
|
+
"Crawlspace",
|
|
40
|
+
"Datenbank Crawler",
|
|
41
|
+
"DeepSeekBot",
|
|
42
|
+
"Devin",
|
|
43
|
+
"Diffbot",
|
|
44
|
+
"DuckAssistBot",
|
|
45
|
+
"Echobot Bot",
|
|
46
|
+
"EchoboxBot",
|
|
47
|
+
"FacebookBot",
|
|
48
|
+
"facebookexternalhit",
|
|
49
|
+
"Factset_spyderbot",
|
|
50
|
+
"FirecrawlAgent",
|
|
51
|
+
"FriendlyCrawler",
|
|
52
|
+
"Gemini-Deep-Research",
|
|
53
|
+
"Google-CloudVertexBot",
|
|
54
|
+
"Google-Extended",
|
|
55
|
+
"Google-Firebase",
|
|
56
|
+
"Google-NotebookLM",
|
|
57
|
+
"GoogleAgent-Mariner",
|
|
58
|
+
"GoogleOther",
|
|
59
|
+
"GoogleOther-Image",
|
|
60
|
+
"GoogleOther-Video",
|
|
61
|
+
"GPTBot",
|
|
62
|
+
"iAskBot",
|
|
63
|
+
"iaskspider",
|
|
64
|
+
"iaskspider/2.0",
|
|
65
|
+
"IbouBot",
|
|
66
|
+
"ICC-Crawler",
|
|
67
|
+
"ImagesiftBot",
|
|
68
|
+
"imageSpider",
|
|
69
|
+
"img2dataset",
|
|
70
|
+
"ISSCyberRiskCrawler",
|
|
71
|
+
"Kangaroo Bot",
|
|
72
|
+
"KlaviyoAIBot",
|
|
73
|
+
"KunatoCrawler",
|
|
74
|
+
"laion-huggingface-processor",
|
|
75
|
+
"LAIONDownloader",
|
|
76
|
+
"LCC",
|
|
77
|
+
"LinerBot",
|
|
78
|
+
"Linguee Bot",
|
|
79
|
+
"LinkupBot",
|
|
80
|
+
"Manus-User",
|
|
81
|
+
"meta-externalagent",
|
|
82
|
+
"Meta-ExternalAgent",
|
|
83
|
+
"meta-externalfetcher",
|
|
84
|
+
"Meta-ExternalFetcher",
|
|
85
|
+
"meta-webindexer",
|
|
86
|
+
"MistralAI-User",
|
|
87
|
+
"MistralAI-User/1.0",
|
|
88
|
+
"MyCentralAIScraperBot",
|
|
89
|
+
"netEstate Imprint Crawler",
|
|
90
|
+
"NotebookLM",
|
|
91
|
+
"NovaAct",
|
|
92
|
+
"OAI-SearchBot",
|
|
93
|
+
"omgili",
|
|
94
|
+
"omgilibot",
|
|
95
|
+
"OpenAI",
|
|
96
|
+
"Operator",
|
|
97
|
+
"PanguBot",
|
|
98
|
+
"Panscient",
|
|
99
|
+
"panscient.com",
|
|
100
|
+
"Perplexity-User",
|
|
101
|
+
"PerplexityBot",
|
|
102
|
+
"PetalBot",
|
|
103
|
+
"PhindBot",
|
|
104
|
+
"Poggio-Citations",
|
|
105
|
+
"Poseidon Research Crawler",
|
|
106
|
+
"QualifiedBot",
|
|
107
|
+
"QuillBot",
|
|
108
|
+
"quillbot.com",
|
|
109
|
+
"SBIntuitionsBot",
|
|
110
|
+
"Scrapy",
|
|
111
|
+
"SemrushBot-OCOB",
|
|
112
|
+
"SemrushBot-SWA",
|
|
113
|
+
"ShapBot",
|
|
114
|
+
"Sidetrade indexer bot",
|
|
115
|
+
"Spider",
|
|
116
|
+
"TerraCotta",
|
|
117
|
+
"Thinkbot",
|
|
118
|
+
"TikTokSpider",
|
|
119
|
+
"Timpibot",
|
|
120
|
+
"TwinAgent",
|
|
121
|
+
"VelenPublicWebCrawler",
|
|
122
|
+
"WARDBot",
|
|
123
|
+
"Webzio-Extended",
|
|
124
|
+
"webzio-extended",
|
|
125
|
+
"wpbot",
|
|
126
|
+
"WRTNBot",
|
|
127
|
+
"YaK",
|
|
128
|
+
"YandexAdditional",
|
|
129
|
+
"YandexAdditionalBot",
|
|
130
|
+
"YouBot",
|
|
131
|
+
"ZanistaBot",
|
|
132
|
+
];
|
package/src/client.vo.ts
CHANGED
|
@@ -9,7 +9,7 @@ export class Client {
|
|
|
9
9
|
private constructor(private readonly value: ClientType) {}
|
|
10
10
|
|
|
11
11
|
static fromParts(ip: ClientIpType | null | undefined, ua: ClientUaType | null | undefined): Client {
|
|
12
|
-
return new Client({ ip: ip ?? "anon", ua: ua ?? "anon" });
|
|
12
|
+
return new Client({ ip: ip ?? "anon", ua: (ua ?? "anon").toLowerCase() });
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
static fromHonoContext(context: Context): Client {
|
|
@@ -23,6 +23,26 @@ export class Client {
|
|
|
23
23
|
return Client.fromParts(ip, ua);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
equals(another: Client): boolean {
|
|
27
|
+
return this.value.ip === another.value.ip && this.value.ua === another.value.ua;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
matchesUa(ua: ClientUaType): boolean {
|
|
31
|
+
return this.value.ua.includes(ua.toLowerCase());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
matchesIp(ip: ClientIpType): boolean {
|
|
35
|
+
return this.value.ip.includes(ip.toLowerCase());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get ip(): ClientIpType {
|
|
39
|
+
return this.value.ip;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get ua(): ClientUaType {
|
|
43
|
+
return this.value.ua;
|
|
44
|
+
}
|
|
45
|
+
|
|
26
46
|
toJSON(): ClientType {
|
|
27
47
|
return this.value;
|
|
28
48
|
}
|
package/src/index.ts
CHANGED
|
@@ -187,6 +187,7 @@ export * from "./shield-captcha-recaptcha.adapter";
|
|
|
187
187
|
export * from "./shield-noop.adapter";
|
|
188
188
|
export * from "./shield-rate-limit.adapter";
|
|
189
189
|
export * from "./shield-timeout.adapter";
|
|
190
|
+
export * from "./shield-user-agent-blocker.adapter";
|
|
190
191
|
export * from "./simulated-error.middleware";
|
|
191
192
|
export * from "./slower.middleware";
|
|
192
193
|
export * from "./static-files.service";
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { WinstonTransport as AxiomTransport } from "@axiomhq/winston";
|
|
2
1
|
import * as tools from "@bgord/tools";
|
|
3
2
|
import * as winston from "winston";
|
|
4
3
|
import type { LogAppType, LoggerPort, LogLevelEnum } from "./logger.port";
|
|
@@ -8,7 +7,6 @@ import type { RedactorPort } from "./redactor.port";
|
|
|
8
7
|
|
|
9
8
|
type LoggerWinstonProductionAdapterConfigType = {
|
|
10
9
|
app: LogAppType;
|
|
11
|
-
AXIOM_API_TOKEN?: string;
|
|
12
10
|
redactor: RedactorPort;
|
|
13
11
|
};
|
|
14
12
|
|
|
@@ -31,12 +29,7 @@ export class LoggerWinstonProductionAdapter {
|
|
|
31
29
|
app: this.config.app,
|
|
32
30
|
environment: NodeEnvironmentEnum.production,
|
|
33
31
|
level,
|
|
34
|
-
transports: [
|
|
35
|
-
file,
|
|
36
|
-
this.config.AXIOM_API_TOKEN
|
|
37
|
-
? new AxiomTransport({ token: this.config.AXIOM_API_TOKEN, dataset: this.config.app })
|
|
38
|
-
: undefined,
|
|
39
|
-
].filter((adapter) => adapter !== undefined),
|
|
32
|
+
transports: [file],
|
|
40
33
|
redactor: this.config.redactor,
|
|
41
34
|
filePath,
|
|
42
35
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as tools from "@bgord/tools";
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
|
+
import { EventEnvelopeSchema } from "../../../event-envelope";
|
|
4
|
+
|
|
5
|
+
export const MINUTE_HAS_PASSED_EVENT = "MINUTE_HAS_PASSED_EVENT";
|
|
6
|
+
|
|
7
|
+
export const MinuteHasPassedEvent = z.object({
|
|
8
|
+
...EventEnvelopeSchema,
|
|
9
|
+
name: z.literal(MINUTE_HAS_PASSED_EVENT),
|
|
10
|
+
payload: z.object({ timestamp: tools.TimestampValue }),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export type MinuteHasPassedEventType = z.infer<typeof MinuteHasPassedEvent>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ClockPort } from "../../../clock.port";
|
|
2
|
+
import { createEventEnvelope } from "../../../event-envelope";
|
|
3
|
+
import type { EventStoreLike } from "../../../event-store-like.types";
|
|
4
|
+
import type { IdProviderPort } from "../../../id-provider.port";
|
|
5
|
+
import type { UnitOfWork } from "../../../job-handler.port";
|
|
6
|
+
import { Jobs } from "../../../jobs.service";
|
|
7
|
+
import {
|
|
8
|
+
HOUR_HAS_PASSED_EVENT,
|
|
9
|
+
HourHasPassedEvent,
|
|
10
|
+
type HourHasPassedEventType,
|
|
11
|
+
} from "../events/HOUR_HAS_PASSED_EVENT";
|
|
12
|
+
|
|
13
|
+
type Dependencies = {
|
|
14
|
+
EventStore: EventStoreLike<HourHasPassedEventType>;
|
|
15
|
+
Clock: ClockPort;
|
|
16
|
+
IdProvider: IdProviderPort;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export class PassageOfTimeHourly implements UnitOfWork {
|
|
20
|
+
constructor(private readonly deps: Dependencies) {}
|
|
21
|
+
|
|
22
|
+
static cron = Jobs.SCHEDULES.EVERY_HOUR;
|
|
23
|
+
|
|
24
|
+
label = "PassageOfTime";
|
|
25
|
+
|
|
26
|
+
async process() {
|
|
27
|
+
const timestamp = this.deps.Clock.nowMs();
|
|
28
|
+
|
|
29
|
+
const event = HourHasPassedEvent.parse({
|
|
30
|
+
...createEventEnvelope("passage_of_time", this.deps),
|
|
31
|
+
name: HOUR_HAS_PASSED_EVENT,
|
|
32
|
+
payload: { timestamp },
|
|
33
|
+
} satisfies HourHasPassedEventType);
|
|
34
|
+
|
|
35
|
+
await this.deps.EventStore.save([event]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ClockPort } from "../../../clock.port";
|
|
2
|
+
import { createEventEnvelope } from "../../../event-envelope";
|
|
3
|
+
import type { EventStoreLike } from "../../../event-store-like.types";
|
|
4
|
+
import type { IdProviderPort } from "../../../id-provider.port";
|
|
5
|
+
import type { UnitOfWork } from "../../../job-handler.port";
|
|
6
|
+
import { Jobs } from "../../../jobs.service";
|
|
7
|
+
import {
|
|
8
|
+
MINUTE_HAS_PASSED_EVENT,
|
|
9
|
+
MinuteHasPassedEvent,
|
|
10
|
+
type MinuteHasPassedEventType,
|
|
11
|
+
} from "../events/MINUTE_HAS_PASSED_EVENT";
|
|
12
|
+
|
|
13
|
+
type Dependencies = {
|
|
14
|
+
EventStore: EventStoreLike<MinuteHasPassedEventType>;
|
|
15
|
+
Clock: ClockPort;
|
|
16
|
+
IdProvider: IdProviderPort;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export class PassageOfTimeMinute implements UnitOfWork {
|
|
20
|
+
constructor(private readonly deps: Dependencies) {}
|
|
21
|
+
|
|
22
|
+
static cron = Jobs.SCHEDULES.EVERY_MINUTE;
|
|
23
|
+
|
|
24
|
+
label = "PassageOfTime";
|
|
25
|
+
|
|
26
|
+
async process() {
|
|
27
|
+
const timestamp = this.deps.Clock.nowMs();
|
|
28
|
+
|
|
29
|
+
const event = MinuteHasPassedEvent.parse({
|
|
30
|
+
...createEventEnvelope("passage_of_time", this.deps),
|
|
31
|
+
name: MINUTE_HAS_PASSED_EVENT,
|
|
32
|
+
payload: { timestamp },
|
|
33
|
+
} satisfies MinuteHasPassedEventType);
|
|
34
|
+
|
|
35
|
+
await this.deps.EventStore.save([event]);
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/setup.service.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as tools from "@bgord/tools";
|
|
2
|
-
import { uaBlocker } from "@hono/ua-blocker";
|
|
3
2
|
import { bodyLimit } from "hono/body-limit";
|
|
4
3
|
import { cors } from "hono/cors";
|
|
5
4
|
import { languageDetector } from "hono/language";
|
|
@@ -7,7 +6,6 @@ import { requestId } from "hono/request-id";
|
|
|
7
6
|
import { secureHeaders } from "hono/secure-headers";
|
|
8
7
|
import { timing } from "hono/timing";
|
|
9
8
|
import { ApiVersion } from "./api-version.middleware";
|
|
10
|
-
import { BOTS_REGEX } from "./bots.vo";
|
|
11
9
|
import type { ClockPort } from "./clock.port";
|
|
12
10
|
import { Context } from "./context.middleware";
|
|
13
11
|
import { CorrelationStorage } from "./correlation-storage.service";
|
|
@@ -17,6 +15,7 @@ import type { I18nConfigType } from "./i18n.service";
|
|
|
17
15
|
import type { IdProviderPort } from "./id-provider.port";
|
|
18
16
|
import type { JsonFileReaderPort } from "./json-file-reader.port";
|
|
19
17
|
import type { LoggerPort } from "./logger.port";
|
|
18
|
+
import { ShieldUserAgentBlockerAdapter } from "./shield-user-agent-blocker.adapter";
|
|
20
19
|
import { TimeZoneOffset } from "./time-zone-offset.middleware";
|
|
21
20
|
import { WeakETagExtractor } from "./weak-etag-extractor.middleware";
|
|
22
21
|
|
|
@@ -44,7 +43,7 @@ export class Setup {
|
|
|
44
43
|
return [
|
|
45
44
|
secureHeaders(secureHeadersOptions),
|
|
46
45
|
bodyLimit({ maxSize: BODY_LIMIT_MAX_SIZE }),
|
|
47
|
-
|
|
46
|
+
new ShieldUserAgentBlockerAdapter().verify,
|
|
48
47
|
ApiVersion.build({ Clock: deps.Clock, JsonFileReader: deps.JsonFileReader }),
|
|
49
48
|
cors(corsOptions),
|
|
50
49
|
languageDetector({
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createMiddleware } from "hono/factory";
|
|
2
|
+
import { HTTPException } from "hono/http-exception";
|
|
3
|
+
import { ALL_BOTS } from "./bots.vo";
|
|
4
|
+
import { Client } from "./client.vo";
|
|
5
|
+
import type { ShieldPort } from "./shield.port";
|
|
6
|
+
|
|
7
|
+
export const UserAgentBlockedError = new HTTPException(403, { message: "app.user.agent.blocked.error" });
|
|
8
|
+
|
|
9
|
+
export class ShieldUserAgentBlockerAdapter implements ShieldPort {
|
|
10
|
+
verify = createMiddleware(async (context, next) => {
|
|
11
|
+
const client = Client.fromHonoContext(context);
|
|
12
|
+
|
|
13
|
+
const detection = ALL_BOTS.some((bot) => client.matchesUa(bot));
|
|
14
|
+
|
|
15
|
+
if (!detection) return next();
|
|
16
|
+
throw UserAgentBlockedError;
|
|
17
|
+
});
|
|
18
|
+
}
|