Page MenuHomePhabricator

D11372.diff
No OneTemporary

D11372.diff

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
@@ -1,6 +1,7 @@
// @flow
import invariant from 'invariant';
+import _isEqual from 'lodash/fp/isEqual.js';
import * as React from 'react';
import uuid from 'uuid';
@@ -9,6 +10,7 @@
import { peerToPeerMessageHandler } from '../handlers/peer-to-peer-message-handler.js';
import { IdentityClientContext } from '../shared/identity-client-context.js';
import { tunnelbrokerHeartbeatTimeout } from '../shared/timeouts.js';
+import { isWebPlatform } from '../types/device-types.js';
import type { MessageReceiveConfirmation } from '../types/tunnelbroker/message-receive-confirmation-types.js';
import type { MessageSentStatus } from '../types/tunnelbroker/message-to-device-request-status-types.js';
import type { MessageToDeviceRequest } from '../types/tunnelbroker/message-to-device-request-types.js';
@@ -23,10 +25,14 @@
} from '../types/tunnelbroker/peer-to-peer-message-types.js';
import type {
AnonymousInitializationMessage,
- TunnelbrokerInitializationMessage,
ConnectionInitializationMessage,
+ TunnelbrokerInitializationMessage,
+ TunnelbrokerDeviceTypes,
} from '../types/tunnelbroker/session-types.js';
import type { Heartbeat } from '../types/websocket/heartbeat-types.js';
+import { getConfig } from '../utils/config.js';
+import { getContentSigningKey } from '../utils/crypto-utils.js';
+import { useSelector } from '../utils/redux-utils.js';
export type ClientMessageToDevice = {
+deviceID: string,
@@ -59,55 +65,72 @@
+children: React.Node,
+shouldBeClosed?: boolean,
+onClose?: () => mixed,
- +initMessage: ?ConnectionInitializationMessage,
+secondaryTunnelbrokerConnection?: SecondaryTunnelbrokerConnection,
};
+function getTunnelbrokerDeviceType(): TunnelbrokerDeviceTypes {
+ return isWebPlatform(getConfig().platformDetails.platform) ? 'web' : 'mobile';
+}
+
function createAnonymousInitMessage(
deviceID: string,
): AnonymousInitializationMessage {
return ({
type: 'AnonymousInitializationMessage',
deviceID,
- deviceType: 'mobile',
+ deviceType: getTunnelbrokerDeviceType(),
}: AnonymousInitializationMessage);
}
function TunnelbrokerProvider(props: Props): React.Node {
- const {
- children,
- shouldBeClosed,
- onClose,
- initMessage: initMessageProp,
- secondaryTunnelbrokerConnection,
- } = props;
- const [connected, setConnected] = React.useState(false);
- const listeners = React.useRef<Set<TunnelbrokerSocketListener>>(new Set());
- const socket = React.useRef<?WebSocket>(null);
- const promises = React.useRef<Promises>({});
- const heartbeatTimeoutID = React.useRef<?TimeoutID>();
+ const { children, shouldBeClosed, onClose, secondaryTunnelbrokerConnection } =
+ props;
+
+ const accessToken = useSelector(state => state.commServicesAccessToken);
+ const userID = useSelector(state => state.currentUserInfo?.id);
+
const [unauthorizedDeviceID, setUnauthorizedDeviceID] =
React.useState<?string>(null);
const isAuthorized = !unauthorizedDeviceID;
- const identityContext = React.useContext(IdentityClientContext);
- invariant(identityContext, 'Identity context should be set');
- const { identityClient } = identityContext;
-
- const initMessage = React.useMemo(() => {
+ const createInitMessage = React.useCallback(async () => {
if (shouldBeClosed) {
return null;
}
- if (!unauthorizedDeviceID) {
- return initMessageProp;
+
+ if (unauthorizedDeviceID) {
+ return createAnonymousInitMessage(unauthorizedDeviceID);
+ }
+
+ if (!accessToken || !userID) {
+ return null;
+ }
+
+ const deviceID = await getContentSigningKey();
+ if (!deviceID) {
+ return null;
}
- return createAnonymousInitMessage(unauthorizedDeviceID);
- }, [shouldBeClosed, unauthorizedDeviceID, initMessageProp]);
+ return ({
+ type: 'ConnectionInitializationMessage',
+ deviceID,
+ accessToken,
+ userID,
+ deviceType: getTunnelbrokerDeviceType(),
+ }: ConnectionInitializationMessage);
+ }, [accessToken, shouldBeClosed, unauthorizedDeviceID, userID]);
const previousInitMessage =
- React.useRef<?TunnelbrokerInitializationMessage>(initMessage);
- const initMessageChanged = initMessage !== previousInitMessage.current;
- previousInitMessage.current = initMessage;
+ React.useRef<?TunnelbrokerInitializationMessage>(null);
+
+ const [connected, setConnected] = React.useState(false);
+ const listeners = React.useRef<Set<TunnelbrokerSocketListener>>(new Set());
+ const socket = React.useRef<?WebSocket>(null);
+ const promises = React.useRef<Promises>({});
+ const heartbeatTimeoutID = React.useRef<?TimeoutID>();
+
+ const identityContext = React.useContext(IdentityClientContext);
+ invariant(identityContext, 'Identity context should be set');
+ const { identityClient } = identityContext;
const stopHeartbeatTimeout = React.useCallback(() => {
if (heartbeatTimeoutID.current) {
@@ -129,6 +152,7 @@
socket.current?.readyState === WebSocket.OPEN ||
socket.current?.readyState === WebSocket.CONNECTING;
+ const connectionChangePromise = React.useRef<?Promise<void>>(null);
// The Tunnelbroker connection can have 4 states:
// - DISCONNECTED: isSocketActive = false, connected = false
// Should be in this state when initMessage is null
@@ -138,144 +162,161 @@
// - DISCONNECTING: isSocketActive = false, connected = true
// This lasts between socket.close() and socket.onclose()
React.useEffect(() => {
- // when initMessage changes, we need to close the socket and open a new one
- if (
- (!initMessage || initMessageChanged) &&
- isSocketActive &&
- socket.current
- ) {
- socket.current?.close();
- return;
- }
-
- // when we're already connected (or pending disconnection),
- // or there's no init message to start with, we don't need to do anything
- if (connected || !initMessage || socket.current) {
- return;
- }
-
- const tunnelbrokerSocket = new WebSocket(tunnnelbrokerURL);
-
- tunnelbrokerSocket.onopen = () => {
- tunnelbrokerSocket.send(JSON.stringify(initMessage));
- };
-
- tunnelbrokerSocket.onclose = () => {
- // this triggers the effect hook again and reconnect
- setConnected(false);
- onClose?.();
- socket.current = null;
- console.log('Connection to Tunnelbroker closed');
- };
- tunnelbrokerSocket.onerror = e => {
- console.log('Tunnelbroker socket error:', e.message);
- };
- tunnelbrokerSocket.onmessage = (event: MessageEvent) => {
- if (typeof event.data !== 'string') {
- console.log('socket received a non-string message');
- return;
- }
- let rawMessage;
+ connectionChangePromise.current = (async () => {
+ await connectionChangePromise.current;
try {
- rawMessage = JSON.parse(event.data);
- } catch (e) {
- console.log('error while parsing Tunnelbroker message:', e.message);
- return;
- }
+ const initMessage = await createInitMessage();
+ const initMessageChanged = !_isEqual(
+ previousInitMessage.current,
+ initMessage,
+ );
+ previousInitMessage.current = initMessage;
+
+ // when initMessage changes, we need to close the socket
+ // and open a new one
+ if (
+ (!initMessage || initMessageChanged) &&
+ isSocketActive &&
+ socket.current
+ ) {
+ socket.current?.close();
+ return;
+ }
- if (!tunnelbrokerMessageValidator.is(rawMessage)) {
- console.log('invalid TunnelbrokerMessage');
- return;
- }
- const message: TunnelbrokerMessage = rawMessage;
+ // when we're already connected (or pending disconnection),
+ // or there's no init message to start with, we don't need
+ // to do anything
+ if (connected || !initMessage || socket.current) {
+ return;
+ }
- resetHeartbeatTimeout();
+ const tunnelbrokerSocket = new WebSocket(tunnnelbrokerURL);
- for (const listener of listeners.current) {
- listener(message);
- }
+ tunnelbrokerSocket.onopen = () => {
+ tunnelbrokerSocket.send(JSON.stringify(initMessage));
+ };
- if (
- message.type ===
- tunnelbrokerMessageTypes.CONNECTION_INITIALIZATION_RESPONSE
- ) {
- if (message.status.type === 'Success' && !connected) {
- setConnected(true);
- console.log(
- 'session with Tunnelbroker created. isAuthorized:',
- isAuthorized,
- );
- } else if (message.status.type === 'Success' && connected) {
- console.log(
- 'received ConnectionInitializationResponse with status: Success for already connected socket',
- );
- } else {
+ tunnelbrokerSocket.onclose = () => {
+ // this triggers the effect hook again and reconnect
setConnected(false);
- console.log(
- 'creating session with Tunnelbroker error:',
- message.status.data,
- );
- }
- } else if (message.type === tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE) {
- const confirmation: MessageReceiveConfirmation = {
- type: tunnelbrokerMessageTypes.MESSAGE_RECEIVE_CONFIRMATION,
- messageIDs: [message.messageID],
+ onClose?.();
+ socket.current = null;
+ console.log('Connection to Tunnelbroker closed');
};
- socket.current?.send(JSON.stringify(confirmation));
-
- let rawPeerToPeerMessage;
- try {
- rawPeerToPeerMessage = JSON.parse(message.payload);
- } catch (e) {
- console.log(
- 'error while parsing Tunnelbroker peer-to-peer message:',
- e.message,
- );
- return;
- }
+ tunnelbrokerSocket.onerror = e => {
+ console.log('Tunnelbroker socket error:', e.message);
+ };
+ tunnelbrokerSocket.onmessage = (event: MessageEvent) => {
+ if (typeof event.data !== 'string') {
+ console.log('socket received a non-string message');
+ return;
+ }
+ let rawMessage;
+ try {
+ rawMessage = JSON.parse(event.data);
+ } catch (e) {
+ console.log('error while parsing Tunnelbroker message:', e.message);
+ return;
+ }
- if (!peerToPeerMessageValidator.is(rawPeerToPeerMessage)) {
- console.log('invalid Tunnelbroker PeerToPeerMessage');
- return;
- }
- const peerToPeerMessage: PeerToPeerMessage = rawPeerToPeerMessage;
- void peerToPeerMessageHandler(peerToPeerMessage, identityClient);
- } else if (
- message.type ===
- tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE_REQUEST_STATUS
- ) {
- for (const status: MessageSentStatus of message.clientMessageIDs) {
- if (status.type === 'Success') {
- promises.current[status.data]?.resolve();
- delete promises.current[status.data];
- } else if (status.type === 'Error') {
- promises.current[status.data.id]?.reject(status.data.error);
- delete promises.current[status.data.id];
- } else if (status.type === 'SerializationError') {
- console.log('SerializationError for message: ', status.data);
- } else if (status.type === 'InvalidRequest') {
- console.log('Tunnelbroker recorded InvalidRequest');
+ if (!tunnelbrokerMessageValidator.is(rawMessage)) {
+ console.log('invalid TunnelbrokerMessage');
+ return;
+ }
+ const message: TunnelbrokerMessage = rawMessage;
+
+ resetHeartbeatTimeout();
+
+ for (const listener of listeners.current) {
+ listener(message);
+ }
+
+ if (
+ message.type ===
+ tunnelbrokerMessageTypes.CONNECTION_INITIALIZATION_RESPONSE
+ ) {
+ if (message.status.type === 'Success' && !connected) {
+ setConnected(true);
+ console.log(
+ 'session with Tunnelbroker created. isAuthorized:',
+ isAuthorized,
+ );
+ } else if (message.status.type === 'Success' && connected) {
+ console.log(
+ 'received ConnectionInitializationResponse with status: Success for already connected socket',
+ );
+ } else {
+ setConnected(false);
+ console.log(
+ 'creating session with Tunnelbroker error:',
+ message.status.data,
+ );
+ }
+ } else if (
+ message.type === tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE
+ ) {
+ const confirmation: MessageReceiveConfirmation = {
+ type: tunnelbrokerMessageTypes.MESSAGE_RECEIVE_CONFIRMATION,
+ messageIDs: [message.messageID],
+ };
+ socket.current?.send(JSON.stringify(confirmation));
+
+ let rawPeerToPeerMessage;
+ try {
+ rawPeerToPeerMessage = JSON.parse(message.payload);
+ } catch (e) {
+ console.log(
+ 'error while parsing Tunnelbroker peer-to-peer message:',
+ e.message,
+ );
+ return;
+ }
+
+ if (!peerToPeerMessageValidator.is(rawPeerToPeerMessage)) {
+ console.log('invalid Tunnelbroker PeerToPeerMessage');
+ return;
+ }
+ const peerToPeerMessage: PeerToPeerMessage = rawPeerToPeerMessage;
+ void peerToPeerMessageHandler(peerToPeerMessage, identityClient);
+ } else if (
+ message.type ===
+ tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE_REQUEST_STATUS
+ ) {
+ for (const status: MessageSentStatus of message.clientMessageIDs) {
+ if (status.type === 'Success') {
+ promises.current[status.data]?.resolve();
+ delete promises.current[status.data];
+ } else if (status.type === 'Error') {
+ promises.current[status.data.id]?.reject(status.data.error);
+ delete promises.current[status.data.id];
+ } else if (status.type === 'SerializationError') {
+ console.log('SerializationError for message: ', status.data);
+ } else if (status.type === 'InvalidRequest') {
+ console.log('Tunnelbroker recorded InvalidRequest');
+ }
+ }
+ } else if (message.type === tunnelbrokerMessageTypes.HEARTBEAT) {
+ const heartbeat: Heartbeat = {
+ type: tunnelbrokerMessageTypes.HEARTBEAT,
+ };
+ socket.current?.send(JSON.stringify(heartbeat));
}
- }
- } else if (message.type === tunnelbrokerMessageTypes.HEARTBEAT) {
- const heartbeat: Heartbeat = {
- type: tunnelbrokerMessageTypes.HEARTBEAT,
};
- socket.current?.send(JSON.stringify(heartbeat));
- }
- };
- socket.current = tunnelbrokerSocket;
+ socket.current = tunnelbrokerSocket;
+ } catch (err) {
+ console.log('Tunnelbroker connection error:', err);
+ }
+ })();
}, [
connected,
- initMessage,
- initMessageChanged,
isSocketActive,
isAuthorized,
resetHeartbeatTimeout,
stopHeartbeatTimeout,
identityClient,
onClose,
+ createInitMessage,
]);
const sendMessageToDeviceRequest: (
diff --git a/native/root.react.js b/native/root.react.js
--- a/native/root.react.js
+++ b/native/root.react.js
@@ -83,7 +83,6 @@
import ThemeHandler from './themes/theme-handler.react.js';
import { provider } from './utils/ethers-utils.js';
import { neynarKey } from './utils/neynar-utils.js';
-import { useTunnelbrokerInitMessage } from './utils/tunnelbroker-utils.js';
// Add custom items to expo-dev-menu
import './dev-menu.js';
@@ -265,8 +264,6 @@
return undefined;
})();
- const tunnelbrokerInitMessage = useTunnelbrokerInitMessage();
-
const gated: React.Node = (
<>
<LifecycleHandler />
@@ -306,7 +303,7 @@
<GestureHandlerRootView style={styles.app}>
<StaffContextProvider>
<IdentityServiceContextProvider>
- <TunnelbrokerProvider initMessage={tunnelbrokerInitMessage}>
+ <TunnelbrokerProvider>
<IdentitySearchProvider>
<FeatureFlagsProvider>
<NavContext.Provider value={navContext}>
diff --git a/native/utils/tunnelbroker-utils.js b/native/utils/tunnelbroker-utils.js
deleted file mode 100644
--- a/native/utils/tunnelbroker-utils.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// @flow
-
-import * as React from 'react';
-
-import type { ConnectionInitializationMessage } from 'lib/types/tunnelbroker/session-types.js';
-
-import { commCoreModule } from '../native-modules.js';
-import { useSelector } from '../redux/redux-utils.js';
-
-function useTunnelbrokerInitMessage(): ?ConnectionInitializationMessage {
- const [deviceID, setDeviceID] = React.useState<?string>();
- const [userID, setUserID] = React.useState<?string>();
- const accessToken = useSelector(state => state.commServicesAccessToken);
-
- React.useEffect(() => {
- void (async () => {
- const { userID: identityUserID, deviceID: contentSigningKey } =
- await commCoreModule.getCommServicesAuthMetadata();
- setDeviceID(contentSigningKey);
- setUserID(identityUserID);
- })();
- }, [accessToken]);
-
- return React.useMemo(() => {
- if (!deviceID || !accessToken || !userID) {
- return null;
- }
- return ({
- type: 'ConnectionInitializationMessage',
- deviceID,
- accessToken,
- userID,
- deviceType: 'mobile',
- }: ConnectionInitializationMessage);
- }, [accessToken, deviceID, userID]);
-}
-
-export { useTunnelbrokerInitMessage };
diff --git a/web/app.react.js b/web/app.react.js
--- a/web/app.react.js
+++ b/web/app.react.js
@@ -71,7 +71,6 @@
import VisibilityHandler from './redux/visibility-handler.react.js';
import history from './router-history.js';
import { MessageSearchStateProvider } from './search/message-search-state-provider.react.js';
-import { createTunnelbrokerInitMessage } from './selectors/tunnelbroker-selectors.js';
import AccountSettings from './settings/account-settings.react.js';
import DangerZone from './settings/danger-zone.react.js';
import KeyserverSelectionList from './settings/keyserver-selection-list.react.js';
@@ -520,8 +519,6 @@
[modalContext.modals],
);
- const tunnelbrokerInitMessage = useSelector(createTunnelbrokerInitMessage);
-
const { lockStatus, releaseLockOrAbortRequest } = useWebLock(
TUNNELBROKER_LOCK_NAME,
);
@@ -532,7 +529,6 @@
return (
<AppThemeWrapper>
<TunnelbrokerProvider
- initMessage={tunnelbrokerInitMessage}
shouldBeClosed={lockStatus !== 'acquired'}
onClose={releaseLockOrAbortRequest}
secondaryTunnelbrokerConnection={secondaryTunnelbrokerConnection}
diff --git a/web/selectors/tunnelbroker-selectors.js b/web/selectors/tunnelbroker-selectors.js
deleted file mode 100644
--- a/web/selectors/tunnelbroker-selectors.js
+++ /dev/null
@@ -1,30 +0,0 @@
-// @flow
-
-import { createSelector } from 'reselect';
-
-import type { ConnectionInitializationMessage } from 'lib/types/tunnelbroker/session-types.js';
-
-import type { AppState } from '../redux/redux-setup.js';
-
-export const createTunnelbrokerInitMessage: AppState => ?ConnectionInitializationMessage =
- createSelector(
- (state: AppState) => state.cryptoStore?.primaryIdentityKeys?.ed25519,
- (state: AppState) => state.commServicesAccessToken,
- (state: AppState) => state.currentUserInfo?.id,
- (
- deviceID: ?string,
- accessToken: ?string,
- userID: ?string,
- ): ?ConnectionInitializationMessage => {
- if (!deviceID || !accessToken || !userID) {
- return null;
- }
- return ({
- type: 'ConnectionInitializationMessage',
- deviceID,
- accessToken,
- userID,
- deviceType: 'web',
- }: ConnectionInitializationMessage);
- },
- );

File Metadata

Mime Type
text/plain
Expires
Fri, Nov 29, 5:49 PM (22 h, 10 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2597682
Default Alt Text
D11372.diff (20 KB)

Event Timeline