From 163b71585e110c2d933bb956f7b281e477cfcd61 Mon Sep 17 00:00:00 2001 From: Jake Goulding Date: Fri, 10 Mar 2023 13:03:09 -0500 Subject: [PATCH] Rewrite websocket reducer with RTK --- ui/frontend/.eslintrc.js | 1 + ui/frontend/.prettierignore | 1 + ui/frontend/actions.ts | 20 ----------- ui/frontend/index.tsx | 2 +- ui/frontend/reducers/websocket.ts | 58 +++++++++++++++++++++--------- ui/frontend/websocketMiddleware.ts | 13 ++++--- ui/src/server_axum/websocket.rs | 11 ++++-- 7 files changed, 61 insertions(+), 45 deletions(-) diff --git a/ui/frontend/.eslintrc.js b/ui/frontend/.eslintrc.js index a4415f611..9e8824186 100644 --- a/ui/frontend/.eslintrc.js +++ b/ui/frontend/.eslintrc.js @@ -68,6 +68,7 @@ module.exports = { 'reducers/output/execute.ts', 'reducers/output/format.ts', 'reducers/output/gist.ts', + 'reducers/websocket.ts', 'websocketActions.ts', 'websocketMiddleware.ts', ], diff --git a/ui/frontend/.prettierignore b/ui/frontend/.prettierignore index 05931cd05..def633a59 100644 --- a/ui/frontend/.prettierignore +++ b/ui/frontend/.prettierignore @@ -17,5 +17,6 @@ node_modules !reducers/output/execute.ts !reducers/output/format.ts !reducers/output/gist.ts +!reducers/websocket.ts !websocketActions.ts !websocketMiddleware.ts diff --git a/ui/frontend/actions.ts b/ui/frontend/actions.ts index ff38f729b..92acac992 100644 --- a/ui/frontend/actions.ts +++ b/ui/frontend/actions.ts @@ -1,6 +1,5 @@ import fetch from 'isomorphic-fetch'; import { ThunkAction as ReduxThunkAction, AnyAction } from '@reduxjs/toolkit'; -import { z } from 'zod'; import { codeSelector, @@ -120,18 +119,8 @@ export enum ActionType { NotificationSeen = 'NOTIFICATION_SEEN', BrowserWidthChanged = 'BROWSER_WIDTH_CHANGED', SplitRatioChanged = 'SPLIT_RATIO_CHANGED', - WebSocketError = 'WEBSOCKET_ERROR', - WebSocketConnected = 'WEBSOCKET_CONNECTED', - WebSocketDisconnected = 'WEBSOCKET_DISCONNECTED', - WebSocketFeatureFlagEnabled = 'WEBSOCKET_FEATURE_FLAG_ENABLED', } -export const WebSocketError = z.object({ - type: z.literal(ActionType.WebSocketError), - error: z.string(), -}); -export type WebSocketError = z.infer; - export const initializeApplication = () => createAction(ActionType.InitializeApplication); export const disableSyncChangesToStorage = () => createAction(ActionType.DisableSyncChangesToStorage); @@ -675,11 +664,6 @@ export const browserWidthChanged = (isSmall: boolean) => export const splitRatioChanged = () => createAction(ActionType.SplitRatioChanged); -export const websocketError = (error: string): WebSocketError => createAction(ActionType.WebSocketError, { error }); -export const websocketConnected = () => createAction(ActionType.WebSocketConnected); -export const websocketDisconnected = () => createAction(ActionType.WebSocketDisconnected); -export const websocketFeatureFlagEnabled = () => createAction(ActionType.WebSocketFeatureFlagEnabled); - function parseChannel(s?: string): Channel | null { switch (s) { case 'stable': @@ -821,9 +805,5 @@ export type Action = | ReturnType | ReturnType | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType | ReturnType ; diff --git a/ui/frontend/index.tsx b/ui/frontend/index.tsx index 6bbc6f00a..e1525b087 100644 --- a/ui/frontend/index.tsx +++ b/ui/frontend/index.tsx @@ -19,11 +19,11 @@ import { performVersionsLoad, reExecuteWithBacktrace, browserWidthChanged, - websocketFeatureFlagEnabled, } from './actions'; import { configureRustErrors } from './highlighting'; import PageSwitcher from './PageSwitcher'; import playgroundApp from './reducers'; +import { websocketFeatureFlagEnabled } from './reducers/websocket'; import Router from './Router'; import configureStore from './configureStore'; diff --git a/ui/frontend/reducers/websocket.ts b/ui/frontend/reducers/websocket.ts index 183be1c83..b95654854 100644 --- a/ui/frontend/reducers/websocket.ts +++ b/ui/frontend/reducers/websocket.ts @@ -1,4 +1,7 @@ -import { Action, ActionType } from '../actions'; +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; +import z from 'zod'; + +import { createWebsocketResponseSchema } from '../websocketActions'; export type State = { connected: boolean; @@ -6,26 +9,49 @@ export type State = { featureFlagEnabled: boolean; }; -const DEFAULT: State = { +const initialState: State = { connected: false, featureFlagEnabled: false, }; -export default function websocket(state = DEFAULT, action: Action): State { - switch (action.type) { - case ActionType.WebSocketConnected: - return { ...state, connected: true, error: undefined }; +const websocketErrorPayloadSchema = z.object({ + error: z.string(), +}); +type websocketErrorPayload = z.infer; + +const slice = createSlice({ + name: 'websocket', + initialState, + reducers: { + connected: (state) => { + state.connected = true; + delete state.error; + }, + + disconnected: (state) => { + state.connected = false; + }, + + error: (state, action: PayloadAction) => { + state.error = action.payload.error; + }, - case ActionType.WebSocketDisconnected: - return { ...state, connected: false }; + featureFlagEnabled: (state) => { + state.featureFlagEnabled = true; + }, + }, +}); - case ActionType.WebSocketError: - return { ...state, error: action.error }; +export const { + connected: websocketConnected, + disconnected: websocketDisconnected, + error: websocketError, + featureFlagEnabled: websocketFeatureFlagEnabled, +} = slice.actions; - case ActionType.WebSocketFeatureFlagEnabled: - return { ...state, featureFlagEnabled: true }; +export const websocketErrorSchema = createWebsocketResponseSchema( + websocketError, + websocketErrorPayloadSchema, +); - default: - return state; - } -} +export default slice.reducer; diff --git a/ui/frontend/websocketMiddleware.ts b/ui/frontend/websocketMiddleware.ts index 9fe1510b4..68eeeabe3 100644 --- a/ui/frontend/websocketMiddleware.ts +++ b/ui/frontend/websocketMiddleware.ts @@ -1,15 +1,18 @@ import { AnyAction, Middleware } from '@reduxjs/toolkit'; import { z } from 'zod'; +import { wsExecuteResponseSchema } from './reducers/output/execute'; import { - WebSocketError, websocketConnected, websocketDisconnected, websocketError, -} from './actions'; -import { wsExecuteResponseSchema } from './reducers/output/execute'; + websocketErrorSchema, +} from './reducers/websocket'; -const WSMessageResponse = z.discriminatedUnion('type', [WebSocketError, wsExecuteResponseSchema]); +const WSMessageResponse = z.discriminatedUnion('type', [ + websocketErrorSchema, + wsExecuteResponseSchema, +]); const reportWebSocketError = async (error: string) => { try { @@ -93,7 +96,7 @@ export const websocketMiddleware = // We cannot get detailed information about the failure // https://stackoverflow.com/a/31003057/155423 const error = 'Generic WebSocket Error'; - store.dispatch(websocketError(error)); + store.dispatch(websocketError({ error })); reportWebSocketError(error); }); diff --git a/ui/src/server_axum/websocket.rs b/ui/src/server_axum/websocket.rs index f374b6695..c0d8b9e2a 100644 --- a/ui/src/server_axum/websocket.rs +++ b/ui/src/server_axum/websocket.rs @@ -63,8 +63,8 @@ impl TryFrom for sandbox::ExecuteRequest { #[derive(Debug, serde::Serialize)] #[serde(tag = "type")] enum MessageResponse { - #[serde(rename = "WEBSOCKET_ERROR")] - Error(WSError), + #[serde(rename = "websocket/error")] + Error { payload: WSError, meta: Meta }, #[serde(rename = "output/execute/wsExecuteResponse")] ExecuteResponse { @@ -174,7 +174,12 @@ pub async fn handle(mut socket: WebSocket) { fn error_to_response(error: Error) -> MessageResponse { let error = error.to_string(); - MessageResponse::Error(WSError { error }) + // TODO: thread through the Meta from the originating request + let meta = serde_json::json!({ "sequenceNumber": -1 }); + MessageResponse::Error { + payload: WSError { error }, + meta, + } } fn response_to_message(response: MessageResponse) -> Message {