diff --git a/web/settings/account-settings.css b/web/settings/account-settings.css
--- a/web/settings/account-settings.css
+++ b/web/settings/account-settings.css
@@ -1,6 +1,7 @@
.container {
flex: 1;
background-color: var(--panel-background-primary-default);
+ overflow: auto;
}
.contentContainer {
diff --git a/web/settings/account-settings.react.js b/web/settings/account-settings.react.js
--- a/web/settings/account-settings.react.js
+++ b/web/settings/account-settings.react.js
@@ -8,6 +8,7 @@
import { useStringForUser } from 'lib/hooks/ens-cache.js';
import { preRequestUserStateSelector } from 'lib/selectors/account-selectors.js';
import { accountHasPassword } from 'lib/shared/account-utils.js';
+import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js';
import {
useDispatchActionPromise,
useServerCall,
@@ -18,6 +19,8 @@
import PasswordChangeModal from './password-change-modal.js';
import BlockListModal from './relationship/block-list-modal.react.js';
import FriendListModal from './relationship/friend-list-modal.react.js';
+import TunnelbrokerMessagesScreen from './tunnelbroker-message-list.react.js';
+import TunnelbrokerTestScreen from './tunnelbroker-test.react.js';
import EditUserAvatar from '../avatars/edit-user-avatar.react.js';
import Button from '../components/button.react.js';
import { useSelector } from '../redux/redux-utils.js';
@@ -60,6 +63,27 @@
const stringForUser = useStringForUser(currentUserInfo);
const staffCanSee = useStaffCanSee();
+ const { sendMessage, connected, addListener, removeListener } =
+ useTunnelbroker();
+ const openTunnelbrokerModal = React.useCallback(
+ () =>
+ pushModal(
+ ,
+ ),
+ [popModal, pushModal, sendMessage],
+ );
+
+ const openTunnelbrokerMessagesModal = React.useCallback(
+ () =>
+ pushModal(
+ ,
+ ),
+ [addListener, popModal, pushModal, removeListener],
+ );
const showAppearanceModal = React.useCallback(
() => pushModal(),
@@ -103,6 +127,34 @@
);
}
+ let tunnelbroker;
+ if (staffCanSee) {
+ tunnelbroker = (
+
+
Tunnerlbroker menu
+
+
+ -
+ Connected
+ {connected.toString()}
+
+ -
+ Send message to device
+
+
+ -
+ Trace received messages
+
+
+
+
+
+ );
+ }
return (
@@ -136,6 +188,7 @@
{preferences}
+ {tunnelbroker}
);
diff --git a/web/settings/tunnelbroker-message-list.css b/web/settings/tunnelbroker-message-list.css
new file mode 100644
--- /dev/null
+++ b/web/settings/tunnelbroker-message-list.css
@@ -0,0 +1,21 @@
+.messageList {
+ display: flex;
+ flex-direction: column;
+ color: #fff;
+ height: 100%;
+ width: 100%;
+ overflow-y: auto;
+}
+
+.messageRow {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ padding: 10px;
+ border-bottom: 1px solid #ddd;
+}
+
+.messageCol {
+ flex: 1;
+ padding: 15px;
+}
diff --git a/web/settings/tunnelbroker-message-list.react.js b/web/settings/tunnelbroker-message-list.react.js
new file mode 100644
--- /dev/null
+++ b/web/settings/tunnelbroker-message-list.react.js
@@ -0,0 +1,50 @@
+// @flow
+
+import * as React from 'react';
+
+import type { TunnelbrokerSocketListener } from 'lib/tunnelbroker/tunnelbroker-context.js';
+import type { TunnelbrokerMessage } from 'lib/types/tunnelbroker/messages.js';
+
+import css from './tunnelbroker-message-list.css';
+import Modal from '../modals/modal.react.js';
+
+type Props = {
+ +addListener: (listener: TunnelbrokerSocketListener) => void,
+ +removeListener: (listener: TunnelbrokerSocketListener) => void,
+ +onClose: () => void,
+};
+
+function TunnelbrokerMessagesScreen(props: Props): React.Node {
+ const { addListener, onClose, removeListener } = props;
+ const [messages, setMessages] = React.useState([]);
+
+ const listener = React.useCallback((msg: TunnelbrokerMessage) => {
+ setMessages(prev => [...prev, msg]);
+ }, []);
+
+ React.useEffect(() => {
+ addListener(listener);
+ return () => removeListener(listener);
+ }, [addListener, listener, removeListener]);
+
+ let messageList = (
+
+ );
+ if (messages.length) {
+ messageList = messages.map(message => (
+
+
{JSON.stringify(message)}
+
+ ));
+ }
+
+ return (
+
+ {messageList}
+
+ );
+}
+
+export default TunnelbrokerMessagesScreen;
diff --git a/web/settings/tunnelbroker-test.css b/web/settings/tunnelbroker-test.css
new file mode 100644
--- /dev/null
+++ b/web/settings/tunnelbroker-test.css
@@ -0,0 +1,25 @@
+.modalBody {
+ padding: 24px 40px 32px;
+ color: var(--fg);
+}
+
+.content {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.footer {
+ display: flex;
+ flex-direction: row-reverse;
+ justify-content: space-between;
+ padding-top: 8px;
+}
+
+.modalError {
+ font-size: var(--xs-font-12);
+ color: var(--error);
+ font-style: italic;
+ padding-left: 6px;
+ align-self: center;
+}
diff --git a/web/settings/tunnelbroker-test.react.js b/web/settings/tunnelbroker-test.react.js
new file mode 100644
--- /dev/null
+++ b/web/settings/tunnelbroker-test.react.js
@@ -0,0 +1,94 @@
+// @flow
+
+import invariant from 'invariant';
+import * as React from 'react';
+
+import { type ClientMessageToDevice } from 'lib/tunnelbroker/tunnelbroker-context.js';
+
+import css from './tunnelbroker-test.css';
+import Button from '../components/button.react.js';
+import Input from '../modals/input.react.js';
+import Modal from '../modals/modal.react.js';
+
+type Props = {
+ +sendMessage: (message: ClientMessageToDevice) => Promise,
+ +onClose: () => void,
+};
+
+function TunnelbrokerTestScreen(props: Props): React.Node {
+ const { sendMessage, onClose } = props;
+ const [recipient, setRecipient] = React.useState('');
+ const [message, setMessage] = React.useState('');
+ const [loading, setLoading] = React.useState(false);
+ const [errorMessage, setErrorMessage] = React.useState('');
+ const recipientInput = React.useRef(null);
+ const messageInput = React.useRef(null);
+
+ const onSubmit = React.useCallback(
+ async event => {
+ event.preventDefault();
+
+ setLoading(true);
+ try {
+ await sendMessage({ deviceID: recipient, payload: message });
+ } catch (e) {
+ setErrorMessage(e.message);
+ }
+ setLoading(false);
+ },
+ [message, recipient, sendMessage],
+ );
+
+ let errorMsg;
+ if (errorMessage) {
+ errorMsg = {errorMessage}
;
+ }
+
+ return (
+
+
+
+ );
+}
+
+export default TunnelbrokerTestScreen;