Commit a7d90d74 authored by Yusuke Iwaki's avatar Yusuke Iwaki

Simplefy login/user registration process!

parent efab24c2
......@@ -2,6 +2,7 @@ package chat.rocket.android.activity;
import android.support.annotation.IdRes;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import chat.rocket.android.helper.OnBackPressListener;
......@@ -9,16 +10,31 @@ abstract class AbstractFragmentActivity extends AppCompatActivity {
protected abstract @IdRes int getLayoutContainerForFragment();
@Override public void onBackPressed() {
Fragment fragment =
getSupportFragmentManager().findFragmentById(getLayoutContainerForFragment());
@Override public final void onBackPressed() {
if (!onBackPress()) {
onBackPresseNotHandled();
}
}
protected boolean onBackPress() {
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(getLayoutContainerForFragment());
if (fragment instanceof OnBackPressListener
&& ((OnBackPressListener) fragment).onBackPressed()) {
//consumed. do nothing.
} else {
super.onBackPressed();
return true;
}
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
return true;
}
return false;
}
protected void onBackPresseNotHandled() {
super.onBackPressed();
}
protected void showFragment(Fragment fragment) {
......
......@@ -7,21 +7,18 @@ import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R;
import chat.rocket.android.fragment.oauth.GitHubOAuthWebViewFragment;
import chat.rocket.android.fragment.server_config.AuthenticatingFragment;
import chat.rocket.android.fragment.server_config.ConnectingToHostFragment;
import chat.rocket.android.fragment.server_config.InputHostnameFragment;
import chat.rocket.android.fragment.server_config.LoginFragment;
import chat.rocket.android.fragment.server_config.WaitingFragment;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.MethodCallHelper;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ServerConfigCredential;
import chat.rocket.android.service.RocketChatService;
import io.realm.Realm;
import io.realm.RealmQuery;
import java.util.List;
import jp.co.crowdworks.realm_java_helpers.RealmObjectObserver;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
/**
* Activity for Login, Sign-up, and Connecting...
......@@ -58,15 +55,6 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
}
}
for (ServerConfig config : configList) {
ServerConfigCredential credential = config.getCredential();
if (credential != null
&& !TextUtils.isEmpty(credential.getType())
&& TextUtils.isEmpty(credential.getErrorMessage())) {
return launchFor(context, config);
}
}
for (ServerConfig config : configList) {
if (TextUtils.isEmpty(config.getToken())) {
return launchFor(context, config);
......@@ -134,19 +122,9 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
final String token = config.getToken();
if (!TextUtils.isEmpty(token)) {
showFragment(new AuthenticatingFragment());
return;
}
final ServerConfigCredential credential = config.getCredential();
if (credential != null
&& !TextUtils.isEmpty(credential.getType())
&& TextUtils.isEmpty(credential.getErrorMessage())) {
if (ServerConfigCredential.hasSecret(credential)) {
showFragment(new AuthenticatingFragment());
} else {
showFragment(getAuthFragmentFor(credential.getType()));
}
showFragment(WaitingFragment.create("Authenticating..."));
new MethodCallHelper(serverConfigId).loginWithToken(token)
.continueWith(new LogcatIfError());
return;
}
......@@ -158,28 +136,13 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
final String error = config.getConnectionError();
String hostname = config.getHostname();
if (!TextUtils.isEmpty(hostname) && TextUtils.isEmpty(error)) {
showFragment(new ConnectingToHostFragment());
showFragment(WaitingFragment.create("Connecting to server..."));
return;
}
showFragment(new InputHostnameFragment());
}
private Fragment getAuthFragmentFor(final String authType) {
if ("github".equals(authType)) {
return GitHubOAuthWebViewFragment.create(serverConfigId);
} else if ("twitter".equals(authType)) {
// TODO
}
RealmHelperBolts.executeTransaction(realm -> realm.where(ServerConfigCredential.class)
.equalTo("type", authType)
.findAll()
.deleteAllFromRealm()
).continueWith(new LogcatIfError());
throw new IllegalArgumentException("Invalid authType given:" + authType);
}
@Override protected void showFragment(Fragment fragment) {
injectServerConfigIdArgTo(fragment);
super.showFragment(fragment);
......@@ -199,9 +162,9 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
fragment.setArguments(args);
}
@Override public void onBackPressed() {
@Override protected void onBackPresseNotHandled() {
if (ServerConfig.hasActiveConnection()) {
super.onBackPressed();
super.onBackPresseNotHandled();
} else {
moveTaskToBack(true);
}
......
......@@ -2,27 +2,25 @@ package chat.rocket.android.fragment.oauth;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.util.Base64;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import chat.rocket.android.fragment.AbstractWebViewFragment;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.MethodCallHelper;
import chat.rocket.android.model.MeteorLoginServiceConfiguration;
import chat.rocket.android.model.ServerConfig;
import java.nio.charset.Charset;
import jp.co.crowdworks.realm_java_helpers.RealmHelper;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import okhttp3.HttpUrl;
import org.json.JSONException;
import org.json.JSONObject;
import timber.log.Timber;
public class GitHubOAuthWebViewFragment extends AbstractWebViewFragment {
public class GitHubOAuthFragment extends AbstractWebViewFragment {
private String serverConfigId;
private String credentialId;
private String hostname;
private String url;
private boolean resultOK;
......@@ -30,17 +28,17 @@ public class GitHubOAuthWebViewFragment extends AbstractWebViewFragment {
/**
* create new Fragment with ServerConfig-ID.
*/
public static Fragment create(final String serverConfigId) {
public static GitHubOAuthFragment create(final String serverConfigId) {
Bundle args = new Bundle();
args.putString("server_config_id", serverConfigId);
Fragment fragment = new GitHubOAuthWebViewFragment();
args.putString("serverConfigId", serverConfigId);
GitHubOAuthFragment fragment = new GitHubOAuthFragment();
fragment.setArguments(args);
return fragment;
}
private boolean hasValidArgs(Bundle args) {
return args != null
&& args.containsKey("server_config_id");
&& args.containsKey("serverConfigId");
}
@Override public void onCreate(@Nullable Bundle savedInstanceState) {
......@@ -48,10 +46,10 @@ public class GitHubOAuthWebViewFragment extends AbstractWebViewFragment {
Bundle args = getArguments();
if (!hasValidArgs(args)) {
throw new IllegalArgumentException(
"server_config_id required");
"serverConfigId required");
}
serverConfigId = args.getString("server_config_id");
serverConfigId = args.getString("serverConfigId");
ServerConfig serverConfig = RealmHelper.executeTransactionForRead(realm ->
realm.where(ServerConfig.class).equalTo("id", serverConfigId).findFirst());
MeteorLoginServiceConfiguration oauthConfig = RealmHelper.executeTransactionForRead(realm ->
......@@ -61,9 +59,8 @@ public class GitHubOAuthWebViewFragment extends AbstractWebViewFragment {
.findFirst());
if (serverConfig == null || oauthConfig == null) {
throw new IllegalArgumentException(
"Invalid server_config_id given,");
"Invalid serverConfigId given,");
}
credentialId = serverConfig.getCredential().getId();
hostname = serverConfig.getHostname();
url = generateURL(oauthConfig.getClientId());
}
......@@ -154,16 +151,8 @@ public class GitHubOAuthWebViewFragment extends AbstractWebViewFragment {
}
private void handleOAuthCallback(final String credentialToken, final String credentialSecret) {
RealmHelperBolts.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("id", serverConfigId)
.put("credential", new JSONObject()
.put("id", credentialId)
.put("type", "github")
.put("credentialToken", credentialToken)
.put("credentialSecret", credentialSecret))
)
).continueWith(new LogcatIfError());
new MethodCallHelper(serverConfigId).loginWithGitHub(credentialToken, credentialSecret)
.continueWith(new LogcatIfError());
}
private void onOAuthCompleted() {
......
......@@ -2,10 +2,14 @@ package chat.rocket.android.fragment.server_config;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import chat.rocket.android.R;
import chat.rocket.android.fragment.AbstractFragment;
import chat.rocket.android.helper.OnBackPressListener;
import chat.rocket.android.helper.TextUtils;
abstract class AbstractServerConfigFragment extends AbstractFragment {
abstract class AbstractServerConfigFragment extends AbstractFragment
implements OnBackPressListener {
protected String serverConfigId;
@Override public void onCreate(@Nullable Bundle savedInstanceState) {
......@@ -23,4 +27,27 @@ abstract class AbstractServerConfigFragment extends AbstractFragment {
return;
}
}
protected void showFragment(Fragment fragment) {
getFragmentManager().beginTransaction()
.add(R.id.content, fragment)
.commit();
}
protected void showFragmentWithBackStack(Fragment fragment) {
getFragmentManager().beginTransaction()
.add(R.id.content, fragment)
.addToBackStack(null)
.commit();
}
@Override public boolean onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
return true;
}
return false;
}
}
package chat.rocket.android.fragment.server_config;
import android.view.View;
import android.widget.TextView;
import chat.rocket.android.R;
/**
* Just show "Authenticating..."
*/
public class AuthenticatingFragment extends AbstractServerConfigFragment {
@Override protected int getLayout() {
return R.layout.fragment_wait_for_connection;
}
@Override protected void onSetupView() {
TextView caption = (TextView) rootView.findViewById(R.id.txt_caption);
caption.setVisibility(View.VISIBLE);
caption.setText("Authenticating...");
}
}
\ No newline at end of file
package chat.rocket.android.fragment.server_config;
import android.view.View;
import android.widget.TextView;
import chat.rocket.android.R;
/**
* Just showing "connecting..." screen.
*/
public class ConnectingToHostFragment extends AbstractServerConfigFragment {
@Override protected int getLayout() {
return R.layout.fragment_wait_for_connection;
}
@Override protected void onSetupView() {
TextView caption = (TextView) rootView.findViewById(R.id.txt_caption);
caption.setVisibility(View.GONE);
}
}
package chat.rocket.android.fragment.server_config;
import android.os.Handler;
import android.os.Message;
import android.support.design.widget.Snackbar;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import chat.rocket.android.R;
import chat.rocket.android.helper.CheckSum;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.fragment.oauth.GitHubOAuthFragment;
import chat.rocket.android.helper.MethodCallHelper;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.MeteorLoginServiceConfiguration;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ServerConfigCredential;
import chat.rocket.android.renderer.ServerConfigCredentialRenderer;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import jp.co.crowdworks.realm_java_helpers.RealmHelper;
import jp.co.crowdworks.realm_java_helpers.RealmListObserver;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONObject;
/**
* Login screen.
......@@ -29,12 +21,6 @@ public class LoginFragment extends AbstractServerConfigFragment {
return R.layout.fragment_login;
}
private Handler errorShowingHandler = new Handler() {
@Override public void handleMessage(Message msg) {
Toast.makeText(rootView.getContext(), (String) msg.obj, Toast.LENGTH_SHORT).show();
}
};
private RealmListObserver<MeteorLoginServiceConfiguration> authProvidersObserver =
new RealmListObserver<MeteorLoginServiceConfiguration>() {
@Override protected RealmResults<MeteorLoginServiceConfiguration> queryItems(Realm realm) {
......@@ -58,54 +44,28 @@ public class LoginFragment extends AbstractServerConfigFragment {
if (TextUtils.isEmpty(username) || TextUtils.isEmpty(passwd)) {
return;
}
view.setEnabled(false);
RealmHelperBolts.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("id", serverConfigId)
.put("credential", new JSONObject()
.put("id", serverConfigId)
.put("type", ServerConfigCredential.TYPE_EMAIL)
.put("errorMessage", JSONObject.NULL)
.put("username", username.toString())
.put("hashedPasswd", CheckSum.sha256(passwd.toString())))
)
).continueWith(new LogcatIfError());
new MethodCallHelper(serverConfigId).loginWithEmail(username.toString(), passwd.toString())
.continueWith(task -> {
if (task.isFaulted()) {
showError(task.getError().getMessage());
view.setEnabled(true);
}
return null;
});
});
final View btnUserRegistration = rootView.findViewById(R.id.btn_user_registration);
btnUserRegistration.setOnClickListener(view -> {
UserRegistrationDialogFragment.create(serverConfigId,
txtUsername.getText().toString(), txtPasswd.getText().toString())
.show(getFragmentManager(), UserRegistrationDialogFragment.class.getSimpleName());
});
showErrorIfNeeded();
}
private void showErrorIfNeeded() {
ServerConfig config = RealmHelper.executeTransactionForRead(realm ->
realm.where(ServerConfig.class)
.equalTo("id", serverConfigId)
.isNotNull("credential.errorMessage")
.findFirst());
if (config != null) {
ServerConfigCredential credential = config.getCredential();
new ServerConfigCredentialRenderer(getContext(), credential)
.usernameInto((TextView) rootView.findViewById(R.id.editor_username));
String errorMessage = credential.getErrorMessage();
if (!TextUtils.isEmpty(errorMessage)) {
showError(errorMessage);
}
}
}
private void showError(String errString) {
errorShowingHandler.removeMessages(0);
Message msg = Message.obtain(errorShowingHandler, 0, errString);
errorShowingHandler.sendMessageDelayed(msg, 160);
Snackbar.make(rootView, errString, Snackbar.LENGTH_SHORT).show();
}
private void onRenderAuthProviders(List<MeteorLoginServiceConfiguration> authProviders) {
......@@ -116,17 +76,17 @@ public class LoginFragment extends AbstractServerConfigFragment {
boolean hasGitHub = false;
for (MeteorLoginServiceConfiguration authProvider : authProviders) {
if (!hasTwitter
&& ServerConfigCredential.TYPE_TWITTER.equals(authProvider.getService())) {
&& "twitter".equals(authProvider.getService())) {
hasTwitter = true;
btnTwitter.setOnClickListener(view -> {
setAuthType(authProvider.getId(), ServerConfigCredential.TYPE_TWITTER);
});
}
if (!hasGitHub
&& ServerConfigCredential.TYPE_GITHUB.equals(authProvider.getService())) {
&& "github".equals(authProvider.getService())) {
hasGitHub = true;
btnGitHub.setOnClickListener(view -> {
setAuthType(authProvider.getId(), ServerConfigCredential.TYPE_GITHUB);
showFragmentWithBackStack(GitHubOAuthFragment.create(serverConfigId));
});
}
}
......@@ -135,17 +95,6 @@ public class LoginFragment extends AbstractServerConfigFragment {
btnGitHub.setVisibility(hasGitHub ? View.VISIBLE : View.GONE);
}
private void setAuthType(final String authProviderId, final String authType) {
RealmHelperBolts.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("id", serverConfigId)
.put("credential", new JSONObject()
.put("id", authProviderId)
.put("type", authType))
.put("errorMessage", JSONObject.NULL))
).continueWith(new LogcatIfError());
}
@Override public void onResume() {
super.onResume();
authProvidersObserver.sub();
......
......@@ -12,13 +12,8 @@ import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import chat.rocket.android.R;
import chat.rocket.android.helper.CheckSum;
import chat.rocket.android.helper.MethodCallHelper;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ServerConfigCredential;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONObject;
/**
* Dialog for user registration.
......@@ -114,22 +109,14 @@ public class UserRegistrationDialogFragment extends DialogFragment {
email = txtEmail.getText().toString();
password = txtPasswd.getText().toString();
MethodCallHelper.registerUser(username, email, password, password).onSuccessTask(task ->
RealmHelperBolts.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("id", serverConfigId)
.put("credential", new JSONObject()
.put("id", serverConfigId)
.put("type", ServerConfigCredential.TYPE_EMAIL)
.put("errorMessage", JSONObject.NULL)
.put("username", email)
.put("hashedPasswd", CheckSum.sha256(password)))
)
)
).onSuccessTask(task -> {
MethodCallHelper methodCallHelper = new MethodCallHelper(serverConfigId);
methodCallHelper.registerUser(username, email, password, password)
.onSuccessTask(task -> methodCallHelper.loginWithEmail(email, password))
.onSuccessTask(task -> {
dismiss();
return null;
}).continueWith(task -> {
return task;
})
.continueWith(task -> {
if (task.isFaulted()) {
Exception exception = task.getError();
showError(exception.getMessage());
......
package chat.rocket.android.fragment.server_config;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import chat.rocket.android.R;
import chat.rocket.android.helper.TextUtils;
/**
* Just showing "connecting..." screen.
*/
public class WaitingFragment extends AbstractServerConfigFragment {
private String caption;
/**
* create new "Waiting..." screen with caption.
*/
public static WaitingFragment create(String caption) {
Bundle args = new Bundle();
args.putString("caption", caption);
WaitingFragment fragment = new WaitingFragment();
fragment.setArguments(args);
return fragment;
}
public WaitingFragment() {}
@Override protected int getLayout() {
return R.layout.fragment_waiting;
}
@Override protected void onSetupView() {
Bundle args = getArguments();
if (args != null) {
caption = args.getString("caption");
}
TextView captionView = (TextView) rootView.findViewById(R.id.txt_caption);
if (TextUtils.isEmpty(caption)) {
captionView.setVisibility(View.GONE);
} else {
captionView.setText(caption);
captionView.setVisibility(View.VISIBLE);
}
}
}
package chat.rocket.android.helper;
import android.util.Patterns;
import bolts.Continuation;
import bolts.Task;
import chat.rocket.android.model.MethodCall;
import chat.rocket.android.model.ServerConfigCredential;
import chat.rocket.android_ddp.DDPClientCallback;
import org.json.JSONArray;
import chat.rocket.android.model.ServerConfig;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONException;
import org.json.JSONObject;
import static android.icu.lang.UCharacter.GraphemeClusterBreak.T;
/**
* Utility class for creating/handling MethodCall.
*/
public class MethodCallHelper {
private static <T> Task<T> injectErrorMessageHandler(Task<T> task) {
private final String serverConfigId;
public MethodCallHelper(String serverConfigId) {
this.serverConfigId = serverConfigId;
}
private Task<JSONObject> injectErrorHandler(Task<JSONObject> task) {
return task.continueWithTask(_task -> {
if (_task.isFaulted()) {
Exception exception = _task.getError();
......@@ -32,21 +36,91 @@ public class MethodCallHelper {
});
}
/**
* Register User.
*/
public static Task<Void> registerUser(String name, String email, String passwd,
String confirmPasswd) {
private interface ParamBuilder {
void buildParam(JSONObject param) throws JSONException;
}
private <T> Task<T> call(String methodName,
Continuation<JSONObject, Task<T>> onSuccess) {
return injectErrorHandler(MethodCall.execute(serverConfigId, methodName, null))
.onSuccessTask(onSuccess);
}
private <T> Task<T> call(String methodName, ParamBuilder paramBuilder,
Continuation<JSONObject, Task<T>> onSuccess) {
JSONObject param = new JSONObject();
try {
param.put("name", name).put("email", email)
.put("pass", passwd).put("confirm-pass", confirmPasswd);
paramBuilder.buildParam(param);
} catch (JSONException exception) {
return Task.forError(exception);
}
return injectErrorMessageHandler(MethodCall.execute("registerUser", param.toString()))
.onSuccessTask(task -> Task.forResult(null)); // nothing to do?
return injectErrorHandler(MethodCall.execute(serverConfigId, methodName, param.toString()))
.onSuccessTask(onSuccess);
}
/**
* Register User.
*/
public Task<Void> registerUser(final String name, final String email,
final String password, final String confirmPassword) {
return call("registerUser", param -> param
.put("name", name)
.put("email", email)
.put("pass", password)
.put("confirm-pass", confirmPassword),
task -> Task.forResult(null)); // nothing to do.
}
private Continuation<String, Task<Void>> saveToken() {
return task -> RealmHelperBolts.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("id", serverConfigId)
.put("token", task.getResult())
.put("tokenVerified", true)));
}
/**
* Login with username/email and password.
*/
public Task<Void> loginWithEmail(final String usernameOrEmail, final String password) {
return call("login", param -> {
if (Patterns.EMAIL_ADDRESS.matcher(usernameOrEmail).matches()) {
param.put("user", new JSONObject().put("email", usernameOrEmail));
} else {
param.put("user", new JSONObject().put("username", usernameOrEmail));
}
param.put("password", new JSONObject()
.put("digest", CheckSum.sha256(password))
.put("algorithm", "sha-256"));
}, task -> Task.forResult(task.getResult().getString("token"))).onSuccessTask(saveToken());
}
/**
* Login with GitHub OAuth.
*/
public Task<Void> loginWithGitHub(final String credentialToken,
final String credentialSecret) {
return call("login", param -> param
.put("oauth", new JSONObject()
.put("credentialToken", credentialToken)
.put("credentialSecret", credentialSecret)),
task -> Task.forResult(task.getResult().getString("token"))).onSuccessTask(saveToken());
}
/**
* Login with token.
*/
public Task<Void> loginWithToken(final String token) {
return call("login", param -> param.put("resume", token),
task -> Task.forResult(task.getResult().getString("token"))).onSuccessTask(saveToken());
}
/**
* Logout.
*/
public Task<Void> logout() {
return call("logout", task -> Task.forResult(null));
}
}
......@@ -18,6 +18,7 @@ import org.json.JSONObject;
public class MethodCall extends RealmObject {
@PrimaryKey private String id;
private String serverConfigId; //not ServerConfig!(not to be notified the change of ServerConfig)
private int syncstate;
private String name;
private String paramsJson;
......@@ -31,6 +32,14 @@ public class MethodCall extends RealmObject {
this.id = id;
}
public String getServerConfigId() {
return serverConfigId;
}
public void setServerConfigId(String serverConfigId) {
this.serverConfigId = serverConfigId;
}
public int getSyncstate() {
return syncstate;
}
......@@ -73,13 +82,15 @@ public class MethodCall extends RealmObject {
}
}
public static Task<JSONObject> execute(String name, String paramsJson) {
public static Task<JSONObject> execute(String serverConfigId, String name, String paramsJson) {
final String newId = UUID.randomUUID().toString();
TaskCompletionSource<JSONObject> task = new TaskCompletionSource<>();
RealmHelperBolts.executeTransaction(realm -> {
MethodCall call = realm.createObject(MethodCall.class, newId);
call.setSyncstate(SyncState.NOT_SYNCED);
call.setName(name);
MethodCall call = realm.createObjectFromJson(MethodCall.class, new JSONObject()
.put("id", newId)
.put("serverConfigId", serverConfigId)
.put("syncstate", SyncState.NOT_SYNCED)
.put("name", name));
call.setParamsJson(paramsJson);
return null;
}).continueWith(_task -> {
......
......@@ -21,7 +21,6 @@ public class ServerConfig extends RealmObject {
private String session;
private String token;
private boolean tokenVerified;
private ServerConfigCredential credential;
public static RealmQuery<ServerConfig> queryLoginRequiredConnections(Realm realm) {
return realm.where(ServerConfig.class).equalTo("tokenVerified", false);
......@@ -94,12 +93,4 @@ public class ServerConfig extends RealmObject {
public void setTokenVerified(boolean tokenVerified) {
this.tokenVerified = tokenVerified;
}
public ServerConfigCredential getCredential() {
return credential;
}
public void setCredential(ServerConfigCredential credential) {
this.credential = credential;
}
}
package chat.rocket.android.model;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android_ddp.DDPClientCallback;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONException;
import org.json.JSONObject;
@SuppressWarnings("PMD.ShortVariable")
public class ServerConfigCredential extends RealmObject {
public static final String TYPE_EMAIL = "email";
public static final String TYPE_TWITTER = "twitter";
public static final String TYPE_GITHUB = "github";
@PrimaryKey private String id;
private String type;
private String credentialToken;
private String credentialSecret;
private String username;
private String hashedPasswd;
private String errorMessage;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCredentialToken() {
return credentialToken;
}
public void setCredentialToken(String credentialToken) {
this.credentialToken = credentialToken;
}
public String getCredentialSecret() {
return credentialSecret;
}
public void setCredentialSecret(String credentialSecret) {
this.credentialSecret = credentialSecret;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getHashedPasswd() {
return hashedPasswd;
}
public void setHashedPasswd(String hashedPasswd) {
this.hashedPasswd = hashedPasswd;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public static boolean hasSecret(ServerConfigCredential credential) {
if (credential == null) {
return false;
}
final String authType = credential.getType();
if (TextUtils.isEmpty(authType)) {
return false;
}
if ("github".equals(authType) || "twitter".equals(authType)) {
return !TextUtils.isEmpty(credential.getCredentialToken())
&& !TextUtils.isEmpty(credential.getCredentialSecret());
} else if ("email".equals(authType)) {
return !TextUtils.isEmpty(credential.getUsername())
&& !TextUtils.isEmpty(credential.getHashedPasswd());
}
return false;
}
public static void logError(final String id, final Exception exception) {
RealmHelperBolts.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(ServerConfigCredential.class, new JSONObject()
.put("id", id)
.put("errorMessage", getErrorMessageFor(exception)))
).continueWith(new LogcatIfError());
}
private static String getErrorMessageFor(Exception exception) throws JSONException {
if (exception instanceof DDPClientCallback.RPC.Error) {
JSONObject error = ((DDPClientCallback.RPC.Error) exception).error;
if (!error.isNull("message")) {
return error.getString("message");
} else {
return error.toString();
}
} else {
return exception.getMessage();
}
}
}
package chat.rocket.android.renderer;
import android.content.Context;
import android.widget.TextView;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ServerConfigCredential;
/**
* renderer class for ServerConfigCredential.
*/
public class ServerConfigCredentialRenderer extends AbstractRenderer<ServerConfigCredential> {
public ServerConfigCredentialRenderer(Context context, ServerConfigCredential credential) {
super(context, credential);
}
/**
* Inject username into TextView.
*/
public ServerConfigCredentialRenderer usernameInto(TextView textView) {
if (!shouldHandle(textView)) {
return this;
}
if ("email".equals(object.getType())
&& !TextUtils.isEmpty(object.getUsername())) {
textView.setText(object.getUsername());
}
return this;
}
}
......@@ -10,7 +10,6 @@ import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.service.ddp_subscriber.LoginServiceConfigurationSubscriber;
import chat.rocket.android.service.observer.LoginCredentialObserver;
import chat.rocket.android.service.observer.MethodCallObserver;
import chat.rocket.android.ws.RocketChatWebSocketAPI;
import chat.rocket.android_ddp.DDPClientCallback;
......@@ -29,7 +28,6 @@ import timber.log.Timber;
public class RocketChatWebSocketThread extends HandlerThread {
private static final Class[] REGISTERABLE_CLASSES = {
LoginServiceConfigurationSubscriber.class,
LoginCredentialObserver.class,
MethodCallObserver.class
};
private final Context appContext;
......
package chat.rocket.android.service.observer;
import android.content.Context;
import bolts.Task;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ServerConfigCredential;
import chat.rocket.android.ws.RocketChatWebSocketAPI;
import chat.rocket.android_ddp.DDPClientCallback;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONObject;
public class LoginCredentialObserver extends AbstractModelObserver<ServerConfig> {
public LoginCredentialObserver(Context context, String serverConfigId,
RocketChatWebSocketAPI api) {
super(context, serverConfigId, api);
}
@Override protected RealmResults<ServerConfig> queryItems(Realm realm) {
return realm.where(ServerConfig.class)
.equalTo("tokenVerified", false)
.beginGroup()
.equalTo("credential.type", "email")
.isNotNull("credential.username")
.isNotNull("credential.hashedPasswd")
.or()
.notEqualTo("credential.type", "email")
.isNotNull("credential.credentialToken")
.isNotNull("credential.credentialSecret")
.endGroup()
.isNull("credential.errorMessage")
.findAll();
}
@Override protected void onCollectionChanged(List<ServerConfig> list) {
if (list.isEmpty()) {
return;
}
ServerConfig config = list.get(0);
final String serverConfigId = config.getId();
final String credentialId = config.getCredential().getId();
login(config).onSuccessTask(task -> {
final String token = task.getResult().result.getString("token");
return RealmHelperBolts.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("id", serverConfigId)
.put("token", token)
.put("tokenVerified", true)));
}).continueWith(task -> {
if (task.isFaulted()) {
ServerConfigCredential.logError(credentialId, task.getError());
}
return null;
});
}
private Task<DDPClientCallback.RPC> login(ServerConfig config) {
if (!TextUtils.isEmpty(config.getToken())) {
return webSocketAPI.loginWithToken(config.getToken());
}
return webSocketAPI.login(config.getCredential());
}
}
......@@ -24,11 +24,22 @@ public class MethodCallObserver extends AbstractModelObserver<MethodCall> {
super(context, serverConfigId, api);
RealmHelperBolts.executeTransaction(realm -> {
RealmResults<MethodCall> pendingMethodCalls = realm.where(MethodCall.class)
.equalTo("serverConfigId", serverConfigId)
.equalTo("syncstate", SyncState.SYNCING)
.findAll();
for (MethodCall call : pendingMethodCalls) {
call.setSyncstate(SyncState.NOT_SYNCED);
}
// clean up records.
realm.where(MethodCall.class)
.equalTo("serverConfigId", serverConfigId)
.beginGroup()
.equalTo("syncstate", SyncState.SYNCED)
.or()
.equalTo("syncstate", SyncState.FAILED)
.endGroup()
.findAll().deleteAllFromRealm();
return null;
}).continueWith(new LogcatIfError());
}
......@@ -36,6 +47,7 @@ public class MethodCallObserver extends AbstractModelObserver<MethodCall> {
@Override protected RealmResults<MethodCall> queryItems(Realm realm) {
return realm.where(MethodCall.class)
.isNotNull("name")
.equalTo("serverConfigId", serverConfigId)
.equalTo("syncstate", SyncState.NOT_SYNCED)
.findAll();
}
......
package chat.rocket.android.ws;
import android.support.annotation.Nullable;
import android.util.Patterns;
import bolts.Task;
import chat.rocket.android.helper.OkHttpHelper;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ServerConfigCredential;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.android_ddp.DDPClientCallback;
import chat.rocket.android_ddp.DDPSubscription;
......@@ -94,56 +92,4 @@ public class RocketChatWebSocketAPI {
return Task.forError(exception);
}
}
/**
* Login with ServerConfigCredential.
*/
public Task<DDPClientCallback.RPC> login(ServerConfigCredential credential) {
JSONObject param = new JSONObject();
try {
String authType = credential.getType();
if ("email".equals(authType)) {
String username = credential.getUsername();
if (Patterns.EMAIL_ADDRESS.matcher(username).matches()) {
param.put("user", new JSONObject().put("email", username));
} else {
param.put("user", new JSONObject().put("username", username));
}
param.put("password", new JSONObject()
.put("digest", credential.getHashedPasswd())
.put("algorithm", "sha-256"));
} else if ("github".equals(authType) || "twitter".equals(authType)) {
param.put("oauth", new JSONObject()
.put("credentialToken", credential.getCredentialToken())
.put("credentialSecret", credential.getCredentialSecret()));
}
} catch (JSONException exception) {
return Task.forError(exception);
}
return ddpClient.rpc("login", new JSONArray().put(param), generateId("login"));
}
/**
* Login with token.
*/
public Task<DDPClientCallback.RPC> loginWithToken(final String token) {
JSONObject param = new JSONObject();
try {
param.put("resume", token);
} catch (JSONException exception) {
return Task.forError(exception);
}
return ddpClient.rpc("login", new JSONArray().put(param), generateId("login-token"));
}
/**
* Logout.
*/
public Task<DDPClientCallback.RPC> logout() {
return ddpClient.rpc("logout", null, generateId("logout"));
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:minWidth="288dp"
android:padding="@dimen/margin_24"
android:gravity="center"
>
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginBottom="@dimen/margin_24"
android:scaleType="fitCenter"
android:src="@mipmap/ic_launcher"/>
<chat.rocket.android.widget.WaitingView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/txt_caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:layout_marginTop="@dimen/margin_16"/>
</LinearLayout>
\ No newline at end of file
......@@ -22,8 +22,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:layout_marginTop="@dimen/margin_32"
android:text="Connecting..."/>
android:layout_marginTop="@dimen/margin_32"/>
</LinearLayout>
</FrameLayout>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment