Page MenuHomePhabricator

D11123.diff
No OneTemporary

D11123.diff

diff --git a/lib/tunnelbroker/secondary-tunnelbroker-connection.js b/lib/tunnelbroker/secondary-tunnelbroker-connection.js
new file mode 100644
--- /dev/null
+++ b/lib/tunnelbroker/secondary-tunnelbroker-connection.js
@@ -0,0 +1,19 @@
+// @flow
+
+import type { MessageToDeviceRequest } from '../types/tunnelbroker/message-to-device-request-types.js';
+
+type RemoveCallback = () => void;
+
+export type SecondaryTunnelbrokerConnection = {
+ // Used by an inactive tab to send messages
+ sendMessage: MessageToDeviceRequest => mixed,
+ // Active tab receives messages from inactive tabs
+ onSendMessage: ((MessageToDeviceRequest) => mixed) => RemoveCallback,
+
+ // Active tab sets the message status of messages from inactive tabs
+ setMessageStatus: (messageID: string, error: ?string) => mixed,
+ // Inactive tabs receive message status and resolve or reject promises
+ onMessageStatus: (
+ ((messageID: string, error: ?string) => mixed),
+ ) => RemoveCallback,
+};
diff --git a/lib/tunnelbroker/tunnelbroker-context.js b/lib/tunnelbroker/tunnelbroker-context.js
--- a/lib/tunnelbroker/tunnelbroker-context.js
+++ b/lib/tunnelbroker/tunnelbroker-context.js
@@ -4,6 +4,7 @@
import * as React from 'react';
import uuid from 'uuid';
+import type { SecondaryTunnelbrokerConnection } from './secondary-tunnelbroker-connection.js';
import { tunnnelbrokerURL } from '../facts/tunnelbroker.js';
import { peerToPeerMessageHandler } from '../handlers/peer-to-peer-message-handler.js';
import { IdentityClientContext } from '../shared/identity-client-context.js';
@@ -58,6 +59,7 @@
+shouldBeClosed?: boolean,
+onClose?: () => mixed,
+initMessage: ?ConnectionInitializationMessage,
+ +secondaryTunnelbrokerConnection?: SecondaryTunnelbrokerConnection,
};
function createAnonymousInitMessage(
@@ -76,6 +78,7 @@
shouldBeClosed,
onClose,
initMessage: initMessageProp,
+ secondaryTunnelbrokerConnection,
} = props;
const [connected, setConnected] = React.useState(false);
const listeners = React.useRef<Set<TunnelbrokerSocketListener>>(new Set());
@@ -269,12 +272,32 @@
onClose,
]);
+ const sendMessageToDeviceRequest: (
+ request: MessageToDeviceRequest,
+ ) => Promise<void> = React.useCallback(
+ request => {
+ return new Promise((resolve, reject) => {
+ const socketActive = connected && socket.current;
+ if (!shouldBeClosed && !socketActive) {
+ throw new Error('Tunnelbroker not connected');
+ }
+ promises.current[request.clientMessageID] = {
+ resolve,
+ reject,
+ };
+ if (socketActive) {
+ socket.current?.send(JSON.stringify(request));
+ } else {
+ secondaryTunnelbrokerConnection?.sendMessage(request);
+ }
+ });
+ },
+ [connected, secondaryTunnelbrokerConnection, shouldBeClosed],
+ );
+
const sendMessage: (message: ClientMessageToDevice) => Promise<void> =
React.useCallback(
(message: ClientMessageToDevice) => {
- if (!connected || !socket.current) {
- throw new Error('Tunnelbroker not connected');
- }
const clientMessageID = uuid.v4();
const messageToDevice: MessageToDeviceRequest = {
type: tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE_REQUEST,
@@ -283,17 +306,53 @@
payload: message.payload,
};
- return new Promise((resolve, reject) => {
- promises.current[clientMessageID] = {
- resolve,
- reject,
- };
- socket.current?.send(JSON.stringify(messageToDevice));
- });
+ return sendMessageToDeviceRequest(messageToDevice);
},
- [connected],
+ [sendMessageToDeviceRequest],
);
+ React.useEffect(
+ () =>
+ secondaryTunnelbrokerConnection?.onSendMessage(message => {
+ if (shouldBeClosed) {
+ // We aren't supposed to be handling it
+ return;
+ }
+
+ void (async () => {
+ try {
+ await sendMessageToDeviceRequest(message);
+ secondaryTunnelbrokerConnection.setMessageStatus(
+ message.clientMessageID,
+ );
+ } catch (error) {
+ secondaryTunnelbrokerConnection.setMessageStatus(
+ message.clientMessageID,
+ error,
+ );
+ }
+ })();
+ }),
+ [
+ secondaryTunnelbrokerConnection,
+ sendMessageToDeviceRequest,
+ shouldBeClosed,
+ ],
+ );
+
+ React.useEffect(
+ () =>
+ secondaryTunnelbrokerConnection?.onMessageStatus((messageID, error) => {
+ if (error) {
+ promises.current[messageID].reject(error);
+ } else {
+ promises.current[messageID].resolve();
+ }
+ delete promises.current[messageID];
+ }),
+ [secondaryTunnelbrokerConnection],
+ );
+
const addListener = React.useCallback(
(listener: TunnelbrokerSocketListener) => {
listeners.current.add(listener);
diff --git a/web/app.react.js b/web/app.react.js
--- a/web/app.react.js
+++ b/web/app.react.js
@@ -28,10 +28,12 @@
} from 'lib/selectors/loading-selectors.js';
import { isLoggedIn } from 'lib/selectors/user-selectors.js';
import { extractMajorDesktopVersion } from 'lib/shared/version-utils.js';
+import type { SecondaryTunnelbrokerConnection } from 'lib/tunnelbroker/secondary-tunnelbroker-connection.js';
import { TunnelbrokerProvider } from 'lib/tunnelbroker/tunnelbroker-context.js';
import type { LoadingStatus } from 'lib/types/loading-types.js';
import type { WebNavInfo } from 'lib/types/nav-types.js';
import type { Dispatch } from 'lib/types/redux-types.js';
+import type { MessageToDeviceRequest } from 'lib/types/tunnelbroker/message-to-device-request-types.js';
import { getConfig, registerConfig } from 'lib/utils/config.js';
import { useDispatch } from 'lib/utils/redux-utils.js';
import { infoFromURL } from 'lib/utils/url-utils.js';
@@ -361,6 +363,119 @@
}
}
+const WEB_TUNNELBROKER_CHANNEL = new BroadcastChannel('shared-tunnelbroker');
+const WEB_TUNNELBROKER_MESSAGE_TYPES = Object.freeze({
+ SEND_MESSAGE: 'send-message',
+ MESSAGE_STATUS: 'message-status',
+});
+
+function useOtherTabsTunnelbrokerConnection(): SecondaryTunnelbrokerConnection {
+ const onSendMessageCallbacks = React.useRef<
+ Set<(MessageToDeviceRequest) => mixed>,
+ >(new Set());
+
+ const onMessageStatusCallbacks = React.useRef<
+ Set<(messageID: string, error: ?string) => mixed>,
+ >(new Set());
+
+ React.useEffect(() => {
+ const messageHandler = (event: MessageEvent) => {
+ if (typeof event.data !== 'object' || !event.data) {
+ console.log(
+ 'Invalid message received from shared ' +
+ 'tunnelbroker broadcast channel',
+ event.data,
+ );
+ return;
+ }
+ const data = event.data;
+ if (data.type === WEB_TUNNELBROKER_MESSAGE_TYPES.SEND_MESSAGE) {
+ if (typeof data.message !== 'object' || !data.message) {
+ console.log(
+ 'Invalid tunnelbroker message request received ' +
+ 'from shared tunnelbroker broadcast channel',
+ event.data,
+ );
+ return;
+ }
+ // We know that the input was already validated
+ const message: MessageToDeviceRequest = (data.message: any);
+
+ for (const callback of onSendMessageCallbacks.current) {
+ callback(message);
+ }
+ } else if (data.type === WEB_TUNNELBROKER_MESSAGE_TYPES.MESSAGE_STATUS) {
+ if (typeof data.messageID !== 'string') {
+ console.log(
+ 'Missing message id in message status message ' +
+ 'from shared tunnelbroker broadcast channel',
+ );
+ return;
+ }
+ const messageID = data.messageID;
+
+ if (
+ typeof data.error !== 'string' &&
+ data.error !== null &&
+ data.error !== undefined
+ ) {
+ console.log(
+ 'Invalid error in message status message ' +
+ 'from shared tunnelbroker broadcast channel',
+ data.error,
+ );
+ return;
+ }
+ const error = data.error;
+
+ for (const callback of onMessageStatusCallbacks.current) {
+ callback(messageID, error);
+ }
+ } else {
+ console.log(
+ 'Invalid message type ' +
+ 'from shared tunnelbroker broadcast channel',
+ data,
+ );
+ }
+ };
+
+ WEB_TUNNELBROKER_CHANNEL.addEventListener('message', messageHandler);
+ return () =>
+ WEB_TUNNELBROKER_CHANNEL.removeEventListener('message', messageHandler);
+ }, [onMessageStatusCallbacks, onSendMessageCallbacks]);
+
+ return React.useMemo(
+ () => ({
+ sendMessage: message =>
+ WEB_TUNNELBROKER_CHANNEL.postMessage({
+ type: WEB_TUNNELBROKER_MESSAGE_TYPES.SEND_MESSAGE,
+ message,
+ }),
+ onSendMessage: callback => {
+ onSendMessageCallbacks.current.add(callback);
+ return () => {
+ onSendMessageCallbacks.current.delete(callback);
+ };
+ },
+ setMessageStatus: (messageID, error) => {
+ WEB_TUNNELBROKER_CHANNEL.postMessage({
+ type: WEB_TUNNELBROKER_MESSAGE_TYPES.MESSAGE_STATUS,
+ messageID,
+ error,
+ });
+ },
+ onMessageStatus: callback => {
+ onMessageStatusCallbacks.current.add(callback);
+ return () => {
+ onMessageStatusCallbacks.current.delete(callback);
+ };
+ },
+ }),
+ [onMessageStatusCallbacks, onSendMessageCallbacks],
+ );
+}
+
const fetchEntriesLoadingStatusSelector = createLoadingStatusSelector(
fetchEntriesActionTypes,
);
@@ -411,12 +526,16 @@
TUNNELBROKER_LOCK_NAME,
);
+ const secondaryTunnelbrokerConnection: SecondaryTunnelbrokerConnection =
+ useOtherTabsTunnelbrokerConnection();
+
return (
<AppThemeWrapper>
<TunnelbrokerProvider
initMessage={tunnelbrokerInitMessage}
shouldBeClosed={lockStatus !== 'acquired'}
onClose={releaseLockOrAbortRequest}
+ secondaryTunnelbrokerConnection={secondaryTunnelbrokerConnection}
>
<IdentitySearchProvider>
<App

File Metadata

Mime Type
text/plain
Expires
Mon, Nov 25, 3:30 AM (21 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2578178
Default Alt Text
D11123.diff (10 KB)

Event Timeline