From f129b8b4d053aa539e66a2aee1a0ca652e41fa92 Mon Sep 17 00:00:00 2001 From: byteface Date: Fri, 25 Feb 2022 09:12:15 +0000 Subject: [PATCH] Adds a lib for server sent events. bump version. --- domonic/webapi/messaging.py | 5 ++ domonic/webapi/sse.py | 151 ++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 domonic/webapi/messaging.py create mode 100644 domonic/webapi/sse.py diff --git a/domonic/webapi/messaging.py b/domonic/webapi/messaging.py new file mode 100644 index 00000000..4ebb85a0 --- /dev/null +++ b/domonic/webapi/messaging.py @@ -0,0 +1,5 @@ +""" + domonic.webapi.messaging + ==================================== + https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API +""" diff --git a/domonic/webapi/sse.py b/domonic/webapi/sse.py new file mode 100644 index 00000000..a52cd761 --- /dev/null +++ b/domonic/webapi/sse.py @@ -0,0 +1,151 @@ +""" + domonic.webapi.sse + ==================================== + https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events + + TODO - consider a blocking verison? + TODO - port one of the polyfills? + https://github.com/EventSource/eventsource/blob/master/lib/eventsource.js + https://github.com/remy/polyfills/blob/master/EventSource.js + https://github.com/Yaffle/EventSource/blob/master/src/eventsource.js + + # TESTING - useage example... clone this and point at the stream + # https://github.com/byteface/SSELoggerDemo + +""" + +from domonic.events import MessageEvent, EventTarget + + +class EventSource(EventTarget): + # https://developer.mozilla.org/en-US/docs/Web/API/EventSource + + CONNECTING = 0 + OPEN = 1 + CLOSED = 2 + + def __init__(self, url, eventSourceInitDict=None, **kwargs): + """ + The EventSource interface provides the ability to asynchronously receive messages from a server using simple push paradigm. + """ + eventSourceInitDict = eventSourceInitDict or kwargs + super().__init__() + self._url = url + self._readyState = EventSource.CLOSED + self._withCredentials = eventSourceInitDict.get('withCredentials', False) + self._lastEventId = eventSourceInitDict.get('lastEventId', None) + + self._onmessage = eventSourceInitDict.get('onmessage', None) + self._onerror = eventSourceInitDict.get('onerror', None) + self._onopen = eventSourceInitDict.get('onopen', None) + self._onreadystatechange = eventSourceInitDict.get('onreadystatechange', None) + + # from sseclient import SSEClient + # messages = SSEClient(url) + # self._readyState = EventSource.OPEN + # for msg in messages: + # self.onmessage(MessageEvent(msg)) + # print(msg) + # self._readyState = EventSource.CLOSED + + def _do_stuff(): + from sseclient import SSEClient + messages = SSEClient(url) + self._readyState = EventSource.OPEN + # self.onopen(MessageEvent(messages)) + for msg in messages: + self.onmessage(MessageEvent(msg)) + # print(msg) + self._readyState = EventSource.CLOSED + + # TODO - use a thread or it will block the main thread + import threading + + # def _on_message(msg): + # self.onmessage(MessageEvent(msg)) + # print(msg) + # self._readyState = EventSource.OPEN + + # def _on_error(err): + # self.onerror(err) + # self._readyState = EventSource.CLOSED + + # def _on_open(msg): + # self.onopen(msg) + # self._readyState = EventSource.OPEN + + # def _on_ready_state_change(msg): + # self.onreadystatechange(msg) + # self._readyState = EventSource.OPEN + + # def _on_close(msg): + # self.onclose(msg) + # self._readyState = EventSource.CLOSED + + self._thread = threading.Thread(target=_do_stuff) + self._thread.start() + + # print('this is so cool!') + + @property + def readyState(self): + """ A number representing the state of the connection. + Possible values are CONNECTING (0), OPEN (1), or CLOSED (2). """ + return self._readyState + + @property + def url(self): + """ A DOMString representing the URL of the source. """ + return self._url + + @property + def withCredentials(self): + """ A boolean value indicating whether the EventSource object was + instantiated with cross-origin (CORS) credentials + set (true), or not (false, the default). """ + return self._withCredentials + + def close(self): + """ Closes the connection to the EventSource. """ + self._readyState = EventSource.CLOSED + # close the thread + self._thread.join() + self._thread = None + + def onreadystatechange(self, event): + """ Called when the state of the connection changes. """ + pass + + def onmessage(self, event): + """ Called when a message is received. """ + if self._onmessage is not None: + self._onmessage(event) + + def onerror(self, event): + """ Called when an error occurs. """ + pass + + def onopen(self, event): + """ Called when the connection is established. """ + pass + + # ''' + # NOTE - if we write custom one rather than use lib... + # def _poll(self): + + # if self._readyState == EventSource.CLOSED: + # return + + # url = 'http://domain.com/events' + # headers = { + # 'Accept': 'text/event-stream', + # 'Cache-Control': 'no-cache', + # 'X-requested-with': 'XMLHttpRequest' + # } + # if self._lastEventId != None: + # headers['Last-Event-ID'] = self._lastEventId + + # import requests + # response = requests.get(url, stream=True, headers=headers) + # # TODO - parse repsonse + # '''