diff --git a/.changeset/bright-dryers-march.md b/.changeset/bright-dryers-march.md new file mode 100644 index 000000000000..b7ea86c368ae --- /dev/null +++ b/.changeset/bright-dryers-march.md @@ -0,0 +1,5 @@ +--- +"@refinedev/core": patch +--- + +Bump `@refinedev/devtools-internal` dependency to reflect the latest changes in the Refine Devtools. diff --git a/.changeset/shiny-planes-obey.md b/.changeset/shiny-planes-obey.md new file mode 100644 index 000000000000..d4e7c29c0c22 --- /dev/null +++ b/.changeset/shiny-planes-obey.md @@ -0,0 +1,11 @@ +--- +"@refinedev/cli": patch +--- + +fix(cli): avoid polluting `process.env` with unwanted environment variables + +Previously, the `@refinedev/cli` used `dotenv` to load environment variables from `.env` files and populate `process.env`. This caused issues when the users app has a different convention for environment variables, e.g. `.env.development`, `.env.production`, etc. + +Now, the `@refinedev/cli` will read the file but avoid populating `process.env` with the variables and keep the values in its scope without passing them to the child processes. This will prevent unwanted environment variables from being passed to the child processes and avoid conflicts with the user's environment variables. + +[Resolves #5803](https://github.com/refinedev/refine/issues/5803) diff --git a/.changeset/twenty-months-bathe.md b/.changeset/twenty-months-bathe.md new file mode 100644 index 000000000000..fd91b0298fc4 --- /dev/null +++ b/.changeset/twenty-months-bathe.md @@ -0,0 +1,26 @@ +--- +"@refinedev/cli": patch +"@refinedev/devtools-server": minor +"@refinedev/devtools-shared": minor +"@refinedev/devtools-ui": minor +"@refinedev/devtools": minor +"@refinedev/devtools-internal": minor +--- + +feat(devtools): ability to change the port of the devtools server + +Now users can change the port of the devtools server by setting the `REFINE_DEVTOOLS_PORT` environment variable. Previously, the port was hardcoded to "5001" and could not be changed. + +If you're using `@refinedev/cli`'s runner commands to start your development server, `REFINE_DEVTOOLS_PORT` will be propagated to your app with appropriate prefix. E.g. if you're using Vite, the environment variable will be `VITE_REFINE_DEVTOOLS_PORT` and it will be used by the `@refinedev/devtools`'s `` component to connect to the devtools server. + +- In Next.js apps, it will be prefixed with `NEXT_PUBLIC_` +- In Craco and Create React App apps, it will be prefixed with `REACT_APP_` +- In Remix apps and other custom setups, the environment variable will be used as is. + +In some scenarios where the environment variables are not passed to the browser, you may need to manually set the Refine Devtools URL in the `` component via the `url` prop. Remix apps do not automatically pass environment variables to the browser, so you will need to set the URL manually. If not set, the default URL will be used. + +While the port can be changed, this feature also allows users to host the devtools server on a different machine or domain and provide the `` with the custom domain URL. This such case will be useful if you're dockerizing your app and devtools server separately. + +**Enterprise Edition**: Refine Devtools running on ports other than "5001" is only available in the Enterprise Edition. If you're using the Community Edition, Refine Devtools will not work if the port is changed. + +[Resolves #5111](https://github.com/refinedev/refine/issues/5111) diff --git a/documentation/docs/enterprise-edition/devtools/docker.tsx b/documentation/docs/enterprise-edition/devtools/docker.tsx new file mode 100644 index 000000000000..874c5d8d64c3 --- /dev/null +++ b/documentation/docs/enterprise-edition/devtools/docker.tsx @@ -0,0 +1,203 @@ +import React from "react"; +import { Sandpack } from "@site/src/components/sandpack"; + +export default function DockerExample() { + return ( + + ); +} + +const AppTsxCode = /* jsx */ ` +import { Refine } from "@refinedev/core"; +import { DevtoolsProvider, DevtoolsPanel } from "@refinedev/devtools"; + +export default function App() { + return ( + + + {/* ... */} + + + + ) +} +`.trim(); + +const PackageJsonCode = /* json */ ` +{ + "name": "my-app", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "refine dev --devtools false -- --host", + "devtools": "refine devtools", + "refine": "refine" + }, + "dependencies": { + "@refinedev/cli": "^2.16.36", + "@refinedev/core": "^4.53.0", + "@refinedev/devtools": "^1.2.6" + } +} +`.trim(); + +const DockerfileDevCode = /* dockerfile */ ` +# We're setting up our development server and running it on port 5173. +# We'll then use Nginx to reverse proxy the requests to the correct services. +# We're running the application at port 5173 and we'll access it via http://my-app.local. + +# Use the official Node.js image as a parent image +FROM refinedev/node + +# Copy the package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the rest of the application +COPY . . + +# Expose the port the app runs on +EXPOSE 5173 + +# Command to run the development server +CMD ["npm", "run", "dev"] +`.trim(); + +const DockerfileDevtoolsCode = /* dockerfile */ ` +# We're setting up our Devtools server and running it on port 5001. +# We'll then use Nginx to reverse proxy the requests to the correct services. +# We're running devtools at port 5001 and we'll access it via http://devtools.local. + +# Use the Refine's Node.js image as a parent image +FROM refinedev/node + +# Copy the package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the rest of the application +COPY . . + +# Expose the port the devtools server runs on +EXPOSE 5001 + +# Command to run the devtools server +CMD ["npm", "run", "devtools"] +`.trim(); + +const DockerComposeYmlCode = /* yaml */ ` +# We're setting up a development environment with two services: dev and devtools. +# The dev service is the main service that runs the application. +# The devtools service is the service that runs the Refine Devtools. +version: "3" +services: + dev: + build: + context: . + dockerfile: Dockerfile.dev + volumes: + - app:/app/refine + - /app/refine/node_modules + networks: + - dev-network + devtools: + build: + context: . + dockerfile: Dockerfile.devtools + volumes: + - app:/app/refine + - /app/refine/node_modules + networks: + - dev-network + nginx: + image: nginx:latest + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + networks: + - dev-network +networks: + dev-network: + driver: bridge +volumes: + app: +`.trim(); + +const NginxConfCode = /* nginx */ ` +# We're setting up a reverse proxy to map the requests to the correct services. +# Then we'll add the necessary aliases to the /etc/hosts file to make the services accessible via the domain names. +events { + worker_connections 1024; +} +http { + server { + listen 80; + server_name my-app.local; + location / { + proxy_pass http://dev:5173; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } + server { + listen 80; + server_name devtools.local; + location / { + proxy_pass http://devtools:5001; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} +`.trim(); diff --git a/documentation/docs/enterprise-edition/devtools/index.md b/documentation/docs/enterprise-edition/devtools/index.md new file mode 100644 index 000000000000..3ec406b47d33 --- /dev/null +++ b/documentation/docs/enterprise-edition/devtools/index.md @@ -0,0 +1,52 @@ +--- +title: Devtools +--- + +import DockerUsage from "./docker.tsx"; + +# Refine Devtools + +In addition to the features provided by the [Refine Devtools](/docs/guides-concepts/development/#using-devtools), Enterprise Edition allows you to change the port of the devtools server or use a custom domain for the devtools server. This is useful if you're dockerizing your app and devtools server separately or using multiple Refine apps and want to use Refine Devtools for multiple apps. + +## Specifying the Port + +You can be using `@refinedev/cli` or `@refinedev/devtools-server` to start the devtools server. Both of these tools will respect the `REFINE_DEVTOOLS_PORT` environment variable. Changing the port is as simple as setting the `REFINE_DEVTOOLS_PORT` environment variable to the desired port number. + +```bash +REFINE_DEVTOOLS_PORT=5002 refine dev +``` + +When `REFINE_DEVTOOLS_PORT` is set in `refine dev` command, it will be automatically propagated to your App and made available as an environment variable. The environment variable will automatically be used by the `` component of the `@refinedev/devtools` to connect to the devtools server. If the environment variable is not working in the browser or you want to use a custom domain, you can manually set the URL in the `` component via the `url` prop. + +```tsx title="App.tsx" +import Refine from "@refinedev/core"; +import { DevtoolsProvider, DevtoolsPanel } from "@refinedev/devtools"; + +const App = () => { + return ( + + + {/* ... */} + + + ); +}; +``` + +## Using Custom Domains with Docker + +In this example, we'll dockerize a Refine app and Refine Devtools separately and serve them on `http://my-app.local` and `http://devtools.local` respectively. After our setup is complete, we'll use the `url` prop of the `` component to connect to the devtools server. + + + +Then, we'll need to update our `/etc/hosts` file to point `my-app.local` and `devtools.local` to `127.0.0.1`, + +```txt +127.0.0.1 my-app.local +127.0.0.1 devtools.local +``` + +That's it! Now you can run your Refine app and Refine Devtools separately in Docker containers and connect to the devtools server using the custom domain. Notice that we're only changing one line in our `App.tsx` file to use the custom domain, rest will be handled by the `@refinedev/devtools` package. diff --git a/documentation/docs/guides-concepts/development/index.md b/documentation/docs/guides-concepts/development/index.md index 92e8a9bbc042..c90d0f1e9255 100644 --- a/documentation/docs/guides-concepts/development/index.md +++ b/documentation/docs/guides-concepts/development/index.md @@ -244,8 +244,13 @@ import { DevtoolsPanel, DevtoolsProvider } from "@refinedev/devtools"; const App = () => { return ( - // highlight-next-line - + {/* highlight-start */} + + {/* highlight-end */} @@ -277,7 +282,13 @@ As an alternative, you can also install the `@refinedev/devtools-server` package **Required Ports** -Devtools server will run on port `5001`. Devtools will serve HTTP and WebSocket connections on this port. Make sure the port is available on your machine. +Devtools server will run on port `5001` by default. Devtools will serve HTTP and WebSocket connections on this port. If you want to change the port, you can set the `REFINE_DEVTOOLS_PORT` environment variable to the desired port number. + +:::simple Enterprise Edition + +Refine Devtools running on ports other than "5001" is only available in the Enterprise Edition. If you're using the Community Edition, Refine Devtools will not work if the port is changed. Checkout [Refine Devtools in Enterprise Edition](/docs/enterprise-edition/devtools) for more information. + +::: ## Using Inferencer diff --git a/documentation/sidebars.js b/documentation/sidebars.js index ef37db728eb2..61f1ed177791 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -894,7 +894,10 @@ module.exports = { type: "category", label: "Enterprise Edition", className: "category-as-header", - items: ["enterprise-edition/okta/index"], + items: [ + "enterprise-edition/okta/index", + "enterprise-edition/devtools/index", + ], }, // Migration Guide { diff --git a/packages/cli/src/commands/runner/dev/index.ts b/packages/cli/src/commands/runner/dev/index.ts index 0ec97265076c..acc670e936d4 100644 --- a/packages/cli/src/commands/runner/dev/index.ts +++ b/packages/cli/src/commands/runner/dev/index.ts @@ -1,12 +1,13 @@ import { ProjectTypes } from "@definitions/projectTypes"; -import { getProjectType } from "@utils/project"; +import { getDevtoolsEnvKeyByProjectType, getProjectType } from "@utils/project"; import { type Command, Option } from "commander"; import { updateNotifier } from "src/update-notifier"; import { devtoolsRunner } from "src/commands/devtools"; import { projectScripts } from "../projectScripts"; import { runScript } from "../runScript"; import { getPlatformOptionDescription, getRunnerDescription } from "../utils"; -import { isDevtoolsInstalled } from "@utils/package"; +import { isDevtoolsInstalled } from "src/utils/package"; +import { ENV } from "src/utils/env"; const dev = (program: Command) => { return program @@ -44,6 +45,8 @@ const action = async ( await updateNotifier(); + const devtoolsPortEnvKey = getDevtoolsEnvKeyByProjectType(projectType); + const devtoolsDefault = await isDevtoolsInstalled(); const devtools = params.devtools === "false" ? false : devtoolsDefault; @@ -52,7 +55,12 @@ const action = async ( devtoolsRunner({ exitOnError: false }); } - runScript(binPath, command); + const envWithDevtoolsPort = + devtools && ENV.REFINE_DEVTOOLS_PORT + ? { [devtoolsPortEnvKey]: ENV.REFINE_DEVTOOLS_PORT } + : undefined; + + runScript(binPath, command, envWithDevtoolsPort); }; export default dev; diff --git a/packages/cli/src/commands/runner/runScript.ts b/packages/cli/src/commands/runner/runScript.ts index 45361caa7f41..da7c9491ed84 100644 --- a/packages/cli/src/commands/runner/runScript.ts +++ b/packages/cli/src/commands/runner/runScript.ts @@ -1,7 +1,12 @@ import { ProjectTypes } from "@definitions/projectTypes"; +import { ENV } from "@utils/env"; import execa from "execa"; -export const runScript = async (binPath: string, args: string[]) => { +export const runScript = async ( + binPath: string, + args: string[], + env: Record = {}, +) => { if (binPath === "unknown") { const supportedProjectTypes = Object.values(ProjectTypes) .filter((v) => v !== "unknown") @@ -19,6 +24,8 @@ export const runScript = async (binPath: string, args: string[]) => { windowsHide: false, env: { FORCE_COLOR: "true", + REFINE_NO_TELEMETRY: ENV.REFINE_NO_TELEMETRY, + ...env, ...process.env, }, }); diff --git a/packages/cli/src/utils/env/index.ts b/packages/cli/src/utils/env/index.ts index b282ecb8d1ed..bfab2e017de7 100644 --- a/packages/cli/src/utils/env/index.ts +++ b/packages/cli/src/utils/env/index.ts @@ -1,6 +1,9 @@ import type { NODE_ENV } from "@definitions/node"; import * as dotenv from "dotenv"; -dotenv.config(); + +const refineEnv: Record = {}; + +dotenv.config({ processEnv: refineEnv }); const envSearchMap: Record, RegExp> = { development: /dev/i, @@ -30,17 +33,16 @@ export const getNodeEnv = (): NODE_ENV => { return env; }; +const getEnvValue = (key: string): string | undefined => { + return process.env[key] || refineEnv[key]; +}; + export const ENV = { NODE_ENV: getNodeEnv(), - REFINE_NO_TELEMETRY: process.env.REFINE_NO_TELEMETRY || "false", + REFINE_NO_TELEMETRY: getEnvValue("REFINE_NO_TELEMETRY") || "false", UPDATE_NOTIFIER_IS_DISABLED: - process.env.UPDATE_NOTIFIER_IS_DISABLED || "false", + getEnvValue("UPDATE_NOTIFIER_IS_DISABLED") || "false", UPDATE_NOTIFIER_CACHE_TTL: - process.env.UPDATE_NOTIFIER_CACHE_TTL || 1000 * 60 * 60 * 24, // 24 hours, - REFINE_PROXY_DOMAIN: process.env.REFINE_PROXY_DOMAIN || "https://refine.dev", - REFINE_PROXY_TARGET: - process.env.REFINE_PROXY_TARGET || "http://localhost:3000", - REFINE_PROXY_PORT: process.env.REFINE_PROXY_PORT || "7313", - REFINE_PROXY_REWRITE_URL: - process.env.REFINE_REWRITE_URL || "http://localhost:7313", + getEnvValue("UPDATE_NOTIFIER_CACHE_TTL") || 1000 * 60 * 60 * 24, + REFINE_DEVTOOLS_PORT: getEnvValue("REFINE_DEVTOOLS_PORT"), }; diff --git a/packages/cli/src/utils/project/index.ts b/packages/cli/src/utils/project/index.ts index e6ce32857dda..5536218659ff 100644 --- a/packages/cli/src/utils/project/index.ts +++ b/packages/cli/src/utils/project/index.ts @@ -77,3 +77,18 @@ export const getUIFramework = (): UIFrameworks | undefined => { return; }; + +export const getDevtoolsEnvKeyByProjectType = ( + projectType: ProjectTypes, +): string => { + switch (projectType) { + case ProjectTypes.REACT_SCRIPT: + return "REACT_APP_REFINE_DEVTOOLS_PORT"; + case ProjectTypes.NEXTJS: + return "NEXT_PUBLIC_REFINE_DEVTOOLS_PORT"; + case ProjectTypes.VITE: + return "VITE_REFINE_DEVTOOLS_PORT"; + default: + return "REFINE_DEVTOOLS_PORT"; + } +}; diff --git a/packages/devtools-server/package.json b/packages/devtools-server/package.json index 780e95b7e917..c9e195b62de8 100644 --- a/packages/devtools-server/package.json +++ b/packages/devtools-server/package.json @@ -51,6 +51,7 @@ "boxen": "^5.1.2", "chalk": "^4.1.2", "dedent": "^0.7.0", + "dotenv": "^16.0.3", "error-stack-parser": "^2.1.4", "execa": "^5.1.1", "express": "^4.18.2", diff --git a/packages/devtools-server/src/constants.ts b/packages/devtools-server/src/constants.ts index 6989a7e67649..09d3f5c3c137 100644 --- a/packages/devtools-server/src/constants.ts +++ b/packages/devtools-server/src/constants.ts @@ -1,5 +1,13 @@ +import dotenv from "dotenv"; + +const refineEnv: Record = {}; +dotenv.config({ processEnv: refineEnv }); + +const RAW_ENV_REFINE_DEVTOOLS_PORT = + process.env.REFINE_DEVTOOLS_PORT || refineEnv.REFINE_DEVTOOLS_PORT; export const DEFAULT_SERVER_PORT = 5001; -export const SERVER_PORT = DEFAULT_SERVER_PORT; +export const SERVER_PORT = + Number(RAW_ENV_REFINE_DEVTOOLS_PORT) || DEFAULT_SERVER_PORT; export const AUTH_SERVER_URL = __DEVELOPMENT__ ? "https://develop.auth.refine.dev" diff --git a/packages/devtools-server/src/serve-ws.ts b/packages/devtools-server/src/serve-ws.ts index 5874a4226641..f5815dbf2c51 100644 --- a/packages/devtools-server/src/serve-ws.ts +++ b/packages/devtools-server/src/serve-ws.ts @@ -1,6 +1,5 @@ import WebSocket from "ws"; import { SERVER_PORT } from "./constants"; -import { DevtoolsEvent, send } from "@refinedev/devtools-shared"; import { bold, cyanBright } from "chalk"; import type http from "http"; @@ -30,11 +29,6 @@ export const serveWs = ( }); ws.on("connection", (client) => { - // send client the devtools client url - send(client as any, DevtoolsEvent.DEVTOOLS_HANDSHAKE, { - url: `http://localhost:${SERVER_PORT}`, - }); - client.on("close", () => { client.terminate(); }); diff --git a/packages/devtools-server/src/setup-server.ts b/packages/devtools-server/src/setup-server.ts index ba23d7623f3f..5ba015d0fdc4 100644 --- a/packages/devtools-server/src/setup-server.ts +++ b/packages/devtools-server/src/setup-server.ts @@ -14,6 +14,13 @@ export const setupServer = (app: Express, onError: () => void) => { "Refine Devtools server", )} (http) failed to start. Port ${SERVER_PORT} is already in use.\n`, ); + console.info( + `${cyanBright.bold( + "\u2139 ", + )}You can change the port by setting the ${bold( + "REFINE_DEVTOOLS_PORT", + )} environment variable.`, + ); } else { console.error( `\n${cyanBright.bold("\u2717 ")}${bold( diff --git a/packages/devtools-shared/src/context.tsx b/packages/devtools-shared/src/context.tsx index 602ee979f1e5..8c7e9c77d913 100644 --- a/packages/devtools-shared/src/context.tsx +++ b/packages/devtools-shared/src/context.tsx @@ -3,60 +3,47 @@ import { DevtoolsEvent } from "./event-types"; import { send } from "./send"; import { receive } from "./receive"; -type DevToolsContextValue = { +type DevtoolsContextValue = { __devtools: boolean; - port: number; - url: string; - secure: boolean; + httpUrl: string; + wsUrl: string; ws: WebSocket | null; - devtoolsUrl?: string; }; -export const DevToolsContext = React.createContext({ +export const DevToolsContext = React.createContext({ __devtools: false, - port: 5001, - url: "localhost", - secure: false, + httpUrl: "http://localhost:5001", + wsUrl: "ws://localhost:5001", ws: null, }); -type Props = React.PropsWithChildren< - Partial> ->; +type Props = React.PropsWithChildren<{ + __devtools?: boolean; + url?: string | [httpUrl: string, wsUrl: string]; +}>; export const DevToolsContextProvider: React.FC = ({ __devtools, - port, + url = ["http://localhost:5001", "ws://localhost:5001"], children, }) => { - const [values, setValues] = React.useState({ + const httpUrl = Array.isArray(url) ? url[0] : url; + const wsUrl = Array.isArray(url) + ? url[1] + : url.replace(/http(s)?:\/\//, "ws$1://"); + + const [values, setValues] = React.useState({ __devtools: __devtools ?? false, - port: port ?? 5001, - url: "localhost", - secure: false, + httpUrl, + wsUrl, ws: null, - devtoolsUrl: "http://localhost:5001", }); const [ws, setWs] = React.useState(null); React.useEffect(() => { let timeout: NodeJS.Timeout | null = null; - const wsInstance = new WebSocket( - `${values.secure ? "wss" : "ws"}://localhost:${values.port}`, - ); - - const unsubscribe = receive( - wsInstance, - DevtoolsEvent.DEVTOOLS_HANDSHAKE, - (data) => { - setValues((v) => ({ - ...v, - devtoolsUrl: data.url, - })); - unsubscribe(); - }, - ); + const wsInstance = new WebSocket(values.wsUrl); wsInstance.addEventListener("open", () => { if (!values.__devtools) { @@ -71,8 +58,6 @@ export const DevToolsContextProvider: React.FC = ({ setWs(wsInstance); return () => { - unsubscribe(); - if (timeout) clearTimeout(timeout); // In strict mode, the WebSocket instance might not be connected yet @@ -88,7 +73,7 @@ export const DevToolsContextProvider: React.FC = ({ }; }, []); - const contextValues = React.useMemo( + const contextValues = React.useMemo( () => ({ ...values, ws, diff --git a/packages/devtools-shared/src/event-types.ts b/packages/devtools-shared/src/event-types.ts index 63364e728fc8..5a94e0fbd644 100644 --- a/packages/devtools-shared/src/event-types.ts +++ b/packages/devtools-shared/src/event-types.ts @@ -11,7 +11,6 @@ import type { TraceType } from "./trace"; export enum DevtoolsEvent { RELOAD = "devtools:reload", DEVTOOLS_INIT = "devtools:init", - DEVTOOLS_HANDSHAKE = "devtools:handshake", DEVTOOLS_ALREADY_CONNECTED = "devtools:already-connected", ACTIVITY = "devtools:send-activity", DEVTOOLS_ACTIVITY_UPDATE = "devtools:activity-update", @@ -62,7 +61,6 @@ type ActivityPayload = export type DevtoolsEventPayloads = { [DevtoolsEvent.RELOAD]: {}; [DevtoolsEvent.DEVTOOLS_INIT]: { url: string }; - [DevtoolsEvent.DEVTOOLS_HANDSHAKE]: { url: string }; [DevtoolsEvent.DEVTOOLS_ALREADY_CONNECTED]: { url: string }; [DevtoolsEvent.ACTIVITY]: ActivityPayload; [DevtoolsEvent.DEVTOOLS_ACTIVITY_UPDATE]: { diff --git a/packages/devtools-ui/src/components/iframe-page-load-handler.tsx b/packages/devtools-ui/src/components/iframe-page-load-handler.tsx new file mode 100644 index 000000000000..f6b9a72635a1 --- /dev/null +++ b/packages/devtools-ui/src/components/iframe-page-load-handler.tsx @@ -0,0 +1,10 @@ +import React from "react"; + +export const IframePageLoadHandler = () => { + // send message to the parent window to notify that the iframe is loaded + React.useEffect(() => { + window.parent.postMessage({ type: "refine-devtools-iframe-loaded" }, "*"); + }, []); + + return null; +}; diff --git a/packages/devtools-ui/src/components/owners.tsx b/packages/devtools-ui/src/components/owners.tsx index 9da140e02908..09b2f951883e 100644 --- a/packages/devtools-ui/src/components/owners.tsx +++ b/packages/devtools-ui/src/components/owners.tsx @@ -6,7 +6,7 @@ import { getOwners } from "src/utils/get-owners"; import { DevToolsContext } from "@refinedev/devtools-shared"; export const Owners = ({ activity }: { activity: Activity }) => { - const { devtoolsUrl } = React.useContext(DevToolsContext); + const { httpUrl } = React.useContext(DevToolsContext); const owners = getOwners(activity); @@ -15,7 +15,7 @@ export const Owners = ({ activity }: { activity: Activity }) => { {owners.map((owner, i) => { const cleanPath = cleanFilePath(owner.file); - const openerUrl = `${devtoolsUrl}/open-in-editor/${cleanPath}?line=${ + const openerUrl = `${httpUrl}/open-in-editor/${cleanPath}?line=${ owner.line ?? 1 }&column=${owner.column ?? 1}`; diff --git a/packages/devtools-ui/src/devtools.tsx b/packages/devtools-ui/src/devtools.tsx index f9599f2f81ab..ecc6f221367d 100644 --- a/packages/devtools-ui/src/devtools.tsx +++ b/packages/devtools-ui/src/devtools.tsx @@ -23,13 +23,14 @@ import { InitialLayout } from "./components/initial-layout"; import { RaffleHandler } from "./components/raffle-handler"; import { MonitorHighlightHandler } from "./components/monitor-highlight-handler"; import { LocationChangeHandler } from "./components/location-change-handler"; +import { IframePageLoadHandler } from "./components/iframe-page-load-handler"; import { getLastLocation } from "./utils/last-location"; dayjs.extend(relativeTime); export const DevToolsApp = () => { return ( - + @@ -102,6 +103,7 @@ export const DevToolsApp = () => { } /> + diff --git a/packages/devtools/src/define.d.ts b/packages/devtools/src/define.d.ts index f98c199ddee5..30ed5cce3de7 100644 --- a/packages/devtools/src/define.d.ts +++ b/packages/devtools/src/define.d.ts @@ -1 +1,9 @@ declare const __DEV_CONDITION__: string; +declare const __IMPORT_META_KEY__: any; +declare const __PROCESS_KEY__: any; + +declare const __PROCESS_ENV_REFINE_DEVTOOLS_PORT_KEY__: any; +declare const __PROCESS_ENV_NEXT_PUBLIC_REFINE_DEVTOOLS_PORT_KEY__: any; +declare const __PROCESS_ENV_REACT_APP_REFINE_DEVTOOLS_PORT_KEY__: any; +declare const __IMPORT_META_ENV_REFINE_DEVTOOLS_PORT_KEY__: any; +declare const __IMPORT_META_ENV_VITE_REFINE_DEVTOOLS_PORT_KEY__: any; diff --git a/packages/devtools/src/panel.tsx b/packages/devtools/src/panel.tsx index 6513cb3f5122..158b92a1cf50 100644 --- a/packages/devtools/src/panel.tsx +++ b/packages/devtools/src/panel.tsx @@ -10,6 +10,8 @@ import { import type { Placement } from "./interfaces/placement"; +const MAX_IFRAME_WAIT_TIME = 1500; + export const DevtoolsPanel = __DEV_CONDITION__ !== "development" ? () => null @@ -17,9 +19,12 @@ export const DevtoolsPanel = const [browser, setBrowser] = React.useState(false); const [visible, setVisible] = React.useState(false); const [placement] = React.useState("bottom"); - const { devtoolsUrl, ws } = React.useContext(DevToolsContext); + const { httpUrl, ws } = React.useContext(DevToolsContext); const [width, setWidth] = React.useState(0); const [selectorActive, setSelectorActive] = React.useState(false); + const [iframeStatus, setIframeStatus] = React.useState< + "loading" | "loaded" | "failed" + >("loading"); const onSelectorHighlight = React.useCallback( (name: string) => { @@ -33,10 +38,6 @@ export const DevtoolsPanel = [ws], ); - const onSelectorOpen = React.useCallback(() => { - setVisible(false); - }, []); - React.useEffect(() => { if (selectorActive) { setVisible(false); @@ -68,6 +69,41 @@ export const DevtoolsPanel = return () => undefined; }, [browser]); + React.useEffect(() => { + if (iframeStatus !== "loaded") { + const onMessage = (event: MessageEvent) => { + if (event.data.type === "refine-devtools-iframe-loaded") { + setIframeStatus("loaded"); + } + }; + + window.addEventListener("message", onMessage); + + return () => { + window.removeEventListener("message", onMessage); + }; + } + + return () => 0; + }, []); + + React.useEffect(() => { + let timeout: number; + if (iframeStatus === "loading") { + timeout = window.setTimeout(() => { + setIframeStatus("failed"); + if (timeout) { + clearInterval(timeout); + } + }, MAX_IFRAME_WAIT_TIME); + } + return () => { + if (typeof timeout !== "undefined") { + clearInterval(timeout); + } + }; + }, [iframeStatus]); + if (!browser) { return null; } @@ -95,17 +131,13 @@ export const DevtoolsPanel = {({ resizing }) => (