Example #1
0
        private async Task ConnectWebSocket(SignInSession session, string seed, string rememberMe)
        {
            HttpResponseMessage response;

            try
            {
                response = await _ws.Connect(session, seed, rememberMe);
            }
            catch (Exception ex)
            {
                if (ex.Message == "Web Socket already connected")
                {
                    throw new UserAlreadySignedIn(session.Username);
                }

                throw;
            }

            if (response.IsSuccessStatusCode)
            {
                return;
            }

            var exception = ParseGenericErrors(await response.Content.ReadAsStringAsync(), response.StatusCode);

            if (exception != null)
            {
                throw exception;
            }

            throw new Exception($"Unknown error during SignIn: {response.StatusCode}");
        }
Example #2
0
        public async Task <HttpResponseMessage> Connect(SignInSession session, string seedString, string rememberMe, int reconnectDelay = 0)
        {
            if (_connected)
            {
                throw new WebSocketError(WsAlreadyConnected, session.Username);
            }

            var timeout = false;
            var timeoutToOpenWebSocket = SetTimeout(
                () =>
            {
                if (!_connected && !Reconnecting)
                {
                    timeout = true;
                    throw new WebSocketError("timeout", "");
                }
            },
                10000
                );

            var url = $"{WsUtils.GetWsUrl(_config.Endpoint)}api?appId={_config.AppId}&sessionId={session.SessionId}&clientId={_clientId}";

            // TODO: handle timeouts
            var webSocket = new WebSocket(url);

            webSocket.Opened          += (sender, e) => OnOpened(sender, e, timeout, timeoutToOpenWebSocket);
            webSocket.DataReceived    += OnDataReceived;
            webSocket.MessageReceived += async(sender, args) => await OnMessageReceived(sender, args, webSocket, seedString, session);

            webSocket.Closed += async(sender, args) => await OnClosed(sender, args, session, seedString, rememberMe, reconnectDelay, timeout);

            webSocket.Error += OnError;
            var result = await webSocket.OpenAsync();

            return(result
                ? new HttpResponseMessage(HttpStatusCode.OK)
                : new HttpResponseMessage(HttpStatusCode.InternalServerError));
        }
Example #3
0
        private async Task OnClosed(object sender, EventArgs args, SignInSession session, string seedString, string rememberMe, int reconnectDelay, bool timeout)
        {
            await _logger.Log("CLOSED");

            if (timeout)
            {
                return;
            }
            if (args is ClosedEventArgs e)
            {
                // TODO
                var serviceRestart     = e.Code == 1012; // statusCodes['Service Restart']
                var clientDisconnected = e.Code == 3000; //statusCodes['No Pong Received']
                var attemptToReconnect =
                    serviceRestart ||
                    clientDisconnected; // || !e.wasClean // closed without explicit call to ws.close()

                if (attemptToReconnect)
                {
                    var delay = serviceRestart && reconnectDelay <= 0
                        ? 0
                        : (reconnectDelay > 0 ? reconnectDelay + BackoffRetryDelay : 1000);

                    Reconnecting = true;
                    await Reconnect(session, seedString, rememberMe, !_reconnected && State != null?State : null, delay);
                }
                else if (e.Code == 3001 /*statusCodes['Client Already Connected']*/)
                {
                    // TODO: reject websocketError?
                    throw new WebSocketError(WsAlreadyConnected, session.Username);
                }
                else
                {
                    Init(null, null, null, null, null, null);
                }
            }
        }
Example #4
0
        public async Task <SignUpResult> SignUp(SignUpRequest signUpRequest)
        {
            try
            {
                ValidateSignUpOrSignInInput(signUpRequest.Username, signUpRequest.Password);
                if (signUpRequest.Profile != null)
                {
                    ValidateProfile(signUpRequest.Profile);
                }
                if (string.IsNullOrEmpty(signUpRequest.Email))
                {
                    throw new Errors.EmailNotValid();
                }
                if (!Config.RememberMeOptions.ContainsKey(signUpRequest.RememberMe))
                {
                    throw new RememberMeValueNotValid();
                }

                var username = signUpRequest.Username.ToLower();
                var email    = signUpRequest.Email.ToLower();
                var appId    = _config.AppId;
                var seed     = Utils.GenerateSeed();

                var(sessionId, creationDate, userId) =
                    await GenerateKeysAndSignUp(username, signUpRequest.Password, seed, email, signUpRequest.Profile);

                var session = new SignInSession
                {
                    Username     = username, SessionId = sessionId,
                    CreationDate = creationDate.ToString(CultureInfo.InvariantCulture)
                };
                var seedString = Convert.ToBase64String(seed);

                _localData.SaveSeedString(signUpRequest.RememberMe, appId, username, seedString);
                _localData.SignInSession(signUpRequest.RememberMe, username, sessionId,
                                         creationDate.ToString(CultureInfo.InvariantCulture));

                await ConnectWebSocket(session, seedString, signUpRequest.RememberMe);

                return(new SignUpResult
                {
                    Username = username,
                    UserId = userId,
                    Email = email,
                    Profile = signUpRequest.Profile,
                });
            }
            catch (Exception ex)
            {
                if (!(ex is IError e))
                {
                    throw new UnknownServiceUnavailable(ex);
                }
                switch (e.Name)
                {
                case "ParamsMustBeObject":
                case "UsernameMissing":
                case "UsernameAlreadyExists":
                case "UsernameCannotBeBlank":
                case "UsernameMustBeString":
                case "UsernameTooLong":
                case "PasswordMissing":
                case "PasswordCannotBeBlank":
                case "PasswordTooShort":
                case "PasswordTooLong":
                case "PasswordMustBeString":
                case "EmailNotValid":
                case "ProfileMustBeObject":
                case "ProfileCannotBeEmpty":
                case "ProfileHasTooManyKeys":
                case "ProfileKeyMustBeString":
                case "ProfileKeyTooLong":
                case "ProfileValueMustBeString":
                case "ProfileValueCannotBeBlank":
                case "ProfileValueTooLong":
                case "RememberMeValueNotValid":
                case "TrialExceededLimit":
                case "AppIdNotSet":
                case "AppIdNotValid":
                case "UserAlreadySignedIn":
                case "ServiceUnavailable":
                    throw;

                default:
                    throw new UnknownServiceUnavailable(ex);
                }
            }
        }
Example #5
0
        public void Init(Action resolveConnection = null, Action <IError> rejectConnection = null, SignInSession session = null, string seedString = null, string rememberMe = null, UserState state = null)
        {
            if (_pingTimeout > 0)
            {
                ClearTimeout(_pingTimeout);
            }

            // TODO: find out the scope of this code
            //for (const property of Object.keys(this)) {
            //    delete this[property];
            //}

            if (Instance4Net != null)
            {
                Instance4Net.Dispose();
                Instance4Net = null;
            }

            _connected = false;

            _resolveConnection  = resolveConnection;
            _rejectConnection   = rejectConnection;
            _connectionResolved = false;

            Session.Username     = session != null && !string.IsNullOrEmpty(session.Username) ? session.Username : "";
            Session.SessionId    = session != null && !string.IsNullOrEmpty(session.SessionId) ? session.SessionId : "";
            Session.CreationDate = session != null && !string.IsNullOrEmpty(session.CreationDate) ? session.CreationDate : "";

            _seedString = seedString;
            Keys.Clear();

            _rememberMe = rememberMe;

            _pendingRequests.Clear();

            State = state ?? new UserState
            {
                Databases    = new Dictionary <string, Database>(),
                DbIdToHash   = new Dictionary <string, string>(),
                DbNameToHash = new Dictionary <string, string>(),
            };
        }
Example #6
0
        private async Task OnMessageReceived(object sender, MessageReceivedEventArgs e, WebSocket webSocket, string seedString, SignInSession session)
        {
            //_logger.Log(sender.ToString());
            _ = _logger.Log($"RECEIVED - {e.Message}");
            try
            {
                var msg   = JObject.Parse(e.Message);
                var route = msg["route"]?.ToString().ToLower() ?? "";

                switch (route)
                {
                case "connection":
                    // TODO: pass parameters
                    Init(null, null, session, seedString, null, null);
                    Instance4Net = webSocket;
                    //this.heartbeat()
                    _connected = true;

                    var connectionMessage = JsonConvert.DeserializeObject <ConnectionMessage>(e.Message);
                    Keys.Salts = connectionMessage.KeySalts;
                    _encryptedValidationMessage = connectionMessage.EncryptedValidationMessage.Data;

                    await SetKeys(seedString);

                    break;

                case "ping":
                    //this.heartbeat()
                    _ = _logger.Log("SENT - PONG");
                    const string action = "Pong";
                    Instance4Net.Send(JsonConvert.SerializeObject(new { action }));
                    break;

                case "applytransactions":
                    // TODO: in progress
                    var dbId       = msg["dbId"].ToString();
                    var dbNameHash = msg["dbNameHash"].ToString();    // || State.dbIdToHash[dbId]
                    if (!State.Databases.TryGetValue(dbNameHash, out var database))
                    {
                        throw new Exception("Missing database");
                    }

                    // queue guarantees transactions will be applied in the order they are received from the server
                    if (database.ApplyTransactionsQueue.Count == 0)
                    {
                        // take a spot in the queue and proceed applying so the next caller knows queue is not empty
                        database.ApplyTransactionsQueue.Enqueue(null);
                    }
                    else
                    {
                        // wait until prior batch in queue finishes applying successfully
                        var promise = new TaskCompletionSource <int>();
                        void Resolve()
                        {
                            var localPromise = promise;

                            localPromise.SetResult(0);
                        }
                        database.ApplyTransactionsQueue.Enqueue(Resolve);
                        await promise.Task;
                    }

                    var openingDatabase = msg["dbNameHash"] != null && msg["dbKey"] != null;
                    if (openingDatabase)
                    {
                        var dbKeyString = AesGcmUtils.DecryptString(Keys.EncryptionKey, msg["dbKey"].ToString());
                        database.DbKeyString = dbKeyString;
                        // TODO
                        //database.dbKey = await AesGcmUtils.GetKeyFromKeyString(dbKeyString);
                    }

                    //if (!database.dbKey) throw new Error('Missing db key')

                    if (msg["bundle"] != null)
                    {
                        /*const bundleSeqNo = message.bundleSeqNo
                         * const base64Bundle = message.bundle
                         * const compressedString = await crypto.aesGcm.decryptString(database.dbKey, base64Bundle)
                         * const plaintextString = LZString.decompress(compressedString)
                         * const bundle = JSON.parse(plaintextString)
                         *
                         * database.applyBundle(bundle, bundleSeqNo)*/
                    }

                    /*const newTransactions = message.transactionLog
                     *  await database.applyTransactions(newTransactions)*/

                    if (!database.Init)
                    {
                        State.DbIdToHash[dbId] = dbNameHash;
                        database.DbId          = dbId;
                        database.Init          = true;
                        _ = _logger.Log("DB - RECEIVED MESSAGE");
                        database.ReceivedMessage();
                    }

                    if (msg["buildBundle"] != null)
                    {
                        BuildBundle(database);
                    }

                    // start applying next batch in queue when this one is finished applying successfully
                    database.ApplyTransactionsQueue.Dequeue();
                    if (database.ApplyTransactionsQueue.Count > 0)
                    {
                        var startApplyingNextBatchInQueue = database.ApplyTransactionsQueue.Peek();
                        startApplyingNextBatchInQueue();
                    }

                    break;

                case "signout":
                case "updateuser":
                case "deleteuser":
                case "createdatabase":
                case "getdatabase":
                case "opendatabase":
                case "insert":
                case "update":
                case "delete":
                case "batchtransaction":
                case "bundle":
                case "validatekey":
                case "getpasswordsalts":
                    var requestId = msg["requestId"]?.ToString().ToLower() ?? "";

                    if (string.IsNullOrEmpty(requestId))
                    {
                        // TODO: log warning
                        Console.WriteLine("Missing request id");
                        return;
                    }

                    //var request = this.requests[requestId];
                    if (!_pendingRequests.TryGetValue(requestId, out var request))
                    {
                        // TODO: log warning
                        Console.WriteLine($"Request {requestId} no longer exists!");
                        return;
                    }
                    else if (request.Resolve == null || request.Reject == null)
                    {
                        return;
                    }

                    var response = msg["response"]?.ToString().ToLower() ?? "";
                    var status   = msg["response"]["status"]?.ToString().ToLower() ?? "";

                    var statusCode         = HttpStatusCode.BadRequest;
                    var successfulResponse = !string.IsNullOrEmpty(response) &&
                                             !string.IsNullOrEmpty(status) &&
                                             Enum.TryParse(status, out statusCode) &&
                                             statusCode == HttpStatusCode.OK;

                    if (successfulResponse)
                    {
                        request.Resolve(requestId);
                    }
                    else
                    {
                        request.Reject(requestId, route, statusCode, response);
                    }

                    break;

                default:
                    Console.WriteLine($"Received unknown message from backend: {msg}");
                    break;
                }
            }
            catch (Exception exception)
            {
                _ = _logger.Log($"ERROR - {exception.Message}");
            }
        }
Example #7
0
 // TODO: handle state
 public async Task Reconnect(SignInSession session, string seed, string rememberMe, UserState state, int reconnectDelay = 0)
 {
     await Task.FromException(new NotImplementedException());
 }