async def handle_user_message(r: web.Request, message: str, ws: web.WebSocketResponse = None): """Process data sent by client""" address = util.get_request_ip(r) uid = ws.id if ws is not None else '0' now = int(round(time.time() * 1000)) if address in r.app['last_msg']: if (now - r.app['last_msg'][address]['last']) < 25: if r.app['last_msg'][address]['count'] > 3: log.server_logger.error( 'client messaging too quickly: %s ms; %s; %s; User-Agent: %s', str(now - r.app['last_msg'][address]['last']), address, uid, str(r.headers.get('User-Agent'))) return None else: r.app['last_msg'][address]['count'] += 1 else: r.app['last_msg'][address]['count'] = 0 else: r.app['last_msg'][address] = {} r.app['last_msg'][address]['count'] = 0 r.app['last_msg'][address]['last'] = now log.server_logger.info('request; %s, %s, %s', message, address, uid) if message not in r.app['active_messages']: r.app['active_messages'].add(message) else: log.server_logger.error('request already active; %s; %s; %s', message, address, uid) return None ret = None try: request_json = json.loads(message) if request_json['action'] in allowed_rpc_actions: if 'request_id' in request_json: requestid = request_json['request_id'] else: requestid = None # adjust counts so nobody can block the node with a huge request if 'count' in request_json: if (request_json['count'] < 0) or (request_json['count'] > 3500): request_json['count'] = 3500 # rpc: account_subscribe (only applies to websocket connections) if request_json['action'] == "account_subscribe" and ws is not None: # If account doesnt match the uuid self-heal resubscribe = True if 'uuid' in request_json: # Perform multi-account upgrade if not already done account = await r.app['rdata'].hget( request_json['uuid'], "account") # No account for this uuid, first subscribe if account is None: resubscribe = False else: # If account isn't stored in list-format, modify it so it is # If it already is, add this account to the list try: account_list = json.loads(account) if 'account' in request_json and request_json[ 'account'].lower() not in account_list: account_list.append( request_json['account'].lower()) await r.app['rdata'].hset( request_json['uuid'], "account", json.dumps(account_list)) except Exception: if 'account' in request_json and request_json[ 'account'].lower() != account.lower(): resubscribe = False else: # Perform upgrade to list style account_list = [] account_list.append(account.lower()) await r.app['rdata'].hset( request_json['uuid'], "account", json.dumps(account_list)) # already subscribed, reconnect (websocket connections) if 'uuid' in request_json and resubscribe: if uid in r.app['clients']: del r.app['clients'][uid] uid = request_json['uuid'] ws.id = uid r.app['clients'][uid] = ws log.server_logger.info('reconnection request;' + address + ';' + uid) try: if 'currency' in request_json and request_json[ 'currency'] in currency_list: currency = request_json['currency'] r.app['cur_prefs'][uid] = currency await r.app['rdata'].hset(uid, "currency", currency) else: setting = await r.app['rdata'].hget( uid, "currency") if setting is not None: r.app['cur_prefs'][uid] = setting else: r.app['cur_prefs'][uid] = 'usd' await r.app['rdata'].hset( uid, "currency", 'usd') # Get relevant account account_list = json.loads(await r.app['rdata'].hget( uid, "account")) if 'account' in request_json: account = request_json['account'] else: # Legacy connections account = account_list[0] if account.replace("nano_", "xrb_") in account_list: account_list.remove( account.replace("nano_", "xrb_")) account = account.replace('xrb_', 'nano_') account_list.append(account) await r.app['rdata'].hset(uid, "account", json.dumps(account_list)) await rpc.rpc_reconnect(ws, r, account) # Store FCM token for this account, for push notifications if 'fcm_token' in request_json: await update_fcm_token_for_account( account, request_json['fcm_token'], r) elif 'fcm_token_v2' in request_json and 'notification_enabled' in request_json: if request_json['notification_enabled']: await update_fcm_token_for_account( account, request_json['fcm_token_v2'], r, v2=True) else: await delete_fcm_token_for_account( account, request_json['fcm_token_v2'], r) except Exception as e: log.server_logger.error('reconnect error; %s; %s; %s', str(e), address, uid) reply = {'error': 'reconnect error', 'detail': str(e)} if requestid is not None: reply['request_id'] = requestid ret = json.dumps(reply) # new user, setup uuid(or use existing if available) and account info else: log.server_logger.info('subscribe request; %s; %s', util.get_request_ip(r), uid) try: if 'currency' in request_json and request_json[ 'currency'] in currency_list: currency = request_json['currency'] else: currency = 'usd' await rpc.rpc_subscribe( ws, r, request_json['account'].replace("nano_", "xrb_"), currency) # Store FCM token if available, for push notifications if 'fcm_token' in request_json: await update_fcm_token_for_account( request_json['account'], request_json['fcm_token'], r) elif 'fcm_token_v2' in request_json and 'notification_enabled' in request_json: if request_json['notification_enabled']: await update_fcm_token_for_account( request_json['account'], request_json['fcm_token_v2'], r, v2=True) else: await delete_fcm_token_for_account( request_json['account'], request_json['fcm_token_v2'], r) except Exception as e: log.server_logger.error('subscribe error;%s;%s;%s', str(e), address, uid) reply = {'error': 'subscribe error', 'detail': str(e)} if requestid is not None: reply['request_id'] = requestid ret = json.dumps(reply) elif request_json['action'] == "fcm_update": # Updating FCM token if 'fcm_token_v2' in request_json and 'account' in request_json and 'enabled' in request_json: if request_json['enabled']: await update_fcm_token_for_account( request_json['account'], request_json['fcm_token_v2'], r, v2=True) else: await delete_fcm_token_for_account( request_json['account'], request_json['fcm_token_v2'], r) # rpc: price_data elif request_json['action'] == "price_data": log.server_logger.info('price data request;%s;%s', util.get_request_ip(r), uid) try: if request_json['currency'].upper() in currency_list: try: price = await r.app['rdata'].hget( "prices", f"{price_prefix}-" + request_json['currency'].lower()) reply = json.dumps({ 'currency': request_json['currency'].lower(), 'price': str(price) }) ret = reply except Exception: log.server_logger.error( 'price data error, unable to get price;%s;%s', util.get_request_ip(r), uid) ret = json.dumps({ 'error': 'price data error - unable to get price' }) else: log.server_logger.error( 'price data error, unknown currency;%s;%s', util.get_request_ip(r), uid) ret = json.dumps({'error': 'unknown currency'}) except Exception as e: log.server_logger.error('price data error;%s;%s;%s', str(e), util.get_request_ip(r), uid) ret = json.dumps({ 'error': 'price data error', 'details': str(e) }) # rpc: account_check elif request_json['action'] == "account_check": log.server_logger.info('account check request;%s;%s', util.get_request_ip(r), uid) try: response = await rpc.rpc_accountcheck( r, uid, request_json['account']) ret = json.dumps(response) except Exception as e: log.server_logger.error('account check error;%s;%s;%s', str(e), util.get_request_ip(r), uid) ret = json.dumps({ 'error': 'account check error', 'detail': str(e) }) # rpc: process elif request_json['action'] == "process": try: do_work = False if 'do_work' in request_json and request_json[ 'do_work'] == True: do_work = True subtype = None if 'subtype' in request_json: subtype = request_json['subtype'] if 'json_block' in request_json and request_json[ 'json_block']: sblock = request_json['block'] else: sblock = json.loads(request_json['block']) reply = await rpc.process_defer(r, uid, sblock, do_work, subtype=subtype) if reply is None: raise Exception ret = json.dumps(reply) except Exception as e: log.server_logger.error( 'process rpc error;%s;%s;%s;User-Agent:%s', str(e), util.get_request_ip(r), uid, str(r.headers.get('User-Agent'))) ret = json.dumps({ 'error': 'process rpc error', 'detail': str(e) }) # rpc: pending elif request_json['action'] == "pending": try: reply = await rpc.pending_defer(r, uid, request_json) if reply is None: raise Exception ret = json.dumps(reply) except Exception as e: log.server_logger.error( 'pending rpc error;%s;%s;%s;User-Agent:%s', str(e), util.get_request_ip(r), uid, str(r.headers.get('User-Agent'))) ret = json.dumps({ 'error': 'pending rpc error', 'detail': str(e) }) elif request_json['action'] == 'account_history': if await r.app['rdata'].hget(uid, "account") is None: await r.app['rdata'].hset( uid, "account", json.dumps([request_json['account']])) try: response = await rpc.json_post(request_json) if response is None: raise Exception ret = json.dumps(response) except Exception as e: log.server_logger.error('rpc error;%s;%s;%s', str(e), util.get_request_ip(r), uid) ret = json.dumps({ 'error': 'account_history rpc error', 'detail': str(e) }) # rpc: fallthrough and error catch else: try: response = await rpc.json_post(request_json) if response is None: raise Exception ret = json.dumps(response) except Exception as e: log.server_logger.error('rpc error;%s;%s;%s', str(e), util.get_request_ip(r), uid) ret = json.dumps({'error': 'rpc error', 'detail': str(e)}) except Exception as e: log.server_logger.exception('uncaught error;%s;%s', util.get_request_ip(r), uid) ret = json.dumps({ 'error': 'general error', 'detail': str(sys.exc_info()) }) finally: r.app['active_messages'].remove(message) return ret
async def handle_user_message(r: web.Request, message: str, ws: web.WebSocketResponse = None): """Process data sent by client""" address = util.get_request_ip(r) uid = ws.id if ws is not None else '0' now = int(round(time.time() * 1000)) if address in r.app['last_msg']: if (now - r.app['last_msg'][address]['last']) < 25: if r.app['last_msg'][address]['count'] > 3: log.server_logger.error( 'client messaging too quickly: %s ms; %s; %s; User-Agent: %s', str(now - r.app['last_msg'][address]['last']), address, uid, str(r.headers.get('User-Agent'))) return None else: r.app['last_msg'][address]['count'] += 1 else: r.app['last_msg'][address]['count'] = 0 else: r.app['last_msg'][address] = {} r.app['last_msg'][address]['count'] = 0 r.app['last_msg'][address]['last'] = now log.server_logger.info('request; %s, %s, %s', message, address, uid) if message not in r.app['active_messages']: r.app['active_messages'].add(message) else: log.server_logger.error('request already active; %s; %s; %s', message, address, uid) return None ret = None try: request_json = json.loads(message) if request_json['action'] in ws_whitelist: # rpc: account_subscribe (only applies to websocket connections) if request_json['action'] == "account_subscribe" and ws is not None: # already subscribed, reconnect (websocket connections) if 'uuid' in request_json: try: del r.app['clients'][uid] except Exception: pass uid = request_json['uuid'] ws.id = uid r.app['clients'][uid] = ws log.server_logger.info('reconnection request;' + address + ';' + uid) try: if 'currency' in request_json and request_json[ 'currency'] in currency_list: currency = request_json['currency'] r.app['cur_prefs'][uid] = currency await r.app['rdata'].hset(uid, "currency", currency) else: setting = await r.app['rdata'].hget( uid, "currency") if setting is not None: r.app['cur_prefs'][uid] = setting else: r.app['cur_prefs'][uid] = 'usd' await r.app['rdata'].hset( uid, "currency", 'usd') # Get relevant account and public key account = request_json[ 'account'] if 'account' in request_json else None b58_pubkey = request_json[ 'b58_pubkey'] if 'b58_pubkey' in request_json else None await ws_reconnect(ws, r, account, b58_pubkey) if 'fcm_token' in request_json and account is not None: if request_json['notification_enabled']: await update_fcm_token_for_account( account, request_json['fcm_token'], r, b58_pubkey) else: await delete_fcm_token( account, request_json['fcm_token'], r) except Exception as e: log.server_logger.error('reconnect error; %s; %s; %s', str(e), address, uid) reply = {'error': 'reconnect error', 'detail': str(e)} ret = json.dumps(reply) # new user, setup uuid(or use existing if available) and account info else: log.server_logger.info('subscribe request; %s; %s', util.get_request_ip(r), uid) try: if 'currency' in request_json and request_json[ 'currency'] in currency_list: currency = request_json['currency'] else: currency = 'usd' account = request_json[ 'account'] if 'account' in request_json else None b58_pubkey = request_json[ 'b58_pubkey'] if 'b58_pubkey' in request_json else None await ws_connect(ws, r, account, currency, b58_pubkey) if 'fcm_token' in request_json and account is not None: if request_json['notification_enabled']: await update_fcm_token_for_account( account, request_json['fcm_token'], r, b58_pubkey) else: await delete_fcm_token( account, request_json['fcm_token'], r) except Exception as e: log.server_logger.error('subscribe error;%s;%s;%s', str(e), address, uid) reply = {'error': 'subscribe error', 'detail': str(e)} ret = json.dumps(reply) elif request_json['action'] == "fcm_update": # Updating FCM token if 'fcm_token' in request_json and 'account' in request_json and 'enabled' in request_json: b58_pubkey = request_json[ 'b58_pubkey'] if 'b58_pubkey' in request_json else None if request_json['enabled']: await update_fcm_token_for_account( request_json['account'], request_json['fcm_token'], r, b58_pubkey) else: await delete_fcm_token(request_json['account'], request_json['fcm_token'], r) elif request_json['action'] == 'fcm_update_bulk': if 'fcm_token' in request_json and 'accounts' in request_json and 'enabled' in request_json: b58_pubkey = request_json[ 'b58_pubkey'] if 'b58_pubkey' in request_json else None if request_json['enabled']: for account in request_json['accounts']: await update_fcm_token_for_account( account, request_json['fcm_token'], r, b58_pubkey) else: for account in request_json['accounts']: await delete_fcm_token(account, request_json['fcm_token'], r) elif request_json['action'] == 'fcm_delete_account': if 'fcm_token' in request_json and 'account' in request_json: b58_pubkey = request_json[ 'b58_pubkey'] if 'b58_pubkey' in request_json else None await delete_fcm_account(request_json['account'], request_json['fcm_token'], r, explicit=True) except Exception as e: log.server_logger.exception('uncaught error;%s;%s', util.get_request_ip(r), uid) ret = json.dumps({ 'error': 'general error', 'detail': str(sys.exc_info()) }) finally: r.app['active_messages'].remove(message) return ret