-
Notifications
You must be signed in to change notification settings - Fork 1
Upgrade order display from polling to WebSockets #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: Collert <17819526+Collert@users.noreply.github.com>
| 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
user-provided value
Show autofix suggestion
Hide autofix suggestion
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
escapeHtmlfunction. - Wrap all potentially tainted interpolations (
data.name,data.special_instructions,data.start_time, etc.) withescapeHtmlwhen constructing HTML inappendOrder.
-
Copy modified lines R22-R36 -
Copy modified line R207 -
Copy modified lines R209-R211 -
Copy modified line R217 -
Copy modified lines R221-R223 -
Copy modified line R225 -
Copy modified lines R228-R230
| @@ -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, "&") | ||
| .replace(/</g, "<") | ||
| .replace(/>/g, ">") | ||
| .replace(/"/g, """) | ||
| .replace(/'/g, "'"); | ||
| } | ||
|
|
||
| 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)) |
| 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
user-provided value
Show autofix suggestion
Hide autofix suggestion
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.jsinside the block forappendOrder.
No external libraries or dependencies are required.
-
Copy modified line R223
| @@ -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); | ||
| } | ||
| } |
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>
The order marking screen and customer-facing order progress screen used polling (
setIntervalat 5-11s) to fetch new orders. This introduces latency and unnecessary server load.Changes
Backend (Django Channels)
channelsanddaphneto requirementspavilion/asgi.pypos_server/consumers.py:OrderConsumerat/ws/orders/for kitchen/bar order markingOrderProgressConsumerat/ws/order-progress/for customer displaypos_server/signals.pyto broadcast order changes on save/deleteFrontend
initial_orders,order_created,order_updated,order_deletedeventsExample
Uses
InMemoryChannelLayerfor 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/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
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.