Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3383800
D11372.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
20 KB
Referenced Files
None
Subscribers
None
D11372.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D11372: [web/native] Create tunnelbroker init message on connection
Attached
Detach File
Event Timeline
Log In to Comment