Add authenticate method in BfxWebsocketClient. Add API_KEY and API_SECRET to Client's constructor parameters. Add new AuthenticationCredentialsError exception.

This commit is contained in:
Davide Casale
2022-11-09 18:26:30 +01:00
parent 7e416219e0
commit 1fc9aacd86
3 changed files with 51 additions and 13 deletions

View File

@@ -7,5 +7,5 @@ class Constants(str, Enum):
PUB_WSS_HOST = "wss://api-pub.bitfinex.com/ws/2"
class Client(object):
def __init__(self, WSS_HOST: str = Constants.WSS_HOST):
self.wss = BfxWebsocketClient(host=WSS_HOST)
def __init__(self, WSS_HOST: str = Constants.WSS_HOST, API_KEY: str = None, API_SECRET: str = None):
self.wss = BfxWebsocketClient(host=WSS_HOST, API_KEY=API_KEY, API_SECRET=API_SECRET)

View File

@@ -1,30 +1,31 @@
import json, asyncio, websockets
import json, asyncio, hmac, hashlib, time, websockets
from pyee.asyncio import AsyncIOEventEmitter
from .manager import Manager
from .channels import Channels
from .errors import ConnectionNotOpen
from .errors import ConnectionNotOpen, AuthenticationCredentialsError
class BfxWebsocketClient(object):
def __init__(self, host, channels=None):
def __init__(self, host, API_KEY=None, API_SECRET=None):
self.host, self.chanIds, self.event_emitter = host, dict(), AsyncIOEventEmitter()
self.manager, self.websocket = Manager(event_emitter=self.event_emitter), None
self.channels = channels or list()
self.API_KEY, self.API_SECRET = API_KEY, API_SECRET
def run_forever(self):
asyncio.run(self.connect())
async def connect(self):
async for websocket in websockets.connect(self.host):
try:
self.websocket = websocket
for channel, parameters in self.channels:
await self.subscribe(channel, **parameters)
else: self.event_emitter.emit("open")
try:
self.event_emitter.emit("open")
if self.API_KEY != None and self.API_SECRET != None:
self.authenticate(self.API_KEY, self.API_SECRET)
async for message in websocket:
message = json.loads(message)
@@ -38,12 +39,21 @@ class BfxWebsocketClient(object):
if message["status"] == "OK":
del self.chanIds[message["chanId"]]
elif isinstance(message, dict) and message["event"] == "auth":
if message["status"] == "OK":
self.chanIds[message["chanId"]] = message
self.event_emitter.emit("authenticated", message)
else: raise AuthenticationCredentialsError("Cannot authenticate with given API-KEY and API-SECRET.")
elif isinstance(message, list):
chanId, parameters = message[0], message[1:]
self.manager.handle(self.chanIds[chanId], *parameters)
except websockets.ConnectionClosed:
continue
@staticmethod
def __require_websocket_connection(function):
async def wrapper(self, *args, **kwargs):
if self.websocket == None or self.websocket.open == False:
@@ -68,6 +78,22 @@ class BfxWebsocketClient(object):
"chanId": chanId
}))
@__require_websocket_connection
async def authenticate(self, API_KEY, API_SECRET, filter=None):
data = { "event": "auth", "filter": filter, "apiKey": API_KEY }
data["authNonce"] = int(time.time()) * 1000
data["authPayload"] = "AUTH" + str(data["authNonce"])
data["authSig"] = hmac.new(
API_SECRET.encode("utf8"),
data["authPayload"].encode("utf8"),
hashlib.sha384
).hexdigest()
await self.websocket.send(json.dumps(data))
async def clear(self):
for chanId in self.chanIds.keys():
await self.unsubscribe(chanId)
@@ -77,3 +103,9 @@ class BfxWebsocketClient(object):
self.event_emitter.on(event, function)
return handler
def once(self, event):
def handler(function):
self.event_emitter.once(event, function)
return handler

View File

@@ -1,7 +1,13 @@
class ConnectionNotOpen(Exception):
"""
This error indicates an attempt to communicate via websocket before starting the connection with the servers.
"""
pass
class AuthenticationCredentialsError(Exception):
"""
This error indicates that the user has provided incorrect credentials (API-KEY and API-SECRET) for authentication.
"""
pass