WebSocket event handler for fast binary uploads Protocol: 1. Client sends TEXT message: "START: : : " 2. Client sends BINARY messages with file data chunks 3. Server sends TEXT "PROGRESS: : " after each chunk 4. Server sends TEXT "DONE" or "ERROR: " when complete
| 1535 | // 3. Server sends TEXT "PROGRESS:<received>:<total>" after each chunk |
| 1536 | // 4. Server sends TEXT "DONE" or "ERROR:<message>" when complete |
| 1537 | void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) { |
| 1538 | switch (type) { |
| 1539 | case WStype_DISCONNECTED: |
| 1540 | LOG_DBG("WS", "Client %u disconnected", num); |
| 1541 | // Only clean up if this is the client that owns the active upload. |
| 1542 | // A new client may have already started a fresh upload before this |
| 1543 | // DISCONNECTED event fires (race condition on quick cancel + retry). |
| 1544 | if (num == wsUploadClientNum && wsUploadInProgress && wsUploadFile) { |
| 1545 | abortWsUpload("WS"); |
| 1546 | } |
| 1547 | break; |
| 1548 | |
| 1549 | case WStype_CONNECTED: { |
| 1550 | LOG_DBG("WS", "Client %u connected", num); |
| 1551 | break; |
| 1552 | } |
| 1553 | |
| 1554 | case WStype_TEXT: { |
| 1555 | // Parse control messages |
| 1556 | String msg = String((char*)payload); |
| 1557 | LOG_DBG("WS", "Text from client %u: %s", num, msg.c_str()); |
| 1558 | |
| 1559 | if (msg.startsWith("START:")) { |
| 1560 | // Reject any START while an upload is already active to prevent |
| 1561 | // leaking the open wsUploadFile handle (owning client re-START included) |
| 1562 | if (wsUploadInProgress) { |
| 1563 | wsServer->sendTXT(num, "ERROR:Upload already in progress"); |
| 1564 | break; |
| 1565 | } |
| 1566 | |
| 1567 | // Parse: START:<filename>:<size>:<path> |
| 1568 | int firstColon = msg.indexOf(':', 6); |
| 1569 | int secondColon = msg.indexOf(':', firstColon + 1); |
| 1570 | |
| 1571 | if (firstColon > 0 && secondColon > 0) { |
| 1572 | wsUploadFileName = msg.substring(6, firstColon); |
| 1573 | String sizeToken = msg.substring(firstColon + 1, secondColon); |
| 1574 | bool sizeValid = sizeToken.length() > 0; |
| 1575 | int digitStart = (sizeValid && sizeToken[0] == '+') ? 1 : 0; |
| 1576 | if (digitStart > 0 && sizeToken.length() < 2) sizeValid = false; |
| 1577 | for (int i = digitStart; i < (int)sizeToken.length() && sizeValid; i++) { |
| 1578 | if (!isdigit((unsigned char)sizeToken[i])) sizeValid = false; |
| 1579 | } |
| 1580 | if (!sizeValid) { |
| 1581 | LOG_DBG("WS", "START rejected: invalid size token '%s'", sizeToken.c_str()); |
| 1582 | wsServer->sendTXT(num, "ERROR:Invalid START format"); |
| 1583 | return; |
| 1584 | } |
| 1585 | wsUploadSize = sizeToken.toInt(); |
| 1586 | wsUploadPath = msg.substring(secondColon + 1); |
| 1587 | wsUploadReceived = 0; |
| 1588 | wsLastProgressSent = 0; |
| 1589 | wsUploadStartTime = millis(); |
| 1590 | |
| 1591 | // Ensure path is valid |
| 1592 | if (!wsUploadPath.startsWith("/")) wsUploadPath = "/" + wsUploadPath; |
| 1593 | if (wsUploadPath.length() > 1 && wsUploadPath.endsWith("/")) { |
| 1594 | wsUploadPath = wsUploadPath.substring(0, wsUploadPath.length() - 1); |
no test coverage detected