Skip to content

Conversation

Copy link

Copilot AI commented Nov 26, 2025

The order marking screen and customer-facing order progress screen used polling (setInterval at 5-11s) to fetch new orders. This introduces latency and unnecessary server load.

Changes

Backend (Django Channels)

  • Added channels and daphne to requirements
  • Configured ASGI application with WebSocket routing in pavilion/asgi.py
  • Created WebSocket consumers in pos_server/consumers.py:
    • OrderConsumer at /ws/orders/ for kitchen/bar order marking
    • OrderProgressConsumer at /ws/order-progress/ for customer display
  • Added Django signals in pos_server/signals.py to broadcast order changes on save/delete

Frontend

  • Replaced polling with WebSocket connections in both JS files
  • Added automatic reconnection with exponential backoff
  • Handle initial_orders, order_created, order_updated, order_deleted events

Example

# Signal broadcasts order updates to all connected clients
@receiver(post_save, sender=Order)
def order_saved(sender, instance, created, **kwargs):
    update_type = 'order_created' if created else 'order_updated'
    broadcast_order_update(instance, update_type)
// Client receives real-time updates
ordersSocket.onmessage = function(e) {
    const data = JSON.parse(e.data);
    if (data.type === 'order_created') {
        appendOrder(data.order);
    }
};

Uses InMemoryChannelLayer for simplicity; swap to Redis for horizontal scaling.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com//advisories
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node /home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

Can you upgrade the order display flow from polling for new orders to using websockets and adding new orders to the screen whenever they appear in the system. Make sure both the order marking screen and the customer facing order progress screen are upgraded.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: Collert <17819526+Collert@users.noreply.github.com>
Comment on lines +188 to +209
newOrder.innerHTML = `<div class="summary">
<h2>${data.name ? data.name : gettext("No name")}</h2>
<div class="name-time">
<span>Order #${orderId}</span>
<span data-timestamp="${data.start_time}" class="timestamp">
${gettext("Prep time")}: <span>${data.start_time}</span>
</span>
</div>
</div>
<div>
<h3>
${channel}
</h3>
${progresses.outerHTML}
<h3>
${data.to_go_order ? "<span class='material-symbols-outlined'>takeout_dining</span> " + gettext("Order to-go") : "<span class='material-symbols-outlined'>restaurant</span> " + gettext("Order for here")}
</h3>
<ul id="order${data.order_id}ul">

</ul>
${data.special_instructions ? '<h3>' + gettext("Special instructions") + ':</h3><p>' + data.special_instructions + '</p>' : ''}
</div>`

Check failure

Code scanning / CodeQL

Client-side cross-site scripting High

Cross-site scripting vulnerability due to
user-provided value
.

Copilot Autofix

AI 3 months ago

To eliminate the XSS vulnerability, any untrusted, user-supplied value written to the DOM in an HTML context must be properly escaped or sanitized. The best fix here is to encode all potentially untrusted data (such as data.name and data.special_instructions) before interpolating them into HTML. The correct encoding is HTML-entity encoding, which prevents injected HTML or JavaScript—even if the supplied strings contain special characters.

The fix should provide a robust escapeHtml function—using a well-tested implementation or a library such as he (if allowed in context)—and use it for all variables interpolated into innerHTML. Apply this escaping in the appendOrder function before template construction, replacing all instances of direct interpolation for user-controlled data.

If using libraries is allowed, using a widely-recognized module like he is safest. If not, provide a minimal but complete escape function at the top of the file or within the relevant scope, and use it for each instance of interpolating untrusted values.

Changes to be made (in pos_server/static/pos_server/kitchen-bar.js):

  • Add an escapeHtml function.
  • Wrap all potentially tainted interpolations (data.name, data.special_instructions, data.start_time, etc.) with escapeHtml when constructing HTML in appendOrder.

Suggested changeset 1
pos_server/static/pos_server/kitchen-bar.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/pos_server/static/pos_server/kitchen-bar.js b/pos_server/static/pos_server/kitchen-bar.js
--- a/pos_server/static/pos_server/kitchen-bar.js
+++ b/pos_server/static/pos_server/kitchen-bar.js
@@ -19,6 +19,21 @@
 let autoDoneTimeout;
 let autoCollectTimeout;
 
+/**
+ * Escapes HTML special characters in a string to prevent XSS.
+ * @param {string} unsafe
+ * @returns {string}
+ */
+function escapeHtml(unsafe) {
+    if (typeof unsafe !== 'string') return unsafe;
+    return unsafe
+        .replace(/&/g, "&amp;")
+        .replace(/</g, "&lt;")
+        .replace(/>/g, "&gt;")
+        .replace(/"/g, "&quot;")
+        .replace(/'/g, "&#039;");
+}
+
 function connectWebSocket() {
     const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
     const wsUrl = `${protocol}//${window.location.host}/ws/orders/`;
@@ -189,26 +204,30 @@
         progresses.appendChild(statusStack)
     }
     newOrder.innerHTML = `<div class="summary">
-                                <h2>${data.name ? data.name : gettext("No name")}</h2>                                    
+                                <h2>${data.name ? escapeHtml(data.name) : escapeHtml(gettext("No name"))}</h2>                                    
                                 <div class="name-time">
-                                    <span>Order #${orderId}</span>
-                                    <span data-timestamp="${data.start_time}" class="timestamp">
-                                        ${gettext("Prep time")}: <span>${data.start_time}</span>
+                                    <span>Order #${escapeHtml(String(orderId))}</span>
+                                    <span data-timestamp="${escapeHtml(String(data.start_time))}" class="timestamp">
+                                        ${escapeHtml(gettext("Prep time"))}: <span>${escapeHtml(String(data.start_time))}</span>
                                     </span>
                                 </div>
                             </div>
                             <div>
                                 <h3>
-                                    ${channel}
+                                    ${escapeHtml(channel)}
                                 </h3>
                                 ${progresses.outerHTML}
                                 <h3>
-                                    ${data.to_go_order ? "<span class='material-symbols-outlined'>takeout_dining</span> " + gettext("Order to-go") : "<span class='material-symbols-outlined'>restaurant</span> " + gettext("Order for here")}
+                                    ${data.to_go_order 
+                                        ? "<span class='material-symbols-outlined'>takeout_dining</span> " + escapeHtml(gettext("Order to-go"))
+                                        : "<span class='material-symbols-outlined'>restaurant</span> " + escapeHtml(gettext("Order for here"))}
                                 </h3>
-                                <ul id="order${data.order_id}ul">
+                                <ul id="order${escapeHtml(String(data.order_id))}ul">
                                     
                                 </ul>
-                                ${data.special_instructions ? '<h3>' + gettext("Special instructions") + ':</h3><p>' + data.special_instructions + '</p>' : ''}
+                                ${data.special_instructions 
+                                    ? '<h3>' + escapeHtml(gettext("Special instructions")) + ':</h3><p>' + escapeHtml(data.special_instructions) + '</p>' 
+                                    : ''}
                             </div>`
     newOrder.addEventListener("click", e => {
         updateSelection(cards.findIndex(cd => cd === e.currentTarget))
EOF
@@ -19,6 +19,21 @@
let autoDoneTimeout;
let autoCollectTimeout;

/**
* Escapes HTML special characters in a string to prevent XSS.
* @param {string} unsafe
* @returns {string}
*/
function escapeHtml(unsafe) {
if (typeof unsafe !== 'string') return unsafe;
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}

function connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/orders/`;
@@ -189,26 +204,30 @@
progresses.appendChild(statusStack)
}
newOrder.innerHTML = `<div class="summary">
<h2>${data.name ? data.name : gettext("No name")}</h2>
<h2>${data.name ? escapeHtml(data.name) : escapeHtml(gettext("No name"))}</h2>
<div class="name-time">
<span>Order #${orderId}</span>
<span data-timestamp="${data.start_time}" class="timestamp">
${gettext("Prep time")}: <span>${data.start_time}</span>
<span>Order #${escapeHtml(String(orderId))}</span>
<span data-timestamp="${escapeHtml(String(data.start_time))}" class="timestamp">
${escapeHtml(gettext("Prep time"))}: <span>${escapeHtml(String(data.start_time))}</span>
</span>
</div>
</div>
<div>
<h3>
${channel}
${escapeHtml(channel)}
</h3>
${progresses.outerHTML}
<h3>
${data.to_go_order ? "<span class='material-symbols-outlined'>takeout_dining</span> " + gettext("Order to-go") : "<span class='material-symbols-outlined'>restaurant</span> " + gettext("Order for here")}
${data.to_go_order
? "<span class='material-symbols-outlined'>takeout_dining</span> " + escapeHtml(gettext("Order to-go"))
: "<span class='material-symbols-outlined'>restaurant</span> " + escapeHtml(gettext("Order for here"))}
</h3>
<ul id="order${data.order_id}ul">
<ul id="order${escapeHtml(String(data.order_id))}ul">

</ul>
${data.special_instructions ? '<h3>' + gettext("Special instructions") + ':</h3><p>' + data.special_instructions + '</p>' : ''}
${data.special_instructions
? '<h3>' + escapeHtml(gettext("Special instructions")) + ':</h3><p>' + escapeHtml(data.special_instructions) + '</p>'
: ''}
</div>`
newOrder.addEventListener("click", e => {
updateSelection(cards.findIndex(cd => cd === e.currentTarget))
Copilot is powered by AI and may make mistakes. Always verify output.
for (const dish of data.dishes) {
if (filters.includes(dish.station)) {
const item = document.createElement("li");
item.innerHTML = `${dish.quantity} X ${dish.name}`;

Check failure

Code scanning / CodeQL

Client-side cross-site scripting High

Cross-site scripting vulnerability due to
user-provided value
.

Copilot Autofix

AI 3 months ago

To fix the DOM-based XSS vulnerability, we must ensure that any user-controlled text inserted into HTML is properly escaped. Since we're using .innerHTML, content will be parsed as HTML. The ideal approach is to set the element's text content (using .textContent) instead of HTML whenever possible. This completely avoids issues with HTML injection.

In this snippet, we are populating a list item with a line like "2 X Sandwich". Both dish.quantity and dish.name can be passed as arguments to a function that sets textContent, safely displaying the values as raw text, not HTML.

Specifically:

  • Find the line item.innerHTML = ${dish.quantity} X ${dish.name}; and replace it with:
    item.textContent = `${dish.quantity} X ${dish.name}`;
    
  • This can be done in file pos_server/static/pos_server/kitchen-bar.js inside the block for appendOrder.

No external libraries or dependencies are required.


Suggested changeset 1
pos_server/static/pos_server/kitchen-bar.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/pos_server/static/pos_server/kitchen-bar.js b/pos_server/static/pos_server/kitchen-bar.js
--- a/pos_server/static/pos_server/kitchen-bar.js
+++ b/pos_server/static/pos_server/kitchen-bar.js
@@ -220,7 +220,7 @@
     for (const dish of data.dishes) {
         if (filters.includes(dish.station)) {
             const item = document.createElement("li");
-            item.innerHTML = `${dish.quantity} X ${dish.name}`;
+            item.textContent = `${dish.quantity} X ${dish.name}`;
             list.appendChild(item);
         }
     }
EOF
@@ -220,7 +220,7 @@
for (const dish of data.dishes) {
if (filters.includes(dish.station)) {
const item = document.createElement("li");
item.innerHTML = `${dish.quantity} X ${dish.name}`;
item.textContent = `${dish.quantity} X ${dish.name}`;
list.appendChild(item);
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
Copilot AI and others added 3 commits November 26, 2025 21:18
Co-authored-by: Collert <17819526+Collert@users.noreply.github.com>
Co-authored-by: Collert <17819526+Collert@users.noreply.github.com>
Co-authored-by: Collert <17819526+Collert@users.noreply.github.com>
Copilot AI changed the title [WIP] Update order display to use websockets for real-time updates Upgrade order display from polling to WebSockets Nov 26, 2025
Copilot AI requested a review from Collert November 26, 2025 21:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants