tina4-python 0.2.72__tar.gz → 0.2.74__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {tina4_python-0.2.72 → tina4_python-0.2.74}/PKG-INFO +15 -1
  2. {tina4_python-0.2.72 → tina4_python-0.2.74}/README.md +12 -0
  3. {tina4_python-0.2.72 → tina4_python-0.2.74}/pyproject.toml +3 -1
  4. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/DatabaseResult.py +8 -5
  5. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/ORM.py +2 -2
  6. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Request.py +3 -0
  7. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Router.py +2 -0
  8. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Webserver.py +37 -21
  9. tina4_python-0.2.74/tina4_python/Websocket.py +39 -0
  10. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/__init__.py +35 -23
  11. tina4_python-0.2.74/tina4_python/public/js/reconnecting-websocket.js +365 -0
  12. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Auth.py +0 -0
  13. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Constant.py +0 -0
  14. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Database.py +0 -0
  15. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Debug.py +0 -0
  16. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Env.py +0 -0
  17. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Localization.py +0 -0
  18. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Messages.py +0 -0
  19. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/MiddleWare.py +0 -0
  20. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Migration.py +0 -0
  21. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Queue.py +0 -0
  22. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Response.py +0 -0
  23. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Session.py +0 -0
  24. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/ShellColors.py +0 -0
  25. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Swagger.py +0 -0
  26. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Template.py +0 -0
  27. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/messages.pot +0 -0
  28. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/css/readme.md +0 -0
  29. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/favicon.ico +0 -0
  30. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/images/403.png +0 -0
  31. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/images/404.png +0 -0
  32. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/images/500.png +0 -0
  33. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/images/logo.png +0 -0
  34. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/images/readme.md +0 -0
  35. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/js/readme.md +0 -0
  36. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/js/tina4helper.js +0 -0
  37. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/swagger/index.html +0 -0
  38. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  39. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/templates/errors/403.twig +0 -0
  40. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/templates/errors/404.twig +0 -0
  41. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/templates/errors/500.twig +0 -0
  42. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/templates/readme.md +0 -0
  43. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  44. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  45. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  46. {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: tina4-python
3
- Version: 0.2.72
3
+ Version: 0.2.74
4
4
  Summary: Tina4Python - This is not another framework for Python
5
5
  Author: Andre van Zuydam
6
6
  Author-email: andrevanzuydam@gmail.com
@@ -16,8 +16,10 @@ Requires-Dist: cryptography (>=42.0.5,<43.0.0)
16
16
  Requires-Dist: libsass (>=0.22.0,<0.23.0)
17
17
  Requires-Dist: litequeue (>=0.9,<0.10)
18
18
  Requires-Dist: mypy (>=0.991,<0.992)
19
+ Requires-Dist: poetry-plugin-export (>=1.9.0,<2.0.0)
19
20
  Requires-Dist: pyjwt (>=2.8.0,<3.0.0)
20
21
  Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
22
+ Requires-Dist: simple-websocket (>=1.1.0,<2.0.0)
21
23
  Requires-Dist: watchdog (>=4.0.0,<5.0.0)
22
24
  Description-Content-Type: text/markdown
23
25
 
@@ -432,3 +434,15 @@ docker run -d --hostname=my-kafka --name=some-kafka -p 9092:9092 apache/kafka
432
434
  docker run -d --hostname=my-rabbit --name=some-rabbit -p 15672:15672 -p 5672:5672 rabbitmq:3
433
435
  ```
434
436
 
437
+ ## Websocket Testing
438
+
439
+ ```bash
440
+ brew install websocat
441
+ ```
442
+
443
+ ```python
444
+ websocat http://localhost:7145/websocket
445
+ ```
446
+
447
+
448
+
@@ -408,3 +408,15 @@ docker run -d --hostname=my-kafka --name=some-kafka -p 9092:9092 apache/kafka
408
408
  ```bash
409
409
  docker run -d --hostname=my-rabbit --name=some-rabbit -p 15672:15672 -p 5672:5672 rabbitmq:3
410
410
  ```
411
+
412
+ ## Websocket Testing
413
+
414
+ ```bash
415
+ brew install websocat
416
+ ```
417
+
418
+ ```python
419
+ websocat http://localhost:7145/websocket
420
+ ```
421
+
422
+
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "tina4-python"
3
- version = "0.2.72"
3
+ version = "0.2.74"
4
4
  description = "Tina4Python - This is not another framework for Python"
5
5
  authors = ["Andre van Zuydam <andrevanzuydam@gmail.com>"]
6
6
  readme = "README.md"
@@ -18,6 +18,8 @@ cryptography = "^42.0.5"
18
18
  watchdog = "^4.0.0"
19
19
  bcrypt = "^4.1.3"
20
20
  litequeue = "^0.9"
21
+ simple-websocket = "^1.1.0"
22
+ poetry-plugin-export = "^1.9.0"
21
23
 
22
24
  [tool.poetry.group.dev.dependencies]
23
25
  flake8 = "^7.0.0"
@@ -54,7 +54,7 @@ class DatabaseResult:
54
54
 
55
55
  return {"recordsTotal": self.total_count, "recordsOffset": self.skip, "recordCount": self.count, "recordsFiltered": self.total_count, "fields": self.columns, "data": self.to_array(), "dataError": self.error}
56
56
 
57
- def to_array(self):
57
+ def to_array(self, _filter=None):
58
58
  """
59
59
  Creates an array or list of the items
60
60
  :return:
@@ -78,17 +78,20 @@ class DatabaseResult:
78
78
  else:
79
79
  json_record[key] = record[key]
80
80
 
81
+ if _filter is not None:
82
+ json_record = _filter(json_record)
83
+
81
84
  json_records.append(json_record)
82
85
 
83
86
  return json_records
84
87
  else:
85
88
  return []
86
89
 
87
- def to_list(self):
88
- return self.to_array()
90
+ def to_list(self, _filter=None):
91
+ return self.to_array(_filter)
89
92
 
90
- def to_json(self):
91
- return json.dumps(self.to_array())
93
+ def to_json(self, _filter=None):
94
+ return json.dumps(self.to_array(_filter))
92
95
 
93
96
  def __getitem__(self, item):
94
97
  if item < len(self.records):
@@ -280,7 +280,7 @@ class ORM:
280
280
  if init_object is not None:
281
281
  self.__populate_orm(init_object)
282
282
 
283
- Debug("Checking for", self.__table_name__, TINA4_LOG_INFO)
283
+ # Debug("Checking for", self.__table_name__, TINA4_LOG_INFO)
284
284
  if self.__dba__:
285
285
  self.__table_exists = self.__dba__.table_exists(self.__table_name__)
286
286
  if not self.__table_exists:
@@ -290,7 +290,7 @@ class ORM:
290
290
  with open(filename, "w") as f:
291
291
  f.write(sql)
292
292
  f.close()
293
- Debug("Table Exists", self.__table_exists, TINA4_LOG_INFO)
293
+ # Debug("Table Exists", self.__table_exists, TINA4_LOG_INFO)
294
294
  else:
295
295
  self.__table_exists = False
296
296
 
@@ -14,5 +14,8 @@ cookies = {}
14
14
  url = None
15
15
  session = None
16
16
  files = {}
17
+ raw_request = None
17
18
  raw_data = None
18
19
  raw_content = None
20
+ transport = None
21
+ asgi_response = None
@@ -148,6 +148,8 @@ class Router:
148
148
  Request.raw_request = request["raw_request"] if "raw_request" in request else None
149
149
  Request.raw_content = request["raw_content"] if "raw_content" in request else None
150
150
  Request.url = url
151
+ Request.transport = request["transport"] if "transport" in request else None
152
+ Request.asgi_response = request["asgi_response"] if "asgi_response" in request else None
151
153
 
152
154
  tina4_python.tina4_current_request = Request
153
155
 
@@ -23,7 +23,7 @@ from tina4_python.Template import Template
23
23
 
24
24
  def is_int(v):
25
25
  try:
26
- f=int(v)
26
+ f = int(v)
27
27
  except ValueError:
28
28
  return False
29
29
  return True
@@ -107,7 +107,7 @@ class Webserver:
107
107
  self.send_header("Connection", "Keep-Alive", headers)
108
108
  self.send_header("Keep-Alive", "timeout=5, max=30", headers)
109
109
 
110
- async def get_response(self, method, asgi_response=False):
110
+ async def get_response(self, method, transport, asgi_response=False):
111
111
  """
112
112
  Get response
113
113
  :param method: GET, POST, PATCH, DELETE, PUT
@@ -152,13 +152,13 @@ class Webserver:
152
152
  if isinstance(start_var, dict) and var_name in start_var:
153
153
  start_var = start_var[var_name]
154
154
  else:
155
- if counter+1 < len(var_names) and is_int(var_names[counter+1]) :
155
+ if counter + 1 < len(var_names) and is_int(var_names[counter + 1]):
156
156
  if var_name not in start_var:
157
157
  start_var[var_name] = []
158
158
  start_var = start_var[var_name]
159
159
  else:
160
- if counter-1 > 0 and is_int(var_names[counter-1]):
161
- index = int(var_names[counter-1])
160
+ if counter - 1 > 0 and is_int(var_names[counter - 1]):
161
+ index = int(var_names[counter - 1])
162
162
  new_value = {var_name: value}
163
163
  if index in range(len(start_var)):
164
164
  start_var[index].update(new_value)
@@ -169,7 +169,7 @@ class Webserver:
169
169
  start_var = start_var[index]
170
170
  else:
171
171
  if isinstance(start_var, dict):
172
- if counter+1 == len(var_names):
172
+ if counter + 1 == len(var_names):
173
173
  start_var[var_name] = value
174
174
  else:
175
175
  start_var[var_name] = {}
@@ -186,7 +186,8 @@ class Webserver:
186
186
  body = None
187
187
 
188
188
  request = {"params": params, "body": body, "raw_data": self.request, "url": self.path, "session": self.session,
189
- "headers": self.lowercase_headers, "raw_request": self.request_raw, "raw_content": self.content_raw}
189
+ "headers": self.lowercase_headers, "raw_request": self.request_raw, "raw_content": self.content_raw,
190
+ "transport": transport, "asgi_response": asgi_response}
190
191
 
191
192
  tina4_python.tina4_current_request = request
192
193
 
@@ -230,9 +231,8 @@ class Webserver:
230
231
  return headers.encode()
231
232
 
232
233
  async def run_server(self):
233
- server = await asyncio.start_server(self.handle_client, self.host_name, self.port)
234
- async with server:
235
- await server.serve_forever()
234
+ self.server = await asyncio.start_server(self.handle_client, self.host_name, self.port)
235
+ await self.server.serve_forever()
236
236
 
237
237
  async def get_data(self, reader):
238
238
  try:
@@ -291,6 +291,7 @@ class Webserver:
291
291
  self.method = protocol[0]
292
292
  self.path = protocol[1]
293
293
 
294
+
294
295
  method_list = [Constant.TINA4_GET, Constant.TINA4_DELETE, Constant.TINA4_PUT, Constant.TINA4_ANY,
295
296
  Constant.TINA4_POST, Constant.TINA4_PATCH, Constant.TINA4_OPTIONS]
296
297
 
@@ -298,7 +299,7 @@ class Webserver:
298
299
 
299
300
  request_handled = False
300
301
  # return static content asap
301
- if self.method == "GET":
302
+ if self.method == "GET" and "sec-websocket-key" not in self.lowercase_headers:
302
303
  # split URL and extract query string
303
304
  url = Router.clean_url(self.path)
304
305
  url_parts = url.split('?')
@@ -314,14 +315,16 @@ class Webserver:
314
315
  self.send_header("Content-Type", mime_type, headers)
315
316
  await self.send_basic_headers(headers)
316
317
  content = file.read()
317
- headers = await self.get_headers(headers, self.response_protocol, HTTP_OK)
318
+ headers = await self.get_headers(headers, self.response_protocol, HTTP_OK)
318
319
  writer.write(headers + content)
319
320
  await writer.drain()
321
+ await writer.close()
320
322
  request_handled = True
321
323
 
322
324
  if not request_handled:
323
325
  # parse cookies
324
326
  cookie_list = {}
327
+ content = ""
325
328
  if "cookie" in self.lowercase_headers:
326
329
  cookie_list_temp = self.lowercase_headers["cookie"].split(";")
327
330
  for cookie_value in cookie_list_temp:
@@ -341,17 +344,27 @@ class Webserver:
341
344
  else:
342
345
  self.cookies[os.getenv("TINA4_SESSION", "PY_SESS")] = self.session.start()
343
346
 
344
- if self.method != "" and contains_method:
345
- content = await (self.get_response(self.method))
346
- writer.write(content)
347
- await writer.drain()
347
+ if "sec-websocket-key" not in self.lowercase_headers:
348
+ try:
349
+ if self.method != "" and contains_method:
350
+ content = await (self.get_response(self.method, writer))
351
+ if content != "":
352
+ writer.write(content)
353
+ await writer.drain()
354
+ writer.close()
355
+ except BrokenPipeError as e:
356
+ # socket got terminated
357
+ pass
358
+ else:
359
+ # for sockets
360
+ await (self.get_response(self.method, writer))
361
+
348
362
 
349
- writer.close()
350
363
  except Exception as e:
351
364
  error_string = tina4_python.global_exception_handler(e)
352
365
  headers = []
353
366
  await self.send_basic_headers(headers)
354
- headers = await self.get_headers(headers, self.response_protocol, HTTP_SERVER_ERROR)
367
+ headers = await self.get_headers(headers, self.response_protocol, HTTP_SERVER_ERROR)
355
368
  url = Router.clean_url(self.path)
356
369
 
357
370
  content_type = "text/html"
@@ -359,9 +372,12 @@ class Webserver:
359
372
  content_type = self.lowercase_headers["content-type"].lower()
360
373
 
361
374
  if content_type == "application/json":
362
- html = json.dumps({"error": "500 - Internal Server Error", "data": {"server": {"url": url}, "error_message": error_string}})
375
+ html = json.dumps({"error": "500 - Internal Server Error",
376
+ "data": {"server": {"url": url}, "error_message": error_string}})
363
377
  else:
364
- html = Template.render_twig_template("errors/500.twig", {"server": {"url": url}, "error_message": error_string})
378
+ html = Template.render_twig_template("errors/500.twig",
379
+ {"server": {"url": url}, "error_message": error_string})
380
+
365
381
  writer.write(headers + html.encode())
366
382
  await writer.drain()
367
383
  writer.close()
@@ -382,6 +398,7 @@ class Webserver:
382
398
  self.port = port
383
399
  self.router_handler = None
384
400
  self.running = False
401
+ self.server = None
385
402
 
386
403
  async def serve_forever(self):
387
404
  await self.run_server()
@@ -389,4 +406,3 @@ class Webserver:
389
406
  def server_close(self):
390
407
  self.running = False
391
408
  self.server_socket.close()
392
-
@@ -0,0 +1,39 @@
1
+ #
2
+ # Tina4 - This is not a 4ramework.
3
+ # Copy-right 2007 - current Tina4
4
+ # License: MIT https://opensource.org/licenses/MIT
5
+ #
6
+ # flake8: noqa: E501
7
+ import importlib
8
+ import os
9
+ from tina4_python.Debug import Debug
10
+
11
+ class Websocket:
12
+ """
13
+ Websocket class which wraps simple_websocket library
14
+ """
15
+ def __init__(self, request):
16
+ try:
17
+ self.server = importlib.import_module("simple_websocket")
18
+ self.request = request
19
+ except Exception as e:
20
+ Debug.error("Error creating Websocket, perhaps you need to install simple_websocket ?", e)
21
+
22
+ async def connection(self):
23
+ """
24
+ Returns a websocket connection
25
+ :return:
26
+ """
27
+ try:
28
+ if self.request.asgi_response:
29
+ connection = await self.server.AioServer.accept(asgi=self.request.transport)
30
+ else:
31
+ if os.name == "nt":
32
+ connection = await self.server.AioServer.accept(sock=self.request.transport.get_extra_info('socket'), headers=self.request.headers)
33
+ else:
34
+ connection = await self.server.AioServer.accept(sock=self.request.transport.get_extra_info('socket').dup(), headers=self.request.headers)
35
+
36
+ return connection
37
+ except Exception as e:
38
+ Debug.error("Could not establish a socket connection:", str(e))
39
+ return None
@@ -13,6 +13,8 @@ import sys
13
13
  import traceback
14
14
  import sass
15
15
  from pathlib import Path
16
+
17
+ from safety.scan.command import scan_system_app
16
18
  from watchdog.observers import Observer
17
19
  from watchdog.events import FileSystemEventHandler, FileSystemEvent
18
20
  from tina4_python.Router import get
@@ -263,8 +265,12 @@ async def app(scope, receive, send):
263
265
  :return:
264
266
  """
265
267
  body = b""
266
- while True and scope['type'] == 'http':
267
- message = await receive()
268
+ while True and scope['type'] == 'http' or scope['type'] == 'websocket':
269
+ if scope['type'] != 'websocket':
270
+ message = await receive()
271
+ else:
272
+ message = {'type': 'websocket'}
273
+
268
274
  if "body" in message:
269
275
  body += message["body"]
270
276
  if message['type'] == 'lifespan.startup':
@@ -273,7 +279,7 @@ async def app(scope, receive, send):
273
279
  elif message['type'] == 'lifespan.shutdown':
274
280
  await send({'type': 'lifespan.shutdown.complete'})
275
281
  return
276
- elif message["type"] == "http.disconnect":
282
+ elif message["type"] == "http.disconnect" or message["type"] == "websocket.disconnect":
277
283
  return
278
284
  elif not message.get("more_body"):
279
285
  webserver = Webserver(scope["server"][0], scope["server"][1])
@@ -293,8 +299,13 @@ async def app(scope, receive, send):
293
299
 
294
300
  webserver.headers = parsed_headers
295
301
  webserver.lowercase_headers = parsed_headers_lowercase
302
+
296
303
  webserver.path = scope["path"]+"?"+scope["query_string"].decode()
297
- webserver.method = scope["method"]
304
+
305
+ if "method" in scope:
306
+ webserver.method = scope["method"]
307
+ else:
308
+ webserver.method = "GET"
298
309
 
299
310
  if message["type"] == "http.request":
300
311
  webserver.content_raw = body
@@ -324,26 +335,27 @@ async def app(scope, receive, send):
324
335
  else:
325
336
  webserver.cookies[os.getenv("TINA4_SESSION", "PY_SESS")] = webserver.session.start()
326
337
 
327
- tina4_response, tina4_headers = await webserver.get_response(scope["method"], True)
338
+ tina4_response, tina4_headers = await webserver.get_response(webserver.method, (scope,receive,send),True)
328
339
 
329
- response_headers = []
330
- for header in tina4_headers:
331
- header = header.split(":")
332
- response_headers.append([header[0].strip().encode(), header[1].strip().encode()])
340
+ if message["type"] != "websocket":
341
+ response_headers = []
342
+ for header in tina4_headers:
343
+ header = header.split(":")
344
+ response_headers.append([header[0].strip().encode(), header[1].strip().encode()])
333
345
 
334
- await send({
335
- 'type': 'http.response.start',
336
- 'status': tina4_response.http_code,
337
- 'headers': response_headers,
338
- })
339
-
340
- if isinstance(tina4_response.content, str):
341
346
  await send({
342
- 'type': 'http.response.body',
343
- 'body': tina4_response.content.encode(),
344
- })
345
- else:
346
- await send({
347
- 'type': 'http.response.body',
348
- 'body': tina4_response.content,
347
+ 'type': 'http.response.start',
348
+ 'status': tina4_response.http_code,
349
+ 'headers': response_headers,
349
350
  })
351
+
352
+ if isinstance(tina4_response.content, str):
353
+ await send({
354
+ 'type': 'http.response.body',
355
+ 'body': tina4_response.content.encode(),
356
+ })
357
+ else:
358
+ await send({
359
+ 'type': 'http.response.body',
360
+ 'body': tina4_response.content,
361
+ })
@@ -0,0 +1,365 @@
1
+ // MIT License:
2
+ //
3
+ // Copyright (c) 2010-2012, Joe Walnes
4
+ //
5
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ // of this software and associated documentation files (the "Software"), to deal
7
+ // in the Software without restriction, including without limitation the rights
8
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ // copies of the Software, and to permit persons to whom the Software is
10
+ // furnished to do so, subject to the following conditions:
11
+ //
12
+ // The above copyright notice and this permission notice shall be included in
13
+ // all copies or substantial portions of the Software.
14
+ //
15
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ // THE SOFTWARE.
22
+
23
+ /**
24
+ * This behaves like a WebSocket in every way, except if it fails to connect,
25
+ * or it gets disconnected, it will repeatedly poll until it successfully connects
26
+ * again.
27
+ *
28
+ * It is API compatible, so when you have:
29
+ * ws = new WebSocket('ws://....');
30
+ * you can replace with:
31
+ * ws = new ReconnectingWebSocket('ws://....');
32
+ *
33
+ * The event stream will typically look like:
34
+ * onconnecting
35
+ * onopen
36
+ * onmessage
37
+ * onmessage
38
+ * onclose // lost connection
39
+ * onconnecting
40
+ * onopen // sometime later...
41
+ * onmessage
42
+ * onmessage
43
+ * etc...
44
+ *
45
+ * It is API compatible with the standard WebSocket API, apart from the following members:
46
+ *
47
+ * - `bufferedAmount`
48
+ * - `extensions`
49
+ * - `binaryType`
50
+ *
51
+ * Latest version: https://github.com/joewalnes/reconnecting-websocket/
52
+ * - Joe Walnes
53
+ *
54
+ * Syntax
55
+ * ======
56
+ * var socket = new ReconnectingWebSocket(url, protocols, options);
57
+ *
58
+ * Parameters
59
+ * ==========
60
+ * url - The url you are connecting to.
61
+ * protocols - Optional string or array of protocols.
62
+ * options - See below
63
+ *
64
+ * Options
65
+ * =======
66
+ * Options can either be passed upon instantiation or set after instantiation:
67
+ *
68
+ * var socket = new ReconnectingWebSocket(url, null, { debug: true, reconnectInterval: 4000 });
69
+ *
70
+ * or
71
+ *
72
+ * var socket = new ReconnectingWebSocket(url);
73
+ * socket.debug = true;
74
+ * socket.reconnectInterval = 4000;
75
+ *
76
+ * debug
77
+ * - Whether this instance should log debug messages. Accepts true or false. Default: false.
78
+ *
79
+ * automaticOpen
80
+ * - Whether or not the websocket should attempt to connect immediately upon instantiation. The socket can be manually opened or closed at any time using ws.open() and ws.close().
81
+ *
82
+ * reconnectInterval
83
+ * - The number of milliseconds to delay before attempting to reconnect. Accepts integer. Default: 1000.
84
+ *
85
+ * maxReconnectInterval
86
+ * - The maximum number of milliseconds to delay a reconnection attempt. Accepts integer. Default: 30000.
87
+ *
88
+ * reconnectDecay
89
+ * - The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. Accepts integer or float. Default: 1.5.
90
+ *
91
+ * timeoutInterval
92
+ * - The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. Accepts integer. Default: 2000.
93
+ *
94
+ */
95
+ (function (global, factory) {
96
+ if (typeof define === 'function' && define.amd) {
97
+ define([], factory);
98
+ } else if (typeof module !== 'undefined' && module.exports){
99
+ module.exports = factory();
100
+ } else {
101
+ global.ReconnectingWebSocket = factory();
102
+ }
103
+ })(this, function () {
104
+
105
+ if (!('WebSocket' in window)) {
106
+ return;
107
+ }
108
+
109
+ function ReconnectingWebSocket(url, protocols, options) {
110
+
111
+ // Default settings
112
+ var settings = {
113
+
114
+ /** Whether this instance should log debug messages. */
115
+ debug: false,
116
+
117
+ /** Whether or not the websocket should attempt to connect immediately upon instantiation. */
118
+ automaticOpen: true,
119
+
120
+ /** The number of milliseconds to delay before attempting to reconnect. */
121
+ reconnectInterval: 1000,
122
+ /** The maximum number of milliseconds to delay a reconnection attempt. */
123
+ maxReconnectInterval: 30000,
124
+ /** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */
125
+ reconnectDecay: 1.5,
126
+
127
+ /** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */
128
+ timeoutInterval: 2000,
129
+
130
+ /** The maximum number of reconnection attempts to make. Unlimited if null. */
131
+ maxReconnectAttempts: null,
132
+
133
+ /** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */
134
+ binaryType: 'blob'
135
+ }
136
+ if (!options) { options = {}; }
137
+
138
+ // Overwrite and define settings with options if they exist.
139
+ for (var key in settings) {
140
+ if (typeof options[key] !== 'undefined') {
141
+ this[key] = options[key];
142
+ } else {
143
+ this[key] = settings[key];
144
+ }
145
+ }
146
+
147
+ // These should be treated as read-only properties
148
+
149
+ /** The URL as resolved by the constructor. This is always an absolute URL. Read only. */
150
+ this.url = url;
151
+
152
+ /** The number of attempted reconnects since starting, or the last successful connection. Read only. */
153
+ this.reconnectAttempts = 0;
154
+
155
+ /**
156
+ * The current state of the connection.
157
+ * Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
158
+ * Read only.
159
+ */
160
+ this.readyState = WebSocket.CONNECTING;
161
+
162
+ /**
163
+ * A string indicating the name of the sub-protocol the server selected; this will be one of
164
+ * the strings specified in the protocols parameter when creating the WebSocket object.
165
+ * Read only.
166
+ */
167
+ this.protocol = null;
168
+
169
+ // Private state variables
170
+
171
+ var self = this;
172
+ var ws;
173
+ var forcedClose = false;
174
+ var timedOut = false;
175
+ var eventTarget = document.createElement('div');
176
+
177
+ // Wire up "on*" properties as event handlers
178
+
179
+ eventTarget.addEventListener('open', function(event) { self.onopen(event); });
180
+ eventTarget.addEventListener('close', function(event) { self.onclose(event); });
181
+ eventTarget.addEventListener('connecting', function(event) { self.onconnecting(event); });
182
+ eventTarget.addEventListener('message', function(event) { self.onmessage(event); });
183
+ eventTarget.addEventListener('error', function(event) { self.onerror(event); });
184
+
185
+ // Expose the API required by EventTarget
186
+
187
+ this.addEventListener = eventTarget.addEventListener.bind(eventTarget);
188
+ this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget);
189
+ this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget);
190
+
191
+ /**
192
+ * This function generates an event that is compatible with standard
193
+ * compliant browsers and IE9 - IE11
194
+ *
195
+ * This will prevent the error:
196
+ * Object doesn't support this action
197
+ *
198
+ * http://stackoverflow.com/questions/19345392/why-arent-my-parameters-getting-passed-through-to-a-dispatched-event/19345563#19345563
199
+ * @param s String The name that the event should use
200
+ * @param args Object an optional object that the event will use
201
+ */
202
+ function generateEvent(s, args) {
203
+ var evt = document.createEvent("CustomEvent");
204
+ evt.initCustomEvent(s, false, false, args);
205
+ return evt;
206
+ };
207
+
208
+ this.open = function (reconnectAttempt) {
209
+ ws = new WebSocket(self.url, protocols || []);
210
+ ws.binaryType = this.binaryType;
211
+
212
+ if (reconnectAttempt) {
213
+ if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) {
214
+ return;
215
+ }
216
+ } else {
217
+ eventTarget.dispatchEvent(generateEvent('connecting'));
218
+ this.reconnectAttempts = 0;
219
+ }
220
+
221
+ if (self.debug || ReconnectingWebSocket.debugAll) {
222
+ console.debug('ReconnectingWebSocket', 'attempt-connect', self.url);
223
+ }
224
+
225
+ var localWs = ws;
226
+ var timeout = setTimeout(function() {
227
+ if (self.debug || ReconnectingWebSocket.debugAll) {
228
+ console.debug('ReconnectingWebSocket', 'connection-timeout', self.url);
229
+ }
230
+ timedOut = true;
231
+ localWs.close();
232
+ timedOut = false;
233
+ }, self.timeoutInterval);
234
+
235
+ ws.onopen = function(event) {
236
+ clearTimeout(timeout);
237
+ if (self.debug || ReconnectingWebSocket.debugAll) {
238
+ console.debug('ReconnectingWebSocket', 'onopen', self.url);
239
+ }
240
+ self.protocol = ws.protocol;
241
+ self.readyState = WebSocket.OPEN;
242
+ self.reconnectAttempts = 0;
243
+ var e = generateEvent('open');
244
+ e.isReconnect = reconnectAttempt;
245
+ reconnectAttempt = false;
246
+ eventTarget.dispatchEvent(e);
247
+ };
248
+
249
+ ws.onclose = function(event) {
250
+ clearTimeout(timeout);
251
+ ws = null;
252
+ if (forcedClose) {
253
+ self.readyState = WebSocket.CLOSED;
254
+ eventTarget.dispatchEvent(generateEvent('close'));
255
+ } else {
256
+ self.readyState = WebSocket.CONNECTING;
257
+ var e = generateEvent('connecting');
258
+ e.code = event.code;
259
+ e.reason = event.reason;
260
+ e.wasClean = event.wasClean;
261
+ eventTarget.dispatchEvent(e);
262
+ if (!reconnectAttempt && !timedOut) {
263
+ if (self.debug || ReconnectingWebSocket.debugAll) {
264
+ console.debug('ReconnectingWebSocket', 'onclose', self.url);
265
+ }
266
+ eventTarget.dispatchEvent(generateEvent('close'));
267
+ }
268
+
269
+ var timeout = self.reconnectInterval * Math.pow(self.reconnectDecay, self.reconnectAttempts);
270
+ setTimeout(function() {
271
+ self.reconnectAttempts++;
272
+ self.open(true);
273
+ }, timeout > self.maxReconnectInterval ? self.maxReconnectInterval : timeout);
274
+ }
275
+ };
276
+ ws.onmessage = function(event) {
277
+ if (self.debug || ReconnectingWebSocket.debugAll) {
278
+ console.debug('ReconnectingWebSocket', 'onmessage', self.url, event.data);
279
+ }
280
+ var e = generateEvent('message');
281
+ e.data = event.data;
282
+ eventTarget.dispatchEvent(e);
283
+ };
284
+ ws.onerror = function(event) {
285
+ if (self.debug || ReconnectingWebSocket.debugAll) {
286
+ console.debug('ReconnectingWebSocket', 'onerror', self.url, event);
287
+ }
288
+ eventTarget.dispatchEvent(generateEvent('error'));
289
+ };
290
+ }
291
+
292
+ // Whether or not to create a websocket upon instantiation
293
+ if (this.automaticOpen == true) {
294
+ this.open(false);
295
+ }
296
+
297
+ /**
298
+ * Transmits data to the server over the WebSocket connection.
299
+ *
300
+ * @param data a text string, ArrayBuffer or Blob to send to the server.
301
+ */
302
+ this.send = function(data) {
303
+ if (ws) {
304
+ if (self.debug || ReconnectingWebSocket.debugAll) {
305
+ console.debug('ReconnectingWebSocket', 'send', self.url, data);
306
+ }
307
+ return ws.send(data);
308
+ } else {
309
+ throw 'INVALID_STATE_ERR : Pausing to reconnect websocket';
310
+ }
311
+ };
312
+
313
+ /**
314
+ * Closes the WebSocket connection or connection attempt, if any.
315
+ * If the connection is already CLOSED, this method does nothing.
316
+ */
317
+ this.close = function(code, reason) {
318
+ // Default CLOSE_NORMAL code
319
+ if (typeof code == 'undefined') {
320
+ code = 1000;
321
+ }
322
+ forcedClose = true;
323
+ if (ws) {
324
+ ws.close(code, reason);
325
+ }
326
+ };
327
+
328
+ /**
329
+ * Additional public API method to refresh the connection if still open (close, re-open).
330
+ * For example, if the app suspects bad data / missed heart beats, it can try to refresh.
331
+ */
332
+ this.refresh = function() {
333
+ if (ws) {
334
+ ws.close();
335
+ }
336
+ };
337
+ }
338
+
339
+ /**
340
+ * An event listener to be called when the WebSocket connection's readyState changes to OPEN;
341
+ * this indicates that the connection is ready to send and receive data.
342
+ */
343
+ ReconnectingWebSocket.prototype.onopen = function(event) {};
344
+ /** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */
345
+ ReconnectingWebSocket.prototype.onclose = function(event) {};
346
+ /** An event listener to be called when a connection begins being attempted. */
347
+ ReconnectingWebSocket.prototype.onconnecting = function(event) {};
348
+ /** An event listener to be called when a message is received from the server. */
349
+ ReconnectingWebSocket.prototype.onmessage = function(event) {};
350
+ /** An event listener to be called when an error occurs. */
351
+ ReconnectingWebSocket.prototype.onerror = function(event) {};
352
+
353
+ /**
354
+ * Whether all instances of ReconnectingWebSocket should log debug messages.
355
+ * Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true.
356
+ */
357
+ ReconnectingWebSocket.debugAll = false;
358
+
359
+ ReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING;
360
+ ReconnectingWebSocket.OPEN = WebSocket.OPEN;
361
+ ReconnectingWebSocket.CLOSING = WebSocket.CLOSING;
362
+ ReconnectingWebSocket.CLOSED = WebSocket.CLOSED;
363
+
364
+ return ReconnectingWebSocket;
365
+ });