Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3358119
D11123.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
10 KB
Referenced Files
None
Subscribers
None
D11123.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D11123: [web] Proxy tunnelbroker messages to the active tab
Attached
Detach File
Event Timeline
Log In to Comment