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.
Files changed (32) hide show
  1. escobar/_version.py +1 -1
  2. escobar/handlers.py +80 -254
  3. escobar/labextension/package.json +5 -3
  4. escobar/labextension/schemas/escobar/package.json.orig +4 -2
  5. escobar/labextension/schemas/escobar/plugin.json +2 -2
  6. escobar/labextension/static/653.2348cd42bb15caeb6a4d.js +1 -0
  7. escobar/labextension/static/{remoteEntry.c9a1accf3c70050fa880.js → remoteEntry.4b2f86eacb2c83e28f43.js} +1 -1
  8. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/package.json +5 -3
  9. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/schemas/escobar/package.json.orig +4 -2
  10. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/schemas/escobar/plugin.json +2 -2
  11. escobar-0.1.92.data/data/share/jupyter/labextensions/escobar/static/653.2348cd42bb15caeb6a4d.js +1 -0
  12. escobar-0.1.92.data/data/share/jupyter/labextensions/escobar/static/oauth-callback.html +131 -0
  13. 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
  14. {escobar-0.1.90.dist-info → escobar-0.1.92.dist-info}/METADATA +1 -1
  15. escobar-0.1.92.dist-info/RECORD +41 -0
  16. escobar/labextension/static/237.d012347a7c8fb32c64ae.js +0 -1
  17. escobar-0.1.90.data/data/share/jupyter/labextensions/escobar/static/237.d012347a7c8fb32c64ae.js +0 -1
  18. escobar-0.1.90.dist-info/RECORD +0 -40
  19. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/install.json +0 -0
  20. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/304.bf7e91be734e5b36cdc9.js +0 -0
  21. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/346.8a1e61ca6789b6fddfa7.js +0 -0
  22. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/379.40dd59dc19d4a6b42d25.js +0 -0
  23. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/57.17e53b4b9a954f39c4d8.js +0 -0
  24. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/648.a7d314faeacc762d891d.js +0 -0
  25. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/666.3bc65aac3a3be183ee19.js +0 -0
  26. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/874.c539726f31a1baa0267e.js +0 -0
  27. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/986.cbcf9d7c1cd8d06be435.js +0 -0
  28. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/style.js +0 -0
  29. {escobar-0.1.90.data → escobar-0.1.92.data}/data/share/jupyter/labextensions/escobar/static/third-party-licenses.json +0 -0
  30. {escobar-0.1.90.dist-info → escobar-0.1.92.dist-info}/WHEEL +0 -0
  31. {escobar-0.1.90.dist-info → escobar-0.1.92.dist-info}/entry_points.txt +0 -0
  32. {escobar-0.1.90.dist-info → escobar-0.1.92.dist-info}/licenses/LICENSE +0 -0
escobar/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # This file is auto-generated by Hatchling. As such, do not:
2
2
  # - modify
3
3
  # - track in version control e.g. be sure to add to .gitignore
4
- __version__ = VERSION = '0.1.90'
4
+ __version__ = VERSION = '0.1.92'
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] ❌ CRITICAL ERROR forwarding message to target:")
529
- print(f"[ESCOBAR-WS] Exception type: {type(e).__name__}")
530
- print(f"[ESCOBAR-WS] Exception message: {str(e)}")
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.90",
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.c9a1accf3c70050fa880.js",
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.90",
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 OAuth Client ID",
42
- "description": "Google OAuth 2.0 Client ID for authentication. Get this from Google Cloud Console.",
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
  },