This would enable us to backup Overleaf content without regular `git pull`, see OC3K `crontab`.
Related to #11, [Overleaf_history_exporter](https://codeberg.org/Benjamin_Loison/Overleaf_history_exporter) and [Improve_websites_thanks_to_open_source/issues/770](https://codeberg.org/Benjamin_Loison/Improve_websites_thanks_to_open_source/issues/770).
+76
Benjamin_Loison
changed title from Be notified in real-time on any activity (for instance cursor move, typing...) to Be notified in real-time on any activity (for instance connection, cursor move, typing...)2024-03-01 20:44:07 +01:00
There is no further request on Firefox when loading this webpage.
Most results seem about hosting our own instance.
https://www.overleaf.com/socket.io/1/websocket/
```
XXXXXXXXXXXXXXXXXXXX:60:60:websocket,flashsocket,htmlfile,xhr-polling,jsonp-polling
```
first part changes everytime. Same for https://www.overleaf.com/socket.io/1/ and https://www.overleaf.com/socket.io/1/uzgxahft/ `uzgxahft` being a random string I generated.
There is no further request on Firefox when loading this webpage.
https://socket.io
[socket.io](https://github.com/socketio/socket.io)
> About
> Realtime application framework (Node.JS server)
so it won't help much it seems.
https://socket.io/docs/v4/client-api/ may help.
https://pypi.org/project/python-socketio/
[python-socketio](https://github.com/miguelgrinberg/python-socketio) has 4.1 k stars and last commit was 3 days ago.
The Socket.IO protocol has been through a number of revisions, and some of these introduced backward incompatible changes, which means that the client and the server must use compatible versions for everything to work.
> The Socket.IO protocol has been through a number of revisions, and some of these introduced backward incompatible changes, which means that the client and the server must use compatible versions for everything to work.
Source: [python-socketio/blob/781dc9a0305f6a795a0467ebc795f4b765084a0d/README.md#version-compatibility](https://github.com/miguelgrinberg/python-socketio/blob/781dc9a0305f6a795a0467ebc795f4b765084a0d/README.md#version-compatibility)
importsocketio# standard Pythonwithsocketio.SimpleClient()assio:# ... connect to a server and use the client# ... no need to manually disconnect!sio.connect('https://www.overleaf.com/socket.io/1/websocket/XXXXXXXXXXXXXXXXXXXX?projectId=XXXXXXXXXXXXXXXXXXXXXXXX',transports=['websocket'])event=sio.receive()print(f'received event: "{event[0]}" with arguments {event[1:]}')
Output:
Traceback (most recent call last):
File "<tmp 3>", line 7, in <module>
sio.connect('wss://www.overleaf.com/socket.io/1/websocket/XXXXXXXXXXXXXXXXXXXX?projectId=XXXXXXXXXXXXXXXXXXXXXXXX', transports = ['websocket'])
File "/home/benjamin_loison/venv/lib/python3.11/site-packages/socketio/simple_client.py", line 82, in connect
self.client.connect(url, headers=headers, auth=auth,
File "/home/benjamin_loison/venv/lib/python3.11/site-packages/socketio/client.py", line 159, in connect
raise exceptions.ConnectionError(exc.args[0]) from None
socketio.exceptions.ConnectionError: Connection error
same if use https://.
<details>
<summary>Python script:</summary>
```python
import socketio
# standard Python
with socketio.SimpleClient() as sio:
# ... connect to a server and use the client
# ... no need to manually disconnect!
sio.connect('https://www.overleaf.com/socket.io/1/websocket/XXXXXXXXXXXXXXXXXXXX?projectId=XXXXXXXXXXXXXXXXXXXXXXXX', transports = ['websocket'])
event = sio.receive()
print(f'received event: "{event[0]}" with arguments {event[1:]}')
```
</details>
<details>
<summary>Output:</summary>
```
Traceback (most recent call last):
File "<tmp 3>", line 7, in <module>
sio.connect('wss://www.overleaf.com/socket.io/1/websocket/XXXXXXXXXXXXXXXXXXXX?projectId=XXXXXXXXXXXXXXXXXXXXXXXX', transports = ['websocket'])
File "/home/benjamin_loison/venv/lib/python3.11/site-packages/socketio/simple_client.py", line 82, in connect
self.client.connect(url, headers=headers, auth=auth,
File "/home/benjamin_loison/venv/lib/python3.11/site-packages/socketio/client.py", line 159, in connect
raise exceptions.ConnectionError(exc.args[0]) from None
socketio.exceptions.ConnectionError: Connection error
```
</details>
same if use `https://`.
So it seems to have broke after 8 minutes and 41 seconds.
Note that in theory there is no need to do anything as I just read the network of a relatively regular web-browser.
```
head -c 27 notifyOnNewRealTimeOverleafActivity.py.log
```
```
2025-09-30 15:35:50.061342
```
```
grep -v "message='2::'" notifyOnNewRealTimeOverleafActivity.py.log | tail -n 1 | head -c 27
```
```
2025-10-06 14:53:09.098792
```
```
tail -n 3 notifyOnNewRealTimeOverleafActivity.py.log
```
<details>
<summary>Output:</summary>
```
2025-10-06 15:01:00.706347 message='2::'
2025-10-06 15:01:25.734648 message='2::'
2025-10-06 15:01:50.762080 message='2::'
```
</details>
So it seems to have broke after 8 minutes and 41 seconds.
Note that in theory there is no need to do anything as I just read the network of a relatively regular web-browser.
Check if no response since 50 seconds seems to be a good way to be notified when the WebSocket does not work anymore.
Alternatively should check if after the same amount of time face again this issue.
Check if no response since 50 seconds seems to be a good way to be notified when the WebSocket does not work anymore.
Alternatively should check if after the same amount of time face again this issue.
ps aux | grep '[n]otifyOnNewRealTimeOverleafActivity.py'
Output:
benjami+ 1648 0.0 0.0 7764 3016 ? Ss Oct05 0:00 /usr/bin/bash /usr/bin/my_cron_shell -c cd notifications/ && ~/venv/bin/python3 notifyOnNewRealTimeOverleafActivity.py
benjami+ 1676 0.0 0.0 7024 880 ? S Oct05 0:00 /usr/bin/bash /usr/bin/my_cron_shell -c cd notifications/ && ~/venv/bin/python3 notifyOnNewRealTimeOverleafActivity.py
benjami+ 1679 0.0 0.3 43384 29600 ? S Oct05 0:11 /home/benjamin_loison/venv/bin/python3 notifyOnNewRealTimeOverleafActivity.py
pkill -f notifyOnNewRealTimeOverleafActivity.py
ps aux | grep '[n]otifyOnNewRealTimeOverleafActivity.py'
Would be interesting to see the window to see if there is any prompt.
Note that my approach may not be stable if the server is not stable, as a regular user may see a prompt, possibly with a button, or just reload the webpage.
```bash
ps aux | grep '[n]otifyOnNewRealTimeOverleafActivity.py'
```
<details>
<summary>Output:</summary>
```
benjami+ 1648 0.0 0.0 7764 3016 ? Ss Oct05 0:00 /usr/bin/bash /usr/bin/my_cron_shell -c cd notifications/ && ~/venv/bin/python3 notifyOnNewRealTimeOverleafActivity.py
benjami+ 1676 0.0 0.0 7024 880 ? S Oct05 0:00 /usr/bin/bash /usr/bin/my_cron_shell -c cd notifications/ && ~/venv/bin/python3 notifyOnNewRealTimeOverleafActivity.py
benjami+ 1679 0.0 0.3 43384 29600 ? S Oct05 0:11 /home/benjamin_loison/venv/bin/python3 notifyOnNewRealTimeOverleafActivity.py
```
</details>
```
pkill -f notifyOnNewRealTimeOverleafActivity.py
```
```bash
ps aux | grep '[n]otifyOnNewRealTimeOverleafActivity.py'
```
Would be interesting to see the window to see if there is any prompt.
Note that my approach may not be stable if the server is not stable, as a regular user may see a prompt, possibly with a button, or just reload the webpage.
importtimeimportthreadingUPDATE_DELAY=1THRESHOLD=UPDATE_DELAY*2NORMAL_BEHAVIOR_PERIOD=3*THRESHOLDlastTime=NonedefverifyLastTime():whileTrue:currentTime=time.time()print(f'Checking at {currentTime}')iflastTimeisnotNoneandlastTime<currentTime-THRESHOLD:print('No more update!')break# Sleeping just enough depending on `lastTime` may not be efficient, but mid range would be interesting.time.sleep(UPDATE_DELAY)thread=threading.Thread(target=verifyLastTime)thread.start()for_inrange(NORMAL_BEHAVIOR_PERIOD):lastTime=time.time()print(f'{lastTime=}')time.sleep(UPDATE_DELAY)thread.join()
Output:
Checking at 1759915873.3947287
lastTime=1759915873.3947918
lastTime=1759915874.3950238
Checking at 1759915874.3957164
lastTime=1759915875.3955274
Checking at 1759915875.3961875
lastTime=1759915876.3960567
Checking at 1759915876.3965623
lastTime=1759915877.396563
Checking at 1759915877.396842
lastTime=1759915878.3968308
Checking at 1759915878.3970864
Checking at 1759915879.3973584
Checking at 1759915880.3978689
No more update!
works as wanted.
<details>
<summary>Python script:</summary>
```python
import time
import threading
UPDATE_DELAY = 1
THRESHOLD = UPDATE_DELAY * 2
NORMAL_BEHAVIOR_PERIOD = 3 * THRESHOLD
lastTime = None
def verifyLastTime():
while True:
currentTime = time.time()
print(f'Checking at {currentTime}')
if lastTime is not None and lastTime < currentTime - THRESHOLD:
print('No more update!')
break
# Sleeping just enough depending on `lastTime` may not be efficient, but mid range would be interesting.
time.sleep(UPDATE_DELAY)
thread = threading.Thread(target = verifyLastTime)
thread.start()
for _ in range(NORMAL_BEHAVIOR_PERIOD):
lastTime = time.time()
print(f'{lastTime=}')
time.sleep(UPDATE_DELAY)
thread.join()
```
</details>
<details>
<summary>Output:</summary>
```
Checking at 1759915873.3947287
lastTime=1759915873.3947918
lastTime=1759915874.3950238
Checking at 1759915874.3957164
lastTime=1759915875.3955274
Checking at 1759915875.3961875
lastTime=1759915876.3960567
Checking at 1759915876.3965623
lastTime=1759915877.396563
Checking at 1759915877.396842
lastTime=1759915878.3968308
Checking at 1759915878.3970864
Checking at 1759915879.3973584
Checking at 1759915880.3978689
No more update!
```
</details>
works as wanted.
2025-10-13 16:15:02.594978 Checking at 1760364902.5949585
2025-10-13 16:15:27.595367 Checking at 1760364927.5953403
2025-10-13 16:15:52.595749 Checking at 1760364952.5957308
grep 'No more Overleaf ping' notifyOnNewRealTimeOverleafActivity.py.log
does not return anything.
An alternative would to schedule a notification function and abort it and start a new one when receive a heartbeat.
2025-10-13 16:19:53.445716 message='6:::3+[null,[{"last_updated_at":"1760365192431","user_id":"XXXXXXXXXXXXXXXXXXXXXXXX","first_name":"Benjamin","last_name":"Loison","email":"benjamin.loison@CENSORED.fr","cursorData":{"row":1088,"column":0,"doc_id":"XXXXXXXXXXXXXXXXXXXXXXXX"},"connected":true,"client_id":"P.XX_XXXXXXXXXXXXXXXXX","client_age":1.001},{"last_updated_at":"1760365192431","user_id":"anonymous-user","first_name":"","last_name":"","email":"","connected":true,"client_id":"P.XXXXXXXXXXXXXXXXXX-X","client_age":1.001}]]'
2025-10-13 16:19:58.916655 Checking at 1760365198.9166238 (lastTime=None)
2025-10-13 16:20:02.831867 message='6:::2+[{"message":"Something went wrong in real-time service"}]'
2025-10-13 16:20:03.585845 message='2::'
2025-10-13 16:20:23.917074 Checking at 1760365223.9170516 (lastTime=None)
2025-10-13 16:20:28.617499 message='2::'
2025-10-13 16:20:48.917365 Checking at 1760365248.9173453 (lastTime=None)
```
tail -n 3 notifyOnNewRealTimeOverleafActivity.py.log
```
<details>
<summary>Output:</summary>
```
2025-10-13 16:15:02.594978 Checking at 1760364902.5949585
2025-10-13 16:15:27.595367 Checking at 1760364927.5953403
2025-10-13 16:15:52.595749 Checking at 1760364952.5957308
```
</details>
```bash
grep 'No more Overleaf ping' notifyOnNewRealTimeOverleafActivity.py.log
```
does not return anything.
An alternative would to schedule a notification function and abort it and start a new one when receive a heartbeat.
```bash
grep 'message' notifyOnNewRealTimeOverleafActivity.py.log | tail -n 3
```
<details>
<summary>Output:</summary>
```
2025-10-10 00:41:14.054577 message='2::'
2025-10-10 00:41:39.084075 message='2::'
2025-10-10 00:42:04.114034 message='2::'
```
</details>
Now using:
```python
print(f'Checking at {currentTime} ({lastTime=})')
```
```bash
./notifyOnNewRealTimeOverleafActivity.py
```
<details>
<summary>Output:</summary>
```
2025-10-13 16:19:53.445716 message='6:::3+[null,[{"last_updated_at":"1760365192431","user_id":"XXXXXXXXXXXXXXXXXXXXXXXX","first_name":"Benjamin","last_name":"Loison","email":"benjamin.loison@CENSORED.fr","cursorData":{"row":1088,"column":0,"doc_id":"XXXXXXXXXXXXXXXXXXXXXXXX"},"connected":true,"client_id":"P.XX_XXXXXXXXXXXXXXXXX","client_age":1.001},{"last_updated_at":"1760365192431","user_id":"anonymous-user","first_name":"","last_name":"","email":"","connected":true,"client_id":"P.XXXXXXXXXXXXXXXXXX-X","client_age":1.001}]]'
2025-10-13 16:19:58.916655 Checking at 1760365198.9166238 (lastTime=None)
2025-10-13 16:20:02.831867 message='6:::2+[{"message":"Something went wrong in real-time service"}]'
2025-10-13 16:20:03.585845 message='2::'
2025-10-13 16:20:23.917074 Checking at 1760365223.9170516 (lastTime=None)
2025-10-13 16:20:28.617499 message='2::'
2025-10-13 16:20:48.917365 Checking at 1760365248.9173453 (lastTime=None)
```
</details>
Next ping was expected at *2025-10-19 18:12:22.633351* which is quite exactly 24 hours after.
I am a bit surprised as I had the feeling that it was previously running for longer.
[Benjamin-Loison/cpython/issues/29](https://github.com/Benjamin-Loison/cpython/issues/29) would help, as the script is still running...
As possibly not multiple persons connected, just knowing that someone left if already nice.
People already connected when the script starts are sent as last_updated_at, see #issuecomment-4363.
args matches id, see #issuecomment-4343. Same if already connected with client_id.
Note that such id changes from a connection to the other for the same authenticated user.
Could maintain a dict to leverage the associated identity.
To be notified when someone leaves, as if do not write continuously can't know.
```
2025-10-20 22:40:34.362884 message='5:::{"name":"clientTracking.clientDisconnected","args":["P.XXXXXXXXXXXXXXXXXXXX"]}'
```
As possibly not multiple persons connected, just knowing that someone left if already nice.
People already connected when the script starts are sent as `last_updated_at`, see [#issuecomment-4363](#issuecomment-4363).
`args` matches `id`, see [#issuecomment-4343](#issuecomment-4343). Same if already connected with `client_id`.
Note that such id changes from a connection to the other for the same authenticated user.
Could maintain a `dict` to leverage the associated identity.
Let us consider that Benjamin-Loison/matrix-commander/issues/16 may take multiple seconds and people may leave faster, as may have concurrency of him coming back for instance, well it would be using different ids.
So let us prefer keeping for too long memory content, but notify faster.
Let us consider that [Benjamin-Loison/matrix-commander/issues/16](https://github.com/Benjamin-Loison/matrix-commander/issues/16) may take multiple seconds and people may leave faster, as may have concurrency of him coming back for instance, well it would be using different ids.
So let us prefer keeping for too long memory content, but notify _faster_.
2025-10-24 22:09:51.217163 message='2::'
2025-10-24 22:10:07.306708 Checking at 1761336607.306685 (lastTime=1761336591.217317)
2025-10-24 22:10:10.633754 message='5:::{"name":"clientTracking.clientDisconnected","args":["P.XXXXXXXXXXXXXXX-XXXX"]}'
2025-10-24 22:10:32.307053 Checking at 1761336632.307014 (lastTime=1761336610.6339831)
2025-10-24 22:10:53.155093 message='2::'
2025-10-24 22:10:57.307489 Checking at 1761336657.3074548 (lastTime=1761336653.1552126)
2025-10-24 22:11:22.307933 Checking at 1761336682.3078983 (lastTime=1761336653.1552126)
2025-10-24 22:11:47.308454 Checking at 1761336707.308422 (lastTime=1761336653.1552126)
2025-10-24 22:13:33.741368 message='1::'
2025-10-24 22:13:33.742285 message='5:::{"name":"connectionAccepted","args":[null,"P.XXXXXXXXXXXXXXXXXXXX"]}'
2025-10-24 22:13:33.930110 message='6:::4+[null,{"_id":"CENSORED...
So 2:: does not seem mandatory.
Disconnection seems related, but not clearly as there is still a single heartbeat after.
<details>
<summary>Output:</summary>
```
2025-10-24 22:09:51.217163 message='2::'
2025-10-24 22:10:07.306708 Checking at 1761336607.306685 (lastTime=1761336591.217317)
2025-10-24 22:10:10.633754 message='5:::{"name":"clientTracking.clientDisconnected","args":["P.XXXXXXXXXXXXXXX-XXXX"]}'
2025-10-24 22:10:32.307053 Checking at 1761336632.307014 (lastTime=1761336610.6339831)
2025-10-24 22:10:53.155093 message='2::'
2025-10-24 22:10:57.307489 Checking at 1761336657.3074548 (lastTime=1761336653.1552126)
2025-10-24 22:11:22.307933 Checking at 1761336682.3078983 (lastTime=1761336653.1552126)
2025-10-24 22:11:47.308454 Checking at 1761336707.308422 (lastTime=1761336653.1552126)
2025-10-24 22:13:33.741368 message='1::'
2025-10-24 22:13:33.742285 message='5:::{"name":"connectionAccepted","args":[null,"P.XXXXXXXXXXXXXXXXXXXX"]}'
2025-10-24 22:13:33.930110 message='6:::4+[null,{"_id":"CENSORED...
```
</details>
So `2::` does not seem mandatory.
Disconnection seems related, but not clearly as there is still a single heartbeat after.

> L'éditeur a été déconnecté. Cliquez n'importe où pour vous reconnecter
Clicking top bar between *Menu* and the document title.

*Actualiser l'éditeur* (not the language mix):

So can make the necessary to always have:
```
2025-10-28 16:46:37.858067 message='2::'
2025-10-28 16:47:02.894618 message='2::'
```
which seems more reliable.
Related to [Benjamin_Loison/gnome-screenshot/-/issues/17#note_2589157](https://gitlab.gnome.org/Benjamin_Loison/gnome-screenshot/-/issues/17#note_2589157).
2025-11-08 19:35:21.220362 Checking at 1762626921.2203474 (lastTime=None)
Traceback (most recent call last):
File "/home/benjamin_loison/notifications/notifyOnNewRealTimeOverleafActivity.py", line 80, in <module>
browser = p.firefox.launch(headless = False)
File "/home/benjamin_loison/venv/lib/python3.13/site-packages/playwright/sync_api/_generated.py", line 14502, in launch
self._sync(
~~~~~~~~~~^
self._impl_obj.launch(
^^^^^^^^^^^^^^^^^^^^^^
...<17 lines>...
)
^
)
^
File "/home/benjamin_loison/venv/lib/python3.13/site-packages/playwright/_impl/_sync_base.py", line 115, in _sync
return task.result()
~~~~~~~~~~~^^
File "/home/benjamin_loison/venv/lib/python3.13/site-packages/playwright/_impl/_browser_type.py", line 98, in launch
await self._channel.send(
"launch", TimeoutSettings.launch_timeout, params
)
File "/home/benjamin_loison/venv/lib/python3.13/site-packages/playwright/_impl/_connection.py", line 69, in send
return await self._connection.wrap_api_call(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...<3 lines>...
)
^
File "/home/benjamin_loison/venv/lib/python3.13/site-packages/playwright/_impl/_connection.py", line 558, in wrap_api_call
raise rewrite_error(error, f"{parsed_st['apiName']}: {error}") from None
playwright._impl._errors.TargetClosedError: BrowserType.launch: Target page, context or browser has been closed
Browser logs:
╔════════════════════════════════════════════════════════════════════════════════════════════════╗
║ Looks like you launched a headed browser without having a XServer running. ║
║ Set either 'headless: true' or use 'xvfb-run <your-playwright-app>' before running Playwright. ║
║ ║
║ <3 Playwright Team ║
╚════════════════════════════════════════════════════════════════════════════════════════════════╝
Call log:
- <launching> /home/benjamin_loison/.cache/ms-playwright/firefox-1490/firefox/firefox -no-remote -wait-for-browser -foreground -profile /tmp/playwright_firefoxdev_profile-9l8V90 -juggler-pipe -silent
- <launched> pid=2833
- [pid=2833][err] Error: no DISPLAY environment variable specified
- [pid=2833] <process did exit: exitCode=1, signal=null>
- [pid=2833] starting temporary directories cleanup
- [pid=2833] <gracefully close start>
- [pid=2833] finished temporary directories cleanup
- [pid=2833] <gracefully close end>
Exception in thread Thread-1 (verifyLastTime):
Traceback (most recent call last):
File "/usr/lib/python3.13/threading.py", line 1043, in _bootstrap_inner
self.run()
~~~~~~~~^^
File "/usr/lib/python3.13/threading.py", line 994, in run
self._target(*self._args, **self._kwargs)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/benjamin_loison/notifications/notifyOnNewRealTimeOverleafActivity.py", line 69, in verifyLastTime
print(f'Checking at {currentTime} ({lastTime=})')
~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/benjamin_loison/notifications/notifyOnNewRealTimeOverleafActivity.py", line 27, in print
with open(f'{os.path.basename(__file__)}.log', 'a') as f:
^^^^^^^^
NameError: name '__file__' is not defined. Did you mean: '__name__'?
2025-11-08 19:35:45.985338 Checking at 1762626945.985241 (lastTime=None)
on OverClock3000 reboot due to crontab:
@reboot cd notifications/ && ~/venv/bin/python3 notifyOnNewRealTimeOverleafActivity.py
it is probably due to headless = False.
Via RDP I don't have this issue.
<details>
<summary>Error:</summary>
```
2025-11-08 19:35:21.220362 Checking at 1762626921.2203474 (lastTime=None)
Traceback (most recent call last):
File "/home/benjamin_loison/notifications/notifyOnNewRealTimeOverleafActivity.py", line 80, in <module>
browser = p.firefox.launch(headless = False)
File "/home/benjamin_loison/venv/lib/python3.13/site-packages/playwright/sync_api/_generated.py", line 14502, in launch
self._sync(
~~~~~~~~~~^
self._impl_obj.launch(
^^^^^^^^^^^^^^^^^^^^^^
...<17 lines>...
)
^
)
^
File "/home/benjamin_loison/venv/lib/python3.13/site-packages/playwright/_impl/_sync_base.py", line 115, in _sync
return task.result()
~~~~~~~~~~~^^
File "/home/benjamin_loison/venv/lib/python3.13/site-packages/playwright/_impl/_browser_type.py", line 98, in launch
await self._channel.send(
"launch", TimeoutSettings.launch_timeout, params
)
File "/home/benjamin_loison/venv/lib/python3.13/site-packages/playwright/_impl/_connection.py", line 69, in send
return await self._connection.wrap_api_call(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...<3 lines>...
)
^
File "/home/benjamin_loison/venv/lib/python3.13/site-packages/playwright/_impl/_connection.py", line 558, in wrap_api_call
raise rewrite_error(error, f"{parsed_st['apiName']}: {error}") from None
playwright._impl._errors.TargetClosedError: BrowserType.launch: Target page, context or browser has been closed
Browser logs:
╔════════════════════════════════════════════════════════════════════════════════════════════════╗
║ Looks like you launched a headed browser without having a XServer running. ║
║ Set either 'headless: true' or use 'xvfb-run <your-playwright-app>' before running Playwright. ║
║ ║
║ <3 Playwright Team ║
╚════════════════════════════════════════════════════════════════════════════════════════════════╝
Call log:
- <launching> /home/benjamin_loison/.cache/ms-playwright/firefox-1490/firefox/firefox -no-remote -wait-for-browser -foreground -profile /tmp/playwright_firefoxdev_profile-9l8V90 -juggler-pipe -silent
- <launched> pid=2833
- [pid=2833][err] Error: no DISPLAY environment variable specified
- [pid=2833] <process did exit: exitCode=1, signal=null>
- [pid=2833] starting temporary directories cleanup
- [pid=2833] <gracefully close start>
- [pid=2833] finished temporary directories cleanup
- [pid=2833] <gracefully close end>
Exception in thread Thread-1 (verifyLastTime):
Traceback (most recent call last):
File "/usr/lib/python3.13/threading.py", line 1043, in _bootstrap_inner
self.run()
~~~~~~~~^^
File "/usr/lib/python3.13/threading.py", line 994, in run
self._target(*self._args, **self._kwargs)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/benjamin_loison/notifications/notifyOnNewRealTimeOverleafActivity.py", line 69, in verifyLastTime
print(f'Checking at {currentTime} ({lastTime=})')
~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/benjamin_loison/notifications/notifyOnNewRealTimeOverleafActivity.py", line 27, in print
with open(f'{os.path.basename(__file__)}.log', 'a') as f:
^^^^^^^^
NameError: name '__file__' is not defined. Did you mean: '__name__'?
2025-11-08 19:35:45.985338 Checking at 1762626945.985241 (lastTime=None)
```
</details>
<details>
<summary>on OverClock3000 reboot due to <code>crontab</code>:</summary>
```
@reboot cd notifications/ && ~/venv/bin/python3 notifyOnNewRealTimeOverleafActivity.py
```
</details>
it is probably due to `headless = False`.
Via RDP I don't have this issue.
Would be nice that if get ping back, then re-enable watcher.
Maybe it is the time of PDF compilation or similar, but I doubt so.
The absence of information let me believe that it is an anonymous connection.
<details>
<summary>Output:</summary>
```
2025-12-05 16:21:25.022162 Checking at 1764948085.0221505 (lastTime=None)
2025-12-05 16:21:27.549076 Waiting forever
2025-12-05 16:21:28.537845 message='1::'
...
2025-12-06 12:31:52.599574 Checking at 1765020712.5995438 (lastTime=1765020706.4025095)
2025-12-06 12:32:11.431761 message='2::'
2025-12-06 12:32:17.600688 Checking at 1765020737.6006556 (lastTime=1765020731.4321938)
2025-12-06 12:32:42.601490 Checking at 1765020762.6014564 (lastTime=1765020731.4321938)
2025-12-06 12:33:07.602441 Checking at 1765020787.6024113 (lastTime=1765020731.4321938)
2025-12-06 12:34:31.562543 message='1::'
2025-12-06 12:34:31.565544 message='5:::{"name":"connectionAccepted","args":[null,"P.XXXXXXXXXXXX-XXXXXXX"]}'
2025-12-06 12:34:31.838339 message='6:::4+[null,{"_id":"XXXXXXXXXXXXXXXXXXXXXXXX","name":"CENSORED_DOCUMENT_TITLE","rootDoc_id":"
...
2025-12-06 12:34:56.560277 message='2::'
2025-12-06 12:35:21.592516 message='2::'
2025-12-06 12:35:46.629341 message='2::'
...
```
</details>
Would be nice that if get ping back, then re-enable watcher.
Maybe it is the time of PDF compilation or similar, but I doubt so.
The absence of information let me believe that it is an anonymous connection.
Help on PlaywrightContextManager in module playwright.sync_api._context_manager object:
class PlaywrightContextManager(builtins.object)
| PlaywrightContextManager() -> None
|
| Methods defined here:
|
| __enter__(self) -> playwright.sync_api._generated.Playwright
|
| __exit__(self, *args: Any) -> None
|
| __init__(self) -> None
| Initialize self. See help(type(self)) for accurate signature.
|
| start(self) -> playwright.sync_api._generated.Playwright
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables
|
| __weakref__
| list of weak references to the object
p=sync_playwright().start()
works as wanted.
```python
help(p)
```
<details>
<summary>Output:</summary>
```
Help on PlaywrightContextManager in module playwright.sync_api._context_manager object:
class PlaywrightContextManager(builtins.object)
| PlaywrightContextManager() -> None
|
| Methods defined here:
|
| __enter__(self) -> playwright.sync_api._generated.Playwright
|
| __exit__(self, *args: Any) -> None
|
| __init__(self) -> None
| Initialize self. See help(type(self)) for accurate signature.
|
| start(self) -> playwright.sync_api._generated.Playwright
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables
|
| __weakref__
| list of weak references to the object
```
</details>
```python
p = sync_playwright().start()
```
works as wanted.
fromplaywright.async_apiimportasync_playwrightimportasyncio#with sync_playwright() as p:p=asyncio.run(async_playwright().start())browser=asyncio.run(p.firefox.launch(headless=False))page=asyncio.run(browser.new_page())asyncio.run(page.goto('https://example.com'))# To distinguish executions.print('Waiting forever')whileTrue:asyncio.run(page.wait_for_timeout(2**31-1))
hangs at browser = asyncio.run(p.firefox.launch(headless = False)).
<details>
<summary>Python script:</summary>
```python
from playwright.async_api import async_playwright
import asyncio
#with sync_playwright() as p:
p = asyncio.run(async_playwright().start())
browser = asyncio.run(p.firefox.launch(headless = False))
page = asyncio.run(browser.new_page())
asyncio.run(page.goto('https://example.com'))
# To distinguish executions.
print('Waiting forever')
while True:
asyncio.run(page.wait_for_timeout(2 ** 31 - 1))
```
</details>
hangs at `browser = asyncio.run(p.firefox.launch(headless = False))`.
Help on function run in module asyncio.runners:
run(main, *, debug=None, loop_factory=None)
Execute the coroutine and return the result.
This function runs the passed coroutine, taking care of
managing the asyncio event loop, finalizing asynchronous
generators and closing the default executor.
This function cannot be called when another asyncio event loop is
running in the same thread.
If debug is True, the event loop will be run in debug mode.
If loop_factory is passed, it is used for new event loop creation.
This function always creates a new event loop and closes it at the end.
It should be used as a main entry point for asyncio programs, and should
ideally only be called once.
The executor is given a timeout duration of 5 minutes to shutdown.
If the executor hasn't finished within that duration, a warning is
emitted and the executor is closed.
Example:
async def main():
await asyncio.sleep(1)
print('hello')
asyncio.run(main())
Excerpt:
This function cannot be called when another asyncio event loop is
running in the same thread.
...
This function always creates a new event loop and closes it at the end.
It should be used as a main entry point for asyncio programs, and should
ideally only be called once.
```python
help(asyncio.run)
```
<details>
<summary>Output:</summary>
```
Help on function run in module asyncio.runners:
run(main, *, debug=None, loop_factory=None)
Execute the coroutine and return the result.
This function runs the passed coroutine, taking care of
managing the asyncio event loop, finalizing asynchronous
generators and closing the default executor.
This function cannot be called when another asyncio event loop is
running in the same thread.
If debug is True, the event loop will be run in debug mode.
If loop_factory is passed, it is used for new event loop creation.
This function always creates a new event loop and closes it at the end.
It should be used as a main entry point for asyncio programs, and should
ideally only be called once.
The executor is given a timeout duration of 5 minutes to shutdown.
If the executor hasn't finished within that duration, a warning is
emitted and the executor is closed.
Example:
async def main():
await asyncio.sleep(1)
print('hello')
asyncio.run(main())
```
</details>
<details>
<summary>Excerpt:</summary>
```
This function cannot be called when another asyncio event loop is
running in the same thread.
...
This function always creates a new event loop and closes it at the end.
It should be used as a main entry point for asyncio programs, and should
ideally only be called once.
```
</details>
DuckDuckGo and Google search *asyncio.run*, *Playwright interactively*, *Playwright async interactively* and *Python Playwright async interactive mode*.
https://playwright.dev/docs/test-ui-mode does not seem to help.
https://chat.mistral.ai/chat/4a4cd652-5296-4667-8af3-992f8201ada6
Related to [Improve_websites_thanks_to_open_source/issues/2806](https://codeberg.org/Benjamin_Loison/Improve_websites_thanks_to_open_source/issues/2806).
<tmp 1>+5:1: DeprecationWarning: There is no current event loop
loop = asyncio.get_event_loop()
<details>
<summary>Warning:</summary>
```
<tmp 1>+5:1: DeprecationWarning: There is no current event loop
loop = asyncio.get_event_loop()
```
</details>
<details>
<summary>In JupyterLab:</summary>
```python
from playwright.async_api import async_playwright
p = await async_playwright().start()
browser = await p.firefox.launch(headless=False)
```
</details>
does not hang, but does not show any window.
Salut, tu sais comment utiliser Playwright en mode interactif Python (de sorte à ce que je puisse exécuter des instructions Playwright que j'écris durant l'exécution en fonction de comment réagit le site Internet) ?
There is no need to await page.wait_for_timeout or similar for the cell output to show ongoing WebSocket traffic, while the cell execution is shown as finished as wanted.
<details>
<summary>I was about to send to the person:</summary>
```
-----BEGIN PGP MESSAGE-----
hF4DTQa9Wom5MBgSAQdArVmapRinF4lsSfTCyFY6OfQHzpS/apayE/KXQRocCmIw
Bqocdu4oXRV7G2UCEqC5cppdI2nyNwKFpyXFgv36jlyuu9D0ZixBJHRJ1OeGvX+n
0oAB+rxl/Y+4zWj80V5jDvtdzNVCIxZL3Pjs2Vug4FlAI4nKIMCu49Q2FeRIonu3
1uCQOGLnULLyacD5PaKHRblY03aWG58loFRM7iCYvGopEYpl2Z1LjdkFEjkgT4Bq
08kpMNbZeSR+1ufj+qwwj0HM6Jpt28FVxAYAmNHD+b9hCg==
=3YOT
-----END PGP MESSAGE-----
```
</details>
<details>
<summary>Message:</summary>
> Salut, tu sais comment utiliser Playwright en mode interactif Python (de sorte à ce que je puisse exécuter des instructions Playwright que j'écris durant l'exécution en fonction de comment réagit le site Internet) ?
>
> ```python
> from playwright.async_api import async_playwright
>
> p = await async_playwright().start()
> browser = await p.firefox.launch(headless=False)
> ```
>
> `async` est nécessaire dans Pyzo et Jupyter Notebook.
> `await p.firefox.launch(headless=False)` n'ouvre pas de fenêtre graphique contrairement à:
>
> Notes personnelles: [Benjamin_Loison/overleaf/issues/3#issuecomment-4631](https://gitea.lemnoslife.com/Benjamin_Loison/overleaf/issues/3#issuecomment-4631).
</details>
<details>
<summary>Python script:</summary>
```python
from playwright.async_api import async_playwright
p = await async_playwright().start()
browser = await p.firefox.launch(headless = False)
page = await browser.new_page()
await page.goto("https://example.com")
```
</details>
works as wanted in JupyterLab.
There is no need to `await page.wait_for_timeout` or similar for the cell output to show ongoing WebSocket traffic, while the cell execution is shown as finished as wanted.
DuckDuckGo search *Playwright get element by text*.
https://playwright.dev/docs/locators#locate-by-text
DuckDuckGo search *Playwright Python click*.
[The Stack Overflow question 76632423](https://stackoverflow.com/q/76632423) thread seems to show that I proceed correctly.
DuckDuckGo search *playwright refresh webpage*.
https://playwright.dev/docs/api/class-page#page-reload
I confirm that await page.reload() solves the issue, now the point is keep listening.
An alternative is just to restart the Python script on exit, but it is unsatisfying and it may be inefficient from a script initialization point of view.
I confirm that `await page.reload()` solves the issue, now the point is keep listening.
An alternative is just to restart the Python script on exit, but it is unsatisfying and it may be inefficient from a script initialization point of view.
Let us try to not use another thread, as it does not seem necessary anymore, as while asyncWait: was commented and the behavior except expiration was as wanted it seems.
`await page.reload()` blocks forever.
Let us try to not use another thread, as it does not seem necessary anymore, as `while asyncWait:` was commented and the behavior except expiration was as wanted it seems.
DuckDuckGo and Google search Jupyter disable Python output buffer does not seem helpful.
Let us uncomment while asyncWait: and see if it helps, otherwise will set up a shared boolean and proceed to webpage reload from the main thread.
DuckDuckGo and Google search *Jupyter disable Python output buffer* does not seem helpful.
Let us uncomment `while asyncWait:` and see if it helps, otherwise will set up a shared boolean and proceed to webpage reload from the main thread.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
This would enable us to backup Overleaf content without regular
git pull, see OC3Kcrontab.Related to #11, Overleaf_history_exporter and Improve_websites_thanks_to_open_source/issues/770.
+76
Be notified in real-time on any activity (for instance cursor move, typing...)to Be notified in real-time on any activity (for instance connection, cursor move, typing...)DuckDuckGo and Google search Overleaf WebSocket.
Most results seem about hosting our own instance.
https://www.overleaf.com/socket.io/1/websocket/
first part changes everytime. Same for https://www.overleaf.com/socket.io/1/ and https://www.overleaf.com/socket.io/1/uzgxahft/
uzgxahftbeing a random string I generated.There is no further request on Firefox when loading this webpage.
Should investigate these keywords. Well these seem to be old technologies except
websocket.Let us read Wikipedia: WebSocket (1265617417). I quite completely read it.
Related to Benjamin-Loison/Contracts-management-website/issues/1.
Output:
DuckDuckGo search
"Protocol "wss" not supported or disabled in libcurl".The Stack Overflow answer 47861907:
Shell:
Shell:
Shell:
Shell:
Shell:
Bash script:
https://socket.io
socket.io
so it won't help much it seems.
https://socket.io/docs/v4/client-api/ may help.
Output:
DuckDuckGo search socket.io Python client.
https://pypi.org/project/python-socketio/
python-socketio has 4.1 k stars and last commit was 3 days ago.
Source: python-socketio/blob/781dc9a0305f6a795a0467ebc795f4b765084a0d/README.md#version-compatibility
https://python-socketio.readthedocs.io/en/stable/client.html
Output:
Python script:
Output:
same if use
https://.DuckDuckGo and Google search Python Overleaf client.
pyoverleaf does not seem helpful.
https://www.overleaf.com/socket.io/
Welcome to socket.io.
https://plmlatex.math.cnrs.fr/project let see when and who did last edit.
May help #21.
The Stack Overflow answer 4152986 helps to add some logs.
notifyOnNewOverleafActivity.py.log:Output:
So it seems to have broke after 8 minutes and 41 seconds.
Note that in theory there is no need to do anything as I just read the network of a relatively regular web-browser.
Check if no response since 50 seconds seems to be a good way to be notified when the WebSocket does not work anymore.
Alternatively should check if after the same amount of time face again this issue.
Output:
Would be interesting to see the window to see if there is any prompt.
Note that my approach may not be stable if the server is not stable, as a regular user may see a prompt, possibly with a button, or just reload the webpage.
Python script:
Output:
works as wanted.
Benjamin_Loison/playwright/issues/3 would help.
Output:
Output:
Output:
does not return anything.
An alternative would to schedule a notification function and abort it and start a new one when receive a heartbeat.
Output:
Now using:
Output:
global lastTimewas missing inserver_message_handler.Output:
It seems correct as I restarted yesterday OverClock3000.
Next ping was expected at 2025-10-19 18:12:22.633351 which is quite exactly 24 hours after.
I am a bit surprised as I had the feeling that it was previously running for longer.
Benjamin-Loison/cpython/issues/29 would help, as the script is still running...
The 24 hours duration is not due to
page.wait_for_timeout, but this Python statement is maybe involved in not stopping the script.I restarted the script to see if again face the issue after 24 hours.
I forgot to save the simplified script reproducing the non stopping behavior.
To be notified when someone leaves, as if do not write continuously can't know.
As possibly not multiple persons connected, just knowing that someone left if already nice.
People already connected when the script starts are sent as
last_updated_at, see #issuecomment-4363.argsmatchesid, see #issuecomment-4343. Same if already connected withclient_id.Note that such id changes from a connection to the other for the same authenticated user.
Could maintain a
dictto leverage the associated identity.For the initialization:
Output:
Could merge
{first,last}_nameat initializion to havenameif connected later.Can first rely on
email.Let us consider that Benjamin-Loison/matrix-commander/issues/16 may take multiple seconds and people may leave faster, as may have concurrency of him coming back for instance, well it would be using different ids.
So let us prefer keeping for too long memory content, but notify faster.
Note that if the person being connected when the script starts is
DO_NOT_NOTIFY_ON_EMAIL, then it is not an issue.Related to Benjamin_Loison/WhatsApp/issues/30#issuecomment-7874381.
headless = Falsemay help figure out what's wrong to not receiving a ping anymore.Output:
So
2::does not seem mandatory.Disconnection seems related, but not clearly as there is still a single heartbeat after.
Thanks to logs if not notified should be able to investigate why.
Clicking top bar between Menu and the document title.
Actualiser l'éditeur (not the language mix):
So can make the necessary to always have:
which seems more reliable.
Related to Benjamin_Loison/gnome-screenshot/-/issues/17#note_2589157.
Using
python3 -ito possibly give orders to PlayWright, but I doubt so.Error:
on OverClock3000 reboot due to
crontab:it is probably due to
headless = False.Via RDP I don't have this issue.
Benjamin_Loison/playwright/issues/8 would help.
Benjamin-Loison/pyzo/issues/91 would help.
Output:
Would be nice that if get ping back, then re-enable watcher.
Maybe it is the time of PDF compilation or similar, but I doubt so.
The absence of information let me believe that it is an anonymous connection.
Output:
works as wanted.
Python script:
Output:
Python script:
Output:
Output:
Python script:
hangs at
browser = asyncio.run(p.firefox.launch(headless = False)).Maybe a single
asyncio.runis intended.Output:
Excerpt:
The issue with a function is that interrupting it does not make code interaction possible it seems.
DuckDuckGo and Google search asyncio.run, Playwright interactively, Playwright async interactively and Python Playwright async interactive mode.
https://playwright.dev/docs/test-ui-mode does not seem to help.
https://chat.mistral.ai/chat/4a4cd652-5296-4667-8af3-992f8201ada6
Related to Improve_websites_thanks_to_open_source/issues/2806.
Warning:
https://chatgpt.com/share/6935f6a2-5444-8002-a4d6-d299fb2495c2
In JupyterLab:
does not hang, but does not show any window.
I was about to send to the person:
Message:
Python script:
works as wanted in JupyterLab.
There is no need to
await page.wait_for_timeoutor similar for the cell output to show ongoing WebSocket traffic, while the cell execution is shown as finished as wanted.DuckDuckGo search Playwright click.
https://playwright.dev/docs/input#mouse-click
Error:
https://playwright.dev/python/docs/input#mouse-click
.click(position={ "x": 0, "y": 0})looks interesting.DuckDuckGo search Playwright click at coordinate.
https://playwright.dev/python/docs/api/class-mouse
Error:
hides:
by opening Menu.
DuckDuckGo and Google search Playwright get page resolution.
Related to Benjamin_Loison/playwright/issues/6#issuecomment-8808858.
to test next time, but when there is not the prompt it seems to click at the wanted location.
Related to Benjamin_Loison/firefox/issues/262.
Benjamin_Loison/jupyterlab/issues/19 would help.
Despite having received No Overleaf ping, source: https://matrix.to/#/!sNARMdEsFZERaQAJzl:matrix.org/$a2FPiyr6QjiFlRdkm4RL1g8orkTlzJ4Y3pf7tko2Nno, the loop was still going on it seems, so I interrupted it, but it closed the Playwright window.
Error:
To not crash due to
print(f'Checking at {currentTime} ({lastTime=}, {asyncWait=})')global asyncWaitis necessary inverifyLastTime.DuckDuckGo search Playwright get element by text.
https://playwright.dev/docs/locators#locate-by-text
DuckDuckGo search Playwright Python click.
The Stack Overflow question 76632423 thread seems to show that I proceed correctly.
DuckDuckGo search playwright refresh webpage.
https://playwright.dev/docs/api/class-page#page-reload
results in out of sync prompt if I remember correctly.
I confirm that
await page.reload()solves the issue, now the point is keep listening.An alternative is just to restart the Python script on exit, but it is unsatisfying and it may be inefficient from a script initialization point of view.
Maybe can't in 2 threads proceed to Playwright
asynccalls, even ifawait page.wait_for_timeout(1)is quite inactive.async def verifyLastTime():does not seem executed in thread.DuckDuckGo search Python thread async.
The Stack Overflow answer 61778654 helps.
does not return anything and works as wanted.
await page.reload()blocks forever.Let us try to not use another thread, as it does not seem necessary anymore, as
while asyncWait:was commented and the behavior except expiration was as wanted it seems.In such case only see Checking output which is not satisfying.
DuckDuckGo and Google search Jupyter disable Python output buffer does not seem helpful.
Let us uncomment
while asyncWait:and see if it helps, otherwise will set up a shared boolean and proceed to webpage reload from the main thread.At least now APT and pip packages are up-to-date.
The person:
would be interested.