ASGI+Daphne+Channels+WebSockets chat app works for ~10 users but 2 phones have strange issues? How debug?

I wrote a Django chat web app that uses ASGI, Django Channels, Daphne and WebSockets.
Around 10 users are using it fine but only 2 have issues.

One says she SOMETIMES has to type her message in the chat form TWICE since the
first time it is sometimes ignored.

Another says SOME chat rooms do not let her type messages but only see other people’s messages.

The issues go away on a different computer but on here IPHONE the issues persist.
What would an iPhone do to make these sporadic issues happen?

Here is the relevant ASGI channels code..

import django.core.asgi
import os

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
APP = django.core.asgi.get_asgi_application()

import bighelp.models
import asgiref.sync
import channels.auth
import channels.routing
import channels.generic.websocket
import django.urls
import json

ROOM    = bighelp.models.Room
MESSAGE = bighelp.models.Message
WRAP    = asgiref.sync.sync_to_async

class EventHandler(channels.generic.websocket.AsyncWebsocketConsumer):
        async def connect(self):
                self.room_ = self.scope["url_route"]["kwargs"]["room_"]
                await self.channel_layer.group_add(self.room_,
                                                   self.channel_name)
                await self.accept()

        async def disconnect(self, code):
                await self.channel_layer.group_discard(self.room_,
                                                       self.channel_name)

        async def receive(self, text_data):
                text  = json.loads(text_data)["text"]
                user  = self.scope["user"]
                user_ = f"{user.first_name} {user.last_name}"
                room_ = self.scope["url_route"]["kwargs"]["room_"]
                room  = await WRAP(ROOM.objects.get)(name = room_)
                await WRAP(MESSAGE.objects.create)(user = user,
                                                   room = room,
                                                   text = text)
                await self.channel_layer.group_send(self.room_,
                                                    {"type"  : "send_",
                                                     "user_" : user_,
                                                     "text"  : text})

        async def send_(self, event):
                text_data = json.dumps({"user_" : event["user_"],
                                        "text"  : event["text"]})
                await self.send(text_data = text_data)

websocket   = [django.urls.re_path(r"ws/chat/(?P<room_>\w+)/$",
                                   EventHandler.as_asgi())]
websocket   = channels.routing.URLRouter(websocket)
websocket   = channels.auth.AuthMiddlewareStack(websocket)
application = channels.routing.ProtocolTypeRouter({"http"      : APP,
                                                   "websocket" : websocket})

and here is the template code…

{% extends "html_base" %}
{% block body_elements %}

<div class = "page" id = "room">

<h1>Chat Room - {{alias}}</h1>

<p>
        <a class = "pink"
           {% if prov %}
                   href = "/provs_appts_lim?room_={{room_}}"
           {% else %}
                   href = "/main?room_={{room_}}"
           {% endif %}>
                Back To Main
        </a>
</p>

<div id = "chat">
        <table id = "chat_table">
                {% for m in messages %}
                     {% with u=m.user.first_name|add:" "|add:m.user.last_name %}
                        <tr {% if   u          == user_ %}
                                    class = "user_"
                            {% elif user_      in m.text %}
                                    class = "user_"
                            {% elif "Everyone" in m.text %}
                                    class = "user_"
                            {% endif %}>
                                <td>[{{m.dt|date:"Y-m-d h:iA"|lower}}]</td>
                                <td>&nbsp;{{u}}</td>
                                <td>&nbsp;|&nbsp;</td>
                                <td>{{m.text|safe}}</td>
                        </tr>
                     {% endwith %}
                {% endfor %}
        </table>
</div>

<p></p>

<input type = "text" id = "box"/>
<button type = "button" id = "btn">Send</button>

<p>&nbsp;</p>

<h2>Available Chat Rooms</h2>

<div
        {% with acct_str=acct|stringformat:"s" %}
                {% if eo_blink and room_ != "Everyone"|add:acct_str %}
                        class = "blink"
                {% endif %}
        {% endwith %}>
        Everyone : <a href = "/chat/Everyone{{acct}}?room_={{room_}}">Enter</a>
</div>
{% for e in rbmi %}
        <div  {% if e.2 and e.1 != room_ %} class = "blink" {% endif %}>
                {{e.0}} : <a href = "/chat/{{e.1}}?room_={{room_}}">Enter</a>
        </div>
{% endfor %}

<p>&nbsp;</p>

<h2>New Chat Room</h2>

<form action = "." method = "post"> {% csrf_token %}
        <div>Participants To Add: {{form.users.errors}}</div>
        <p>{{form.users}}</p>

        <input type = "submit" value = "Enter">
</form>

<p>
        <a class = "pink"
           {% if prov %}
                   href = "/provs_appts_lim?room_={{room_}}"
           {% else %}
                   href = "/main?room_={{room_}}"
           {% endif %}>
                Back To Main
        </a>
</p>

</div>

<script>
        chat     = document.getElementById("chat");
        chat_tab = document.getElementById("chat_table");
        btn      = document.getElementById("btn");
        box      = document.getElementById("box");
        domain   = window.location.host;
        ws       = new WebSocket(`wss://${domain}/ws/chat/{{room_}}/`);
        url_re   = /(https:\/\/[^\s]+)/g;
        tabs     = {{tabs|safe}};
        tab      = 9;

        function dt() {
                now   = new Date();
                year  = now.getFullYear();
                month = String(now.getMonth() + 1).padStart(2, "0");
                day   = String(now.getDate()).padStart(2, "0");
                hour  = now.getHours();
                min   = now.getMinutes().toString().padStart(2, "0");
                ampm  = "am";
                if (hour >= 12) {ampm = "pm";}
                hour = (hour % 12).toString().padStart(2, "0");
                if (hour == "00") {hour = "12"};

                return `${year}-${month}-${day} ${hour}:${min}${ampm}`;
        }

        function replace_on_tab(event) {
                code = event.keyCode || event.which;
                if (code === tab) {
                        event.preventDefault();
                        index      = box.value.lastIndexOf(" ") + 1;
                        search     = box.value.substring(index);
                        replace    = tabs.find(tab => tab.startsWith(search));
                        replace   += ": ";
                        box.value  = box.value.substring(0, index) + replace;
                }
        }

        function send_box_text(event) {
                text = box.value.trim().replace(url_re, function(url) {
                            return '<a href = "' + url + '">' + url + "</a>";});
                if (text !== "") {
                        ws.send(JSON.stringify({"text": text}));
                        box.value = "";
                }
        }

        function print_box_text(event) {
                message             = JSON.parse(event.data);
                tr                  = "<tr>";
                if ((message.user_ == "{{user_}}")     ||
                    message.text.includes("{{user_}}") ||
                    message.text.includes("Everyone")) {
                        tr = '<tr class = "user_">';
                }
                chat_tab.innerHTML += `${tr}
                                           <td>[${dt()}]</td>
                                           <td>&nbsp;${message.user_}</td>
                                           <td>&nbsp;|&nbsp;</td>
                                           <td>${message.text}</td>
                                       </tr>`
                chat.scrollTop      = chat.scrollHeight;
        }

        btn.addEventListener("click",   send_box_text);
        box.addEventListener("keydown", replace_on_tab);
        ws.onmessage = print_box_text;
</script>

{% endblock %}

To start the debugging process, the first things I’d do would be to add some code to the consumer to print (or log) all messages received from the phone - try to determine if the messages are even being sent.

If you’re not seeing them in the consumer, then it’s an issue of debugging the JavaScript in the phone to identify why those messages aren’t being sent through the websocket.

Ken

Thanks. How do enterprise chat apps do it? My fear is every odd phone
would have some new reason it kills WebSocket connections. So, it seems
like it would take a long time to get a production ready Django chat app
that was robust across nearly all phone types.

Related to that difficulty, debugging Javascript would have to be done
on the client’s browser. Hopefully, it is an issue with the server consumer code!

I will try your good idea of adding print statements to the consumer. If that doesn’t solve
it, then things get much trickier.

Thanks again,

Chris

Automatic reconnect and retry on the websocket being dropped. (And if necessary, periodic handshake packets to ensure the connection remains alive.)

Websockets are not reliable, especially in a mobile environment. They will drop. WiFi is better, but still not perfect. (Wired ethernet does tend to be extremely reliable.)

Yep… Not only that, but also on every browser supported by the platform.

It won’t be. Not based upon the symptoms you are describing here. If it’s limited to one platform, then it’s almost assuredly an issue with that platform.

Automatic reconnect and retry on the websocket being dropped.
(And if necessary, periodic handshake packets to ensure the connection
remains alive.)

Ken

Thanks. The above does seems like what the pros would do. Thanks. I’ll try it. You’ve
given me hope.

cs