@zuplo/cli 7.9.8 → 7.9.11
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/node_modules/@inquirer/ansi/README.md +35 -3
- package/node_modules/@inquirer/ansi/package.json +2 -2
- package/node_modules/@inquirer/checkbox/README.md +75 -5
- package/node_modules/@inquirer/checkbox/package.json +5 -5
- package/node_modules/@inquirer/confirm/README.md +81 -5
- package/node_modules/@inquirer/confirm/dist/index.d.ts +10 -0
- package/node_modules/@inquirer/confirm/dist/index.js +29 -6
- package/node_modules/@inquirer/confirm/package.json +4 -4
- package/node_modules/@inquirer/core/README.md +35 -3
- package/node_modules/@inquirer/core/dist/lib/__probe/p1.d.ts +9 -0
- package/node_modules/@inquirer/core/dist/lib/__probe/p1.js +4 -0
- package/node_modules/@inquirer/core/dist/lib/__probe/p2.d.ts +9 -0
- package/node_modules/@inquirer/core/dist/lib/__probe/p2.js +3 -0
- package/node_modules/@inquirer/core/dist/lib/__probe/p3.d.ts +9 -0
- package/node_modules/@inquirer/core/dist/lib/__probe/p3.js +5 -0
- package/node_modules/@inquirer/core/dist/lib/create-prompt.js +4 -3
- package/node_modules/@inquirer/core/dist/lib/hook-engine.js +8 -10
- package/node_modules/@inquirer/core/dist/lib/make-theme.js +2 -2
- package/node_modules/@inquirer/core/package.json +4 -4
- package/node_modules/@inquirer/editor/README.md +75 -5
- package/node_modules/@inquirer/editor/package.json +5 -5
- package/node_modules/@inquirer/expand/README.md +75 -5
- package/node_modules/@inquirer/expand/dist/index.js +3 -3
- package/node_modules/@inquirer/expand/package.json +4 -4
- package/node_modules/@inquirer/external-editor/README.md +35 -3
- package/node_modules/@inquirer/external-editor/package.json +2 -2
- package/node_modules/@inquirer/input/README.md +75 -5
- package/node_modules/@inquirer/input/package.json +4 -4
- package/node_modules/@inquirer/number/README.md +75 -5
- package/node_modules/@inquirer/number/package.json +4 -4
- package/node_modules/@inquirer/password/README.md +75 -5
- package/node_modules/@inquirer/password/package.json +5 -5
- package/node_modules/@inquirer/rawlist/README.md +75 -5
- package/node_modules/@inquirer/rawlist/dist/index.js +5 -4
- package/node_modules/@inquirer/rawlist/package.json +4 -4
- package/node_modules/@inquirer/search/README.md +74 -4
- package/node_modules/@inquirer/search/dist/index.js +1 -1
- package/node_modules/@inquirer/search/package.json +4 -4
- package/node_modules/@inquirer/select/README.md +75 -5
- package/node_modules/@inquirer/select/package.json +5 -5
- package/node_modules/@inquirer/type/dist/utils.d.ts +11 -0
- package/node_modules/@inquirer/type/dist/utils.js +15 -1
- package/node_modules/@inquirer/type/package.json +2 -2
- package/node_modules/@modelcontextprotocol/client/README.md +1 -7
- package/node_modules/@modelcontextprotocol/client/package.json +3 -3
- package/node_modules/@modelcontextprotocol/core/package.json +1 -1
- package/node_modules/@modelcontextprotocol/sdk/README.md +8 -2
- package/node_modules/@modelcontextprotocol/sdk/package.json +1 -1
- package/node_modules/@modelcontextprotocol/server/README.md +1 -7
- package/node_modules/@modelcontextprotocol/server/package.json +3 -3
- package/node_modules/@posthog/core/dist/utils/bot-detection.d.ts +7 -1
- package/node_modules/@posthog/core/dist/utils/bot-detection.d.ts.map +1 -1
- package/node_modules/@posthog/core/dist/utils/bot-detection.js +25 -5
- package/node_modules/@posthog/core/dist/utils/bot-detection.mjs +21 -5
- package/node_modules/@posthog/core/package.json +1 -1
- package/node_modules/@posthog/core/src/utils/bot-detection.spec.ts +100 -0
- package/node_modules/@posthog/core/src/utils/bot-detection.ts +48 -4
- package/node_modules/@zuplo/core/customer.cli.minified.js +156 -156
- package/node_modules/@zuplo/core/index.minified.js +160 -160
- package/node_modules/@zuplo/core/package.json +1 -1
- package/node_modules/@zuplo/graphql/package.json +1 -1
- package/node_modules/@zuplo/openapi-tools/package.json +1 -1
- package/node_modules/@zuplo/otel/package.json +1 -1
- package/node_modules/@zuplo/runtime/out/esm/{chunk-6O4OZCO6.js → chunk-7AHIER5R.js} +2 -2
- package/node_modules/@zuplo/runtime/out/esm/{chunk-WFAFGL5Y.js → chunk-DDUIJEX3.js} +67 -67
- package/node_modules/@zuplo/runtime/out/esm/chunk-DDUIJEX3.js.map +1 -0
- package/node_modules/@zuplo/runtime/out/esm/chunk-KESHY2P3.js +37 -0
- package/node_modules/@zuplo/runtime/out/esm/chunk-KESHY2P3.js.map +1 -0
- package/node_modules/@zuplo/runtime/out/esm/{chunk-54HRCPVQ.js → chunk-QTXBMT6O.js} +2 -2
- package/node_modules/@zuplo/runtime/out/esm/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/internal/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/mocks/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/sdks/aws/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/types/index.d.ts +12 -0
- package/node_modules/@zuplo/runtime/out/types/internal/index.d.ts +12 -0
- package/node_modules/@zuplo/runtime/package.json +1 -1
- package/node_modules/fast-copy/README.md +8 -7
- package/node_modules/fast-copy/dist/cjs/index.cjs +1 -1
- package/node_modules/fast-copy/dist/cjs/index.cjs.map +1 -1
- package/node_modules/fast-copy/dist/es/index.mjs +1 -1
- package/node_modules/fast-copy/dist/es/index.mjs.map +1 -1
- package/node_modules/fast-copy/dist/umd/index.js +1 -1
- package/node_modules/fast-copy/dist/umd/index.js.map +1 -1
- package/node_modules/fast-copy/package.json +1 -1
- package/package.json +12 -12
- package/node_modules/@inquirer/core/dist/lib/promise-polyfill.d.ts +0 -7
- package/node_modules/@inquirer/core/dist/lib/promise-polyfill.js +0 -14
- package/node_modules/@zuplo/runtime/out/esm/chunk-MWIJG5KC.js +0 -37
- package/node_modules/@zuplo/runtime/out/esm/chunk-MWIJG5KC.js.map +0 -1
- package/node_modules/@zuplo/runtime/out/esm/chunk-WFAFGL5Y.js.map +0 -1
- /package/node_modules/@zuplo/runtime/out/esm/{chunk-6O4OZCO6.js.map → chunk-7AHIER5R.js.map} +0 -0
- /package/node_modules/@zuplo/runtime/out/esm/{chunk-WFAFGL5Y.js.LEGAL.txt → chunk-DDUIJEX3.js.LEGAL.txt} +0 -0
- /package/node_modules/@zuplo/runtime/out/esm/{chunk-MWIJG5KC.js.LEGAL.txt → chunk-KESHY2P3.js.LEGAL.txt} +0 -0
- /package/node_modules/@zuplo/runtime/out/esm/{chunk-54HRCPVQ.js.map → chunk-QTXBMT6O.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inquirer/type",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Inquirer core TS types",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"answer",
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
},
|
|
85
85
|
"main": "./dist/index.js",
|
|
86
86
|
"types": "./dist/index.d.ts",
|
|
87
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "a9436bd94d55a439df308f50e77a7cd0b2078e98"
|
|
88
88
|
}
|
|
@@ -2,13 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The MCP (Model Context Protocol) TypeScript client SDK. Build MCP clients that connect to MCP servers.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
> [!WARNING]
|
|
7
|
-
> **v2 is the stable release line**, implementing the [2026-07-28 MCP spec](https://modelcontextprotocol.io/specification/2026-07-28). Migrating from v1? Start with the [migration guide](https://ts.sdk.modelcontextprotocol.io/v2/migration/).
|
|
8
|
-
|
|
9
|
-
<!-- prettier-ignore -->
|
|
10
|
-
> [!NOTE]
|
|
11
|
-
> This is **v2** of the MCP TypeScript SDK. It replaces the monolithic `@modelcontextprotocol/sdk` package from v1. See the **[migration guide](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/migration/upgrade-to-v2.md)** if you're coming from v1.
|
|
5
|
+
> **This is v2 of the MCP TypeScript SDK, the stable release line**, implementing the [2026-07-28 MCP spec](https://modelcontextprotocol.io/specification/2026-07-28). It replaces the single `@modelcontextprotocol/sdk` package from v1. Start with the [documentation](https://ts.sdk.modelcontextprotocol.io/v2/); coming from v1, see the [migration guide](https://ts.sdk.modelcontextprotocol.io/v2/migration/upgrade-to-v2). Found a problem? [Open an issue](https://github.com/modelcontextprotocol/typescript-sdk/issues/new?template=v2-feedback.yml).
|
|
12
6
|
|
|
13
7
|
## Install
|
|
14
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelcontextprotocol/client",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Model Context Protocol implementation for TypeScript - Client package",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"jose": "^6.1.3",
|
|
129
129
|
"pkce-challenge": "^5.0.0",
|
|
130
130
|
"zod": "^4.2.0",
|
|
131
|
-
"@modelcontextprotocol/core": "2.3.
|
|
131
|
+
"@modelcontextprotocol/core": "2.3.1"
|
|
132
132
|
},
|
|
133
133
|
"devDependencies": {
|
|
134
134
|
"@cfworker/json-schema": "^4.1.1",
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
"typescript-eslint": "^8.48.1",
|
|
148
148
|
"vitest": "^4.0.15",
|
|
149
149
|
"tsdown": "^0.18.0",
|
|
150
|
-
"@modelcontextprotocol/core-internal": "^2.0.
|
|
150
|
+
"@modelcontextprotocol/core-internal": "^2.0.4",
|
|
151
151
|
"@modelcontextprotocol/tsconfig": "^2.0.0",
|
|
152
152
|
"@modelcontextprotocol/vitest-config": "^2.0.0",
|
|
153
153
|
"@modelcontextprotocol/eslint-config": "^2.0.0",
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
# MCP TypeScript SDK [](https://www.npmjs.com/package/@modelcontextprotocol/sdk) [](https://github.com/modelcontextprotocol/typescript-sdk/blob/
|
|
1
|
+
# MCP TypeScript SDK [](https://www.npmjs.com/package/@modelcontextprotocol/sdk) [](https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.x/LICENSE)
|
|
2
|
+
|
|
3
|
+
> **This is v1.x of the MCP TypeScript SDK, the maintenance line.** It implements the MCP spec up to 2025-11-25. Support for the [2026-07-28 spec](https://modelcontextprotocol.io/specification/2026-07-28) is not planned for v1.x: it is in v2, the current stable release, which
|
|
4
|
+
> ships as separate packages, [`@modelcontextprotocol/server`](https://www.npmjs.com/package/@modelcontextprotocol/server) and [`@modelcontextprotocol/client`](https://www.npmjs.com/package/@modelcontextprotocol/client). See the
|
|
5
|
+
> [v2 documentation](https://ts.sdk.modelcontextprotocol.io/v2/) and the [migration guide](https://ts.sdk.modelcontextprotocol.io/v2/migration/upgrade-to-v2).
|
|
6
|
+
>
|
|
7
|
+
> **Staying on v1.x?** It continues to receive bug fixes and security updates for at least six months after the v2 release (2026-07-27). The v1 documentation is at <https://ts.sdk.modelcontextprotocol.io/>.
|
|
2
8
|
|
|
3
9
|
<details>
|
|
4
10
|
<summary>Table of Contents</summary>
|
|
@@ -17,7 +23,7 @@
|
|
|
17
23
|
## Overview
|
|
18
24
|
|
|
19
25
|
The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements
|
|
20
|
-
[the
|
|
26
|
+
[the 2025-11-25 MCP specification](https://modelcontextprotocol.io/specification/2025-11-25), making it easy to:
|
|
21
27
|
|
|
22
28
|
- Create MCP servers that expose resources, prompts and tools
|
|
23
29
|
- Build MCP clients that can connect to any MCP server
|
|
@@ -2,13 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The MCP (Model Context Protocol) TypeScript server SDK. Build MCP servers that expose tools, resources, and prompts.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
> [!WARNING]
|
|
7
|
-
> **v2 is the stable release line**, implementing the [2026-07-28 MCP spec](https://modelcontextprotocol.io/specification/2026-07-28). Migrating from v1? Start with the [migration guide](https://ts.sdk.modelcontextprotocol.io/v2/migration/).
|
|
8
|
-
|
|
9
|
-
<!-- prettier-ignore -->
|
|
10
|
-
> [!NOTE]
|
|
11
|
-
> This is **v2** of the MCP TypeScript SDK. It replaces the monolithic `@modelcontextprotocol/sdk` package from v1. See the **[migration guide](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/migration/upgrade-to-v2.md)** if you're coming from v1.
|
|
5
|
+
> **This is v2 of the MCP TypeScript SDK, the stable release line**, implementing the [2026-07-28 MCP spec](https://modelcontextprotocol.io/specification/2026-07-28). It replaces the single `@modelcontextprotocol/sdk` package from v1. Start with the [documentation](https://ts.sdk.modelcontextprotocol.io/v2/); coming from v1, see the [migration guide](https://ts.sdk.modelcontextprotocol.io/v2/migration/upgrade-to-v2). Found a problem? [Open an issue](https://github.com/modelcontextprotocol/typescript-sdk/issues/new?template=v2-feedback.yml).
|
|
12
6
|
|
|
13
7
|
## Install
|
|
14
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelcontextprotocol/server",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Model Context Protocol implementation for TypeScript - Server package",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
],
|
|
124
124
|
"dependencies": {
|
|
125
125
|
"zod": "^4.2.0",
|
|
126
|
-
"@modelcontextprotocol/core": "2.3.
|
|
126
|
+
"@modelcontextprotocol/core": "2.3.1"
|
|
127
127
|
},
|
|
128
128
|
"devDependencies": {
|
|
129
129
|
"@cfworker/json-schema": "^4.1.1",
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
"typescript": "^5.9.3",
|
|
142
142
|
"typescript-eslint": "^8.48.1",
|
|
143
143
|
"vitest": "^4.0.15",
|
|
144
|
-
"@modelcontextprotocol/core-internal": "^2.0.
|
|
144
|
+
"@modelcontextprotocol/core-internal": "^2.0.4",
|
|
145
145
|
"@modelcontextprotocol/eslint-config": "^2.0.0",
|
|
146
146
|
"@modelcontextprotocol/test-helpers": "^2.0.0",
|
|
147
147
|
"@modelcontextprotocol/tsconfig": "^2.0.0",
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export declare const DEFAULT_BLOCKED_UA_STRS: string[];
|
|
2
|
+
/** Exposed for tests only. Not part of the public API. */
|
|
3
|
+
export declare function __resetBotDetectionCacheForTests(): void;
|
|
2
4
|
/**
|
|
3
|
-
* Block various web spiders from executing our JS and sending false capturing data
|
|
5
|
+
* Block various web spiders from executing our JS and sending false capturing data.
|
|
6
|
+
*
|
|
7
|
+
* @param ua - User agent string to check
|
|
8
|
+
* @param customBlockedUserAgents - Additional UA substrings to block
|
|
9
|
+
* @returns true if the UA matches a known-bot substring
|
|
4
10
|
*/
|
|
5
11
|
export declare const isBlockedUA: (ua: string | undefined, customBlockedUserAgents?: string[]) => boolean;
|
|
6
12
|
//# sourceMappingURL=bot-detection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bot-detection.d.ts","sourceRoot":"","sources":["../../src/utils/bot-detection.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,uBAAuB,UAmGnC,CAAA;
|
|
1
|
+
{"version":3,"file":"bot-detection.d.ts","sourceRoot":"","sources":["../../src/utils/bot-detection.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,uBAAuB,UAmGnC,CAAA;AAeD,0DAA0D;AAC1D,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD;AAaD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,OAAiB,MAAM,GAAG,SAAS,4BAA2B,MAAM,EAAE,KAAQ,OAsBrG,CAAA"}
|
|
@@ -106,22 +106,42 @@ const DEFAULT_BLOCKED_UA_STRS = [
|
|
|
106
106
|
'google-inspectiontool',
|
|
107
107
|
'bytespider'
|
|
108
108
|
];
|
|
109
|
+
const UA_CACHE = new Map();
|
|
110
|
+
const MAX_CACHE_ENTRIES = 256;
|
|
111
|
+
function __resetBotDetectionCacheForTests() {
|
|
112
|
+
UA_CACHE.clear();
|
|
113
|
+
}
|
|
114
|
+
function buildCacheKey(ua, custom) {
|
|
115
|
+
let customEncoded = '';
|
|
116
|
+
for(let i = 0; i < custom.length; i++){
|
|
117
|
+
const c = custom[i];
|
|
118
|
+
customEncoded += c.length + ':' + c;
|
|
119
|
+
}
|
|
120
|
+
return ua.length + ':' + ua + '|' + custom.length + ':' + customEncoded;
|
|
121
|
+
}
|
|
109
122
|
const isBlockedUA = function(ua, customBlockedUserAgents = []) {
|
|
110
123
|
if (!ua) return false;
|
|
124
|
+
const key = buildCacheKey(ua, customBlockedUserAgents);
|
|
125
|
+
const cached = UA_CACHE.get(key);
|
|
126
|
+
if (void 0 !== cached) return cached;
|
|
111
127
|
const uaLower = ua.toLowerCase();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
const result = DEFAULT_BLOCKED_UA_STRS.concat(customBlockedUserAgents).some((blockedUA)=>-1 !== uaLower.indexOf(blockedUA.toLowerCase()));
|
|
129
|
+
if (UA_CACHE.size >= MAX_CACHE_ENTRIES) UA_CACHE.clear();
|
|
130
|
+
UA_CACHE.set(key, result);
|
|
131
|
+
return result;
|
|
116
132
|
};
|
|
117
|
-
__webpack_require__.d(__webpack_exports__, {
|
|
133
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
134
|
+
__resetBotDetectionCacheForTests: ()=>__resetBotDetectionCacheForTests
|
|
135
|
+
}, {
|
|
118
136
|
DEFAULT_BLOCKED_UA_STRS: DEFAULT_BLOCKED_UA_STRS,
|
|
119
137
|
isBlockedUA: isBlockedUA
|
|
120
138
|
});
|
|
121
139
|
exports.DEFAULT_BLOCKED_UA_STRS = __webpack_exports__.DEFAULT_BLOCKED_UA_STRS;
|
|
140
|
+
exports.__resetBotDetectionCacheForTests = __webpack_exports__.__resetBotDetectionCacheForTests;
|
|
122
141
|
exports.isBlockedUA = __webpack_exports__.isBlockedUA;
|
|
123
142
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
124
143
|
"DEFAULT_BLOCKED_UA_STRS",
|
|
144
|
+
"__resetBotDetectionCacheForTests",
|
|
125
145
|
"isBlockedUA"
|
|
126
146
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
127
147
|
Object.defineProperty(exports, '__esModule', {
|
|
@@ -77,12 +77,28 @@ const DEFAULT_BLOCKED_UA_STRS = [
|
|
|
77
77
|
'google-inspectiontool',
|
|
78
78
|
'bytespider'
|
|
79
79
|
];
|
|
80
|
+
const UA_CACHE = new Map();
|
|
81
|
+
const MAX_CACHE_ENTRIES = 256;
|
|
82
|
+
function __resetBotDetectionCacheForTests() {
|
|
83
|
+
UA_CACHE.clear();
|
|
84
|
+
}
|
|
85
|
+
function buildCacheKey(ua, custom) {
|
|
86
|
+
let customEncoded = '';
|
|
87
|
+
for(let i = 0; i < custom.length; i++){
|
|
88
|
+
const c = custom[i];
|
|
89
|
+
customEncoded += c.length + ':' + c;
|
|
90
|
+
}
|
|
91
|
+
return ua.length + ':' + ua + '|' + custom.length + ':' + customEncoded;
|
|
92
|
+
}
|
|
80
93
|
const isBlockedUA = function(ua, customBlockedUserAgents = []) {
|
|
81
94
|
if (!ua) return false;
|
|
95
|
+
const key = buildCacheKey(ua, customBlockedUserAgents);
|
|
96
|
+
const cached = UA_CACHE.get(key);
|
|
97
|
+
if (void 0 !== cached) return cached;
|
|
82
98
|
const uaLower = ua.toLowerCase();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
const result = DEFAULT_BLOCKED_UA_STRS.concat(customBlockedUserAgents).some((blockedUA)=>-1 !== uaLower.indexOf(blockedUA.toLowerCase()));
|
|
100
|
+
if (UA_CACHE.size >= MAX_CACHE_ENTRIES) UA_CACHE.clear();
|
|
101
|
+
UA_CACHE.set(key, result);
|
|
102
|
+
return result;
|
|
87
103
|
};
|
|
88
|
-
export { DEFAULT_BLOCKED_UA_STRS, isBlockedUA };
|
|
104
|
+
export { DEFAULT_BLOCKED_UA_STRS, __resetBotDetectionCacheForTests, isBlockedUA };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { DEFAULT_BLOCKED_UA_STRS, isBlockedUA, __resetBotDetectionCacheForTests } from './bot-detection'
|
|
2
|
+
|
|
3
|
+
beforeEach(() => {
|
|
4
|
+
__resetBotDetectionCacheForTests()
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
const REAL_CHROME_UAS = [
|
|
8
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.193 Safari/537.36',
|
|
9
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.199 Safari/537.36',
|
|
10
|
+
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/123.0.6312.122 Mobile/15E148 Safari/604.1',
|
|
11
|
+
'Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Mobile Safari/537.36',
|
|
12
|
+
// Chrome 154 and 155 stable — released 2026-09.
|
|
13
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.8037.58 Safari/537.36',
|
|
14
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/155.0.8059.12 Safari/537.36',
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
describe('DEFAULT_BLOCKED_UA_STRS', () => {
|
|
18
|
+
it('contains at least 50 entries', () => {
|
|
19
|
+
expect(DEFAULT_BLOCKED_UA_STRS.length).toBeGreaterThan(50)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('contains critical entries', () => {
|
|
23
|
+
expect(DEFAULT_BLOCKED_UA_STRS).toContain('googlebot')
|
|
24
|
+
expect(DEFAULT_BLOCKED_UA_STRS).toContain('headlesschrome')
|
|
25
|
+
expect(DEFAULT_BLOCKED_UA_STRS).toContain('bot/')
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('isBlockedUA', () => {
|
|
30
|
+
it('returns false for undefined ua', () => {
|
|
31
|
+
expect(isBlockedUA(undefined)).toBe(false)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('returns false for real Chrome UAs (including Chrome 154 and 155)', () => {
|
|
35
|
+
for (const ua of REAL_CHROME_UAS) {
|
|
36
|
+
expect(isBlockedUA(ua)).toBe(false)
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('returns true for known bot substrings', () => {
|
|
41
|
+
expect(isBlockedUA('Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)')).toBe(true)
|
|
42
|
+
expect(isBlockedUA('Mozilla/5.0 bingbot/2.0')).toBe(true)
|
|
43
|
+
expect(isBlockedUA('headlessChrome/119.0')).toBe(true)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('is case-insensitive on the UA input', () => {
|
|
47
|
+
const bot = 'Mozilla/5.0 (compatible; Googlebot/2.1)'
|
|
48
|
+
expect(isBlockedUA(bot)).toBe(true)
|
|
49
|
+
expect(isBlockedUA(bot.toLowerCase())).toBe(true)
|
|
50
|
+
expect(isBlockedUA(bot.toUpperCase())).toBe(true)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('accepts customBlockedUserAgents', () => {
|
|
54
|
+
expect(isBlockedUA('custombot', ['custombot'])).toBe(true)
|
|
55
|
+
expect(isBlockedUA('custombot', [])).toBe(false)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('isBlockedUA session memoization', () => {
|
|
60
|
+
it('returns the same result on repeated calls with identical inputs', () => {
|
|
61
|
+
const ua = REAL_CHROME_UAS[0]
|
|
62
|
+
const first = isBlockedUA(ua)
|
|
63
|
+
const second = isBlockedUA(ua)
|
|
64
|
+
expect(second).toBe(first)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('cache is bucketed per customBlockedUserAgents list', () => {
|
|
68
|
+
const ua = 'Mozilla/5.0 custombot'
|
|
69
|
+
expect(isBlockedUA(ua, [])).toBe(false)
|
|
70
|
+
expect(isBlockedUA(ua, ['custombot'])).toBe(true)
|
|
71
|
+
expect(isBlockedUA(ua, [])).toBe(false)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('__resetBotDetectionCacheForTests wipes the cache', () => {
|
|
75
|
+
isBlockedUA(REAL_CHROME_UAS[0])
|
|
76
|
+
__resetBotDetectionCacheForTests()
|
|
77
|
+
// Still correct after reset; this just proves the reset doesn't corrupt state.
|
|
78
|
+
expect(isBlockedUA(REAL_CHROME_UAS[0])).toBe(false)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('handles a burst of many distinct UAs without breaking correctness', () => {
|
|
82
|
+
for (let i = 0; i < 500; i++) {
|
|
83
|
+
const ua = `Mozilla/5.0 (burst-${i}) AppleWebKit/537.36`
|
|
84
|
+
expect(isBlockedUA(ua)).toBe(false)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe('isBlockedUA cache-key delimiter injection', () => {
|
|
90
|
+
it('does not collide when custom list contains delimiter bytes', () => {
|
|
91
|
+
const ua = 'MyAgent/1.0'
|
|
92
|
+
// Length-prefixed keys mean embedded ':' or '|' in list entries cannot
|
|
93
|
+
// forge a key that aliases another (ua, custom[]) pair.
|
|
94
|
+
const a = isBlockedUA(ua, ['foo:1', 'bar'])
|
|
95
|
+
const b = isBlockedUA(ua, ['foo', '1:bar'])
|
|
96
|
+
// Both lookups are independent cache slots and both correctly return false.
|
|
97
|
+
expect(a).toBe(false)
|
|
98
|
+
expect(b).toBe(false)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
@@ -102,17 +102,61 @@ export const DEFAULT_BLOCKED_UA_STRS = [
|
|
|
102
102
|
]
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
*
|
|
105
|
+
* Session-level memoization for isBlockedUA.
|
|
106
|
+
*
|
|
107
|
+
* navigator.userAgent is constant across all events captured in a browser
|
|
108
|
+
* session, and PostHog's autocapture path calls isBlockedUA on every event.
|
|
109
|
+
* Memoizing the boolean result turns an O(N) substring-scan over all N events
|
|
110
|
+
* into O(1) after the first lookup. The map is keyed on (ua, customBlockedUserAgents)
|
|
111
|
+
* — a config change yields a fresh slot. If the cache grows past MAX_CACHE_ENTRIES
|
|
112
|
+
* we drop it entirely (healthy sessions see O(1) distinct keys).
|
|
113
|
+
*/
|
|
114
|
+
const UA_CACHE = new Map<string, boolean>()
|
|
115
|
+
const MAX_CACHE_ENTRIES = 256
|
|
116
|
+
|
|
117
|
+
/** Exposed for tests only. Not part of the public API. */
|
|
118
|
+
export function __resetBotDetectionCacheForTests(): void {
|
|
119
|
+
UA_CACHE.clear()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function buildCacheKey(ua: string, custom: string[]): string {
|
|
123
|
+
// Length-prefixed encoding is collision-free regardless of what bytes
|
|
124
|
+
// appear inside ua or custom entries.
|
|
125
|
+
let customEncoded = ''
|
|
126
|
+
for (let i = 0; i < custom.length; i++) {
|
|
127
|
+
const c = custom[i]
|
|
128
|
+
customEncoded += c.length + ':' + c
|
|
129
|
+
}
|
|
130
|
+
return ua.length + ':' + ua + '|' + custom.length + ':' + customEncoded
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Block various web spiders from executing our JS and sending false capturing data.
|
|
135
|
+
*
|
|
136
|
+
* @param ua - User agent string to check
|
|
137
|
+
* @param customBlockedUserAgents - Additional UA substrings to block
|
|
138
|
+
* @returns true if the UA matches a known-bot substring
|
|
106
139
|
*/
|
|
107
140
|
export const isBlockedUA = function (ua: string | undefined, customBlockedUserAgents: string[] = []): boolean {
|
|
108
141
|
if (!ua) {
|
|
109
142
|
return false
|
|
110
143
|
}
|
|
111
144
|
|
|
145
|
+
const key = buildCacheKey(ua, customBlockedUserAgents)
|
|
146
|
+
const cached = UA_CACHE.get(key)
|
|
147
|
+
if (cached !== undefined) {
|
|
148
|
+
return cached
|
|
149
|
+
}
|
|
150
|
+
|
|
112
151
|
const uaLower = ua.toLowerCase()
|
|
113
|
-
|
|
114
|
-
const blockedUaLower = blockedUA.toLowerCase()
|
|
152
|
+
const result = DEFAULT_BLOCKED_UA_STRS.concat(customBlockedUserAgents).some((blockedUA) => {
|
|
115
153
|
// can't use includes because IE 11 :/
|
|
116
|
-
return uaLower.indexOf(
|
|
154
|
+
return uaLower.indexOf(blockedUA.toLowerCase()) !== -1
|
|
117
155
|
})
|
|
156
|
+
|
|
157
|
+
if (UA_CACHE.size >= MAX_CACHE_ENTRIES) {
|
|
158
|
+
UA_CACHE.clear()
|
|
159
|
+
}
|
|
160
|
+
UA_CACHE.set(key, result)
|
|
161
|
+
return result
|
|
118
162
|
}
|