Skip to content

Commit 0ecaa1d

Browse files
fix(reactotron-app): fix unescaped regex symbols crash (#1550 by @camilossantos2809)
## Please verify the following: - [x] `yarn build-and-test:local` passes - [x] I have added tests for any new features, if relevant - [ ] `README.md` (or relevant documentation) has been updated with your changes ## Describe your PR This PR resolves a crash caused by unescaped regex symbols in the timeline search. - Added a try/catch block to prevent rendering interruptions. - Introduced a utility function to escape regex special characters and integrated it into the existing filter function. - Now the filter function doesn't throw an error and is able to search using the special symbols. Fixes #1541 ![Screenshot 2025-03-15 at 12 19 45](https://github.com/user-attachments/assets/b7317324-46c5-4082-b1c1-f6f453a60c5f)
1 parent ff19a47 commit 0ecaa1d

File tree

5 files changed

+52
-2
lines changed

5 files changed

+52
-2
lines changed

Diff for: apps/reactotron-app/src/renderer/pages/timeline/index.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,13 @@ function Timeline() {
9090
setHiddenCommands,
9191
} = useContext(TimelineContext)
9292

93-
let filteredCommands = filterCommands(commands, search, hiddenCommands)
93+
let filteredCommands
94+
try {
95+
filteredCommands = filterCommands(commands, search, hiddenCommands)
96+
} catch (error) {
97+
console.error(error)
98+
filteredCommands = commands
99+
}
94100

95101
if (isReversed) {
96102
filteredCommands = filteredCommands.reverse()
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { escapeRegex } from "./escape-regex"
2+
3+
it("should return same string when no special characters", () => {
4+
expect(escapeRegex("hello")).toBe("hello")
5+
})
6+
it("should escape dot character", () => {
7+
expect(escapeRegex("hello.world")).toBe("hello\\.world")
8+
})
9+
it("should escape multiple special characters", () => {
10+
expect(escapeRegex("(test)*")).toBe("\\(test\\)\\*")
11+
})
12+
it("should escape all regex special characters", () => {
13+
const specialChars = ".*+?^${}()|[]\\"
14+
const expected = "\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\"
15+
expect(escapeRegex(specialChars)).toBe(expected)
16+
})
17+
it("should handle empty string", () => {
18+
expect(escapeRegex("")).toBe("")
19+
})

Diff for: lib/reactotron-core-ui/src/utils/escape-regex.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Escapes special characters in a string to be used in a regular expression.
3+
*
4+
* @param str The string in which to escape special regex characters.
5+
* @returns A new string with special regex characters escaped.
6+
* @example
7+
* escapeRegex("foo.bar") // => "foo\.bar"
8+
*/
9+
export function escapeRegex(str: string) {
10+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
11+
}

Diff for: lib/reactotron-core-ui/src/utils/filterCommands/filterCommands.test.ts

+13
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ const TEST_COMMANDS = [
1010
{ type: "ADUMMYOBJ", payload: { triggerType: "SEARCHTRIGGERTYPE" } },
1111
{ type: "ADUMMYOBJ", payload: { description: "SEARCHDESCRIPTION" } },
1212
{ type: "ADUMMYOBJ", payload: { request: { url: "SEARCHURL" } } },
13+
{ type: "REGEX", payload: { message: "[1234] Log text" } },
14+
{ type: "REGEX", payload: { message: "123 Log text" } },
15+
{ type: "REGEX", payload: { message: "Log text (123)" } },
1316
{ type: "log", payload: { debug: "LOGDEBUG" } },
1417
{ type: "client.intro", payload: { connection: "SEARCHCONNECTION" } },
1518
{
@@ -189,6 +192,16 @@ const TESTS = [
189192
},
190193
],
191194
},
195+
{
196+
name: "search that results in a invalid regex",
197+
search: "[123",
198+
result: [{ type: "REGEX", payload: { message: "[1234] Log text" } }],
199+
},
200+
{
201+
name: "another search that results in a invalid regex",
202+
search: "123)",
203+
result: [{ type: "REGEX", payload: { message: "Log text (123)" } }],
204+
},
192205
]
193206

194207
describe("utils/filterCommands", () => {

Diff for: lib/reactotron-core-ui/src/utils/filterCommands/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { CommandType } from "reactotron-core-contract"
22
import type { CommandTypeKey } from "reactotron-core-contract"
3+
import { escapeRegex } from "../escape-regex"
34

45
function path(...searchPath) {
56
return (obj) => {
@@ -32,7 +33,7 @@ export function filterSearch(commands: any[], search: string) {
3233

3334
if (trimmedSearch === "") return [...commands]
3435

35-
const searchRegex = new RegExp(trimmedSearch.replace(/\s/, "."), "i")
36+
const searchRegex = new RegExp(escapeRegex(trimmedSearch).replace(/\s/, "."), "i")
3637

3738
const matching = (value: string) => {
3839
if (!value) {

0 commit comments

Comments
 (0)