I've set up a Telegram bot that launches a web app, in any way: inline button, keyboard, link... Once the tasks in the web app are completed, the user may close the web app using the "close" button and not through any button in my web app that I can log.
I'd like my bot to do some operations in the database when this happens.
How can I detect when the web app closes?
answerWebAppQuery
cannot be used for this purpose, unload
, beforeunload
, pagehide
and blur
and similar events are not detected, and the web_app_close
event is fired from the app to the bot and as such cannot be intercepted. How can I know when the web app was closed? There is a possible workaround but it relies on the user finishing the interaction the way I want them to do and not to use the "close" button, which is unrealistic.
A workaround that works better is using socket.io. You store data on the socket before the connection closes; when the app is closed, the connection dies and you can do whatever you want with that data on the backend side. The most important limitation is that this works only with one worker on your web server (see Gunicorn details below). If you need more workers you start getting into sticky session territory. Everything would be easier if we could just have an "onClose" event somewhere, or intercept "web_app_close" on the app side...
On the JS side:
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<script type="text/javascript">
let socket = io.connect(window.location.origin);
socket.on("connect", function() {
alert("Connected to the server");
socket.emit("store_client_data", {
"field": "content"
});
});
</script>
On the server side, example using Flask:
from flask import Flask, request
from flask_socketio import SocketIO
import requests
app = Flask(__name__)
socketio = SocketIO(app)
# ...other routes...
clients_data = {}
@socketio.on("store_client_data")
def handle_store_client_data(data):
# Save client's data using their session ID (or any other unique identifier)
clients_data[request.sid] = data
print(f"Data stored for client {request.sid}")
@socketio.on("connect")
def handle_connect():
print("Client connected", flush=True)
@socketio.on("disconnect")
def handle_disconnect():
print("Client disconnected", flush=True)
client_data = clients_data.get(request.sid, None)
# Send POST request
response = requests.post("http://localhost/onclose", json=client_data)
# Remove data from dictionary to free up memory
del clients_data[request.sid]
if __name__ == "__main__":
socketio.run(app, async_mode="gevent", host="0.0.0.0", port=8000)
Where /onclose
is a route that does the thing you want.
Example web server configuration using Nginx and Let's Encrypt certificates:
server {
listen 80;
location / {
proxy_pass http://0.0.0.0:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 443 ssl;
server_name test.mywebsite.org;
ssl_certificate /etc/letsencrypt/live/test.mywebsite.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/test.mywebsite.org/privkey.pem;
location / {
proxy_pass http://0.0.0.0:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /socket.io/ {
proxy_pass http://0.0.0.0:8000/socket.io/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Example Gunicorn command to start the web app:
/usr/bin/env gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker --workers 1 --bind 0.0.0.0:8000 app.app:app