001package com.pusher.client.user.impl;
002
003import com.google.gson.Gson;
004import com.google.gson.JsonSyntaxException;
005import com.pusher.client.AuthenticationFailureException;
006import com.pusher.client.UserAuthenticator;
007import com.pusher.client.channel.PusherEvent;
008import com.pusher.client.channel.SubscriptionEventListener;
009import com.pusher.client.channel.impl.ChannelManager;
010import com.pusher.client.connection.ConnectionEventListener;
011import com.pusher.client.connection.ConnectionState;
012import com.pusher.client.connection.ConnectionStateChange;
013import com.pusher.client.connection.impl.InternalConnection;
014import com.pusher.client.user.User;
015import com.pusher.client.user.impl.message.AuthenticationResponse;
016import com.pusher.client.user.impl.message.SigninMessage;
017import com.pusher.client.util.Factory;
018
019import java.util.Map;
020import java.util.logging.Logger;
021
022public class InternalUser implements User {
023
024    private static final Gson GSON = new Gson();
025    private static final Logger log = Logger.getLogger(User.class.getName());
026
027    private static class ConnectionStateChangeHandler implements ConnectionEventListener {
028
029        private final InternalUser user;
030
031        public ConnectionStateChangeHandler(InternalUser user) {
032            this.user = user;
033        }
034
035        @Override
036        public void onConnectionStateChange(ConnectionStateChange change) {
037            switch (change.getCurrentState()) {
038                case CONNECTED:
039                    user.attemptSignin();
040                    break;
041                case CONNECTING:
042                case DISCONNECTED:
043                    user.disconnect();
044                    break;
045                default:
046                    // NOOP
047            }
048        }
049
050        @Override
051        public void onError(String message, String code, Exception e) {
052            log.warning(message);
053        }
054    }
055
056    private final InternalConnection connection;
057    private final UserAuthenticator userAuthenticator;
058    private final ChannelManager channelManager;
059    private boolean signinRequested;
060    private final ServerToUserChannel serverToUserChannel;
061    private String userId;
062
063    public InternalUser(InternalConnection connection, UserAuthenticator userAuthenticator, Factory factory) {
064        this.connection = connection;
065        this.userAuthenticator = userAuthenticator;
066        this.channelManager = factory.getChannelManager();
067        this.signinRequested = false;
068        this.serverToUserChannel = new ServerToUserChannel(this, factory);
069
070        connection.bind(ConnectionState.ALL, new ConnectionStateChangeHandler(this));
071    }
072
073    public void signin() throws AuthenticationFailureException {
074        if (signinRequested || userId != null) {
075            return;
076        }
077
078        signinRequested = true;
079        attemptSignin();
080    }
081
082    public void handleEvent(PusherEvent event) {
083        if (event.getEventName().equals("pusher:signin_success")) {
084            onSigninSuccess(event);
085        }
086    }
087
088    private void attemptSignin() throws AuthenticationFailureException {
089        if (!signinRequested || userId != null) {
090            return;
091        }
092
093        if (connection.getState() != ConnectionState.CONNECTED) {
094            // Signin will be attempted when the connection is connected
095            return;
096        }
097
098        AuthenticationResponse authenticationResponse = getAuthenticationResponse();
099        connection.sendMessage(authenticationResponseToSigninMessage(authenticationResponse));
100    }
101
102    private static String authenticationResponseToSigninMessage(AuthenticationResponse authenticationResponse) {
103        return GSON.toJson(new SigninMessage(authenticationResponse.getAuth(), authenticationResponse.getUserData()));
104    }
105
106    private AuthenticationResponse getAuthenticationResponse() throws AuthenticationFailureException {
107        String response = userAuthenticator.authenticate(connection.getSocketId());
108        try {
109            AuthenticationResponse authenticationResponse = GSON.fromJson(response, AuthenticationResponse.class);
110            if (authenticationResponse.getAuth() == null || authenticationResponse.getUserData() == null) {
111                throw new AuthenticationFailureException(
112                        "Didn't receive all the fields expected from the UserAuthenticator. Expected auth and user_data"
113                );
114            }
115            return authenticationResponse;
116        } catch (JsonSyntaxException e) {
117            throw new AuthenticationFailureException("Unable to parse response from AuthenticationResponse");
118        }
119    }
120
121    private void onSigninSuccess(PusherEvent event) {
122        try {
123            String userData = (String) GSON.fromJson(event.getData(), Map.class).get("user_data");
124            userId = (String) GSON.fromJson(userData, Map.class).get("id");
125        } catch (Exception e) {
126            log.severe("Failed parsing user data after signin");
127            return;
128        }
129
130        if (userId == null) {
131            log.severe("User data doesn't contain an id");
132            return;
133        }
134        channelManager.subscribeTo(serverToUserChannel, null);
135    }
136
137    private void disconnect() {
138        if (serverToUserChannel.isSubscribed()) {
139            channelManager.unsubscribeFrom(serverToUserChannel.getName());
140        }
141        userId = null;
142    }
143
144    @Override
145    public String userId() {
146        return userId;
147    }
148
149    @Override
150    public void bind(String eventName, SubscriptionEventListener listener) {
151        serverToUserChannel.bind(eventName, listener);
152    }
153
154    @Override
155    public void bindGlobal(SubscriptionEventListener listener) {
156        serverToUserChannel.bindGlobal(listener);
157    }
158
159    @Override
160    public void unbind(String eventName, SubscriptionEventListener listener) {
161        serverToUserChannel.unbind(eventName, listener);
162    }
163
164    @Override
165    public void unbindGlobal(SubscriptionEventListener listener) {
166        serverToUserChannel.unbindGlobal(listener);
167    }
168}