The Roku Remote Meets a Real Roku
The Electron port of my old Roku remote worked perfectly against the emulator. The emulator is a Node.js process I wrote that speaks SSDP and ECP over localhost. Everything passed. I ordered two Rokus off eBay to test on real hardware.
They arrived. I launched the app. It found the Roku, showed the device name, and then did nothing. Buttons silent. Channel list empty.
Two bugs. Neither showed up in the emulator.
Bug one: Electron's renderer cannot fetch local HTTP
The app discovers devices in the Electron main process using Node's dgram module for SSDP. That part worked fine on real hardware - the Roku responded to multicast, the main process fetched the device info, the name appeared in the UI.
But every subsequent HTTP call - keypresses, the channel list, channel icons - ran in the renderer process. Direct fetch() calls to http://192.168.x.x:8060/.
Electron's renderer loads pages via file://. The browser security model that Electron inherits treats file:// pages as having a null origin, and HTTP requests to local network addresses from that context are blocked. The calls were silently failing. The catch handler on the device-info fetch set rokurl = null, which made every button press show the device picker instead of sending a keypress.
The emulator ran on localhost. Localhost is treated differently. The bug was invisible until real hardware put a real IP in the URL.
The fix is one pattern applied consistently: everything HTTP goes through the main process via IPC, the same way SSDP discovery already did. Add an ipcMain.handle('roku-fetch', ...) that does the actual fetch in Node, expose it through the preload bridge, replace every renderer fetch() with window.roku.fetch(). The main process has no browser security restrictions. It talks to the Roku fine.
The deeper lesson: the emulator validated the protocol. It could not validate the security boundary between renderer and main process - that boundary only exists when the page is loaded from a file, not from localhost.
Bug two: Roku locked ECP behind a settings flag
After the IPC fix, buttons sent requests and got responses. The responses were 403.
Roku added ECP authentication somewhere around 2019-2020. On current Roku firmware the default for "Control by mobile apps" is "Limited" - which means the device responds to discovery but rejects keypresses and app queries from unauthorized clients.
When I built the original Chrome App in 2015, ECP was wide open. No auth, no settings flag, just UDP multicast to find the device and HTTP to the ECP port. That was the world the original code assumed. The Electron port inherited that assumption.
The setting to change: Settings → System → Advanced system settings → Control by mobile apps → Enabled.
Once that was on, the app worked. Channels populated, buttons fired, the channel tiles rendered. Ten years of firmware updates had quietly added a gate that the 2015 code - and the 2026 port - never anticipated.
I added an inline warning to the app: if query/apps returns 403, the channel area shows the exact navigation path instead of staying empty. A user without the context I now have would have had no idea what was wrong.
What the emulator teaches and what it does not
The emulator was worth building. It let me develop SSDP discovery, ECP keypress handling, multi-device switching, and the channel grid without needing hardware at every step. For all of that it was fast and accurate.
But it runs on localhost, on the same machine, as the same user, with no auth. It cannot simulate:
- The security boundary between Electron's renderer and main process
- Network auth policies that real devices enforce
- Firmware behavior that has changed since the protocol was first documented
The bugs that survive to hardware testing are the bugs that require hardware to reproduce. Both of these did. The emulator was right about everything it could be right about. It was silent about everything else.
The app now has the IPC fix and the ECP warning in place. If you run it against a Roku that still has ECP set to "Limited", the channel area tells you exactly what to change and where to find it. The next person to hit this won't need to read a debug log to figure it out.
This is the third post in the Roku Remote series. The original story is The Chrome Extension I Shipped, and Walked Away From. The port is The Chrome App I Walked Away From, and Came Back To. Download the app at abhijeetapsunde.com/apps/roku-remote.