001package com.pusher.client; 002 003import com.pusher.client.channel.Channel; 004import com.pusher.client.channel.ChannelEventListener; 005import com.pusher.client.channel.PresenceChannel; 006import com.pusher.client.channel.PresenceChannelEventListener; 007import com.pusher.client.channel.PrivateChannel; 008import com.pusher.client.channel.PrivateChannelEventListener; 009import com.pusher.client.channel.PrivateEncryptedChannel; 010import com.pusher.client.channel.PrivateEncryptedChannelEventListener; 011import com.pusher.client.channel.PusherEvent; 012import com.pusher.client.channel.SubscriptionEventListener; 013import com.pusher.client.channel.impl.ChannelManager; 014import com.pusher.client.channel.impl.InternalChannel; 015import com.pusher.client.channel.impl.PresenceChannelImpl; 016import com.pusher.client.channel.impl.PrivateChannelImpl; 017import com.pusher.client.channel.impl.PrivateEncryptedChannelImpl; 018import com.pusher.client.connection.Connection; 019import com.pusher.client.connection.ConnectionEventListener; 020import com.pusher.client.connection.ConnectionState; 021import com.pusher.client.connection.impl.InternalConnection; 022import com.pusher.client.user.User; 023import com.pusher.client.user.impl.InternalUser; 024import com.pusher.client.util.Factory; 025 026/** 027 * This class is the main entry point for accessing Pusher. 028 * 029 * <p> 030 * By creating a new {@link Pusher} instance and calling {@link 031 * Pusher#connect()} a connection to Pusher is established. 032 * </p> 033 * 034 * <p> 035 * Subscriptions for data are represented by 036 * {@link com.pusher.client.channel.Channel} objects, or subclasses thereof. 037 * Subscriptions are created by calling {@link Pusher#subscribe(String)}, 038 * {@link Pusher#subscribePrivate(String)}, 039 * {@link Pusher#subscribePresence(String)} or one of the overloads. 040 * </p> 041 */ 042public class Pusher implements Client { 043 044 private final PusherOptions pusherOptions; 045 private final InternalConnection connection; 046 private final ChannelManager channelManager; 047 private final Factory factory; 048 private final InternalUser user; 049 050 /** 051 * Creates a new instance of Pusher. 052 * 053 * <p> 054 * Note that if you use this constructor you will not be able to subscribe 055 * to private or presence channels because no {@link ChannelAuthorizer} has been 056 * set. If you want to use private or presence channels: 057 * <ul> 058 * <li>Create an implementation of the {@link ChannelAuthorizer} interface, or use 059 * the {@link com.pusher.client.util.HttpChannelAuthorizer} provided.</li> 060 * <li>Create an instance of {@link PusherOptions} and set the authorizer on 061 * it by calling {@link PusherOptions#setChannelAuthorizer(ChannelAuthorizer)}.</li> 062 * <li>Use the {@link #Pusher(String, PusherOptions)} constructor to create 063 * an instance of Pusher.</li> 064 * </ul> 065 * 066 * <p> 067 * The {@link com.pusher.client.example.PrivateChannelExampleApp} and 068 * {@link com.pusher.client.example.PresenceChannelExampleApp} example 069 * applications show how to do this. 070 * </p> 071 * 072 * @param apiKey Your Pusher API key. 073 */ 074 public Pusher(final String apiKey) { 075 this(apiKey, new PusherOptions()); 076 } 077 078 /** 079 * Creates a new instance of Pusher. 080 * 081 * @param apiKey Your Pusher API key. 082 * @param pusherOptions Options for the Pusher client library to use. 083 */ 084 public Pusher(final String apiKey, final PusherOptions pusherOptions) { 085 this(apiKey, pusherOptions, new Factory()); 086 } 087 088 /** 089 * Creates a new Pusher instance using the provided Factory, package level 090 * access for unit tests only. 091 */ 092 Pusher(final String apiKey, final PusherOptions pusherOptions, final Factory factory) { 093 if (apiKey == null || apiKey.length() == 0) { 094 throw new IllegalArgumentException("API Key cannot be null or empty"); 095 } 096 097 if (pusherOptions == null) { 098 throw new IllegalArgumentException("PusherOptions cannot be null"); 099 } 100 101 this.pusherOptions = pusherOptions; 102 this.factory = factory; 103 connection = factory.getConnection(apiKey, this.pusherOptions, this::handleEvent); 104 channelManager = factory.getChannelManager(); 105 user = factory.newUser(connection, pusherOptions.getUserAuthenticator()); 106 channelManager.setConnection(connection); 107 } 108 109 private void handleEvent(PusherEvent event) { 110 user.handleEvent(event); 111 channelManager.handleEvent(event); 112 } 113 114 /* Connection methods */ 115 116 /** 117 * Gets the underlying {@link Connection} object that is being used by this 118 * instance of {@linkplain Pusher}. 119 * 120 * @return The {@link Connection} object. 121 */ 122 public Connection getConnection() { 123 return connection; 124 } 125 126 /** 127 * Connects to Pusher. Any {@link ConnectionEventListener}s that have 128 * already been registered using the 129 * {@link Connection#bind(ConnectionState, ConnectionEventListener)} method 130 * will receive connection events. 131 * 132 * <p>Calls are ignored (a connection is not attempted) if the {@link Connection#getState()} is not {@link com.pusher.client.connection.ConnectionState#DISCONNECTED} or {@link com.pusher.client.connection.ConnectionState#DISCONNECTING}.</p> 133 */ 134 public void connect() { 135 connect(null); 136 } 137 138 /** 139 * Binds a {@link ConnectionEventListener} to the specified events and then 140 * connects to Pusher. This is equivalent to binding a 141 * {@link ConnectionEventListener} using the 142 * {@link Connection#bind(ConnectionState, ConnectionEventListener)} method 143 * before connecting. 144 * 145 * <p>Calls are ignored (a connection is not attempted) if the {@link Connection#getState()} is not {@link com.pusher.client.connection.ConnectionState#DISCONNECTED}.</p> 146 * 147 * @param eventListener A {@link ConnectionEventListener} that will receive connection 148 * events. This can be null if you are not interested in 149 * receiving connection events, in which case you should call 150 * {@link #connect()} instead of this method. 151 * @param connectionStates An optional list of {@link ConnectionState}s to bind your 152 * {@link ConnectionEventListener} to before connecting to 153 * Pusher. If you do not specify any {@link ConnectionState}s 154 * then your {@link ConnectionEventListener} will be bound to all 155 * connection events. This is equivalent to calling 156 * {@link #connect(ConnectionEventListener, ConnectionState...)} 157 * with {@link ConnectionState#ALL}. 158 * @throws IllegalArgumentException If the {@link ConnectionEventListener} is null and at least 159 * one connection state has been specified. 160 */ 161 public void connect(final ConnectionEventListener eventListener, ConnectionState... connectionStates) { 162 if (eventListener != null) { 163 if (connectionStates.length == 0) { 164 connectionStates = new ConnectionState[]{ConnectionState.ALL}; 165 } 166 167 for (final ConnectionState state : connectionStates) { 168 connection.bind(state, eventListener); 169 } 170 } else { 171 if (connectionStates.length > 0) { 172 throw new IllegalArgumentException("Cannot bind to connection states with a null connection event listener"); 173 } 174 } 175 176 connection.connect(); 177 } 178 179 /** 180 * Disconnect from Pusher. 181 * 182 * <p> 183 * Calls are ignored if the {@link Connection#getState()}, retrieved from {@link Pusher#getConnection}, is 184 * {@link com.pusher.client.connection.ConnectionState#DISCONNECTING} or {@link com.pusher.client.connection.ConnectionState#DISCONNECTED}. 185 * </p> 186 */ 187 public void disconnect() { 188 if (connection.getState() != ConnectionState.DISCONNECTING && connection.getState() != ConnectionState.DISCONNECTED) { 189 connection.disconnect(); 190 } 191 } 192 193 /** 194 * @return The {@link com.pusher.client.user.User} associated with this Pusher connection. 195 */ 196 public User user() { 197 return user; 198 } 199 200 /** 201 * Signs in on the Pusher connection as the current user. 202 * 203 * <p> 204 * Requires {@link PusherOptions#setUserAuthenticator} to have been called. 205 * </p> 206 * 207 * @throws IllegalStateException if no {@link UserAuthenticator} has been set. 208 */ 209 public void signin() { 210 throwExceptionIfNoUserAuthenticatorHasBeenSet(); 211 user.signin(); 212 } 213 214 /* Subscription methods */ 215 216 /** 217 * Subscribes to a public {@link Channel}. 218 * <p> 219 * Note that subscriptions should be registered only once with a Pusher 220 * instance. Subscriptions are persisted over disconnection and 221 * re-registered with the server automatically on reconnection. This means 222 * that subscriptions may also be registered before connect() is called, 223 * they will be initiated on connection. 224 * 225 * @param channelName The name of the {@link Channel} to subscribe to. 226 * @return The {@link Channel} object representing your subscription. 227 */ 228 public Channel subscribe(final String channelName) { 229 return subscribe(channelName, null); 230 } 231 232 /** 233 * Binds a {@link ChannelEventListener} to the specified events and then 234 * subscribes to a public {@link Channel}. 235 * 236 * @param channelName The name of the {@link Channel} to subscribe to. 237 * @param listener A {@link ChannelEventListener} to receive events. This can be 238 * null if you don't want to bind a listener at subscription 239 * time, in which case you should call {@link #subscribe(String)} 240 * instead of this method. 241 * @param eventNames An optional list of event names to bind your 242 * {@link ChannelEventListener} to before subscribing. 243 * @return The {@link Channel} object representing your subscription. 244 * @throws IllegalArgumentException If any of the following are true: 245 * <ul> 246 * <li>The channel name is null.</li> 247 * <li>You are already subscribed to this channel.</li> 248 * <li>The channel name starts with "private-". If you want to 249 * subscribe to a private channel, call 250 * {@link #subscribePrivate(String, PrivateChannelEventListener, String...)} 251 * instead of this method.</li> 252 * <li>At least one of the specified event names is null.</li> 253 * <li>You have specified at least one event name and your 254 * {@link ChannelEventListener} is null.</li> 255 * </ul> 256 */ 257 public Channel subscribe(final String channelName, final ChannelEventListener listener, final String... eventNames) { 258 final InternalChannel channel = factory.newPublicChannel(channelName); 259 channelManager.subscribeTo(channel, listener, eventNames); 260 261 return channel; 262 } 263 264 /** 265 * Subscribes to a {@link com.pusher.client.channel.PrivateChannel} which 266 * requires authentication. 267 * 268 * @param channelName The name of the channel to subscribe to. 269 * @return A new {@link com.pusher.client.channel.PrivateChannel} 270 * representing the subscription. 271 * @throws IllegalStateException if a {@link com.pusher.client.ChannelAuthorizer} has not been set 272 * for the {@link Pusher} instance via 273 * {@link #Pusher(String, PusherOptions)}. 274 */ 275 public PrivateChannel subscribePrivate(final String channelName) { 276 return subscribePrivate(channelName, null); 277 } 278 279 /** 280 * Subscribes to a {@link com.pusher.client.channel.PrivateChannel} which 281 * requires authentication. 282 * 283 * @param channelName The name of the channel to subscribe to. 284 * @param listener A listener to be informed of both Pusher channel protocol events and subscription data events. 285 * @param eventNames An optional list of names of events to be bound to on the channel. The equivalent of calling {@link com.pusher.client.channel.Channel#bind(String, SubscriptionEventListener)} one or more times. 286 * @return A new {@link com.pusher.client.channel.PrivateChannel} representing the subscription. 287 * @throws IllegalStateException if a {@link com.pusher.client.ChannelAuthorizer} has not been set for the {@link Pusher} instance via {@link #Pusher(String, PusherOptions)}. 288 */ 289 public PrivateChannel subscribePrivate( 290 final String channelName, 291 final PrivateChannelEventListener listener, 292 final String... eventNames 293 ) { 294 throwExceptionIfNoChannelAuthorizerHasBeenSet(); 295 296 final PrivateChannelImpl channel = factory.newPrivateChannel( 297 connection, 298 channelName, 299 pusherOptions.getChannelAuthorizer() 300 ); 301 channelManager.subscribeTo(channel, listener, eventNames); 302 303 return channel; 304 } 305 306 /** 307 * Subscribes to a {@link com.pusher.client.channel.PrivateEncryptedChannel} which 308 * requires authentication. 309 * 310 * @param channelName The name of the channel to subscribe to. 311 * @param listener A listener to be informed of both Pusher channel protocol events and 312 * subscription data events. 313 * @param eventNames An optional list of names of events to be bound to on the channel. 314 * The equivalent of calling 315 * {@link com.pusher.client.channel.Channel#bind(String, SubscriptionEventListener)} 316 * one or more times. 317 * @return A new {@link com.pusher.client.channel.PrivateEncryptedChannel} representing 318 * the subscription. 319 * @throws IllegalStateException if a {@link com.pusher.client.ChannelAuthorizer} has not been set for 320 * the {@link Pusher} instance via {@link #Pusher(String, PusherOptions)}. 321 */ 322 public PrivateEncryptedChannel subscribePrivateEncrypted( 323 final String channelName, 324 final PrivateEncryptedChannelEventListener listener, 325 final String... eventNames 326 ) { 327 throwExceptionIfNoChannelAuthorizerHasBeenSet(); 328 329 final PrivateEncryptedChannelImpl channel = factory.newPrivateEncryptedChannel( 330 connection, 331 channelName, 332 pusherOptions.getChannelAuthorizer() 333 ); 334 channelManager.subscribeTo(channel, listener, eventNames); 335 336 return channel; 337 } 338 339 /** 340 * Subscribes to a {@link com.pusher.client.channel.PresenceChannel} which 341 * requires authentication. 342 * 343 * @param channelName The name of the channel to subscribe to. 344 * @return A new {@link com.pusher.client.channel.PresenceChannel} 345 * representing the subscription. 346 * @throws IllegalStateException if a {@link com.pusher.client.ChannelAuthorizer} has not been set 347 * for the {@link Pusher} instance via 348 * {@link #Pusher(String, PusherOptions)}. 349 */ 350 public PresenceChannel subscribePresence(final String channelName) { 351 return subscribePresence(channelName, null); 352 } 353 354 /** 355 * Subscribes to a {@link com.pusher.client.channel.PresenceChannel} which 356 * requires authentication. 357 * 358 * @param channelName The name of the channel to subscribe to. 359 * @param listener A listener to be informed of Pusher channel protocol, including presence-specific events, and subscription data events. 360 * @param eventNames An optional list of names of events to be bound to on the channel. The equivalent of calling {@link com.pusher.client.channel.Channel#bind(String, SubscriptionEventListener)} one or more times. 361 * @return A new {@link com.pusher.client.channel.PresenceChannel} representing the subscription. 362 * @throws IllegalStateException if a {@link com.pusher.client.ChannelAuthorizer} has not been set for the {@link Pusher} instance via {@link #Pusher(String, PusherOptions)}. 363 */ 364 public PresenceChannel subscribePresence( 365 final String channelName, 366 final PresenceChannelEventListener listener, 367 final String... eventNames 368 ) { 369 throwExceptionIfNoChannelAuthorizerHasBeenSet(); 370 371 final PresenceChannelImpl channel = factory.newPresenceChannel( 372 connection, 373 channelName, 374 pusherOptions.getChannelAuthorizer() 375 ); 376 channelManager.subscribeTo(channel, listener, eventNames); 377 378 return channel; 379 } 380 381 /** 382 * Unsubscribes from a channel using via the name of the channel. 383 * 384 * @param channelName the name of the channel to be unsubscribed from. 385 */ 386 public void unsubscribe(final String channelName) { 387 channelManager.unsubscribeFrom(channelName); 388 } 389 390 /* implementation detail */ 391 392 private void throwExceptionIfNoChannelAuthorizerHasBeenSet() { 393 if (pusherOptions.getChannelAuthorizer() == null) { 394 throw new IllegalStateException( 395 "Cannot subscribe to a private or presence channel because no ChannelAuthorizer has been set. Call PusherOptions.setChannelAuthorizer() before connecting to Pusher" 396 ); 397 } 398 } 399 400 private void throwExceptionIfNoUserAuthenticatorHasBeenSet() { 401 if (pusherOptions.getUserAuthenticator() == null) { 402 throw new IllegalStateException( 403 "Cannot sign in because no UserAuthenticator has been set. Call PusherOptions.setUserAuthenticator() before connecting to Pusher" 404 ); 405 } 406 } 407 408 /** 409 * @param channelName The name of the public channel to be retrieved 410 * @return A public channel, or null if it could not be found 411 * @throws IllegalArgumentException if you try to retrieve a private or presence channel. 412 */ 413 public Channel getChannel(String channelName) { 414 return channelManager.getChannel(channelName); 415 } 416 417 /** 418 * @param channelName The name of the private channel to be retrieved 419 * @return A private channel, or null if it could not be found 420 * @throws IllegalArgumentException if you try to retrieve a public or presence channel. 421 */ 422 public PrivateChannel getPrivateChannel(String channelName) { 423 return channelManager.getPrivateChannel(channelName); 424 } 425 426 /** 427 * @param channelName The name of the private encrypted channel to be retrieved 428 * @return A private encrypted channel, or null if it could not be found 429 * @throws IllegalArgumentException if you try to retrieve a public or presence channel. 430 */ 431 public PrivateEncryptedChannel getPrivateEncryptedChannel(String channelName) { 432 return channelManager.getPrivateEncryptedChannel(channelName); 433 } 434 435 /** 436 * @param channelName The name of the presence channel to be retrieved 437 * @return A presence channel, or null if it could not be found 438 * @throws IllegalArgumentException if you try to retrieve a public or private channel. 439 */ 440 public PresenceChannel getPresenceChannel(String channelName) { 441 return channelManager.getPresenceChannel(channelName); 442 } 443}