feat(streaming): suppress WS echo via origin tagging
Inject _origin=ws:<clientId> into JSON payloads on inbound WS messages and strip it on broadcast while skipping the origin client. Documents behavior in StreamingAPI.md.
This commit is contained in:
@@ -110,8 +110,6 @@ void ApiServer::setupWebSocket() {
|
||||
DeserializationError err = deserializeJson(doc, (const char*)data, len);
|
||||
if (!err) {
|
||||
LOG_INFO("API", "Received event: " + String(doc["event"].as<String>()));
|
||||
String raw; raw.reserve(len + 1); for (size_t i = 0; i < len; ++i) raw += (char)data[i];
|
||||
LOG_INFO("API", "Received raw WS data: " + raw);
|
||||
String eventName = doc["event"].as<String>();
|
||||
String payloadStr;
|
||||
if (doc["payload"].is<const char*>()) {
|
||||
@@ -122,8 +120,19 @@ void ApiServer::setupWebSocket() {
|
||||
}
|
||||
// Allow empty payload; services may treat it as defaults
|
||||
if (eventName.length() > 0) {
|
||||
// Inject origin tag into payload JSON if possible
|
||||
String enriched = payloadStr;
|
||||
if (payloadStr.length() > 0) {
|
||||
JsonDocument pd;
|
||||
if (!deserializeJson(pd, payloadStr)) {
|
||||
pd["_origin"] = String("ws:") + String(client->id());
|
||||
String tmp; serializeJson(pd, tmp); enriched = tmp;
|
||||
} else {
|
||||
// If payload is plain string, leave as-is (no origin)
|
||||
}
|
||||
}
|
||||
std::string ev = eventName.c_str();
|
||||
ctx.fire(ev, &payloadStr);
|
||||
ctx.fire(ev, &enriched);
|
||||
// Acknowledge
|
||||
client->text("{\"ok\":true}");
|
||||
} else {
|
||||
@@ -135,23 +144,45 @@ void ApiServer::setupWebSocket() {
|
||||
}
|
||||
} else if (type == WS_EVT_CONNECT) {
|
||||
client->text("{\"hello\":\"ws connected\"}");
|
||||
wsClients.push_back(client);
|
||||
} else if (type == WS_EVT_DISCONNECT) {
|
||||
// No-op
|
||||
wsClients.erase(std::remove(wsClients.begin(), wsClients.end(), client), wsClients.end());
|
||||
}
|
||||
});
|
||||
|
||||
// Subscribe to all local events and forward to websocket clients
|
||||
ctx.onAny([this](const std::string& event, void* dataPtr) {
|
||||
String* payloadStr = static_cast<String*>(dataPtr);
|
||||
JsonDocument doc;
|
||||
doc["event"] = event.c_str();
|
||||
if (payloadStr) {
|
||||
doc["payload"] = *payloadStr;
|
||||
} else {
|
||||
doc["payload"] = "";
|
||||
String* payloadStrPtr = static_cast<String*>(dataPtr);
|
||||
String payloadStr = payloadStrPtr ? *payloadStrPtr : String("");
|
||||
|
||||
// Extract and strip origin if present
|
||||
String origin;
|
||||
String cleanedPayload = payloadStr;
|
||||
if (payloadStr.length() > 0) {
|
||||
JsonDocument pd;
|
||||
if (!deserializeJson(pd, payloadStr)) {
|
||||
if (pd["_origin"].is<const char*>()) {
|
||||
origin = pd["_origin"].as<const char*>();
|
||||
pd.remove("_origin");
|
||||
String tmp; serializeJson(pd, tmp); cleanedPayload = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonDocument outDoc;
|
||||
outDoc["event"] = event.c_str();
|
||||
outDoc["payload"] = cleanedPayload;
|
||||
String out; serializeJson(outDoc, out);
|
||||
|
||||
if (origin.startsWith("ws:")) {
|
||||
uint32_t originId = (uint32_t)origin.substring(3).toInt();
|
||||
for (auto* c : wsClients) {
|
||||
if (c && c->id() != originId) {
|
||||
c->text(out);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ws.textAll(out);
|
||||
}
|
||||
String out;
|
||||
serializeJson(doc, out);
|
||||
ws.textAll(out);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user