escobar 0.1.90__py3-none-any.whl → 0.1.92__py3-none-any.whl
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.
- escobar/_version.py +1 -1
- escobar/handlers.py +80 -254
- escobar/labextension/package.json +5 -3
- escobar/labextension/schemas/escobar/package.json.orig +4 -2
- escobar/labextension/schemas/escobar/plugin.json +2 -2
- escobar/labextension/static/653.2348cd42bb15caeb6a4d.js +1 -0
- escobar/labextension/static/{remoteEntry.c9a1accf3c70050fa880.js → remoteEntry.4b2f86eacb2c83e28f43.js} +1 -1
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/package.json +5 -3
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/schemas/escobar/package.json.orig +4 -2
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/schemas/escobar/plugin.json +2 -2
- escobar-0.1.92.data/data/share/jupyter/labextensions/escobar/static/653.2348cd42bb15caeb6a4d.js +1 -0
- escobar-0.1.92.data/data/share/jupyter/labextensions/escobar/static/oauth-callback.html +131 -0
- escobar-0.1.90.data/data/share/jupyter/labextensions/escobar/static/remoteEntry.c9a1accf3c70050fa880.js → escobar-0.1.92.data/data/share/jupyter/labextensions/escobar/static/remoteEntry.4b2f86eacb2c83e28f43.js +1 -1
- {escobar-0.1.90.dist-info → escobar-0.1.92.dist-info}/METADATA +1 -1
- escobar-0.1.92.dist-info/RECORD +41 -0
- escobar/labextension/static/237.d012347a7c8fb32c64ae.js +0 -1
- escobar-0.1.90.data/data/share/jupyter/labextensions/escobar/static/237.d012347a7c8fb32c64ae.js +0 -1
- escobar-0.1.90.dist-info/RECORD +0 -40
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/install.json +0 -0
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/304.bf7e91be734e5b36cdc9.js +0 -0
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/346.8a1e61ca6789b6fddfa7.js +0 -0
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/379.40dd59dc19d4a6b42d25.js +0 -0
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/57.17e53b4b9a954f39c4d8.js +0 -0
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/648.a7d314faeacc762d891d.js +0 -0
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/666.3bc65aac3a3be183ee19.js +0 -0
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/874.c539726f31a1baa0267e.js +0 -0
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/986.cbcf9d7c1cd8d06be435.js +0 -0
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/style.js +0 -0
- {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/third-party-licenses.json +0 -0
- {escobar-0.1.90.dist-info → escobar-0.1.92.dist-info}/WHEEL +0 -0
- {escobar-0.1.90.dist-info → escobar-0.1.92.dist-info}/entry_points.txt +0 -0
- {escobar-0.1.90.dist-info → escobar-0.1.92.dist-info}/licenses/LICENSE +0 -0
escobar/_version.py
CHANGED
escobar/handlers.py
CHANGED
@@ -6,8 +6,6 @@ import websockets
|
|
6
6
|
import ssl
|
7
7
|
import logging
|
8
8
|
import time
|
9
|
-
import threading
|
10
|
-
import psutil
|
11
9
|
from urllib.parse import urlparse, urlunparse
|
12
10
|
from jupyter_server.base.handlers import JupyterHandler
|
13
11
|
from jupyter_server.utils import url_path_join
|
@@ -17,10 +15,81 @@ import tornado.websocket
|
|
17
15
|
import aiohttp
|
18
16
|
from traitlets.config import LoggingConfigurable
|
19
17
|
import mimetypes
|
18
|
+
import requests
|
19
|
+
from google.oauth2 import id_token
|
20
|
+
from google.auth.transport import requests as google_requests
|
20
21
|
|
21
22
|
# Default proxy port
|
22
23
|
DEFAULT_PROXY_PORT = 3000
|
23
24
|
|
25
|
+
|
26
|
+
class OAuthCallbackHandler(JupyterHandler):
|
27
|
+
"""
|
28
|
+
Handler for /static/oauth-callback.html endpoint.
|
29
|
+
Serves the OAuth callback HTML file for Google authentication.
|
30
|
+
"""
|
31
|
+
|
32
|
+
async def get(self):
|
33
|
+
"""Handle GET requests to serve the OAuth callback HTML"""
|
34
|
+
try:
|
35
|
+
# Get the path to the static directory
|
36
|
+
import os
|
37
|
+
import pkg_resources
|
38
|
+
|
39
|
+
# Try to get the file from the installed package first
|
40
|
+
try:
|
41
|
+
# This works when the package is installed via pip
|
42
|
+
# The static files are installed in the labextension directory
|
43
|
+
callback_content = pkg_resources.resource_string('escobar', 'labextension/static/oauth-callback.html').decode('utf-8')
|
44
|
+
|
45
|
+
self.log.info(f"🔐 CALLBACK: Serving OAuth callback from installed package")
|
46
|
+
|
47
|
+
# Set proper headers
|
48
|
+
self.set_header('Content-Type', 'text/html; charset=UTF-8')
|
49
|
+
self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate')
|
50
|
+
self.set_header('Pragma', 'no-cache')
|
51
|
+
self.set_header('Expires', '0')
|
52
|
+
|
53
|
+
# Write the HTML content
|
54
|
+
self.write(callback_content)
|
55
|
+
self.log.info(f"🔐 CALLBACK: Successfully served OAuth callback HTML from package")
|
56
|
+
return
|
57
|
+
|
58
|
+
except (ImportError, FileNotFoundError, OSError):
|
59
|
+
# Fallback to file system path for development
|
60
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
61
|
+
project_root = os.path.dirname(current_dir)
|
62
|
+
callback_file_path = os.path.join(project_root, 'static', 'oauth-callback.html')
|
63
|
+
|
64
|
+
self.log.info(f"🔐 CALLBACK: Serving OAuth callback from: {callback_file_path}")
|
65
|
+
|
66
|
+
# Check if file exists
|
67
|
+
if not os.path.exists(callback_file_path):
|
68
|
+
self.log.error(f"🔐 CALLBACK: OAuth callback file not found: {callback_file_path}")
|
69
|
+
self.set_status(404)
|
70
|
+
self.finish("OAuth callback file not found")
|
71
|
+
return
|
72
|
+
|
73
|
+
# Read and serve the HTML file
|
74
|
+
with open(callback_file_path, 'r', encoding='utf-8') as f:
|
75
|
+
html_content = f.read()
|
76
|
+
|
77
|
+
# Set proper headers
|
78
|
+
self.set_header('Content-Type', 'text/html; charset=UTF-8')
|
79
|
+
self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate')
|
80
|
+
self.set_header('Pragma', 'no-cache')
|
81
|
+
self.set_header('Expires', '0')
|
82
|
+
|
83
|
+
# Write the HTML content
|
84
|
+
self.write(html_content)
|
85
|
+
self.log.info(f"🔐 CALLBACK: Successfully served OAuth callback HTML")
|
86
|
+
|
87
|
+
except Exception as e:
|
88
|
+
self.log.error(f"🔐 CALLBACK: Error serving OAuth callback: {e}")
|
89
|
+
self.set_status(500)
|
90
|
+
self.finish(f"Error serving OAuth callback: {str(e)}")
|
91
|
+
|
92
|
+
|
24
93
|
class ProxyHandler(JupyterHandler):
|
25
94
|
"""
|
26
95
|
Handler for /proxy endpoint.
|
@@ -154,91 +223,8 @@ class ProxyHandler(JupyterHandler):
|
|
154
223
|
class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
|
155
224
|
"""
|
156
225
|
WebSocket proxy handler that forwards connections from /ws to target server
|
157
|
-
Enhanced with comprehensive logging for debugging intermittent connection issues
|
158
226
|
"""
|
159
227
|
|
160
|
-
# Class-level connection tracking
|
161
|
-
_connection_count = 0
|
162
|
-
_active_connections = {}
|
163
|
-
|
164
|
-
def _log_system_state(self, context=""):
|
165
|
-
"""Log current system state for debugging"""
|
166
|
-
try:
|
167
|
-
# System resource information
|
168
|
-
cpu_percent = psutil.cpu_percent(interval=0.1)
|
169
|
-
memory = psutil.virtual_memory()
|
170
|
-
|
171
|
-
print(f"[ESCOBAR-WS] === SYSTEM STATE {context} ===")
|
172
|
-
print(f"[ESCOBAR-WS] CPU Usage: {cpu_percent}%")
|
173
|
-
print(f"[ESCOBAR-WS] Memory Usage: {memory.percent}% ({memory.used / 1024 / 1024:.1f}MB used)")
|
174
|
-
print(f"[ESCOBAR-WS] Active Connections: {len(self._active_connections)}")
|
175
|
-
print(f"[ESCOBAR-WS] Total Connections Created: {self._connection_count}")
|
176
|
-
print(f"[ESCOBAR-WS] Current Thread: {threading.current_thread().name}")
|
177
|
-
print(f"[ESCOBAR-WS] === END SYSTEM STATE ===")
|
178
|
-
except Exception as e:
|
179
|
-
print(f"[ESCOBAR-WS] Error logging system state: {e}")
|
180
|
-
|
181
|
-
def _log_connection_lifecycle(self, event, details=None):
|
182
|
-
"""Log connection lifecycle events with timing"""
|
183
|
-
timestamp = time.time()
|
184
|
-
connection_id = getattr(self, 'connection_id', 'UNKNOWN')
|
185
|
-
|
186
|
-
print(f"[ESCOBAR-WS] === CONNECTION LIFECYCLE EVENT ===")
|
187
|
-
print(f"[ESCOBAR-WS] Event: {event}")
|
188
|
-
print(f"[ESCOBAR-WS] Connection ID: {connection_id}")
|
189
|
-
print(f"[ESCOBAR-WS] Timestamp: {timestamp}")
|
190
|
-
print(f"[ESCOBAR-WS] Human Time: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(timestamp))}")
|
191
|
-
if details:
|
192
|
-
print(f"[ESCOBAR-WS] Details: {details}")
|
193
|
-
print(f"[ESCOBAR-WS] === END LIFECYCLE EVENT ===")
|
194
|
-
|
195
|
-
def _log_message_timing(self, direction, message_size, start_time=None):
|
196
|
-
"""Log message timing and throughput"""
|
197
|
-
current_time = time.time()
|
198
|
-
|
199
|
-
print(f"[ESCOBAR-WS] === MESSAGE TIMING ===")
|
200
|
-
print(f"[ESCOBAR-WS] Direction: {direction}")
|
201
|
-
print(f"[ESCOBAR-WS] Message Size: {message_size} bytes")
|
202
|
-
print(f"[ESCOBAR-WS] Timestamp: {current_time}")
|
203
|
-
|
204
|
-
if start_time:
|
205
|
-
duration = current_time - start_time
|
206
|
-
throughput = message_size / duration if duration > 0 else 0
|
207
|
-
print(f"[ESCOBAR-WS] Processing Duration: {duration:.4f}s")
|
208
|
-
print(f"[ESCOBAR-WS] Throughput: {throughput:.2f} bytes/sec")
|
209
|
-
|
210
|
-
print(f"[ESCOBAR-WS] === END MESSAGE TIMING ===")
|
211
|
-
|
212
|
-
def _log_target_connection_health(self):
|
213
|
-
"""Log target connection health metrics"""
|
214
|
-
if not self.target_ws:
|
215
|
-
print(f"[ESCOBAR-WS] === TARGET HEALTH: NO CONNECTION ===")
|
216
|
-
return
|
217
|
-
|
218
|
-
print(f"[ESCOBAR-WS] === TARGET CONNECTION HEALTH ===")
|
219
|
-
try:
|
220
|
-
print(f"[ESCOBAR-WS] Target State: {self.target_ws.state}")
|
221
|
-
print(f"[ESCOBAR-WS] Target State Name: {self.target_ws.state.name}")
|
222
|
-
print(f"[ESCOBAR-WS] Target State Value: {self.target_ws.state.value}")
|
223
|
-
|
224
|
-
# Check if we can access additional properties
|
225
|
-
if hasattr(self.target_ws, 'ping_interval'):
|
226
|
-
print(f"[ESCOBAR-WS] Ping Interval: {self.target_ws.ping_interval}")
|
227
|
-
if hasattr(self.target_ws, 'ping_timeout'):
|
228
|
-
print(f"[ESCOBAR-WS] Ping Timeout: {self.target_ws.ping_timeout}")
|
229
|
-
if hasattr(self.target_ws, 'close_timeout'):
|
230
|
-
print(f"[ESCOBAR-WS] Close Timeout: {self.target_ws.close_timeout}")
|
231
|
-
|
232
|
-
except Exception as e:
|
233
|
-
print(f"[ESCOBAR-WS] Error checking target health: {e}")
|
234
|
-
|
235
|
-
print(f"[ESCOBAR-WS] === END TARGET HEALTH ===")
|
236
|
-
|
237
|
-
def _generate_connection_id(self):
|
238
|
-
"""Generate unique connection ID for tracking"""
|
239
|
-
WebSocketProxyHandler._connection_count += 1
|
240
|
-
return f"CONN-{WebSocketProxyHandler._connection_count}-{int(time.time())}"
|
241
|
-
|
242
228
|
def _resolve_target_url_for_docker(self, url):
|
243
229
|
"""
|
244
230
|
Resolve target URL for Docker environment.
|
@@ -269,16 +255,11 @@ class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
|
|
269
255
|
# Determine if we're in Docker
|
270
256
|
is_docker = dockerenv_exists or docker_env or cgroup_docker
|
271
257
|
|
272
|
-
print(f"[ESCOBAR-WS] Docker detection indicators: {docker_indicators}")
|
273
|
-
print(f"[ESCOBAR-WS] Final Docker detection result: {is_docker}")
|
274
|
-
|
275
258
|
if not is_docker:
|
276
|
-
print(f"[ESCOBAR-WS] Not in Docker container, using original URL: {url}")
|
277
259
|
return url
|
278
260
|
|
279
261
|
# Parse the URL to extract components
|
280
262
|
parsed = urlparse(url)
|
281
|
-
print(f"[ESCOBAR-WS] Parsed URL - hostname: '{parsed.hostname}', netloc: '{parsed.netloc}'")
|
282
263
|
|
283
264
|
# Check if hostname is localhost or 127.0.0.1
|
284
265
|
if parsed.hostname in ['localhost', '127.0.0.1']:
|
@@ -287,10 +268,7 @@ class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
|
|
287
268
|
new_parsed = parsed._replace(netloc=new_netloc)
|
288
269
|
new_url = urlunparse(new_parsed)
|
289
270
|
|
290
|
-
print(f"[ESCOBAR-WS] Docker hostname resolution: {url} → {new_url}")
|
291
271
|
return new_url
|
292
|
-
else:
|
293
|
-
print(f"[ESCOBAR-WS] Docker container detected, but hostname '{parsed.hostname}' is not localhost/127.0.0.1, keeping original: {url}")
|
294
272
|
|
295
273
|
return url
|
296
274
|
|
@@ -299,25 +277,6 @@ class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
|
|
299
277
|
super().__init__(*args, **kwargs)
|
300
278
|
self.target_ws = None
|
301
279
|
|
302
|
-
# Generate unique connection ID for tracking
|
303
|
-
self.connection_id = self._generate_connection_id()
|
304
|
-
self.connection_start_time = time.time()
|
305
|
-
|
306
|
-
# Add to active connections tracking
|
307
|
-
WebSocketProxyHandler._active_connections[self.connection_id] = {
|
308
|
-
'start_time': self.connection_start_time,
|
309
|
-
'handler': self
|
310
|
-
}
|
311
|
-
|
312
|
-
# Log connection creation
|
313
|
-
self._log_connection_lifecycle("HANDLER_CREATED", {
|
314
|
-
'connection_id': self.connection_id,
|
315
|
-
'total_connections': len(WebSocketProxyHandler._active_connections)
|
316
|
-
})
|
317
|
-
|
318
|
-
# Log system state at connection creation
|
319
|
-
self._log_system_state("CONNECTION_INIT")
|
320
|
-
|
321
280
|
# Debug environment information
|
322
281
|
print(f"[ESCOBAR-WS] Environment WEBSOCKET_PROXY_TARGET: {os.getenv('WEBSOCKET_PROXY_TARGET', 'NOT_SET')}")
|
323
282
|
print(f"[ESCOBAR-WS] Running in container: {os.path.exists('/.dockerenv')}")
|
@@ -336,39 +295,28 @@ class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
|
|
336
295
|
print(f"[ESCOBAR-WS] All WEBSOCKET environment vars: {websocket_env_vars}")
|
337
296
|
|
338
297
|
self.is_closing = False
|
339
|
-
self.message_count_sent = 0
|
340
|
-
self.message_count_received = 0
|
341
|
-
self.last_activity_time = time.time()
|
342
298
|
|
343
299
|
def _get_user_bonnie_url(self):
|
344
300
|
"""
|
345
301
|
Get user-configured Bonnie URL from bonnie_url query parameter.
|
346
302
|
This allows the frontend to override the environment variable.
|
347
303
|
"""
|
348
|
-
print(f"[ESCOBAR-WS] === READING USER BONNIE URL FROM QUERY PARAMETER ===")
|
349
|
-
|
350
304
|
try:
|
351
305
|
# Get the bonnieUrl from query parameters
|
352
306
|
bonnie_url = self.get_argument('bonnie_url', None)
|
353
|
-
print(f"[ESCOBAR-WS] bonnie_url query parameter value: '{bonnie_url}'")
|
354
307
|
|
355
308
|
if bonnie_url and bonnie_url.strip():
|
356
309
|
# Validate that it's a WebSocket URL
|
357
310
|
if bonnie_url.startswith(('ws://', 'wss://')):
|
358
|
-
print(f"[ESCOBAR-WS] Valid user Bonnie URL from query parameter: {bonnie_url}")
|
359
311
|
return bonnie_url.strip()
|
360
312
|
else:
|
361
|
-
print(f"[ESCOBAR-WS] Invalid Bonnie URL format (must start with ws:// or wss://): {bonnie_url}")
|
362
313
|
return None
|
363
314
|
else:
|
364
|
-
print(f"[ESCOBAR-WS] No bonnie_url query parameter provided")
|
365
315
|
return None
|
366
316
|
|
367
317
|
except Exception as e:
|
368
318
|
print(f"[ESCOBAR-WS] Error reading bonnie_url query parameter: {e}")
|
369
319
|
return None
|
370
|
-
finally:
|
371
|
-
print(f"[ESCOBAR-WS] === END BONNIE URL QUERY PARAMETER READ ===")
|
372
320
|
|
373
321
|
def _get_target_url(self):
|
374
322
|
"""
|
@@ -482,136 +430,38 @@ class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
|
|
482
430
|
|
483
431
|
async def on_message(self, message):
|
484
432
|
"""Called when a message is received from the client"""
|
485
|
-
print(f"[ESCOBAR-WS] === CLIENT MESSAGE RECEIVED ===")
|
486
|
-
print(f"[ESCOBAR-WS] Message length: {len(message)}")
|
487
|
-
print(f"[ESCOBAR-WS] Message type: {type(message)}")
|
488
|
-
print(f"[ESCOBAR-WS] Message preview: {message[:500]}...")
|
489
|
-
print(f"[ESCOBAR-WS] Target WS exists: {self.target_ws is not None}")
|
490
|
-
print(f"[ESCOBAR-WS] Is closing: {self.is_closing}")
|
491
|
-
|
492
433
|
if self.target_ws and not self.is_closing:
|
493
|
-
# Enhanced debugging before forwarding
|
494
|
-
print(f"[ESCOBAR-WS] === PRE-FORWARD DIAGNOSTICS ===")
|
495
|
-
print(f"[ESCOBAR-WS] Target WS type: {type(self.target_ws)}")
|
496
|
-
print(f"[ESCOBAR-WS] Target WS state: {getattr(self.target_ws, 'state', 'NO_STATE_ATTR')}")
|
497
|
-
|
498
|
-
# Check if connection is actually open
|
499
|
-
try:
|
500
|
-
is_open = self.target_ws.state.name == 'OPEN'
|
501
|
-
print(f"[ESCOBAR-WS] Target WS is OPEN: {is_open}")
|
502
|
-
except AttributeError:
|
503
|
-
print(f"[ESCOBAR-WS] Cannot check target WS state - no state attribute")
|
504
|
-
is_open = True # Assume open and let send() fail with proper error
|
505
|
-
|
506
|
-
# Check connection properties
|
507
|
-
try:
|
508
|
-
print(f"[ESCOBAR-WS] Target WS closed property: {getattr(self.target_ws, 'closed', 'NO_CLOSED_ATTR')}")
|
509
|
-
except:
|
510
|
-
print(f"[ESCOBAR-WS] Cannot access target WS closed property")
|
511
|
-
|
512
|
-
# Message type analysis
|
513
|
-
if isinstance(message, str):
|
514
|
-
print(f"[ESCOBAR-WS] Message is TEXT (string)")
|
515
|
-
elif isinstance(message, bytes):
|
516
|
-
print(f"[ESCOBAR-WS] Message is BINARY (bytes)")
|
517
|
-
else:
|
518
|
-
print(f"[ESCOBAR-WS] Message is UNKNOWN type: {type(message)}")
|
519
|
-
|
520
|
-
print(f"[ESCOBAR-WS] === ATTEMPTING MESSAGE FORWARD ===")
|
521
|
-
|
522
434
|
try:
|
523
435
|
# Forward message to target server
|
524
436
|
await self.target_ws.send(message)
|
525
|
-
print(f"[ESCOBAR-WS] ✅ Message successfully forwarded to target")
|
526
437
|
logging.debug(f"Forwarded message to target: {message[:100]}...")
|
527
438
|
except Exception as e:
|
528
|
-
print(f"[ESCOBAR-WS]
|
529
|
-
print(f"[ESCOBAR-WS]
|
530
|
-
print(f"[ESCOBAR-WS]
|
531
|
-
print(f"[ESCOBAR-WS] Exception args: {getattr(e, 'args', 'NO_ARGS')}")
|
532
|
-
|
533
|
-
# Check target connection state after error
|
534
|
-
try:
|
535
|
-
print(f"[ESCOBAR-WS] Target WS state after error: {self.target_ws.state}")
|
536
|
-
except:
|
537
|
-
print(f"[ESCOBAR-WS] Cannot check target WS state after error")
|
538
|
-
|
539
|
-
# Additional exception details
|
540
|
-
if hasattr(e, '__dict__'):
|
541
|
-
print(f"[ESCOBAR-WS] Exception attributes: {e.__dict__}")
|
542
|
-
if hasattr(e, 'errno'):
|
543
|
-
print(f"[ESCOBAR-WS] Errno: {e.errno}")
|
544
|
-
if hasattr(e, 'strerror'):
|
545
|
-
print(f"[ESCOBAR-WS] Strerror: {e.strerror}")
|
546
|
-
|
547
|
-
# Import specific exception types for better diagnosis
|
548
|
-
import websockets.exceptions
|
549
|
-
if isinstance(e, websockets.exceptions.ConnectionClosed):
|
550
|
-
print(f"[ESCOBAR-WS] ConnectionClosed - code: {e.code}, reason: {e.reason}")
|
551
|
-
elif isinstance(e, websockets.exceptions.InvalidState):
|
552
|
-
print(f"[ESCOBAR-WS] InvalidState - connection in wrong state")
|
553
|
-
elif isinstance(e, websockets.exceptions.PayloadTooBig):
|
554
|
-
print(f"[ESCOBAR-WS] PayloadTooBig - message too large")
|
555
|
-
|
439
|
+
print(f"[ESCOBAR-WS] ERROR forwarding message to target:")
|
440
|
+
print(f"[ESCOBAR-WS] Error: {str(e)}")
|
441
|
+
print(f"[ESCOBAR-WS] Error type: {type(e).__name__}")
|
556
442
|
logging.error(f"Error forwarding message to target: {str(e)}")
|
557
|
-
|
558
|
-
# Don't close immediately - let's see if we can recover
|
559
|
-
print(f"[ESCOBAR-WS] Closing client connection due to forward error")
|
560
|
-
self.close(code=1011, reason=f"Target forward error: {type(e).__name__}")
|
561
|
-
else:
|
562
|
-
print(f"[ESCOBAR-WS] Cannot forward message - target_ws: {self.target_ws}, is_closing: {self.is_closing}")
|
563
|
-
print(f"[ESCOBAR-WS] === END CLIENT MESSAGE ===")
|
443
|
+
self.close(code=1011, reason="Target connection error")
|
564
444
|
|
565
445
|
async def _forward_from_target(self):
|
566
446
|
"""Forward messages from target server to client"""
|
567
|
-
print(f"[ESCOBAR-WS] === STARTING TARGET MESSAGE FORWARDING ===")
|
568
|
-
print(f"[ESCOBAR-WS] Target WS state: {self.target_ws.state if self.target_ws else 'None'}")
|
569
|
-
|
570
447
|
try:
|
571
|
-
message_count = 0
|
572
448
|
async for message in self.target_ws:
|
573
|
-
message_count += 1
|
574
|
-
print(f"[ESCOBAR-WS] === TARGET MESSAGE #{message_count} ===")
|
575
|
-
print(f"[ESCOBAR-WS] Message length: {len(message)}")
|
576
|
-
print(f"[ESCOBAR-WS] Message type: {type(message)}")
|
577
|
-
print(f"[ESCOBAR-WS] Message preview: {message[:500]}...")
|
578
|
-
print(f"[ESCOBAR-WS] Is closing: {self.is_closing}")
|
579
|
-
|
580
449
|
if not self.is_closing:
|
581
|
-
print(f"[ESCOBAR-WS] Forwarding message to client")
|
582
450
|
# Forward message to client
|
583
451
|
self.write_message(message)
|
584
|
-
print(f"[ESCOBAR-WS] Message successfully forwarded to client")
|
585
452
|
logging.debug(f"Forwarded message from target: {message[:100]}...")
|
586
453
|
else:
|
587
|
-
print(f"[ESCOBAR-WS] Breaking forwarding loop - connection is closing")
|
588
454
|
break
|
589
|
-
print(f"[ESCOBAR-WS] === END TARGET MESSAGE #{message_count} ===")
|
590
455
|
|
591
456
|
except websockets.exceptions.ConnectionClosed as e:
|
592
|
-
print(f"[ESCOBAR-WS] === TARGET CONNECTION CLOSED DETAILS ===")
|
593
|
-
print(f"[ESCOBAR-WS] Target websocket connection closed")
|
594
|
-
print(f"[ESCOBAR-WS] Close code: {getattr(e, 'code', 'NO_CODE')}")
|
595
|
-
print(f"[ESCOBAR-WS] Close reason: {getattr(e, 'reason', 'NO_REASON')}")
|
596
|
-
print(f"[ESCOBAR-WS] Exception type: {type(e).__name__}")
|
597
|
-
print(f"[ESCOBAR-WS] Exception str: {str(e)}")
|
598
|
-
print(f"[ESCOBAR-WS] Target WS final state: {getattr(self.target_ws, 'state', 'NO_STATE') if self.target_ws else 'NO_TARGET_WS'}")
|
599
|
-
print(f"[ESCOBAR-WS] Messages processed before disconnect: {message_count}")
|
600
|
-
print(f"[ESCOBAR-WS] === END TARGET CONNECTION CLOSED DETAILS ===")
|
601
|
-
|
602
457
|
logging.info(f"Target websocket connection closed - code: {getattr(e, 'code', 'NO_CODE')}, reason: {getattr(e, 'reason', 'NO_REASON')}")
|
603
458
|
|
604
459
|
if not self.is_closing:
|
605
|
-
print(f"[ESCOBAR-WS] Closing client connection due to target disconnect")
|
606
460
|
self.close(code=1011, reason="Target server disconnected")
|
607
461
|
except Exception as e:
|
608
462
|
print(f"[ESCOBAR-WS] === TARGET MESSAGE FORWARDING ERROR ===")
|
609
463
|
print(f"[ESCOBAR-WS] Error: {str(e)}")
|
610
464
|
print(f"[ESCOBAR-WS] Error type: {type(e).__name__}")
|
611
|
-
print(f"[ESCOBAR-WS] Messages processed before error: {message_count}")
|
612
|
-
print(f"[ESCOBAR-WS] Target WS state: {getattr(self.target_ws, 'state', 'NO_STATE') if self.target_ws else 'NO_TARGET_WS'}")
|
613
|
-
if hasattr(e, '__dict__'):
|
614
|
-
print(f"[ESCOBAR-WS] Error attributes: {e.__dict__}")
|
615
465
|
if hasattr(e, 'errno'):
|
616
466
|
print(f"[ESCOBAR-WS] Errno: {e.errno}")
|
617
467
|
if hasattr(e, 'strerror'):
|
@@ -620,66 +470,37 @@ class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
|
|
620
470
|
|
621
471
|
logging.error(f"Error receiving from target websocket: {str(e)}")
|
622
472
|
if not self.is_closing:
|
623
|
-
print(f"[ESCOBAR-WS] Closing client connection due to target error")
|
624
473
|
self.close(code=1011, reason="Target connection error")
|
625
|
-
|
626
|
-
print(f"[ESCOBAR-WS] === TARGET MESSAGE FORWARDING ENDED ===")
|
627
474
|
|
628
475
|
def on_close(self):
|
629
476
|
"""Called when websocket connection is closed"""
|
630
|
-
print(f"[ESCOBAR-WS] === CLIENT CONNECTION CLOSED ===")
|
631
|
-
print(f"[ESCOBAR-WS] Setting is_closing flag to True")
|
632
477
|
self.is_closing = True
|
633
|
-
print(f"[ESCOBAR-WS] Target WS exists: {self.target_ws is not None}")
|
634
478
|
|
635
479
|
logging.info("WebSocket connection closed")
|
636
480
|
|
637
481
|
# Close target connection if it exists
|
638
482
|
if self.target_ws:
|
639
|
-
print(f"[ESCOBAR-WS] Scheduling target connection cleanup")
|
640
483
|
asyncio.create_task(self._close_target_connection())
|
641
|
-
else:
|
642
|
-
print(f"[ESCOBAR-WS] No target connection to clean up")
|
643
|
-
|
644
|
-
print(f"[ESCOBAR-WS] === END CLIENT CONNECTION CLOSED ===")
|
645
484
|
|
646
485
|
async def _close_target_connection(self):
|
647
486
|
"""Safely close the target websocket connection"""
|
648
|
-
print(f"[ESCOBAR-WS] === CLOSING TARGET CONNECTION ===")
|
649
487
|
try:
|
650
488
|
if self.target_ws:
|
651
|
-
print(f"[ESCOBAR-WS] Target WS exists, checking state")
|
652
|
-
print(f"[ESCOBAR-WS] Target WS type: {type(self.target_ws)}")
|
653
|
-
print(f"[ESCOBAR-WS] Target WS state: {getattr(self.target_ws, 'state', 'NO_STATE_ATTR')}")
|
654
|
-
|
655
489
|
# Check if connection is already closed using the correct websockets library API
|
656
490
|
try:
|
657
491
|
is_closed = self.target_ws.state.name == 'CLOSED'
|
658
|
-
print(f"[ESCOBAR-WS] Target WS is closed: {is_closed}")
|
659
492
|
except AttributeError:
|
660
493
|
# Fallback: just try to close it regardless of state
|
661
|
-
print(f"[ESCOBAR-WS] Cannot check state, will attempt to close anyway")
|
662
494
|
is_closed = False
|
663
495
|
|
664
496
|
if not is_closed:
|
665
|
-
print(f"[ESCOBAR-WS] Target WS is open, closing it")
|
666
497
|
await self.target_ws.close()
|
667
|
-
print(f"[ESCOBAR-WS] Target connection closed successfully")
|
668
498
|
logging.info("Target websocket connection closed")
|
669
|
-
else:
|
670
|
-
print(f"[ESCOBAR-WS] Target WS already closed")
|
671
|
-
else:
|
672
|
-
print(f"[ESCOBAR-WS] No target WS to close")
|
673
499
|
except Exception as e:
|
674
500
|
print(f"[ESCOBAR-WS] ERROR closing target connection:")
|
675
501
|
print(f"[ESCOBAR-WS] Error: {str(e)}")
|
676
502
|
print(f"[ESCOBAR-WS] Error type: {type(e).__name__}")
|
677
|
-
print(f"[ESCOBAR-WS] Target WS type: {type(self.target_ws) if self.target_ws else 'None'}")
|
678
|
-
if hasattr(e, '__dict__'):
|
679
|
-
print(f"[ESCOBAR-WS] Error attributes: {e.__dict__}")
|
680
503
|
logging.error(f"Error closing target websocket: {str(e)}")
|
681
|
-
|
682
|
-
print(f"[ESCOBAR-WS] === END CLOSING TARGET CONNECTION ===")
|
683
504
|
|
684
505
|
|
685
506
|
def setup_handlers(web_app):
|
@@ -747,9 +568,14 @@ def setup_handlers(web_app):
|
|
747
568
|
for pattern in ws_patterns:
|
748
569
|
print(f"[ESCOBAR-WS] - {pattern}")
|
749
570
|
|
571
|
+
# Register the OAuth callback endpoint
|
572
|
+
oauth_callback_pattern = url_path_join(base_url, "static", "oauth-callback.html")
|
573
|
+
print(f"[ESCOBAR-WS] OAuth callback pattern: {oauth_callback_pattern}")
|
574
|
+
|
750
575
|
# Build handlers list with all WebSocket patterns
|
751
576
|
handlers = [
|
752
577
|
(proxy_pattern, ProxyHandler),
|
578
|
+
(oauth_callback_pattern, OAuthCallbackHandler),
|
753
579
|
*[(pattern, WebSocketProxyHandler) for pattern in ws_patterns]
|
754
580
|
]
|
755
581
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "escobar",
|
3
|
-
"version": "0.1.
|
3
|
+
"version": "0.1.92",
|
4
4
|
"description": "AI CHAT EXTENSION",
|
5
5
|
"keywords": [
|
6
6
|
"jupyter",
|
@@ -19,7 +19,9 @@
|
|
19
19
|
"files": [
|
20
20
|
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
|
21
21
|
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
|
22
|
-
"schema/*.json"
|
22
|
+
"schema/*.json",
|
23
|
+
"static/**/*",
|
24
|
+
"src/icons/*.svg"
|
23
25
|
],
|
24
26
|
"main": "lib/index.js",
|
25
27
|
"types": "lib/index.d.ts",
|
@@ -123,7 +125,7 @@
|
|
123
125
|
"outputDir": "escobar/labextension",
|
124
126
|
"schemaDir": "schema",
|
125
127
|
"_build": {
|
126
|
-
"load": "static/remoteEntry.
|
128
|
+
"load": "static/remoteEntry.4b2f86eacb2c83e28f43.js",
|
127
129
|
"extension": "./extension",
|
128
130
|
"style": "./style"
|
129
131
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "escobar",
|
3
|
-
"version": "0.1.
|
3
|
+
"version": "0.1.92",
|
4
4
|
"description": "AI CHAT EXTENSION",
|
5
5
|
"keywords": [
|
6
6
|
"jupyter",
|
@@ -19,7 +19,9 @@
|
|
19
19
|
"files": [
|
20
20
|
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
|
21
21
|
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
|
22
|
-
"schema/*.json"
|
22
|
+
"schema/*.json",
|
23
|
+
"static/**/*",
|
24
|
+
"src/icons/*.svg"
|
23
25
|
],
|
24
26
|
"main": "lib/index.js",
|
25
27
|
"types": "lib/index.d.ts",
|
@@ -38,8 +38,8 @@
|
|
38
38
|
},
|
39
39
|
"googleClientId": {
|
40
40
|
"type": "string",
|
41
|
-
"title": "Google
|
42
|
-
"description": "Google OAuth
|
41
|
+
"title": "Google Client ID",
|
42
|
+
"description": "Google OAuth Client ID for authentication. Get this from Google Cloud Console.",
|
43
43
|
"default": ""
|
44
44
|
}
|
45
45
|
},
|