diff --git a/lib/reactotron-react-native/src/helpers/parseURL.test.ts b/lib/reactotron-react-native/src/helpers/parseURL.test.ts new file mode 100644 index 000000000..0afa09357 --- /dev/null +++ b/lib/reactotron-react-native/src/helpers/parseURL.test.ts @@ -0,0 +1,71 @@ +import { getHostFromUrl } from "./parseURL" + +describe("getHostFromUrl", () => { + it("should throw when no host is found", () => { + expect(() => { + getHostFromUrl("") + }).toThrow() + }) + + it("should get host from URL without scheme", () => { + Object.entries({ + localhost: "localhost", + "127.0.0.1": "127.0.0.1", + "[::1]": "[::1]", + }).forEach(([host, url]) => { + expect(getHostFromUrl(url)).toEqual(host) + }) + expect(getHostFromUrl("localhost")).toEqual("localhost") + expect(getHostFromUrl("127.0.0.1")).toEqual("127.0.0.1") + }) + + it("should get the host from URL with http scheme", () => { + Object.entries({ + localhost: "http://localhost", + "example.com": "http://example.com", + }).forEach(([host, url]) => { + expect(getHostFromUrl(url)).toEqual(host) + }) + }) + + it("should get the host from URL with https scheme", () => { + Object.entries({ + localhost: "https://localhost", + "example.com": "https://example.com", + }).forEach(([host, url]) => { + expect(getHostFromUrl(url)).toEqual(host) + }) + }) + + it("should get the host from URL and ignore path, port, and query params", () => { + Object.entries({ + localhost: + "http://localhost:8081/.expo/.virtual-metro-entry.bundle?platform=ios&dev=true&lazy=true&minify=false&inlineSourceMap=false&modulesOnly=false&runModule=true&app=com.reactotronapp", + "192.168.1.141": + "https://192.168.1.141:8081/.expo/.virtual-metro-entry.bundle?platform=ios&dev=true&lazy=true&minify=false&inlineSourceMap=false&modulesOnly=false&runModule=true&app=com.reactotronapp", + }).forEach(([host, url]) => { + expect(getHostFromUrl(url)).toEqual(host) + }) + }) + + it("should get the host from an IPv6 URL and ignore path, port, and query params", () => { + Object.entries({ + "[::1]": + "http://[::1]:8081/.expo/.virtual-metro-entry.bundle?platform=ios&dev=true&lazy=true&minify=false&inlineSourceMap=false&modulesOnly=false&runModule=true&app=com.reactotronapp", + "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]": + "https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8081/.expo/.virtual-metro-entry.bundle?platform=ios&dev=true&lazy=true&minify=false&inlineSourceMap=false&modulesOnly=false&runModule=true&app=com.reactotronapp", + }).forEach(([host, url]) => { + expect(getHostFromUrl(url)).toEqual(host) + }) + }) + + it("should get the host from URL with hyphens", () => { + expect(getHostFromUrl("https://example-app.com")).toEqual("example-app.com") + }) + + it("should throw when the URL is an unsupported scheme", () => { + expect(() => { + getHostFromUrl("file:///Users/tron") + }).toThrow() + }) +}) diff --git a/lib/reactotron-react-native/src/helpers/parseURL.ts b/lib/reactotron-react-native/src/helpers/parseURL.ts new file mode 100644 index 000000000..863adc1e4 --- /dev/null +++ b/lib/reactotron-react-native/src/helpers/parseURL.ts @@ -0,0 +1,19 @@ +/** + * Given a valid http(s) URL, the host for the given URL + * is returned. + * + * @param url {string} URL to extract the host from + * @returns {string} host of given URL or throws + */ +// Using a capture group to extract the hostname from a URL +export function getHostFromUrl(url: string) { + // Group 1: http(s):// + // Group 2: host + // Group 3: port + // Group 4: rest + const host = url.match(/^(?:https?:\/\/)?(\[[^\]]+\]|[^/:\s]+)(?::\d+)?(?:[/?#]|$)/)?.[1] + + if (typeof host !== "string") throw new Error("Invalid URL - host not found") + + return host +} diff --git a/lib/reactotron-react-native/src/reactotron-react-native.ts b/lib/reactotron-react-native/src/reactotron-react-native.ts index 5e333c848..bf80b8119 100644 --- a/lib/reactotron-react-native/src/reactotron-react-native.ts +++ b/lib/reactotron-react-native/src/reactotron-react-native.ts @@ -19,6 +19,7 @@ import networking, { NetworkingOptions } from "./plugins/networking" import storybook from "./plugins/storybook" import devTools from "./plugins/devTools" import trackGlobalLogs from "./plugins/trackGlobalLogs" +import { getHostFromUrl } from "./helpers/parseURL" const constants = NativeModules.PlatformConstants || {} @@ -33,13 +34,18 @@ let tempClientId: string | null = null * * On an Android emulator, if you want to connect any servers of local, you will need run adb reverse on your terminal. This function gets the localhost IP of host machine directly to bypass this. */ -const getHost = (defaultHost = "localhost") => - typeof NativeModules?.SourceCode?.getConstants().scriptURL === "string" // type guard in case this ever breaks https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/NativeModules/specs/NativeSourceCode.js#L15-L21 - ? NativeModules.SourceCode.scriptURL // Example: 'http://192.168.0.100:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.helloworld' - .split("://")[1] // Remove the scheme: '192.168.0.100:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=com.helloworld' - .split("/")[0] // Remove the path: '192.168.0.100:8081' - .split(":")[0] // Remove the port: '192.168.0.100' - : defaultHost +const getHost = (defaultHost = "localhost") => { + try { + // RN Reference: https://github.com/facebook/react-native/blob/main/packages/react-native/src/private/specs/modules/NativeSourceCode.js + const scriptURL = NativeModules?.SourceCode?.getConstants().scriptURL + if (typeof scriptURL !== "string") throw new Error("Invalid non-string URL") + + return getHostFromUrl(scriptURL) + } catch (error) { + console.warn(`getHost: "${error.message}" for scriptURL - Falling back to ${defaultHost}`) + return defaultHost + } +} const DEFAULTS: ClientOptions = { createSocket: (path: string) => new WebSocket(path), // eslint-disable-line diff --git a/scripts/reset.sh b/scripts/reset.sh index 796369505..be19fb3cc 100644 --- a/scripts/reset.sh +++ b/scripts/reset.sh @@ -1,5 +1,8 @@ #!/bin/bash +echo "Nx Reset - Clears all the cached Nx artifacts and metadata about the workspace and shuts down the Nx Daemon." +yarn nx reset + sh scripts/clean.sh echo "Removing all node_modules folders from the project"