diff --git a/web/src/js/__tests__/ducks/modes/wireguardSpec.tsx b/web/src/js/__tests__/ducks/modes/wireguardSpec.tsx new file mode 100644 index 000000000..e6fea6bdf --- /dev/null +++ b/web/src/js/__tests__/ducks/modes/wireguardSpec.tsx @@ -0,0 +1,110 @@ +import { enableFetchMocks } from "jest-fetch-mock"; +import wireguardReducer, { + getMode, + initialState, + toggleWireguard, +} from "../../../ducks/modes/wireguard"; +import { TStore } from "../tutils"; +import * as options from "../../../ducks/options"; + +describe("wireguardReducer", () => { + it("should return the initial state", () => { + const state = wireguardReducer(undefined, {}); + expect(state).toEqual(initialState); + }); + + it("should dispatch MODE_WIREGUARD_TOGGLE and updateMode", async () => { + enableFetchMocks(); + + const store = TStore(); + + expect(store.getState().modes.wireguard.active).toBe(false); + await store.dispatch(toggleWireguard()); + expect(store.getState().modes.wireguard.active).toBe(true); + expect(fetchMock).toHaveBeenCalled(); + }); + + it('should handle RECEIVE_OPTIONS action with data.mode containing "wireguard", an host and a port', () => { + const action = { + type: options.RECEIVE, + data: { + mode: { + value: ["wireguard:/path_example@localhost:8081"], + }, + }, + }; + const newState = wireguardReducer(initialState, action); + expect(newState.active).toBe(true); + expect(newState.listen_host).toBe("localhost"); + expect(newState.listen_port).toBe(8081); + expect(newState.path).toBe("/path_example"); + }); + + it('should handle RECEIVE_OPTIONS action with data.mode containing just "wireguard"', () => { + const initialState = { + active: false, + listen_host: "localhost", + listen_port: 8080, + }; + const action = { + type: options.RECEIVE, + data: { + mode: { + value: ["wireguard"], + }, + }, + }; + const newState = wireguardReducer(initialState, action); + expect(newState.active).toBe(true); + expect(newState.listen_host).toBe(""); + expect(newState.listen_port).toBe(""); + expect(newState.path).toBe(""); + }); + + it("should handle RECEIVE_OPTIONS action with data.mode containing another mode", () => { + const initialState = { + active: false, + listen_host: "localhost", + listen_port: 8080, + path: "/path_example", + }; + const action = { + type: options.RECEIVE, + data: { + mode: { + value: ["local"], + }, + }, + }; + const newState = wireguardReducer(initialState, action); + expect(newState.active).toBe(false); + expect(newState.listen_host).toBe(initialState.listen_host); + expect(newState.listen_port).toBe(initialState.listen_port); + expect(newState.path).toBe(initialState.path); + }); +}); + +describe("getMode", () => { + it("should return the correct mode string when active", () => { + const modes = { + wireguard: { + active: true, + }, + }; + const mode = getMode(modes); + expect(JSON.stringify(mode)).toBe(JSON.stringify(["wireguard"])); + }); + + it("should return an empty string when not active", () => { + const modes = { + wireguard: { + active: false, + path: "/path_example", + listen_host: "localhost", + listen_port: 8080, + }, + }; + const mode = getMode(modes); + expect(JSON.stringify(mode)).toBe(JSON.stringify([])); + }); +}); diff --git a/web/src/js/__tests__/ducks/tutils.ts b/web/src/js/__tests__/ducks/tutils.ts index fb93c81cd..592f34f32 100644 --- a/web/src/js/__tests__/ducks/tutils.ts +++ b/web/src/js/__tests__/ducks/tutils.ts @@ -132,6 +132,9 @@ export const testState: RootState = { active: false, applications: "", }, + wireguard: { + active: false, + }, }, }; diff --git a/web/src/js/components/Modes.tsx b/web/src/js/components/Modes.tsx index d1d236c9e..d3f055548 100644 --- a/web/src/js/components/Modes.tsx +++ b/web/src/js/components/Modes.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import Local from "./Modes/Local"; import Regular from "./Modes/Regular"; +import Wireguard from "./Modes/Wireguard"; export default function Modes() { return ( @@ -12,6 +13,7 @@ export default function Modes() {
+
); diff --git a/web/src/js/components/Modes/Wireguard.tsx b/web/src/js/components/Modes/Wireguard.tsx new file mode 100644 index 000000000..ec8ea7cb9 --- /dev/null +++ b/web/src/js/components/Modes/Wireguard.tsx @@ -0,0 +1,27 @@ +import * as React from "react"; +import { ModeToggle } from "./ModeToggle"; +import { useAppDispatch, useAppSelector } from "../../ducks"; +import { toggleWireguard } from "../../ducks/modes/wireguard"; + +export default function Wireguard() { + const dispatch = useAppDispatch(); + + const { active, error } = useAppSelector((state) => state.modes.wireguard); + + return ( +
+

WireGuard Server

+

+ Start a WireGuard™ server and connect an external device for + transparent proxying. +

+ dispatch(toggleWireguard())} + > + Run WireGuard Server + + {error &&
{error}
} +
+ ); +} diff --git a/web/src/js/ducks/modes.ts b/web/src/js/ducks/modes.ts index f4797fd56..5e88ecce1 100644 --- a/web/src/js/ducks/modes.ts +++ b/web/src/js/ducks/modes.ts @@ -1,10 +1,12 @@ import { combineReducers } from "redux"; import regularReducer from "./modes/regular"; import localReducer from "./modes/local"; +import wireguardReducer from "./modes/wireguard"; const modes = combineReducers({ regular: regularReducer, local: localReducer, + wireguard: wireguardReducer, //add new modes here }); diff --git a/web/src/js/ducks/modes/utils.ts b/web/src/js/ducks/modes/utils.ts index e62b5698e..27de54ae9 100644 --- a/web/src/js/ducks/modes/utils.ts +++ b/web/src/js/ducks/modes/utils.ts @@ -1,5 +1,6 @@ import { getMode as getRegularModeConfig } from "./regular"; import { getMode as getLocalModeConfig } from "./local"; +import { getMode as getWireguardModeConfig } from "./wireguard"; import { fetchApi, rpartition } from "../../utils"; export interface ModeState { @@ -17,6 +18,7 @@ export const updateMode = () => { const activeModes: string[] = [ ...getRegularModeConfig(modes), ...getLocalModeConfig(modes), + ...getWireguardModeConfig(modes), //add new modes here ]; const response = await fetchApi.put("/options", { diff --git a/web/src/js/ducks/modes/wireguard.ts b/web/src/js/ducks/modes/wireguard.ts new file mode 100644 index 000000000..a81885d9b --- /dev/null +++ b/web/src/js/ducks/modes/wireguard.ts @@ -0,0 +1,73 @@ +import { + RECEIVE as RECEIVE_OPTIONS, + UPDATE as UPDATE_OPTIONS, +} from "../options"; +import { + ModeState, + includeModeState, + updateMode, + getModesOfType, +} from "./utils"; + +export const MODE_WIREGUARD_TOGGLE = "MODE_WIREGUARD_TOGGLE"; + +interface WireguardState extends ModeState { + path?: string; +} + +export const initialState: WireguardState = { + active: false, + path: "", +}; + +export const getMode = (modes) => { + const wireguardMode = modes.wireguard; + return includeModeState("wireguard", wireguardMode); +}; + +export const toggleWireguard = () => { + return async (dispatch) => { + dispatch({ type: MODE_WIREGUARD_TOGGLE }); + + const result = await dispatch(updateMode()); + + if (!result.success) { + //TODO: handle error + } + }; +}; + +const wireguardReducer = (state = initialState, action): WireguardState => { + switch (action.type) { + case MODE_WIREGUARD_TOGGLE: + return { + ...state, + active: !state.active, + }; + case UPDATE_OPTIONS: + case RECEIVE_OPTIONS: + if (action.data && action.data.mode) { + const currentModeConfig = getModesOfType( + "wireguard", + action.data.mode.value, + )[0]; + const isActive = currentModeConfig !== undefined; + return { + ...state, + active: isActive, + path: isActive ? currentModeConfig.data : state.path, + listen_host: isActive + ? currentModeConfig.listen_host + : state.listen_host, + listen_port: isActive + ? (currentModeConfig.listen_port as number) + : state.listen_port, + }; + } + return state; + default: + return state; + } +}; + +export default wireguardReducer;