Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions wled00/data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,32 @@ function loadSkinCSS(cId) {
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
}

// load usermod UI inject code (served by the device when usermods provide any);
// umInjectSafe(state) is then called after every state render, see readState()
function loadUmInject(s) {
if (gId("um")) return; // already loaded
let scE = d.createElement("script");
scE.id = "um";
scE.src = getURL("/um.js");
scE.async = false;
scE.onload = () => umInjectSafe(s); // render once with the state captured at load time
scE.onerror = (ev) => {
console.log("Usermod inject script not present or failed to load", ev);
};
d.body.appendChild(scE);
}

// run usermod UI inject code, shielding the UI from exceptions in usermod-provided JS
// (an uncaught throw here would abort readState() and trigger requestJson()'s retry loop)
function umInjectSafe(s) {
if (typeof umInject != "function") return;
try {
umInject(s);
} catch (e) {
console.error("Usermod UI inject error:", e);
}
}
function onLoad()
{
let l = window.location;
Expand Down Expand Up @@ -1604,6 +1630,7 @@ function readState(s,command=false)
selectedFx = i.fx;
redrawPalPrev(); // if any color changed (random palette did at least)
updateUI();
umInjectSafe(s); // usermod UI injections (see loadUmInject())
return true;
}

Expand Down Expand Up @@ -1802,6 +1829,7 @@ async function requestJson(command=null, retry=0) {
}
var s = json.state ? json.state : json;
readState(s);
if (json.info && json.info.u) loadUmInject(s); // usermods present: load their UI inject code

reqsLegal = true;
resolve();
Expand Down
6 changes: 6 additions & 0 deletions wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,10 @@ typedef struct UM_Exchange_Data {
} um_data_t;
const unsigned int um_data_size = sizeof(um_data_t); // 12 bytes

// usermods can inject JS into the main web UI via addUIInjectCode() (served at /um.js);
// external usermods can test this macro to stay compatible with older WLED bases
#define WLED_ENABLE_UM_UI_INJECT

class Usermod {
protected:
um_data_t *um_data; // um_data should be allocated using new in (derived) Usermod's setup() or constructor
Expand All @@ -367,6 +371,7 @@ class Usermod {
virtual bool onUdpPacket(uint8_t* payload, size_t len) { return false; } //fired upon UDP packet received
virtual void onUpdateBegin(bool) {} // fired prior to and after unsuccessful firmware update
virtual void onStateChange(uint8_t mode) {} // fired upon WLED state change
virtual void addUIInjectCode(Print &dest) {} // print JS code injecting UI elements into the main web UI (served at /um.js, run after every state render)
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}

// API shims
Expand Down Expand Up @@ -407,6 +412,7 @@ namespace UsermodManager {
bool onUdpPacket(uint8_t* payload, size_t len);
void onUpdateBegin(bool);
void onStateChange(uint8_t);
void addUIInjectCode(Print &dest);
Usermod* lookup(uint16_t mod_id);
size_t getModCount();
};
Expand Down
1 change: 1 addition & 0 deletions wled00/um_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ bool UsermodManager::onUdpPacket(uint8_t* payload, size_t len) {
}
void UsermodManager::onUpdateBegin(bool init) { for (auto mod = DYNARRAY_BEGIN(usermods); mod < DYNARRAY_END(usermods); ++mod) (*mod)->onUpdateBegin(init); } // notify usermods that update is to begin
void UsermodManager::onStateChange(uint8_t mode) { for (auto mod = DYNARRAY_BEGIN(usermods); mod < DYNARRAY_END(usermods); ++mod) (*mod)->onStateChange(mode); } // notify usermods that WLED state changed
void UsermodManager::addUIInjectCode(Print &dest) { for (auto mod = DYNARRAY_BEGIN(usermods); mod < DYNARRAY_END(usermods); ++mod) (*mod)->addUIInjectCode(dest); } // collect usermod UI inject JS (served at /um.js)

/*
* Enables usermods to lookup another Usermod.
Expand Down
12 changes: 12 additions & 0 deletions wled00/wled_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,18 @@ void initServer()
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), (String)getFreeHeapSize());
});

// usermod UI inject code: the main UI loads this script and calls umInject(state) after
// every state render, letting usermods add their own elements without patching index.js
server.on(F("/um.js"), HTTP_GET, [](AsyncWebServerRequest *request) {
AsyncResponseStream *response = request->beginResponseStream(FPSTR(CONTENT_TYPE_JAVASCRIPT));
response->addHeader(FPSTR(s_cache_control), F("no-store"));
response->addHeader(F("Expires"), F("0"));
response->print(F("function umInject(s){"));
UsermodManager::addUIInjectCode(*response);
response->print(F("}"));
request->send(response);
});

#ifdef WLED_ENABLE_USERMOD_PAGE
server.on("/u", HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, "", 200, FPSTR(CONTENT_TYPE_HTML), PAGE_usermod, PAGE_usermod_length);
Expand Down
Loading