7
7
8
8
import static org .whispersystems .textsecuregcm .metrics .MetricsUtil .name ;
9
9
10
+ import com .google .common .annotations .VisibleForTesting ;
10
11
import io .micrometer .core .instrument .Tags ;
11
-
12
12
import java .time .Duration ;
13
13
import java .util .Optional ;
14
+ import java .util .function .Function ;
14
15
import org .slf4j .Logger ;
15
16
import org .slf4j .LoggerFactory ;
16
17
import org .whispersystems .textsecuregcm .auth .AuthenticatedDevice ;
23
24
import org .whispersystems .textsecuregcm .push .PushNotificationManager ;
24
25
import org .whispersystems .textsecuregcm .push .PushNotificationScheduler ;
25
26
import org .whispersystems .textsecuregcm .push .ReceiptSender ;
26
- import org .whispersystems .textsecuregcm .push .RedisMessageAvailabilityManager ;
27
27
import org .whispersystems .textsecuregcm .storage .Account ;
28
28
import org .whispersystems .textsecuregcm .storage .AccountsManager ;
29
29
import org .whispersystems .textsecuregcm .storage .ClientReleaseManager ;
30
30
import org .whispersystems .textsecuregcm .storage .Device ;
31
31
import org .whispersystems .textsecuregcm .storage .MessagesManager ;
32
+ import org .whispersystems .websocket .WebSocketClient ;
32
33
import org .whispersystems .websocket .session .WebSocketSessionContext ;
33
34
import org .whispersystems .websocket .setup .WebSocketConnectListener ;
34
35
import reactor .core .scheduler .Scheduler ;
@@ -45,69 +46,82 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
45
46
private static final Logger log = LoggerFactory .getLogger (AuthenticatedConnectListener .class );
46
47
47
48
private final AccountsManager accountsManager ;
48
- private final ReceiptSender receiptSender ;
49
- private final MessagesManager messagesManager ;
50
- private final MessageMetrics messageMetrics ;
51
- private final PushNotificationManager pushNotificationManager ;
52
- private final PushNotificationScheduler pushNotificationScheduler ;
53
- private final RedisMessageAvailabilityManager redisMessageAvailabilityManager ;
54
49
private final DisconnectionRequestManager disconnectionRequestManager ;
55
- private final Scheduler messageDeliveryScheduler ;
56
- private final ClientReleaseManager clientReleaseManager ;
57
- private final MessageDeliveryLoopMonitor messageDeliveryLoopMonitor ;
58
- private final ExperimentEnrollmentManager experimentEnrollmentManager ;
50
+ private final WebSocketConnectionBuilder webSocketConnectionBuilder ;
59
51
60
52
private final OpenWebSocketCounter openAuthenticatedWebSocketCounter ;
61
53
private final OpenWebSocketCounter openUnauthenticatedWebSocketCounter ;
62
54
55
+ @ VisibleForTesting
56
+ @ FunctionalInterface
57
+ interface WebSocketConnectionBuilder {
58
+ WebSocketConnection buildWebSocketConnection (Account account , Device device , WebSocketClient client );
59
+ }
60
+
63
61
public AuthenticatedConnectListener (
64
62
final AccountsManager accountsManager ,
65
63
final ReceiptSender receiptSender ,
66
64
final MessagesManager messagesManager ,
67
65
final MessageMetrics messageMetrics ,
68
66
final PushNotificationManager pushNotificationManager ,
69
67
final PushNotificationScheduler pushNotificationScheduler ,
70
- final RedisMessageAvailabilityManager redisMessageAvailabilityManager ,
71
68
final DisconnectionRequestManager disconnectionRequestManager ,
72
69
final Scheduler messageDeliveryScheduler ,
73
70
final ClientReleaseManager clientReleaseManager ,
74
71
final MessageDeliveryLoopMonitor messageDeliveryLoopMonitor ,
75
72
final ExperimentEnrollmentManager experimentEnrollmentManager ) {
76
73
74
+ this (accountsManager ,
75
+ disconnectionRequestManager ,
76
+ (account , device , client ) -> new WebSocketConnection (receiptSender ,
77
+ messagesManager ,
78
+ messageMetrics ,
79
+ pushNotificationManager ,
80
+ pushNotificationScheduler ,
81
+ account ,
82
+ device ,
83
+ client ,
84
+ messageDeliveryScheduler ,
85
+ clientReleaseManager ,
86
+ messageDeliveryLoopMonitor ,
87
+ experimentEnrollmentManager ),
88
+ authenticated -> new OpenWebSocketCounter (OPEN_WEBSOCKET_GAUGE_NAME ,
89
+ NEW_CONNECTION_COUNTER_NAME ,
90
+ CONNECTED_DURATION_TIMER_NAME ,
91
+ Duration .ofHours (3 ),
92
+ Tags .of (AUTHENTICATED_TAG_NAME , String .valueOf (authenticated )))
93
+ );
94
+ }
95
+
96
+ @ VisibleForTesting AuthenticatedConnectListener (
97
+ final AccountsManager accountsManager ,
98
+ final DisconnectionRequestManager disconnectionRequestManager ,
99
+ final WebSocketConnectionBuilder webSocketConnectionBuilder ,
100
+ final Function <Boolean , OpenWebSocketCounter > openWebSocketCounterBuilder ) {
101
+
77
102
this .accountsManager = accountsManager ;
78
- this .receiptSender = receiptSender ;
79
- this .messagesManager = messagesManager ;
80
- this .messageMetrics = messageMetrics ;
81
- this .pushNotificationManager = pushNotificationManager ;
82
- this .pushNotificationScheduler = pushNotificationScheduler ;
83
- this .redisMessageAvailabilityManager = redisMessageAvailabilityManager ;
84
103
this .disconnectionRequestManager = disconnectionRequestManager ;
85
- this .messageDeliveryScheduler = messageDeliveryScheduler ;
86
- this .clientReleaseManager = clientReleaseManager ;
87
- this .messageDeliveryLoopMonitor = messageDeliveryLoopMonitor ;
88
- this .experimentEnrollmentManager = experimentEnrollmentManager ;
89
-
90
- openAuthenticatedWebSocketCounter =
91
- new OpenWebSocketCounter (OPEN_WEBSOCKET_GAUGE_NAME , NEW_CONNECTION_COUNTER_NAME , CONNECTED_DURATION_TIMER_NAME , Duration .ofHours (3 ), Tags .of (AUTHENTICATED_TAG_NAME , "true" ));
104
+ this .webSocketConnectionBuilder = webSocketConnectionBuilder ;
92
105
93
- openUnauthenticatedWebSocketCounter =
94
- new OpenWebSocketCounter ( OPEN_WEBSOCKET_GAUGE_NAME , NEW_CONNECTION_COUNTER_NAME , CONNECTED_DURATION_TIMER_NAME , Duration . ofHours ( 3 ), Tags . of ( AUTHENTICATED_TAG_NAME , " false" ) );
106
+ openAuthenticatedWebSocketCounter = openWebSocketCounterBuilder . apply ( true );
107
+ openUnauthenticatedWebSocketCounter = openWebSocketCounterBuilder . apply ( false );
95
108
}
96
109
97
110
@ Override
98
111
public void onWebSocketConnect (final WebSocketSessionContext context ) {
99
112
100
113
final boolean authenticated = (context .getAuthenticated () != null );
101
- final OpenWebSocketCounter openWebSocketCounter =
102
- authenticated ? openAuthenticatedWebSocketCounter : openUnauthenticatedWebSocketCounter ;
103
114
104
- openWebSocketCounter .countOpenWebSocket (context );
115
+ ( authenticated ? openAuthenticatedWebSocketCounter : openUnauthenticatedWebSocketCounter ) .countOpenWebSocket (context );
105
116
106
117
if (authenticated ) {
107
118
final AuthenticatedDevice auth = context .getAuthenticated (AuthenticatedDevice .class );
108
119
109
- final Optional <Account > maybeAuthenticatedAccount = accountsManager .getByAccountIdentifier (auth .accountIdentifier ());
110
- final Optional <Device > maybeAuthenticatedDevice = maybeAuthenticatedAccount .flatMap (account -> account .getDevice (auth .deviceId ()));
120
+ final Optional <Account > maybeAuthenticatedAccount =
121
+ accountsManager .getByAccountIdentifier (auth .accountIdentifier ());
122
+
123
+ final Optional <Device > maybeAuthenticatedDevice =
124
+ maybeAuthenticatedAccount .flatMap (account -> account .getDevice (auth .deviceId ()));
111
125
112
126
if (maybeAuthenticatedAccount .isEmpty () || maybeAuthenticatedDevice .isEmpty ()) {
113
127
log .warn ("{}:{} not found when opening authenticated WebSocket" , auth .accountIdentifier (), auth .deviceId ());
@@ -116,18 +130,10 @@ public void onWebSocketConnect(final WebSocketSessionContext context) {
116
130
return ;
117
131
}
118
132
119
- final WebSocketConnection connection = new WebSocketConnection (receiptSender ,
120
- messagesManager ,
121
- messageMetrics ,
122
- pushNotificationManager ,
123
- pushNotificationScheduler ,
124
- maybeAuthenticatedAccount .get (),
125
- maybeAuthenticatedDevice .get (),
126
- context .getClient (),
127
- messageDeliveryScheduler ,
128
- clientReleaseManager ,
129
- messageDeliveryLoopMonitor ,
130
- experimentEnrollmentManager );
133
+ final WebSocketConnection connection =
134
+ webSocketConnectionBuilder .buildWebSocketConnection (maybeAuthenticatedAccount .get (),
135
+ maybeAuthenticatedDevice .get (),
136
+ context .getClient ());
131
137
132
138
disconnectionRequestManager .addListener (maybeAuthenticatedAccount .get ().getIdentifier (IdentityType .ACI ),
133
139
maybeAuthenticatedDevice .get ().getId (),
@@ -138,27 +144,11 @@ public void onWebSocketConnect(final WebSocketSessionContext context) {
138
144
maybeAuthenticatedDevice .get ().getId (),
139
145
connection );
140
146
141
- // We begin the shutdown process by removing this client's "presence," which means it will again begin to
142
- // receive push notifications for inbound messages. We should do this first because, at this point, the
143
- // connection has already closed and attempts to actually deliver a message via the connection will not succeed.
144
- // It's preferable to start sending push notifications as soon as possible.
145
- redisMessageAvailabilityManager .handleClientDisconnected (auth .accountIdentifier (), auth .deviceId ());
146
-
147
- // Finally, stop trying to deliver messages and send a push notification if the connection is aware of any
148
- // undelivered messages.
149
147
connection .stop ();
150
148
});
151
149
152
150
try {
153
- // Once we "start" the websocket connection, we'll cancel any scheduled "you may have new messages" push
154
- // notifications and begin delivering any stored messages for the connected device. We have not yet declared the
155
- // client as "present" yet. If a message arrives at this point, we will update the message availability state
156
- // correctly, but we may also send a spurious push notification.
157
151
connection .start ();
158
-
159
- // Finally, we register this client's presence, which suppresses push notifications. We do this last because
160
- // receiving extra push notifications is generally preferable to missing out on a push notification.
161
- redisMessageAvailabilityManager .handleClientConnected (auth .accountIdentifier (), auth .deviceId (), connection );
162
152
} catch (final Exception e ) {
163
153
log .warn ("Failed to initialize websocket" , e );
164
154
context .getClient ().close (1011 , "Unexpected error initializing connection" );
0 commit comments