@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.
Files changed (95) hide show
  1. package/node_modules/@inquirer/ansi/README.md +35 -3
  2. package/node_modules/@inquirer/ansi/package.json +2 -2
  3. package/node_modules/@inquirer/checkbox/README.md +75 -5
  4. package/node_modules/@inquirer/checkbox/package.json +5 -5
  5. package/node_modules/@inquirer/confirm/README.md +81 -5
  6. package/node_modules/@inquirer/confirm/dist/index.d.ts +10 -0
  7. package/node_modules/@inquirer/confirm/dist/index.js +29 -6
  8. package/node_modules/@inquirer/confirm/package.json +4 -4
  9. package/node_modules/@inquirer/core/README.md +35 -3
  10. package/node_modules/@inquirer/core/dist/lib/__probe/p1.d.ts +9 -0
  11. package/node_modules/@inquirer/core/dist/lib/__probe/p1.js +4 -0
  12. package/node_modules/@inquirer/core/dist/lib/__probe/p2.d.ts +9 -0
  13. package/node_modules/@inquirer/core/dist/lib/__probe/p2.js +3 -0
  14. package/node_modules/@inquirer/core/dist/lib/__probe/p3.d.ts +9 -0
  15. package/node_modules/@inquirer/core/dist/lib/__probe/p3.js +5 -0
  16. package/node_modules/@inquirer/core/dist/lib/create-prompt.js +4 -3
  17. package/node_modules/@inquirer/core/dist/lib/hook-engine.js +8 -10
  18. package/node_modules/@inquirer/core/dist/lib/make-theme.js +2 -2
  19. package/node_modules/@inquirer/core/package.json +4 -4
  20. package/node_modules/@inquirer/editor/README.md +75 -5
  21. package/node_modules/@inquirer/editor/package.json +5 -5
  22. package/node_modules/@inquirer/expand/README.md +75 -5
  23. package/node_modules/@inquirer/expand/dist/index.js +3 -3
  24. package/node_modules/@inquirer/expand/package.json +4 -4
  25. package/node_modules/@inquirer/external-editor/README.md +35 -3
  26. package/node_modules/@inquirer/external-editor/package.json +2 -2
  27. package/node_modules/@inquirer/input/README.md +75 -5
  28. package/node_modules/@inquirer/input/package.json +4 -4
  29. package/node_modules/@inquirer/number/README.md +75 -5
  30. package/node_modules/@inquirer/number/package.json +4 -4
  31. package/node_modules/@inquirer/password/README.md +75 -5
  32. package/node_modules/@inquirer/password/package.json +5 -5
  33. package/node_modules/@inquirer/rawlist/README.md +75 -5
  34. package/node_modules/@inquirer/rawlist/dist/index.js +5 -4
  35. package/node_modules/@inquirer/rawlist/package.json +4 -4
  36. package/node_modules/@inquirer/search/README.md +74 -4
  37. package/node_modules/@inquirer/search/dist/index.js +1 -1
  38. package/node_modules/@inquirer/search/package.json +4 -4
  39. package/node_modules/@inquirer/select/README.md +75 -5
  40. package/node_modules/@inquirer/select/package.json +5 -5
  41. package/node_modules/@inquirer/type/dist/utils.d.ts +11 -0
  42. package/node_modules/@inquirer/type/dist/utils.js +15 -1
  43. package/node_modules/@inquirer/type/package.json +2 -2
  44. package/node_modules/@modelcontextprotocol/client/README.md +1 -7
  45. package/node_modules/@modelcontextprotocol/client/package.json +3 -3
  46. package/node_modules/@modelcontextprotocol/core/package.json +1 -1
  47. package/node_modules/@modelcontextprotocol/sdk/README.md +8 -2
  48. package/node_modules/@modelcontextprotocol/sdk/package.json +1 -1
  49. package/node_modules/@modelcontextprotocol/server/README.md +1 -7
  50. package/node_modules/@modelcontextprotocol/server/package.json +3 -3
  51. package/node_modules/@posthog/core/dist/utils/bot-detection.d.ts +7 -1
  52. package/node_modules/@posthog/core/dist/utils/bot-detection.d.ts.map +1 -1
  53. package/node_modules/@posthog/core/dist/utils/bot-detection.js +25 -5
  54. package/node_modules/@posthog/core/dist/utils/bot-detection.mjs +21 -5
  55. package/node_modules/@posthog/core/package.json +1 -1
  56. package/node_modules/@posthog/core/src/utils/bot-detection.spec.ts +100 -0
  57. package/node_modules/@posthog/core/src/utils/bot-detection.ts +48 -4
  58. package/node_modules/@zuplo/core/customer.cli.minified.js +156 -156
  59. package/node_modules/@zuplo/core/index.minified.js +160 -160
  60. package/node_modules/@zuplo/core/package.json +1 -1
  61. package/node_modules/@zuplo/graphql/package.json +1 -1
  62. package/node_modules/@zuplo/openapi-tools/package.json +1 -1
  63. package/node_modules/@zuplo/otel/package.json +1 -1
  64. package/node_modules/@zuplo/runtime/out/esm/{chunk-6O4OZCO6.js → chunk-7AHIER5R.js} +2 -2
  65. package/node_modules/@zuplo/runtime/out/esm/{chunk-WFAFGL5Y.js → chunk-DDUIJEX3.js} +67 -67
  66. package/node_modules/@zuplo/runtime/out/esm/chunk-DDUIJEX3.js.map +1 -0
  67. package/node_modules/@zuplo/runtime/out/esm/chunk-KESHY2P3.js +37 -0
  68. package/node_modules/@zuplo/runtime/out/esm/chunk-KESHY2P3.js.map +1 -0
  69. package/node_modules/@zuplo/runtime/out/esm/{chunk-54HRCPVQ.js → chunk-QTXBMT6O.js} +2 -2
  70. package/node_modules/@zuplo/runtime/out/esm/index.js +1 -1
  71. package/node_modules/@zuplo/runtime/out/esm/internal/index.js +1 -1
  72. package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js +1 -1
  73. package/node_modules/@zuplo/runtime/out/esm/mocks/index.js +1 -1
  74. package/node_modules/@zuplo/runtime/out/esm/sdks/aws/index.js +1 -1
  75. package/node_modules/@zuplo/runtime/out/types/index.d.ts +12 -0
  76. package/node_modules/@zuplo/runtime/out/types/internal/index.d.ts +12 -0
  77. package/node_modules/@zuplo/runtime/package.json +1 -1
  78. package/node_modules/fast-copy/README.md +8 -7
  79. package/node_modules/fast-copy/dist/cjs/index.cjs +1 -1
  80. package/node_modules/fast-copy/dist/cjs/index.cjs.map +1 -1
  81. package/node_modules/fast-copy/dist/es/index.mjs +1 -1
  82. package/node_modules/fast-copy/dist/es/index.mjs.map +1 -1
  83. package/node_modules/fast-copy/dist/umd/index.js +1 -1
  84. package/node_modules/fast-copy/dist/umd/index.js.map +1 -1
  85. package/node_modules/fast-copy/package.json +1 -1
  86. package/package.json +12 -12
  87. package/node_modules/@inquirer/core/dist/lib/promise-polyfill.d.ts +0 -7
  88. package/node_modules/@inquirer/core/dist/lib/promise-polyfill.js +0 -14
  89. package/node_modules/@zuplo/runtime/out/esm/chunk-MWIJG5KC.js +0 -37
  90. package/node_modules/@zuplo/runtime/out/esm/chunk-MWIJG5KC.js.map +0 -1
  91. package/node_modules/@zuplo/runtime/out/esm/chunk-WFAFGL5Y.js.map +0 -1
  92. /package/node_modules/@zuplo/runtime/out/esm/{chunk-6O4OZCO6.js.map → chunk-7AHIER5R.js.map} +0 -0
  93. /package/node_modules/@zuplo/runtime/out/esm/{chunk-WFAFGL5Y.js.LEGAL.txt → chunk-DDUIJEX3.js.LEGAL.txt} +0 -0
  94. /package/node_modules/@zuplo/runtime/out/esm/{chunk-MWIJG5KC.js.LEGAL.txt → chunk-KESHY2P3.js.LEGAL.txt} +0 -0
  95. /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.1.1",
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": "5748bd9966b5e175ddf5995ee4cdce6f60c4272e"
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
- <!-- prettier-ignore -->
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.0",
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.0"
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.3",
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelcontextprotocol/core",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "Model Context Protocol for TypeScript — public Zod schemas (spec + OAuth/OpenID)",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Anthropic, PBC (https://anthropic.com)",
@@ -1,4 +1,10 @@
1
- # MCP TypeScript SDK [![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fsdk)](https://www.npmjs.com/package/@modelcontextprotocol/sdk) [![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%2Fsdk)](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/LICENSE)
1
+ # MCP TypeScript SDK [![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fsdk)](https://www.npmjs.com/package/@modelcontextprotocol/sdk) [![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%2Fsdk)](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 full MCP specification](https://modelcontextprotocol.io/specification/draft), making it easy to:
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelcontextprotocol/sdk",
3
- "version": "1.32.0",
3
+ "version": "1.32.1",
4
4
  "description": "Model Context Protocol implementation for TypeScript",
5
5
  "license": "MIT",
6
6
  "author": "Anthropic, PBC (https://anthropic.com)",
@@ -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
- <!-- prettier-ignore -->
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.0",
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.0"
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.3",
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;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,OAAiB,MAAM,GAAG,SAAS,4BAA2B,MAAM,EAAE,KAAQ,OAWrG,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
- return DEFAULT_BLOCKED_UA_STRS.concat(customBlockedUserAgents).some((blockedUA)=>{
113
- const blockedUaLower = blockedUA.toLowerCase();
114
- return -1 !== uaLower.indexOf(blockedUaLower);
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
- return DEFAULT_BLOCKED_UA_STRS.concat(customBlockedUserAgents).some((blockedUA)=>{
84
- const blockedUaLower = blockedUA.toLowerCase();
85
- return -1 !== uaLower.indexOf(blockedUaLower);
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 };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/core",
3
- "version": "1.55.3",
3
+ "version": "1.56.0",
4
4
  "bugs": {
5
5
  "url": "https://github.com/PostHog/posthog-js/issues"
6
6
  },
@@ -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
- * Block various web spiders from executing our JS and sending false capturing data
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
- return DEFAULT_BLOCKED_UA_STRS.concat(customBlockedUserAgents).some((blockedUA) => {
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(blockedUaLower) !== -1
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
  }