escobar 0.1.90__py3-none-any.whl → 0.1.91__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 (31) hide show
  1. escobar/_version.py +1 -1
  2. escobar/handlers.py +57 -254
  3. escobar/labextension/package.json +2 -2
  4. escobar/labextension/schemas/escobar/package.json.orig +1 -1
  5. escobar/labextension/schemas/escobar/plugin.json +2 -2
  6. escobar/labextension/static/653.cd0a1dbb61ede2a5de00.js +1 -0
  7. escobar/labextension/static/{remoteEntry.c9a1accf3c70050fa880.js → remoteEntry.48a97be89d680e21e498.js} +1 -1
  8. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/package.json +2 -2
  9. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/schemas/escobar/package.json.orig +1 -1
  10. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/schemas/escobar/plugin.json +2 -2
  11. escobar-0.1.91.data/data/share/jupyter/labextensions/escobar/static/653.cd0a1dbb61ede2a5de00.js +1 -0
  12. escobar-0.1.90.data/data/share/jupyter/labextensions/escobar/static/remoteEntry.c9a1accf3c70050fa880.js → escobar-0.1.91.data/data/share/jupyter/labextensions/escobar/static/remoteEntry.48a97be89d680e21e498.js +1 -1
  13. {escobar-0.1.90.dist-info → escobar-0.1.91.dist-info}/METADATA +1 -1
  14. escobar-0.1.91.dist-info/RECORD +40 -0
  15. escobar/labextension/static/237.d012347a7c8fb32c64ae.js +0 -1
  16. escobar-0.1.90.data/data/share/jupyter/labextensions/escobar/static/237.d012347a7c8fb32c64ae.js +0 -1
  17. escobar-0.1.90.dist-info/RECORD +0 -40
  18. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/install.json +0 -0
  19. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/static/304.bf7e91be734e5b36cdc9.js +0 -0
  20. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/static/346.8a1e61ca6789b6fddfa7.js +0 -0
  21. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/static/379.40dd59dc19d4a6b42d25.js +0 -0
  22. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/static/57.17e53b4b9a954f39c4d8.js +0 -0
  23. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/static/648.a7d314faeacc762d891d.js +0 -0
  24. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/static/666.3bc65aac3a3be183ee19.js +0 -0
  25. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/static/874.c539726f31a1baa0267e.js +0 -0
  26. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/static/986.cbcf9d7c1cd8d06be435.js +0 -0
  27. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/static/style.js +0 -0
  28. {escobar-0.1.90.data → escobar-0.1.91.data}/data/share/jupyter/labextensions/escobar/static/third-party-licenses.json +0 -0
  29. {escobar-0.1.90.dist-info → escobar-0.1.91.dist-info}/WHEEL +0 -0
  30. {escobar-0.1.90.dist-info → escobar-0.1.91.dist-info}/entry_points.txt +0 -0
  31. {escobar-0.1.90.dist-info → escobar-0.1.91.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.91'
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,58 @@ 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
+ current_dir = os.path.dirname(os.path.abspath(__file__))
38
+ project_root = os.path.dirname(current_dir)
39
+ callback_file_path = os.path.join(project_root, 'static', 'oauth-callback.html')
40
+
41
+ self.log.info(f"🔐 CALLBACK: Serving OAuth callback from: {callback_file_path}")
42
+
43
+ # Check if file exists
44
+ if not os.path.exists(callback_file_path):
45
+ self.log.error(f"🔐 CALLBACK: OAuth callback file not found: {callback_file_path}")
46
+ self.set_status(404)
47
+ self.finish("OAuth callback file not found")
48
+ return
49
+
50
+ # Read and serve the HTML file
51
+ with open(callback_file_path, 'r', encoding='utf-8') as f:
52
+ html_content = f.read()
53
+
54
+ # Set proper headers
55
+ self.set_header('Content-Type', 'text/html; charset=UTF-8')
56
+ self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate')
57
+ self.set_header('Pragma', 'no-cache')
58
+ self.set_header('Expires', '0')
59
+
60
+ # Write the HTML content
61
+ self.write(html_content)
62
+ self.log.info(f"🔐 CALLBACK: Successfully served OAuth callback HTML")
63
+
64
+ except Exception as e:
65
+ self.log.error(f"🔐 CALLBACK: Error serving OAuth callback: {e}")
66
+ self.set_status(500)
67
+ self.finish(f"Error serving OAuth callback: {str(e)}")
68
+
69
+
24
70
  class ProxyHandler(JupyterHandler):
25
71
  """
26
72
  Handler for /proxy endpoint.
@@ -154,91 +200,8 @@ class ProxyHandler(JupyterHandler):
154
200
  class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
155
201
  """
156
202
  WebSocket proxy handler that forwards connections from /ws to target server
157
- Enhanced with comprehensive logging for debugging intermittent connection issues
158
203
  """
159
204
 
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
205
  def _resolve_target_url_for_docker(self, url):
243
206
  """
244
207
  Resolve target URL for Docker environment.
@@ -269,16 +232,11 @@ class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
269
232
  # Determine if we're in Docker
270
233
  is_docker = dockerenv_exists or docker_env or cgroup_docker
271
234
 
272
- print(f"[ESCOBAR-WS] Docker detection indicators: {docker_indicators}")
273
- print(f"[ESCOBAR-WS] Final Docker detection result: {is_docker}")
274
-
275
235
  if not is_docker:
276
- print(f"[ESCOBAR-WS] Not in Docker container, using original URL: {url}")
277
236
  return url
278
237
 
279
238
  # Parse the URL to extract components
280
239
  parsed = urlparse(url)
281
- print(f"[ESCOBAR-WS] Parsed URL - hostname: '{parsed.hostname}', netloc: '{parsed.netloc}'")
282
240
 
283
241
  # Check if hostname is localhost or 127.0.0.1
284
242
  if parsed.hostname in ['localhost', '127.0.0.1']:
@@ -287,10 +245,7 @@ class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
287
245
  new_parsed = parsed._replace(netloc=new_netloc)
288
246
  new_url = urlunparse(new_parsed)
289
247
 
290
- print(f"[ESCOBAR-WS] Docker hostname resolution: {url} → {new_url}")
291
248
  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
249
 
295
250
  return url
296
251
 
@@ -299,25 +254,6 @@ class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
299
254
  super().__init__(*args, **kwargs)
300
255
  self.target_ws = None
301
256
 
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
257
  # Debug environment information
322
258
  print(f"[ESCOBAR-WS] Environment WEBSOCKET_PROXY_TARGET: {os.getenv('WEBSOCKET_PROXY_TARGET', 'NOT_SET')}")
323
259
  print(f"[ESCOBAR-WS] Running in container: {os.path.exists('/.dockerenv')}")
@@ -336,39 +272,28 @@ class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
336
272
  print(f"[ESCOBAR-WS] All WEBSOCKET environment vars: {websocket_env_vars}")
337
273
 
338
274
  self.is_closing = False
339
- self.message_count_sent = 0
340
- self.message_count_received = 0
341
- self.last_activity_time = time.time()
342
275
 
343
276
  def _get_user_bonnie_url(self):
344
277
  """
345
278
  Get user-configured Bonnie URL from bonnie_url query parameter.
346
279
  This allows the frontend to override the environment variable.
347
280
  """
348
- print(f"[ESCOBAR-WS] === READING USER BONNIE URL FROM QUERY PARAMETER ===")
349
-
350
281
  try:
351
282
  # Get the bonnieUrl from query parameters
352
283
  bonnie_url = self.get_argument('bonnie_url', None)
353
- print(f"[ESCOBAR-WS] bonnie_url query parameter value: '{bonnie_url}'")
354
284
 
355
285
  if bonnie_url and bonnie_url.strip():
356
286
  # Validate that it's a WebSocket URL
357
287
  if bonnie_url.startswith(('ws://', 'wss://')):
358
- print(f"[ESCOBAR-WS] Valid user Bonnie URL from query parameter: {bonnie_url}")
359
288
  return bonnie_url.strip()
360
289
  else:
361
- print(f"[ESCOBAR-WS] Invalid Bonnie URL format (must start with ws:// or wss://): {bonnie_url}")
362
290
  return None
363
291
  else:
364
- print(f"[ESCOBAR-WS] No bonnie_url query parameter provided")
365
292
  return None
366
293
 
367
294
  except Exception as e:
368
295
  print(f"[ESCOBAR-WS] Error reading bonnie_url query parameter: {e}")
369
296
  return None
370
- finally:
371
- print(f"[ESCOBAR-WS] === END BONNIE URL QUERY PARAMETER READ ===")
372
297
 
373
298
  def _get_target_url(self):
374
299
  """
@@ -482,136 +407,38 @@ class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
482
407
 
483
408
  async def on_message(self, message):
484
409
  """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
410
  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
411
  try:
523
412
  # Forward message to target server
524
413
  await self.target_ws.send(message)
525
- print(f"[ESCOBAR-WS] ✅ Message successfully forwarded to target")
526
414
  logging.debug(f"Forwarded message to target: {message[:100]}...")
527
415
  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
-
416
+ print(f"[ESCOBAR-WS] ERROR forwarding message to target:")
417
+ print(f"[ESCOBAR-WS] Error: {str(e)}")
418
+ print(f"[ESCOBAR-WS] Error type: {type(e).__name__}")
556
419
  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 ===")
420
+ self.close(code=1011, reason="Target connection error")
564
421
 
565
422
  async def _forward_from_target(self):
566
423
  """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
424
  try:
571
- message_count = 0
572
425
  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
426
  if not self.is_closing:
581
- print(f"[ESCOBAR-WS] Forwarding message to client")
582
427
  # Forward message to client
583
428
  self.write_message(message)
584
- print(f"[ESCOBAR-WS] Message successfully forwarded to client")
585
429
  logging.debug(f"Forwarded message from target: {message[:100]}...")
586
430
  else:
587
- print(f"[ESCOBAR-WS] Breaking forwarding loop - connection is closing")
588
431
  break
589
- print(f"[ESCOBAR-WS] === END TARGET MESSAGE #{message_count} ===")
590
432
 
591
433
  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
434
  logging.info(f"Target websocket connection closed - code: {getattr(e, 'code', 'NO_CODE')}, reason: {getattr(e, 'reason', 'NO_REASON')}")
603
435
 
604
436
  if not self.is_closing:
605
- print(f"[ESCOBAR-WS] Closing client connection due to target disconnect")
606
437
  self.close(code=1011, reason="Target server disconnected")
607
438
  except Exception as e:
608
439
  print(f"[ESCOBAR-WS] === TARGET MESSAGE FORWARDING ERROR ===")
609
440
  print(f"[ESCOBAR-WS] Error: {str(e)}")
610
441
  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
442
  if hasattr(e, 'errno'):
616
443
  print(f"[ESCOBAR-WS] Errno: {e.errno}")
617
444
  if hasattr(e, 'strerror'):
@@ -620,66 +447,37 @@ class WebSocketProxyHandler(tornado.websocket.WebSocketHandler):
620
447
 
621
448
  logging.error(f"Error receiving from target websocket: {str(e)}")
622
449
  if not self.is_closing:
623
- print(f"[ESCOBAR-WS] Closing client connection due to target error")
624
450
  self.close(code=1011, reason="Target connection error")
625
-
626
- print(f"[ESCOBAR-WS] === TARGET MESSAGE FORWARDING ENDED ===")
627
451
 
628
452
  def on_close(self):
629
453
  """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
454
  self.is_closing = True
633
- print(f"[ESCOBAR-WS] Target WS exists: {self.target_ws is not None}")
634
455
 
635
456
  logging.info("WebSocket connection closed")
636
457
 
637
458
  # Close target connection if it exists
638
459
  if self.target_ws:
639
- print(f"[ESCOBAR-WS] Scheduling target connection cleanup")
640
460
  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
461
 
646
462
  async def _close_target_connection(self):
647
463
  """Safely close the target websocket connection"""
648
- print(f"[ESCOBAR-WS] === CLOSING TARGET CONNECTION ===")
649
464
  try:
650
465
  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
466
  # Check if connection is already closed using the correct websockets library API
656
467
  try:
657
468
  is_closed = self.target_ws.state.name == 'CLOSED'
658
- print(f"[ESCOBAR-WS] Target WS is closed: {is_closed}")
659
469
  except AttributeError:
660
470
  # Fallback: just try to close it regardless of state
661
- print(f"[ESCOBAR-WS] Cannot check state, will attempt to close anyway")
662
471
  is_closed = False
663
472
 
664
473
  if not is_closed:
665
- print(f"[ESCOBAR-WS] Target WS is open, closing it")
666
474
  await self.target_ws.close()
667
- print(f"[ESCOBAR-WS] Target connection closed successfully")
668
475
  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
476
  except Exception as e:
674
477
  print(f"[ESCOBAR-WS] ERROR closing target connection:")
675
478
  print(f"[ESCOBAR-WS] Error: {str(e)}")
676
479
  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
480
  logging.error(f"Error closing target websocket: {str(e)}")
681
-
682
- print(f"[ESCOBAR-WS] === END CLOSING TARGET CONNECTION ===")
683
481
 
684
482
 
685
483
  def setup_handlers(web_app):
@@ -747,9 +545,14 @@ def setup_handlers(web_app):
747
545
  for pattern in ws_patterns:
748
546
  print(f"[ESCOBAR-WS] - {pattern}")
749
547
 
548
+ # Register the OAuth callback endpoint
549
+ oauth_callback_pattern = url_path_join(base_url, "static", "oauth-callback.html")
550
+ print(f"[ESCOBAR-WS] OAuth callback pattern: {oauth_callback_pattern}")
551
+
750
552
  # Build handlers list with all WebSocket patterns
751
553
  handlers = [
752
554
  (proxy_pattern, ProxyHandler),
555
+ (oauth_callback_pattern, OAuthCallbackHandler),
753
556
  *[(pattern, WebSocketProxyHandler) for pattern in ws_patterns]
754
557
  ]
755
558
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "escobar",
3
- "version": "0.1.90",
3
+ "version": "0.1.91",
4
4
  "description": "AI CHAT EXTENSION",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -123,7 +123,7 @@
123
123
  "outputDir": "escobar/labextension",
124
124
  "schemaDir": "schema",
125
125
  "_build": {
126
- "load": "static/remoteEntry.c9a1accf3c70050fa880.js",
126
+ "load": "static/remoteEntry.48a97be89d680e21e498.js",
127
127
  "extension": "./extension",
128
128
  "style": "./style"
129
129
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "escobar",
3
- "version": "0.1.90",
3
+ "version": "0.1.91",
4
4
  "description": "AI CHAT EXTENSION",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -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
  },