hotfix jointlist
This commit is contained in:
+41
-14
@@ -221,30 +221,57 @@ def handle_join(data):
|
||||
global active_users
|
||||
room = str(data["room"])
|
||||
username = data.get("username", "Gość")
|
||||
sid = request.sid
|
||||
|
||||
# Trzymamy obecność per SID, a nie tylko per nazwa użytkownika. Dzięki temu
|
||||
# ponowiony join z tego samego połączenia jest idempotentny, a dwie karty
|
||||
# tego samego użytkownika nie psują statusu obecności przy disconnect.
|
||||
room_users = active_users.setdefault(room, {})
|
||||
already_joined = sid in room_users
|
||||
room_users[sid] = username
|
||||
join_room(room)
|
||||
|
||||
if room not in active_users:
|
||||
active_users[room] = set()
|
||||
active_users[room].add(username)
|
||||
|
||||
shopping_list = db.session.get(ShoppingList, int(data["room"]))
|
||||
|
||||
list_title = shopping_list.title if shopping_list else "Twoja lista"
|
||||
|
||||
emit("user_joined", {"username": username}, to=room)
|
||||
emit("user_list", {"users": list(active_users[room])}, to=room)
|
||||
emit("joined_confirmation", {"room": room, "list_title": list_title})
|
||||
if not already_joined:
|
||||
# Nie pokazujemy użytkownikowi komunikatu, że sam właśnie dołączył.
|
||||
emit("user_joined", {"username": username}, to=room, include_self=False)
|
||||
emit(
|
||||
"user_list",
|
||||
{"users": sorted(set(room_users.values()))},
|
||||
to=room,
|
||||
)
|
||||
|
||||
emit(
|
||||
"joined_confirmation",
|
||||
{"room": room, "list_title": list_title},
|
||||
to=sid,
|
||||
)
|
||||
return {"ok": True, "room": room, "already_joined": already_joined}
|
||||
|
||||
|
||||
@socketio.on("disconnect")
|
||||
def handle_disconnect(sid):
|
||||
def handle_disconnect(_reason=None):
|
||||
global active_users
|
||||
username = current_user.username if current_user.is_authenticated else "Gość"
|
||||
for room, users in active_users.items():
|
||||
if username in users:
|
||||
users.remove(username)
|
||||
sid = request.sid
|
||||
|
||||
for room, room_users in list(active_users.items()):
|
||||
if not isinstance(room_users, dict) or sid not in room_users:
|
||||
continue
|
||||
|
||||
username = room_users.pop(sid)
|
||||
remaining_users = sorted(set(room_users.values()))
|
||||
|
||||
# Jeśli ten sam użytkownik ma drugą kartę/urządzenie w pokoju,
|
||||
# nie ogłaszamy jego wyjścia dopóki ostatnie połączenie nie zniknie.
|
||||
if username not in room_users.values():
|
||||
emit("user_left", {"username": username}, to=room)
|
||||
emit("user_list", {"users": list(users)}, to=room)
|
||||
|
||||
emit("user_list", {"users": remaining_users}, to=room)
|
||||
|
||||
if not room_users:
|
||||
active_users.pop(room, None)
|
||||
|
||||
|
||||
@socketio.on("add_item")
|
||||
|
||||
@@ -27,7 +27,11 @@ function setupList(listId, username) {
|
||||
window.usernameForReconnect = username;
|
||||
window.CURRENT_LIST_USERNAME = username;
|
||||
|
||||
socket.emit('join_list', { room: listId, username: username });
|
||||
// Jedno miejsce odpowiada za dołączanie do pokoju. Dzięki temu setupList()
|
||||
// nie ściga się z eventem `connect` i nie wysyła podwójnego join_list.
|
||||
if (typeof window.ensureCurrentListJoined === 'function') {
|
||||
window.ensureCurrentListJoined();
|
||||
}
|
||||
|
||||
const newItemInput = document.getElementById('newItem');
|
||||
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
let didReceiveFirstFullList = false;
|
||||
|
||||
// Przeglądarki mobilne i desktopowe mogą zamrozić kartę lub zerwać transport
|
||||
// bez natychmiastowej zmiany socket.connected. Po wznowieniu odświeżamy transport,
|
||||
// ponownie dołączamy do pokoju i pobieramy aktualny stan listy.
|
||||
const WAKE_RECONNECT_THRESHOLD_MS = 2500;
|
||||
// Karta może zostać zamrożona lub transport może umrzeć bez natychmiastowej
|
||||
// zmiany socket.connected. Po wznowieniu najpierw weryfikujemy połączenie ACK-em.
|
||||
// Reconnect robimy dopiero wtedy, gdy transport faktycznie nie odpowiada.
|
||||
const WAKE_RECONNECT_DEBOUNCE_MS = 200;
|
||||
const WAKE_WATCHDOG_INTERVAL_MS = 10000;
|
||||
const WAKE_WATCHDOG_GAP_MS = 25000;
|
||||
let pageHiddenAt = null;
|
||||
let recoveryTimer = null;
|
||||
let recoveryForceRequested = false;
|
||||
let recoveryInProgress = false;
|
||||
let firstConnect = true;
|
||||
let wasReconnected = false;
|
||||
let lastWatchdogTick = Date.now();
|
||||
let joinedSocketId = null;
|
||||
let joinedRoomId = null;
|
||||
|
||||
function hasActiveListRoom() {
|
||||
return Boolean(window.LIST_ID && window.usernameForReconnect);
|
||||
@@ -23,8 +21,6 @@ function disableCheckboxes(disable) {
|
||||
document.querySelectorAll('#items input[type="checkbox"]').forEach(cb => {
|
||||
if (disable) {
|
||||
// Zapamiętaj stan biznesowy tylko przy pierwszym nałożeniu blokady transportu.
|
||||
// Dzięki temu checkboxy wyłączone np. przez archiwizację nie zostaną
|
||||
// przypadkiem odblokowane po ponownej synchronizacji.
|
||||
if (cb.dataset.socketDisabledBefore === undefined) {
|
||||
cb.dataset.socketDisabledBefore = cb.disabled ? '1' : '0';
|
||||
}
|
||||
@@ -39,14 +35,27 @@ function disableCheckboxes(disable) {
|
||||
});
|
||||
}
|
||||
|
||||
function rejoinCurrentList() {
|
||||
if (!socket.connected || !hasActiveListRoom()) return;
|
||||
function ensureCurrentListJoined() {
|
||||
if (!socket.connected || !hasActiveListRoom()) return false;
|
||||
|
||||
const roomId = String(window.LIST_ID);
|
||||
const socketId = socket.id || null;
|
||||
|
||||
// join_list może przyjść zarówno z setupList(), jak i z eventu connect.
|
||||
// Ten klucz sprawia, że dla jednego fizycznego połączenia wysyłamy go tylko raz.
|
||||
if (joinedSocketId === socketId && joinedRoomId === roomId) return false;
|
||||
|
||||
joinedSocketId = socketId;
|
||||
joinedRoomId = roomId;
|
||||
socket.emit('join_list', {
|
||||
room: window.LIST_ID,
|
||||
username: window.usernameForReconnect
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
window.ensureCurrentListJoined = ensureCurrentListJoined;
|
||||
|
||||
function requestCurrentList({ verify = false } = {}) {
|
||||
if (!socket.connected || !window.LIST_ID) return;
|
||||
|
||||
@@ -64,6 +73,7 @@ function requestCurrentList({ verify = false } = {}) {
|
||||
function recoverListSocket({ force = false } = {}) {
|
||||
recoveryForceRequested = recoveryForceRequested || force;
|
||||
clearTimeout(recoveryTimer);
|
||||
|
||||
recoveryTimer = setTimeout(() => {
|
||||
const shouldForce = recoveryForceRequested;
|
||||
recoveryForceRequested = false;
|
||||
@@ -80,15 +90,18 @@ function recoverListSocket({ force = false } = {}) {
|
||||
if (recoveryInProgress) return;
|
||||
recoveryInProgress = true;
|
||||
disableCheckboxes(true);
|
||||
joinedSocketId = null;
|
||||
joinedRoomId = null;
|
||||
|
||||
// disconnect/connect czyści potencjalnie "martwy" transport po uśpieniu karty/procesu.
|
||||
// Twardy reconnect tylko po realnym błędzie/timeout ACK, nie po samym
|
||||
// przełączeniu karty lub powrocie aplikacji na pierwszy plan.
|
||||
socket.disconnect();
|
||||
setTimeout(() => socket.connect(), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasActiveListRoom()) {
|
||||
rejoinCurrentList();
|
||||
// Jeśli transport żyje, synchronizacja wystarczy. Timeout sam uruchomi reconnect.
|
||||
requestCurrentList({ verify: true });
|
||||
}
|
||||
}, WAKE_RECONNECT_DEBOUNCE_MS);
|
||||
@@ -97,42 +110,26 @@ function recoverListSocket({ force = false } = {}) {
|
||||
// Udostępnione dla akcji w live.js (np. timeout checkboxa).
|
||||
window.recoverListSocket = recoverListSocket;
|
||||
|
||||
// Powrót po zablokowaniu telefonu / zmianie aplikacji.
|
||||
// Powrót po zablokowaniu telefonu, zmianie aplikacji lub karty.
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.hidden) {
|
||||
pageHiddenAt = Date.now();
|
||||
return;
|
||||
}
|
||||
|
||||
const hiddenFor = pageHiddenAt === null ? 0 : Date.now() - pageHiddenAt;
|
||||
pageHiddenAt = null;
|
||||
recoverListSocket({ force: hiddenFor >= WAKE_RECONNECT_THRESHOLD_MS });
|
||||
if (!document.hidden) recoverListSocket();
|
||||
});
|
||||
|
||||
// BFCache może przywrócić stronę ze starym transportem Socket.IO.
|
||||
window.addEventListener('pageshow', event => {
|
||||
recoverListSocket({ force: Boolean(event.persisted) });
|
||||
});
|
||||
|
||||
window.addEventListener('pagehide', () => {
|
||||
pageHiddenAt = Date.now();
|
||||
window.addEventListener('pageshow', () => {
|
||||
recoverListSocket();
|
||||
});
|
||||
|
||||
window.addEventListener('focus', () => {
|
||||
recoverListSocket({ force: false });
|
||||
recoverListSocket();
|
||||
});
|
||||
|
||||
window.addEventListener('online', () => {
|
||||
recoverListSocket({ force: hasActiveListRoom() });
|
||||
});
|
||||
|
||||
// Dodatkowe lifecycle events obsługiwane przez część przeglądarek mobilnych.
|
||||
document.addEventListener('freeze', () => {
|
||||
pageHiddenAt = Date.now();
|
||||
recoverListSocket();
|
||||
});
|
||||
|
||||
document.addEventListener('resume', () => {
|
||||
recoverListSocket({ force: hasActiveListRoom() });
|
||||
recoverListSocket();
|
||||
});
|
||||
|
||||
// Watchdog łapie także wznowienia, przy których przeglądarka nie wyśle poprawnie
|
||||
@@ -143,30 +140,24 @@ setInterval(() => {
|
||||
lastWatchdogTick = now;
|
||||
|
||||
if (!document.hidden && gap >= WAKE_WATCHDOG_GAP_MS) {
|
||||
recoverListSocket({ force: hasActiveListRoom() });
|
||||
recoverListSocket();
|
||||
}
|
||||
}, WAKE_WATCHDOG_INTERVAL_MS);
|
||||
|
||||
socket.on('connect', function () {
|
||||
const isReconnect = !firstConnect;
|
||||
recoveryInProgress = false;
|
||||
joinedSocketId = null;
|
||||
joinedRoomId = null;
|
||||
|
||||
if (hasActiveListRoom()) {
|
||||
if (isReconnect) {
|
||||
disableCheckboxes(true);
|
||||
wasReconnected = true;
|
||||
}
|
||||
rejoinCurrentList();
|
||||
|
||||
// Nie polegamy wyłącznie na joined_confirmation — po wake-up lista ma się
|
||||
// zsynchronizować nawet jeśli pojedyncze zdarzenie zaginie.
|
||||
setTimeout(requestCurrentList, 250);
|
||||
disableCheckboxes(true);
|
||||
ensureCurrentListJoined();
|
||||
}
|
||||
|
||||
firstConnect = false;
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
joinedSocketId = null;
|
||||
joinedRoomId = null;
|
||||
if (window.LIST_ID) disableCheckboxes(true);
|
||||
});
|
||||
|
||||
@@ -175,11 +166,9 @@ socket.on('connect_error', function () {
|
||||
});
|
||||
|
||||
socket.off('joined_confirmation');
|
||||
socket.on('joined_confirmation', function (data) {
|
||||
if (wasReconnected) {
|
||||
showToast(`Lista: ${data.list_title} – ponownie dołączono.`, 'info');
|
||||
wasReconnected = false;
|
||||
}
|
||||
socket.on('joined_confirmation', function () {
|
||||
// Potwierdzenie dołączenia nie jest komunikatem dla użytkownika. Po zwykłym
|
||||
// reconnectcie karta ma wrócić do działania bez sztucznego "ponownie dołączono".
|
||||
requestCurrentList();
|
||||
});
|
||||
|
||||
|
||||
@@ -565,6 +565,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ static_asset_url('static_bp.serve_js_lib', 'Sortable.min.js') }}"></script>
|
||||
<script>
|
||||
@@ -586,5 +588,3 @@
|
||||
</script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'list_item_form.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -374,6 +374,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
const isShare = document.getElementById('items').dataset.isShare === 'true';
|
||||
@@ -396,5 +398,3 @@
|
||||
</script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'list_item_form.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user