001package com.pusher.client;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.net.Proxy;
006import java.util.Properties;
007
008/**
009 * Configuration for a {@link com.pusher.client.Pusher} instance.
010 */
011public class PusherOptions {
012
013    private static final String SRC_LIB_DEV_VERSION = "@version@";
014    private static final String LIB_DEV_VERSION = "0.0.0-dev";
015    public static final String LIB_VERSION = readVersionFromProperties();
016
017    private static final String URI_SUFFIX = "?client=java-client&protocol=5&version=" + LIB_VERSION;
018    private static final String WS_SCHEME = "ws";
019    private static final String WSS_SCHEME = "wss";
020
021    private static final int WS_PORT = 80;
022    private static final int WSS_PORT = 443;
023    private static final String PUSHER_DOMAIN = "pusher.com";
024
025    private static final long DEFAULT_ACTIVITY_TIMEOUT = 120000;
026    private static final long DEFAULT_PONG_TIMEOUT = 30000;
027
028    private static final int MAX_RECONNECTION_ATTEMPTS = 6; //Taken from the Swift lib
029    private static final int MAX_RECONNECT_GAP_IN_SECONDS = 30;
030
031    // Note that the primary cluster lives on a different domain
032    // (others are subdomains of pusher.com). This is not an oversight.
033    // Legacy reasons.
034    private String host = "ws.pusherapp.com";
035    private int wsPort = WS_PORT;
036    private int wssPort = WSS_PORT;
037    private boolean useTLS = true;
038    private long activityTimeout = DEFAULT_ACTIVITY_TIMEOUT;
039    private long pongTimeout = DEFAULT_PONG_TIMEOUT;
040    private UserAuthenticator userAuthenticator;
041    private ChannelAuthorizer channelAuthorizer;
042    private Authorizer authorizer;
043    private Proxy proxy = Proxy.NO_PROXY;
044    private int maxReconnectionAttempts = MAX_RECONNECTION_ATTEMPTS;
045    private int maxReconnectGapInSeconds = MAX_RECONNECT_GAP_IN_SECONDS;
046
047    /**
048     * @deprecated Please use isUseTLS
049     */
050    @Deprecated
051    public boolean isEncrypted() {
052        return useTLS;
053    }
054
055    /**
056     * @deprecated Please use setUseTLS
057     */
058    @Deprecated
059    public PusherOptions setEncrypted(final boolean encrypted) {
060        this.useTLS = encrypted;
061        return this;
062    }
063
064    /**
065     * @return whether the connection to Pusher should use TLS
066     */
067    public boolean isUseTLS() {
068        return useTLS;
069    }
070
071    /**
072     * Sets whether the connection to Pusher should be use TLS.
073     *
074     * @param useTLS whether the connection should use TLS, by default this is true
075     * @return this, for chaining
076     */
077    public PusherOptions setUseTLS(final boolean useTLS) {
078        this.useTLS = useTLS;
079        return this;
080    }
081
082    /**
083     * Gets the user authenticator to be used when signing in.
084     *
085     * @return the user authenticator
086     */
087    public UserAuthenticator getUserAuthenticator() {
088        return userAuthenticator;
089    }
090
091    /**
092     * Sets the user authenticator to be used when signing in.
093     *
094     * @param userAuthenticator The user authenticator to be used.
095     * @return this, for chaining
096     */
097    public PusherOptions setUserAuthenticator(final UserAuthenticator userAuthenticator) {
098        this.userAuthenticator = userAuthenticator;
099        return this;
100    }
101
102    /**
103     * Gets the channel authorizer to be used when authorizing private and presence
104     * channels.
105     *
106     * @return the channel authorizer
107     */
108    public ChannelAuthorizer getChannelAuthorizer() {
109        return channelAuthorizer;
110    }
111
112    /**
113     * Sets the channel authorizer to be used when authorizing private and presence
114     * channels.
115     *
116     * @param channelAuthorizer The channel authorizer to be used.
117     * @return this, for chaining
118     */
119    public PusherOptions setChannelAuthorizer(final ChannelAuthorizer channelAuthorizer) {
120        this.channelAuthorizer = channelAuthorizer;
121        return this;
122    }
123
124    /**
125     * @deprecated Please use getChannelauthorizer
126     */
127    @Deprecated
128    public Authorizer getAuthorizer() {
129        return authorizer;
130    }
131
132    /**
133     * @deprecated Please use setChannelauthorizer
134     */
135    @Deprecated
136    public PusherOptions setAuthorizer(final Authorizer authorizer) {
137        this.authorizer = authorizer;
138        return setChannelAuthorizer(authorizer);
139    }
140
141    /**
142     * The host to which connections will be made.
143     * <p>
144     * Note that if you wish to connect to a standard Pusher cluster, the
145     * convenience method setCluster will set the host and ports correctly from
146     * a single argument.
147     *
148     * @param host The host
149     * @return this, for chaining
150     */
151    public PusherOptions setHost(final String host) {
152        this.host = host;
153        return this;
154    }
155
156    /**
157     * The port to which non TLS connections will be made.
158     * <p>
159     * Note that if you wish to connect to a standard Pusher cluster, the
160     * convenience method setCluster will set the host and ports correctly from
161     * a single argument.
162     *
163     * @param wsPort port number
164     * @return this, for chaining
165     */
166    public PusherOptions setWsPort(final int wsPort) {
167        this.wsPort = wsPort;
168        return this;
169    }
170
171    /**
172     * The port to which encrypted connections will be made.
173     * <p>
174     * Note that if you wish to connect to a standard Pusher cluster, the
175     * convenience method setCluster will set the host and ports correctly from
176     * a single argument.
177     *
178     * @param wssPort port number
179     * @return this, for chaining
180     */
181    public PusherOptions setWssPort(final int wssPort) {
182        this.wssPort = wssPort;
183        return this;
184    }
185
186    public PusherOptions setCluster(final String cluster) {
187        host = "ws-" + cluster + "." + PUSHER_DOMAIN;
188        wsPort = WS_PORT;
189        wssPort = WSS_PORT;
190        return this;
191    }
192
193    /**
194     * The number of milliseconds of inactivity at which a "ping" will be
195     * triggered to check the connection.
196     * <p>
197     * The default value is 120,000 (2 minutes). On some connections, where
198     * intermediate hops between the application and Pusher are aggressively
199     * culling connections they consider to be idle, a lower value may help
200     * preserve the connection.
201     *
202     * @param activityTimeout time to consider connection idle, in milliseconds
203     * @return this, for chaining
204     */
205    public PusherOptions setActivityTimeout(final long activityTimeout) {
206        if (activityTimeout < 1000) {
207            throw new IllegalArgumentException(
208                    "Activity timeout must be at least 1,000ms (and is recommended to be much higher)"
209            );
210        }
211
212        this.activityTimeout = activityTimeout;
213        return this;
214    }
215
216    public long getActivityTimeout() {
217        return activityTimeout;
218    }
219
220    /**
221     * The number of milliseconds after a "ping" is sent that the client will
222     * wait to receive a "pong" response from the server before considering the
223     * connection broken and triggering a transition to the disconnected state.
224     * <p>
225     * The default value is 30,000.
226     *
227     * @param pongTimeout time to wait for pong response, in milliseconds
228     * @return this, for chaining
229     */
230    public PusherOptions setPongTimeout(final long pongTimeout) {
231        if (pongTimeout < 1000) {
232            throw new IllegalArgumentException("Pong timeout must be at least 1,000ms (and is recommended to be much higher)");
233        }
234
235        this.pongTimeout = pongTimeout;
236        return this;
237    }
238
239    /**
240     * Number of reconnect attempts when websocket connection failed
241     *
242     * @param maxReconnectionAttempts number of max reconnection attempts, default = {@link #MAX_RECONNECTION_ATTEMPTS} 6
243     * @return this, for chaining
244     */
245    public PusherOptions setMaxReconnectionAttempts(int maxReconnectionAttempts) {
246        this.maxReconnectionAttempts = maxReconnectionAttempts;
247        return this;
248    }
249
250    /**
251     * The delay in two reconnection extends exponentially (1, 2, 4, .. seconds) This property sets the maximum in between two
252     * reconnection attempts.
253     *
254     * @param maxReconnectGapInSeconds time in seconds of the maximum gab between two reconnection attempts, default = {@link #MAX_RECONNECT_GAP_IN_SECONDS} 30s
255     * @return this, for chaining
256     */
257    public PusherOptions setMaxReconnectGapInSeconds(int maxReconnectGapInSeconds) {
258        this.maxReconnectGapInSeconds = maxReconnectGapInSeconds;
259        return this;
260    }
261
262    public long getPongTimeout() {
263        return pongTimeout;
264    }
265
266    /**
267     * Construct the URL for the WebSocket connection based on the options
268     * previous set on this object and the provided API key
269     *
270     * @param apiKey The API key
271     * @return the WebSocket URL
272     */
273    public String buildUrl(final String apiKey) {
274        return String.format(
275                "%s://%s:%s/app/%s%s",
276                useTLS ? WSS_SCHEME : WS_SCHEME,
277                host,
278                useTLS ? wssPort : wsPort,
279                apiKey,
280                URI_SUFFIX
281        );
282    }
283
284    /**
285     * The default value is Proxy.NO_PROXY.
286     *
287     * @param proxy Specify a proxy, e.g. <code>options.setProxy( new Proxy( Proxy.Type.HTTP, new InetSocketAddress( "proxyaddress", 80 ) ) )</code>;
288     * @return this, for chaining
289     */
290    public PusherOptions setProxy(Proxy proxy) {
291        if (proxy == null) {
292            throw new IllegalArgumentException("proxy must not be null (instead use Proxy.NO_PROXY)");
293        }
294        this.proxy = proxy;
295        return this;
296    }
297
298    /**
299     * @return The proxy to be used when opening a websocket connection to Pusher.
300     */
301    public Proxy getProxy() {
302        return this.proxy;
303    }
304
305    /**
306     * @return the maximum reconnection attempts
307     */
308    public int getMaxReconnectionAttempts() {
309        return maxReconnectionAttempts;
310    }
311
312    /**
313     * @return the maximum reconnection gap in seconds
314     */
315    public int getMaxReconnectGapInSeconds() {
316        return maxReconnectGapInSeconds;
317    }
318
319    private static String readVersionFromProperties() {
320        InputStream inStream = null;
321        try {
322            final Properties p = new Properties();
323            inStream = PusherOptions.class.getResourceAsStream("/pusher.properties");
324            p.load(inStream);
325            String version = (String) p.get("version");
326
327            // If the properties file contents indicates the version is being run
328            // from source then replace with a dev indicator. Otherwise the Pusher
329            // Socket API will reject the connection.
330            if (version.equals(SRC_LIB_DEV_VERSION)) {
331                version = LIB_DEV_VERSION;
332            }
333
334            if (version != null && version.length() > 0) {
335                return version;
336            }
337        } catch (final Exception e) {
338            // Fall back to fixed value
339        } finally {
340            try {
341                if (inStream != null) {
342                    inStream.close();
343                }
344            } catch (final IOException e) {
345                // Ignore problem closing stream
346            }
347        }
348        return "0.0.0";
349    }
350}