From 7065dd9dec8ec3b6e12fc07da2d42f936fb805f8 Mon Sep 17 00:00:00 2001 From: Joshua Yoes <37849890+joshuayoes@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:34:47 -0700 Subject: [PATCH 1/5] feat(reactotron-core-contract): add new interfaces for API responses, async storage mutations, benchmarks, client introductions, custom commands, displays, images, and saga task completions --- .../src/apiResponse.ts | 15 +++++++++ .../src/asyncStorage.ts | 5 +++ lib/reactotron-core-contract/src/benchmark.ts | 8 +++++ .../src/clientIntro.ts | 8 +++++ lib/reactotron-core-contract/src/command.ts | 31 ++++++++++++------- .../src/customCommand.ts | 15 +++++++++ lib/reactotron-core-contract/src/display.ts | 6 ++++ lib/reactotron-core-contract/src/image.ts | 8 +++++ .../src/reactotron-core-contract.ts | 11 ++++++- lib/reactotron-core-contract/src/repl.ts | 6 ++++ .../src/sagaTaskComplete.ts | 26 ++++++++++++++++ 11 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 lib/reactotron-core-contract/src/apiResponse.ts create mode 100644 lib/reactotron-core-contract/src/asyncStorage.ts create mode 100644 lib/reactotron-core-contract/src/benchmark.ts create mode 100644 lib/reactotron-core-contract/src/clientIntro.ts create mode 100644 lib/reactotron-core-contract/src/customCommand.ts create mode 100644 lib/reactotron-core-contract/src/display.ts create mode 100644 lib/reactotron-core-contract/src/image.ts create mode 100644 lib/reactotron-core-contract/src/repl.ts create mode 100644 lib/reactotron-core-contract/src/sagaTaskComplete.ts diff --git a/lib/reactotron-core-contract/src/apiResponse.ts b/lib/reactotron-core-contract/src/apiResponse.ts new file mode 100644 index 000000000..6f44253e6 --- /dev/null +++ b/lib/reactotron-core-contract/src/apiResponse.ts @@ -0,0 +1,15 @@ +export interface ApiResponsePayload { + duration: number + request: { + data: any + headers: { [key: string]: string } + method: string + params: any + url: string + } + response: { + body: string + headers: { [key: string]: string } + status: number + } +} diff --git a/lib/reactotron-core-contract/src/asyncStorage.ts b/lib/reactotron-core-contract/src/asyncStorage.ts new file mode 100644 index 000000000..437824af5 --- /dev/null +++ b/lib/reactotron-core-contract/src/asyncStorage.ts @@ -0,0 +1,5 @@ +export interface AsyncStorageMutationPayload { + action: string + /** Storage data can be any type */ + data: any +} diff --git a/lib/reactotron-core-contract/src/benchmark.ts b/lib/reactotron-core-contract/src/benchmark.ts new file mode 100644 index 000000000..216a2623b --- /dev/null +++ b/lib/reactotron-core-contract/src/benchmark.ts @@ -0,0 +1,8 @@ +export interface BenchmarkReportPayload { + title: string + steps: Array<{ + title: string + time: number + delta: number + }> +} diff --git a/lib/reactotron-core-contract/src/clientIntro.ts b/lib/reactotron-core-contract/src/clientIntro.ts new file mode 100644 index 000000000..eb9986df5 --- /dev/null +++ b/lib/reactotron-core-contract/src/clientIntro.ts @@ -0,0 +1,8 @@ +export interface ClientIntroPayload { + name: string + clientId?: string + environment?: string + reactotronVersion?: string + /** Additional fields added by server on receipt */ + address?: string +} diff --git a/lib/reactotron-core-contract/src/command.ts b/lib/reactotron-core-contract/src/command.ts index 999291caa..5e7293f68 100644 --- a/lib/reactotron-core-contract/src/command.ts +++ b/lib/reactotron-core-contract/src/command.ts @@ -1,5 +1,14 @@ +import type { ApiResponsePayload } from "./apiResponse" +import type { AsyncStorageMutationPayload } from "./asyncStorage" +import type { BenchmarkReportPayload } from "./benchmark" +import type { ClientIntroPayload } from "./clientIntro" +import type { CustomCommandRegisterPayload, CustomCommandUnregisterPayload } from "./customCommand" +import type { DisplayPayload } from "./display" +import type { ImagePayload } from "./image" import type { LogPayload } from "./log" import { EditorOpenPayload } from "./openInEditor" +import type { ReplExecuteResponsePayload, ReplLsResponsePayload } from "./repl" +import type { SagaTaskCompletePayload } from "./sagaTaskComplete" import type { StateActionCompletePayload, StateActionDispatchPayload, @@ -50,14 +59,14 @@ export const CommandType = { export type CommandTypeKey = (typeof CommandType)[keyof typeof CommandType] export interface CommandMap { - [CommandType.ApiResponse]: any - [CommandType.AsyncStorageMutation]: any - [CommandType.Benchmark]: any - [CommandType.ClientIntro]: any - [CommandType.Display]: any - [CommandType.Image]: any + [CommandType.ApiResponse]: ApiResponsePayload + [CommandType.AsyncStorageMutation]: AsyncStorageMutationPayload + [CommandType.Benchmark]: BenchmarkReportPayload + [CommandType.ClientIntro]: ClientIntroPayload + [CommandType.Display]: DisplayPayload + [CommandType.Image]: ImagePayload [CommandType.Log]: LogPayload - [CommandType.SagaTaskComplete]: any + [CommandType.SagaTaskComplete]: SagaTaskCompletePayload [CommandType.StateActionComplete]: StateActionCompletePayload [CommandType.StateKeysResponse]: StateKeysResponsePayload [CommandType.StateValuesChange]: StateValuesChangePayload @@ -69,11 +78,11 @@ export interface CommandMap { [CommandType.StateValuesSubscribe]: StateValuesSubscribePayload [CommandType.StateKeysRequest]: StateKeysRequestPayload [CommandType.StateValuesRequest]: StateValuesRequestPayload - [CommandType.CustomCommandRegister]: any - [CommandType.CustomCommandUnregister]: any + [CommandType.CustomCommandRegister]: CustomCommandRegisterPayload + [CommandType.CustomCommandUnregister]: CustomCommandUnregisterPayload [CommandType.Clear]: undefined - [CommandType.ReplLsResponse]: any - [CommandType.ReplExecuteResponse]: any + [CommandType.ReplLsResponse]: ReplLsResponsePayload + [CommandType.ReplExecuteResponse]: ReplExecuteResponsePayload [CommandType.DevtoolsOpen]: undefined [CommandType.DevtoolsReload]: undefined [CommandType.EditorOpen]: EditorOpenPayload diff --git a/lib/reactotron-core-contract/src/customCommand.ts b/lib/reactotron-core-contract/src/customCommand.ts new file mode 100644 index 000000000..77c4775f6 --- /dev/null +++ b/lib/reactotron-core-contract/src/customCommand.ts @@ -0,0 +1,15 @@ +export interface CustomCommandRegisterPayload { + id: number + command: string + title?: string + description?: string + args?: Array<{ + name: string + type: string + }> +} + +export interface CustomCommandUnregisterPayload { + id: number + command: string +} diff --git a/lib/reactotron-core-contract/src/display.ts b/lib/reactotron-core-contract/src/display.ts new file mode 100644 index 000000000..c104c518a --- /dev/null +++ b/lib/reactotron-core-contract/src/display.ts @@ -0,0 +1,6 @@ +export interface DisplayPayload { + name: string + value?: any + preview?: string + image?: string | { uri: string } +} diff --git a/lib/reactotron-core-contract/src/image.ts b/lib/reactotron-core-contract/src/image.ts new file mode 100644 index 000000000..5a66750ad --- /dev/null +++ b/lib/reactotron-core-contract/src/image.ts @@ -0,0 +1,8 @@ +export interface ImagePayload { + uri: string + preview: string + caption?: string + width?: number + height?: number + filename?: string +} diff --git a/lib/reactotron-core-contract/src/reactotron-core-contract.ts b/lib/reactotron-core-contract/src/reactotron-core-contract.ts index c379b34e0..259dc57f8 100644 --- a/lib/reactotron-core-contract/src/reactotron-core-contract.ts +++ b/lib/reactotron-core-contract/src/reactotron-core-contract.ts @@ -1,6 +1,15 @@ +export * from "./apiResponse" +export * from "./asyncStorage" +export * from "./benchmark" +export * from "./clientIntro" export * from "./command" +export * from "./customCommand" +export * from "./diff" +export * from "./display" +export * from "./image" export * from "./log" export * from "./openInEditor" +export * from "./repl" +export * from "./sagaTaskComplete" export * from "./server-events" export * from "./state" -export * from "./diff" diff --git a/lib/reactotron-core-contract/src/repl.ts b/lib/reactotron-core-contract/src/repl.ts new file mode 100644 index 000000000..424910315 --- /dev/null +++ b/lib/reactotron-core-contract/src/repl.ts @@ -0,0 +1,6 @@ +export type ReplLsResponsePayload = string[] + +/** + * Result of eval() - can be anything + */ +export type ReplExecuteResponsePayload = any diff --git a/lib/reactotron-core-contract/src/sagaTaskComplete.ts b/lib/reactotron-core-contract/src/sagaTaskComplete.ts new file mode 100644 index 000000000..f4c841767 --- /dev/null +++ b/lib/reactotron-core-contract/src/sagaTaskComplete.ts @@ -0,0 +1,26 @@ +export interface SagaTaskCompleteChild { + depth: number + /** Human-readable description of what the saga effect does */ + description: string + duration: number + effectId: number + /** The input parameters/data for the effect - can be any value from saga middleware */ + extra: any + name: string + parentEffectId: number + /** The output/return value from the effect - can be actions, API responses, computed values, etc. */ + result: any + status: string + /** Data about the winning branch in a race() effect, null for non-race effects */ + winner: any + /** Truthy if this effect lost a race() or was cancelled, null otherwise */ + loser: any +} + +export interface SagaTaskCompletePayload { + children: SagaTaskCompleteChild[] + /** Human-readable description of what triggered the saga, may be undefined */ + description?: string + duration: number + triggerType: string +} From 5ebfd472f314727b82b593b2685f9e9c0bd188b3 Mon Sep 17 00:00:00 2001 From: Joshua Yoes <37849890+joshuayoes@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:35:50 -0700 Subject: [PATCH 2/5] refactor(reactotron-core-client): enhance type definitions and payload structures for plugins and commands --- .../src/plugins/api-response.ts | 7 +++++- .../src/plugins/benchmark.ts | 3 ++- .../src/plugins/image.ts | 10 +------- .../src/plugins/repl.ts | 5 ++-- .../src/reactotron-core-client.ts | 24 ++++++++----------- 5 files changed, 22 insertions(+), 27 deletions(-) diff --git a/lib/reactotron-core-client/src/plugins/api-response.ts b/lib/reactotron-core-client/src/plugins/api-response.ts index 8ada9cd06..1a3d85c7e 100644 --- a/lib/reactotron-core-client/src/plugins/api-response.ts +++ b/lib/reactotron-core-client/src/plugins/api-response.ts @@ -1,3 +1,4 @@ +import type { ApiResponsePayload } from "reactotron-core-contract" import type { ReactotronCore, Plugin } from "../reactotron-core-client" /** @@ -6,7 +7,11 @@ import type { ReactotronCore, Plugin } from "../reactotron-core-client" const apiResponse = () => (reactotron: ReactotronCore) => { return { features: { - apiResponse: (request: { status: number }, response: any, duration: number) => { + apiResponse: ( + request: ApiResponsePayload["request"], + response: ApiResponsePayload["response"], + duration: number + ) => { const ok = response && response.status && diff --git a/lib/reactotron-core-client/src/plugins/benchmark.ts b/lib/reactotron-core-client/src/plugins/benchmark.ts index 4d53bd9ba..7194708dd 100644 --- a/lib/reactotron-core-client/src/plugins/benchmark.ts +++ b/lib/reactotron-core-client/src/plugins/benchmark.ts @@ -1,3 +1,4 @@ +import type { BenchmarkReportPayload } from "reactotron-core-contract" import type { ReactotronCore, Plugin } from "../reactotron-core-client" /** @@ -7,7 +8,7 @@ const benchmark = () => (reactotron: ReactotronCore) => { const { startTimer } = reactotron const benchmark = (title: string) => { - const steps = [] as Array<{title: string, time: number, delta: number}> + const steps: BenchmarkReportPayload["steps"] = [] const elapsed = startTimer() const step = (stepTitle: string) => { const previousTime = steps.length === 0 ? 0 : (steps[steps.length - 1] as any).time diff --git a/lib/reactotron-core-client/src/plugins/image.ts b/lib/reactotron-core-client/src/plugins/image.ts index 0b886a8c9..5f80355aa 100644 --- a/lib/reactotron-core-client/src/plugins/image.ts +++ b/lib/reactotron-core-client/src/plugins/image.ts @@ -1,14 +1,6 @@ +import type { ImagePayload } from "reactotron-core-contract" import type { ReactotronCore, Plugin } from "../reactotron-core-client" -export interface ImagePayload { - uri: string - preview: string - caption?: string - width?: number - height?: number - filename?: string -} - /** * Provides an image. */ diff --git a/lib/reactotron-core-client/src/plugins/repl.ts b/lib/reactotron-core-client/src/plugins/repl.ts index 61278b347..6402c38fe 100644 --- a/lib/reactotron-core-client/src/plugins/repl.ts +++ b/lib/reactotron-core-client/src/plugins/repl.ts @@ -1,3 +1,4 @@ +import type { ReplExecuteResponsePayload, ReplLsResponsePayload } from "reactotron-core-contract" import type { ReactotronCore, Plugin } from "../reactotron-core-client" // eslint-disable-next-line @typescript-eslint/ban-types @@ -12,7 +13,7 @@ const repl = () => (reactotron: ReactotronCore) => { switch (type.substr(5)) { case "ls": - reactotron.send("repl.ls.response", Object.keys(myRepls)) + reactotron.send("repl.ls.response", Object.keys(myRepls) as ReplLsResponsePayload) break // case "cd": // const changeTo = myRepls.find(r => r.name === payload) @@ -40,7 +41,7 @@ const repl = () => (reactotron: ReactotronCore) => { "repl.execute.response", function () { return eval(payload) // eslint-disable-line no-eval - }.call(myRepls) + }.call(myRepls) as ReplExecuteResponsePayload ) break } diff --git a/lib/reactotron-core-client/src/reactotron-core-client.ts b/lib/reactotron-core-client/src/reactotron-core-client.ts index 792e197d3..78d7d92ff 100644 --- a/lib/reactotron-core-client/src/reactotron-core-client.ts +++ b/lib/reactotron-core-client/src/reactotron-core-client.ts @@ -1,5 +1,5 @@ import WebSocket from "ws" -import type { Command, CommandTypeKey } from "reactotron-core-contract" +import type { Command, CommandTypeKey, DisplayPayload } from "reactotron-core-contract" import validate from "./validate" import logger from "./plugins/logger" import image from "./plugins/image" @@ -44,11 +44,7 @@ export interface Plugin extends LifeCycleMethods { export type PluginCreator = (client: Client) => Plugin -interface DisplayConfig { - name: string - value?: object | string | number | boolean | null | undefined - preview?: string - image?: string | { uri: string } +interface DisplayConfig extends DisplayPayload { important?: boolean } @@ -291,7 +287,7 @@ export class ReactotronImpl this.send("client.intro", { environment, ...client, - name, + name: name!, clientId, reactotronCoreClientVersion: "REACTOTRON_CORE_CLIENT_VERSION", }) @@ -421,11 +417,11 @@ export class ReactotronImpl */ display(config: DisplayConfig) { const { name, value, preview, image: img, important = false } = config - const payload = { + const payload: DisplayPayload = { name, - value: value || null, - preview: preview || null, - image: img || null, + value: value || undefined, + preview: preview || undefined, + image: img || undefined, } this.send("display", payload, important) } @@ -531,7 +527,7 @@ export class ReactotronImpl this.customCommands = this.customCommands.filter((cc) => cc.id !== command.id) this.send("customCommand.unregister", { - id: command.id, + id: command.id!, command: command.command, }) }) @@ -572,7 +568,7 @@ export class ReactotronImpl this.customCommands.push(customHandler) this.send("customCommand.register", { - id: customHandler.id, + id: customHandler.id!, command: customHandler.command, title: customHandler.title, description: customHandler.description, @@ -583,7 +579,7 @@ export class ReactotronImpl this.customCommands = this.customCommands.filter((cc) => cc.id !== customHandler.id) this.send("customCommand.unregister", { - id: customHandler.id, + id: customHandler.id!, command: customHandler.command, }) } From 4cef5680fe1828b81e65a88244de32b9c318f89c Mon Sep 17 00:00:00 2001 From: Joshua Yoes <37849890+joshuayoes@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:36:17 -0700 Subject: [PATCH 3/5] refactor(reactotron-core-ui): remove redundant interface definitions and import payload types from reactotron-core-contract --- .../ApiResponseCommand/index.tsx | 17 +------------- .../AsyncStorageMutationCommand/index.tsx | 6 +---- .../BenchmarkReportCommand/index.tsx | 10 +-------- .../ClientIntroCommand/index.tsx | 5 +---- .../timelineCommands/DisplayCommand/index.tsx | 8 +------ .../timelineCommands/ImageCommand/index.tsx | 10 +-------- .../SagaTaskCompleteCommand/Stateless.tsx | 22 +------------------ .../SagaTaskCompleteCommand/index.tsx | 3 ++- 8 files changed, 9 insertions(+), 72 deletions(-) diff --git a/lib/reactotron-core-ui/src/timelineCommands/ApiResponseCommand/index.tsx b/lib/reactotron-core-ui/src/timelineCommands/ApiResponseCommand/index.tsx index 6ff7a28f9..9db3ff5fd 100644 --- a/lib/reactotron-core-ui/src/timelineCommands/ApiResponseCommand/index.tsx +++ b/lib/reactotron-core-ui/src/timelineCommands/ApiResponseCommand/index.tsx @@ -1,6 +1,7 @@ import React, { useState, FunctionComponent } from "react" import styled from "styled-components" import { MdCallReceived, MdCallMade, MdReceipt, MdContentCopy } from "react-icons/md" +import type { ApiResponsePayload } from "reactotron-core-contract" import TimelineCommand from "../../components/TimelineCommand" import TimelineCommandTabButton from "../../components/TimelineCommandTabButton" @@ -32,22 +33,6 @@ export enum Tab { ResponseBody = "responseBody", } -interface ApiResponsePayload { - duration: number - request: { - data: any // ¯\_(ツ)_/¯ - headers: { [key: string]: string } - method: string - params: any // ¯\_(ツ)_/¯ - url: string - } - response: { - body: string - headers: { [key: string]: string } - status: number - } -} - interface Props extends TimelineCommandProps { initialTab?: Tab } diff --git a/lib/reactotron-core-ui/src/timelineCommands/AsyncStorageMutationCommand/index.tsx b/lib/reactotron-core-ui/src/timelineCommands/AsyncStorageMutationCommand/index.tsx index b4d202fb8..1c3b00c38 100644 --- a/lib/reactotron-core-ui/src/timelineCommands/AsyncStorageMutationCommand/index.tsx +++ b/lib/reactotron-core-ui/src/timelineCommands/AsyncStorageMutationCommand/index.tsx @@ -1,15 +1,11 @@ import React, { FunctionComponent } from "react" +import type { AsyncStorageMutationPayload } from "reactotron-core-contract" import TimelineCommand from "../../components/TimelineCommand" import { KeyContainer, RowContainer, ValueContainer } from "../../utils/makeTable" import { TimelineCommandProps, buildTimelineCommand } from "../BaseCommand" import { makeTableWithContentView } from "../../components/ContentView" -interface AsyncStorageMutationPayload { - action: string - data: any -} - interface Props extends TimelineCommandProps {} const AsyncStorageMutationCommand: FunctionComponent = ({ command, isOpen, setIsOpen }) => { diff --git a/lib/reactotron-core-ui/src/timelineCommands/BenchmarkReportCommand/index.tsx b/lib/reactotron-core-ui/src/timelineCommands/BenchmarkReportCommand/index.tsx index f4157572c..fc1998224 100644 --- a/lib/reactotron-core-ui/src/timelineCommands/BenchmarkReportCommand/index.tsx +++ b/lib/reactotron-core-ui/src/timelineCommands/BenchmarkReportCommand/index.tsx @@ -1,5 +1,6 @@ import React, { FunctionComponent } from "react" import styled from "styled-components" +import type { BenchmarkReportPayload } from "reactotron-core-contract" import TimelineCommand from "../../components/TimelineCommand" import { TimelineCommandProps, buildTimelineCommand } from "../BaseCommand" @@ -39,15 +40,6 @@ const StepGraph = styled.div.attrs(() => ({}))` right: ${(props) => props.$endPercent}%; ` -interface BenchmarkReportPayload { - title: string - steps: { - title: string - time: number - delta: number - }[] -} - interface Props extends TimelineCommandProps {} const BenchmarkReportCommand: FunctionComponent = ({ command, isOpen, setIsOpen }) => { diff --git a/lib/reactotron-core-ui/src/timelineCommands/ClientIntroCommand/index.tsx b/lib/reactotron-core-ui/src/timelineCommands/ClientIntroCommand/index.tsx index a26a3b639..e40dcf9fa 100644 --- a/lib/reactotron-core-ui/src/timelineCommands/ClientIntroCommand/index.tsx +++ b/lib/reactotron-core-ui/src/timelineCommands/ClientIntroCommand/index.tsx @@ -1,13 +1,10 @@ import React, { FunctionComponent } from "react" +import type { ClientIntroPayload } from "reactotron-core-contract" import TimelineCommand from "../../components/TimelineCommand" import makeTable from "../../utils/makeTable" import { TimelineCommandProps, buildTimelineCommand } from "../BaseCommand" -interface ClientIntroPayload { - name: string -} - interface Props extends TimelineCommandProps {} const ClientIntroCommand: FunctionComponent = ({ command, isOpen, setIsOpen }) => { diff --git a/lib/reactotron-core-ui/src/timelineCommands/DisplayCommand/index.tsx b/lib/reactotron-core-ui/src/timelineCommands/DisplayCommand/index.tsx index 78771ee8a..9a461bd13 100644 --- a/lib/reactotron-core-ui/src/timelineCommands/DisplayCommand/index.tsx +++ b/lib/reactotron-core-ui/src/timelineCommands/DisplayCommand/index.tsx @@ -1,6 +1,7 @@ import React, { FunctionComponent } from "react" import styled from "styled-components" import { MdContentCopy } from "react-icons/md" +import type { DisplayPayload } from "reactotron-core-contract" import TimelineCommand from "../../components/TimelineCommand" import ContentView from "../../components/ContentView" @@ -13,13 +14,6 @@ const Image = styled.img` max-height: 100%; ` -interface DisplayPayload { - name: string - value?: any - image?: any - preview?: string -} - interface Props extends TimelineCommandProps {} function buildToolbar(commandPayload, copyToClipboard: (text: string) => void) { diff --git a/lib/reactotron-core-ui/src/timelineCommands/ImageCommand/index.tsx b/lib/reactotron-core-ui/src/timelineCommands/ImageCommand/index.tsx index 040ed74d6..9b5567301 100644 --- a/lib/reactotron-core-ui/src/timelineCommands/ImageCommand/index.tsx +++ b/lib/reactotron-core-ui/src/timelineCommands/ImageCommand/index.tsx @@ -1,5 +1,6 @@ import React, { FunctionComponent } from "react" import styled from "styled-components" +import type { ImagePayload } from "reactotron-core-contract" import TimelineCommand from "../../components/TimelineCommand" import { TimelineCommandProps, buildTimelineCommand } from "../BaseCommand" @@ -20,15 +21,6 @@ const Filename = styled.div` color: ${(props) => props.theme.highlight}; ` -interface ImagePayload { - uri: string - preview: string - caption?: string - width?: number - height?: number - filename?: string -} - interface Props extends TimelineCommandProps {} const ImageCommand: FunctionComponent = ({ command, isOpen, setIsOpen }) => { diff --git a/lib/reactotron-core-ui/src/timelineCommands/SagaTaskCompleteCommand/Stateless.tsx b/lib/reactotron-core-ui/src/timelineCommands/SagaTaskCompleteCommand/Stateless.tsx index 6ef884bac..48f14849a 100644 --- a/lib/reactotron-core-ui/src/timelineCommands/SagaTaskCompleteCommand/Stateless.tsx +++ b/lib/reactotron-core-ui/src/timelineCommands/SagaTaskCompleteCommand/Stateless.tsx @@ -6,6 +6,7 @@ import { MdEject as IconStatusCancelled, MdError as IconStatusRejected, } from "react-icons/md" +import type { SagaTaskCompleteChild, SagaTaskCompletePayload } from "reactotron-core-contract" import TimelineCommand from "../../components/TimelineCommand" import ContentView from "../../components/ContentView" @@ -68,27 +69,6 @@ const EffectDescription = styled.div` padding-bottom: 4px; ` -export interface SagaTaskCompleteChild { - depth: number - description: string - duration: number - effectId: number - extra: { type: string } - name: string - parentEffectId: number - result: { type: string } - status: string - winner: any - loser: any -} - -export interface SagaTaskCompletePayload { - children: SagaTaskCompleteChild[] - description: any // TODO: ¯\_(ツ)_/¯ - duration: number - triggerType: string -} - interface Props extends TimelineCommandProps { isDetailsOpen: boolean setIsDetailsOpen: (isDetailsOpen: boolean) => void diff --git a/lib/reactotron-core-ui/src/timelineCommands/SagaTaskCompleteCommand/index.tsx b/lib/reactotron-core-ui/src/timelineCommands/SagaTaskCompleteCommand/index.tsx index 45c7e8221..cf51736e0 100644 --- a/lib/reactotron-core-ui/src/timelineCommands/SagaTaskCompleteCommand/index.tsx +++ b/lib/reactotron-core-ui/src/timelineCommands/SagaTaskCompleteCommand/index.tsx @@ -1,8 +1,9 @@ import React, { FunctionComponent, useState } from "react" +import type { SagaTaskCompletePayload } from "reactotron-core-contract" import { TimelineCommandProps, buildTimelineCommand } from "../BaseCommand" -import StatelessSagaTaskCompleteCommand, { SagaTaskCompletePayload } from "./Stateless" +import StatelessSagaTaskCompleteCommand from "./Stateless" interface Props extends TimelineCommandProps {} From 0d4eec34e4f61781ab5ea1760e58b2f0fc1eabc8 Mon Sep 17 00:00:00 2001 From: Joshua Yoes <37849890+joshuayoes@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:46:52 -0700 Subject: [PATCH 4/5] docs(reactotron-core-contract): update README.md to include comprehensive usage examples, installation instructions, and detailed command types and payloads --- lib/reactotron-core-contract/README.md | 280 ++++++++++++++++++++++++- 1 file changed, 277 insertions(+), 3 deletions(-) diff --git a/lib/reactotron-core-contract/README.md b/lib/reactotron-core-contract/README.md index d208ab879..41e385a72 100644 --- a/lib/reactotron-core-contract/README.md +++ b/lib/reactotron-core-contract/README.md @@ -1,5 +1,279 @@ -# reactotron-core-contracts +# reactotron-core-contract -Typescript contracts for websocket messages between `reactotron-core-server` and `reactotron-core-client` +TypeScript contracts for WebSocket messages between `reactotron-core-server` and `reactotron-core-client`. -See [`reactotron-core-client`](../reactotron-core-client/README.md) for more examples of how it should be used. +This package provides the type definitions and command enums that ensure type-safe communication between Reactotron clients, plugins, and servers. + +## Installation + +```bash +npm install reactotron-core-contract +# or +yarn add reactotron-core-contract +``` + +## What's Included + +### Command Types Enum + +The `CommandType` object contains all available command type strings as constants: + +```typescript +import { CommandType } from "reactotron-core-contract" + +CommandType.Log // "log" +CommandType.ApiResponse // "api.response" +CommandType.Benchmark // "benchmark.report" +CommandType.Display // "display" +CommandType.Image // "image" +// ... and many more +``` + +### Payload Types + +Each command has a corresponding payload type that defines its structure: + +```typescript +import type { + LogPayload, + ApiResponsePayload, + BenchmarkReportPayload, + DisplayPayload, + ImagePayload, + StateActionCompletePayload, + // ... and more +} from "reactotron-core-contract" +``` + +### Command Interface + +The base `Command` interface that all messages follow: + +```typescript +import type { Command, CommandTypeKey } from "reactotron-core-contract" + +interface Command { + type: CommandTypeKey + connectionId: number + clientId?: string + date: Date + deltaTime: number + important: boolean + messageId: number + payload: Payload + diff?: any +} +``` + +## Usage in Clients + +Clients use the contract types to send properly typed commands to the server: + +```typescript +import { CommandType } from "reactotron-core-contract" +import type { LogPayload, ApiResponsePayload, DisplayPayload } from "reactotron-core-contract" + +// Sending a log command +const logPayload: LogPayload = { + level: "debug", + message: "Hello, Reactotron!", +} +client.send(CommandType.Log, logPayload) + +// Sending an API response +const apiPayload: ApiResponsePayload = { + request: { + url: "https://api.example.com/users", + method: "GET", + data: null, + headers: { Accept: "application/json" }, + params: {}, + }, + response: { + body: '{"users": [...]}', + status: 200, + headers: { "Content-Type": "application/json" }, + }, + duration: 245, +} +client.send(CommandType.ApiResponse, apiPayload) + +// Sending a custom display event +const displayPayload: DisplayPayload = { + name: "User Login", + value: { userId: 123, username: "steve" }, + preview: "Steve logged in successfully", +} +client.send(CommandType.Display, displayPayload, true) // marked important +``` + +## Usage in Plugins + +Plugins import payload types to ensure they're sending properly structured data: + +### Example: API Response Plugin + +```typescript +import type { ApiResponsePayload } from "reactotron-core-contract" +import type { ReactotronCore, Plugin } from "reactotron-core-client" + +const apiResponse = () => (reactotron: ReactotronCore) => { + return { + features: { + apiResponse: ( + request: ApiResponsePayload["request"], + response: ApiResponsePayload["response"], + duration: number + ) => { + reactotron.send("api.response", { request, response, duration }) + }, + }, + } satisfies Plugin +} +``` + +### Example: Benchmark Plugin + +```typescript +import type { BenchmarkReportPayload } from "reactotron-core-contract" +import type { ReactotronCore, Plugin } from "reactotron-core-client" + +const benchmark = () => (reactotron: ReactotronCore) => { + const benchmark = (title: string) => { + const steps: BenchmarkReportPayload["steps"] = [] + + const step = (stepTitle: string) => { + steps.push({ + title: stepTitle, + time: performance.now(), + delta: 0, + }) + } + + const stop = () => { + reactotron.send("benchmark.report", { title, steps }) + } + + return { step, stop } + } + + return { + features: { benchmark }, + } satisfies Plugin +} +``` + +### Example: Handling Server Commands + +Plugins can also handle commands from the server by checking the command type: + +```typescript +import { CommandType } from "reactotron-core-contract" +import type { StateValuesRequestPayload } from "reactotron-core-contract" + +const myPlugin = () => (reactotron) => { + return { + onCommand: (command) => { + // Type-safe command handling + if (command.type === CommandType.StateValuesRequest) { + const payload = command.payload as StateValuesRequestPayload + const { path } = payload + // Handle the request... + } + }, + } +} +``` + +## Available Command Types & Payloads + +### Client → Server Commands + +| Command Type | Payload Type | Description | +| ------------------------------------- | -------------------------------- | ---------------------------------- | +| `CommandType.Log` | `LogPayload` | Log messages, warnings, and errors | +| `CommandType.ApiResponse` | `ApiResponsePayload` | HTTP request/response information | +| `CommandType.Benchmark` | `BenchmarkReportPayload` | Performance benchmark results | +| `CommandType.ClientIntro` | `ClientIntroPayload` | Client connection information | +| `CommandType.Display` | `DisplayPayload` | Custom display events | +| `CommandType.Image` | `ImagePayload` | Image data with metadata | +| `CommandType.SagaTaskComplete` | `SagaTaskCompletePayload` | Redux-saga task completion data | +| `CommandType.StateActionComplete` | `StateActionCompletePayload` | State action completion | +| `CommandType.StateValuesChange` | `StateValuesChangePayload` | State value change notifications | +| `CommandType.StateKeysResponse` | `StateKeysResponsePayload` | Response with state keys | +| `CommandType.StateValuesResponse` | `StateValuesResponsePayload` | Response with state values | +| `CommandType.StateBackupResponse` | `StateBackupResponsePayload` | State backup data | +| `CommandType.AsyncStorageMutation` | `AsyncStorageMutationPayload` | AsyncStorage changes | +| `CommandType.CustomCommandRegister` | `CustomCommandRegisterPayload` | Register a custom command | +| `CommandType.CustomCommandUnregister` | `CustomCommandUnregisterPayload` | Unregister a custom command | +| `CommandType.ReplLsResponse` | `ReplLsResponsePayload` | REPL list response | +| `CommandType.ReplExecuteResponse` | `ReplExecuteResponsePayload` | REPL execution result | + +### Server → Client Commands + +| Command Type | Payload Type | Description | +| ---------------------------------- | ----------------------------- | ---------------------------- | +| `CommandType.StateValuesRequest` | `StateValuesRequestPayload` | Request state values at path | +| `CommandType.StateKeysRequest` | `StateKeysRequestPayload` | Request state keys at path | +| `CommandType.StateValuesSubscribe` | `StateValuesSubscribePayload` | Subscribe to state changes | +| `CommandType.StateActionDispatch` | `StateActionDispatchPayload` | Dispatch an action | +| `CommandType.StateBackupRequest` | `StateBackupRequestPayload` | Request state backup | +| `CommandType.StateRestoreRequest` | `StateRestoreRequestPayload` | Restore state from backup | +| `CommandType.Clear` | `undefined` | Clear the timeline | + +### React Native Specific + +| Command Type | Description | +| ---------------------------- | --------------------------- | +| `CommandType.DevtoolsOpen` | Open React Native DevTools | +| `CommandType.DevtoolsReload` | Reload the React Native app | +| `CommandType.EditorOpen` | Open a file in the editor | +| `CommandType.Storybook` | Toggle Storybook | +| `CommandType.Overlay` | Toggle overlay | + +## For Plugin Authors + +When creating a Reactotron plugin: + +1. Import the relevant payload types from `reactotron-core-contract` +2. Use `CommandType` enum for command type strings +3. Type your plugin functions with the payload types +4. Return a plugin object that satisfies the `Plugin` interface + +Example plugin structure: + +```typescript +import type { MyPayloadType } from "reactotron-core-contract" +import type { ReactotronCore, Plugin } from "reactotron-core-client" + +export default (config = {}) => + (reactotron: ReactotronCore) => { + return { + // Handle server commands + onCommand: (command) => { + // Check command.type and handle accordingly + }, + + // Add features to Reactotron instance + features: { + myFeature: (data: MyPayloadType) => { + reactotron.send("my.command", data) + }, + }, + + // Lifecycle hooks + onConnect: () => { + /* ... */ + }, + onDisconnect: () => { + /* ... */ + }, + } satisfies Plugin + } +``` + +## Learn More + +- [Reactotron Core Client](../reactotron-core-client/README.md) - How to use the client +- [Creating Plugins](../../docs/plugins/index.md) - Guide to creating plugins +- [Custom Commands](../../docs/custom-commands.md) - Working with custom commands From 8a362294fb47496780e778f4cb86f23c9c718f09 Mon Sep 17 00:00:00 2001 From: Joshua Yoes <37849890+joshuayoes@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:25:21 -0700 Subject: [PATCH 5/5] refactor(reactotron-core-contract): simplify BenchmarkReportPayload by using BenchmarkStep type for steps --- lib/reactotron-core-contract/src/benchmark.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/reactotron-core-contract/src/benchmark.ts b/lib/reactotron-core-contract/src/benchmark.ts index 216a2623b..a8f86659f 100644 --- a/lib/reactotron-core-contract/src/benchmark.ts +++ b/lib/reactotron-core-contract/src/benchmark.ts @@ -1,8 +1,10 @@ +export type BenchmarkStep = { + title: string + time: number + delta: number +} + export interface BenchmarkReportPayload { title: string - steps: Array<{ - title: string - time: number - delta: number - }> + steps: BenchmarkStep[] }