This guide explains how to create distributed, real-time applications using RCWeb technology. By leveraging a custom lightweight Java web server, RCWeb proxies raw JavaScript between connected web browsers, transforming them into a real-time distributed system without complex backend logic.
At the center of RCWeb is the assets/core/comms.js library. This library establishes a robust,
bidirectional connection to the RCWeb server.
abcd-efgh). Browsers in the same room can communicate.
rc.send(js, target)).
The server proxies this string, and receiving clients execute it directly via eval() inside
rc.runJavaScriptUpdate().
The rc.send(js, target) and rc.sendFunctionCall(target, funcName, ...args)
functions send JavaScript to clients in the same virtual room.
The target parameter defines which clients receive the execution payload.
It can be a comma-separated list of apps, clients, or * for all.
"chat" — Only clients with rc.app == "chat"
will
receive the JavaScript."12345678" — Only the specific client with
rc.client == "12345678" will receive it.
"*" — All types and all clients on the page will
receive the JavaScript.There are two ways to send JavaScript commands to other clients:
rc.send(js, target))The rc.send(js, target) function sends a raw string of JavaScript to be executed on the target
clients.
This gives you full control but requires you to manually serialize and escape any arguments.
// Example: Manually constructing a JS string
rc.send("myApp.doAction('" + rc.client + "', 'hello world');", "viewer");
rc.sendFunctionCall(target, funcName, ...args))rc.sendFunctionCall for most use cases.
The rc.sendFunctionCall helper simplifies communication by automatically serializing your
arguments (strings, numbers, objects) into a valid JavaScript string. This prevents common errors like
forgetting to escape quotes or handling newlines incorrectly.
// Example: Using the helper for the same action
rc.sendFunctionCall("viewer", "myApp.doAction", rc.client, "hello world");
The target string may also include modifiers to deny specific targets by prefixing them with an exclamation
mark (!):
"!chat" — All clients except those with
rc.app == "chat" will receive it.
"!12345678" — All clients except
the client with ID "12345678" will receive it.Allowed and denied targets can be combined. For example, "chat!12345678" targets
all clients with rc.app == "chat" except the client with ID
"12345678".
Every RCWeb application HTML file requires the injection of server variables and the inclusion of the core library:
<script>
var rc = {
"version": "${version}",
"app": "${app}", // e.g. "chat"
"room": "${roomId}", // e.g. "abcd-efgh"
"client": "${clientId}", // e.g. "12345678"
"commsWebSocket": "${websocket}" // Websocket to communicate with all clients in same room
};
</script>
<script src="/assets/core/comms.js"></script>
After defining application logic, establish the connection using rc.connect().
RCWeb Apps generally follow one of two architectural patterns: Asymmetric (Sender/Viewer) or Symmetric (Peer-to-Peer Collaborative).
This pattern is perfect for remote controls, digital signage, or multi-player games where mobile devices act as controllers acting upon a primary shared screen.
Example: /v/script.js, /c/script.js
The viewer app exposes a global API that the controllers will call. It doesn't typically send commands itself; it just listens, applies state changes, and renders visuals.
// Example: Viewer API
var myApp = (function() {
return {
doAction: function(clientId, data) {
console.log("Action received from " + clientId + " with data: " + data);
// Update UI/State
}
};
})();
// Start app
rc.connect();
The controller attaches event listeners to UI inputs (like buttons or joysticks) and constructs JavaScript strings targeting the viewer.
// Example: Sending commands
document.getElementById('actionBtn').addEventListener('click', function() {
// Send to clients in same room running the 'viewer' app
rc.sendFunctionCall("viewer", "myApp.doAction", rc.client, "hello world");
});
rc.connect();
This pattern is used when all connected browsers are equal participants sharing synchronized state, such as collaborative document editing or shared file repositories.
Examples: chat/chat.js, notepad/notepad.js,
gallery/gallery.js, files/files.js
When local state changes (e.g., typing in a textarea), the client broadcasts an update command to all other clients in the same room running the same app.
// In notepad.js, when user types in the textarea
var sendNotes = function () {
var notes = document.getElementById("notepad").value;
// Broadcast to all 'notepad' clients using sendFunctionCall
rc.sendFunctionCall("notepad", "notepad.updateNotes", rc.client, notes);
}
When a new client joins, it may miss the current state. RCWeb Apps handle this by triggering a refresh request when the network connects or when the peer count increases.
// 1. New client joins and asks for current state
rc.onUpdateNetworkStatus = function (heading, info) {
if (rc.connected) {
rc.sendFunctionCall("app", "app.refresh");
}
}
// 2. Existing clients receive the request and ask everyone to broadcast
var refresh = function() {
window.clearTimeout(refreshTimeout);
refreshTimeout = window.setTimeout(function() {
rc.sendFunctionCall("app", "app.broadcastState");
}, 50); // Debounce to prevent flooding
}
RCWeb provides hooks into the eval() execution loop inside comms.js. You can
override these to provide feedback to senders.
rc.onUpdateSuccess(js): Triggered when local execution succeeds. Useful for measuring
latency.rc.onUpdateError(error): Triggered on local execution failure.The RC Viewer uses these hooks to send acknowledgements back to the RC Controller:
// In rc.js (Viewer)
rc.onUpdateError = function (error) {
var escapedError = error.message.replaceAll("'", "\\'").replaceAll("\n", "\\n");
// Forward error back to the controller UI
rc.send("rc.onViewUpdateError('" + escapedError + "', '" + rc.client + "');", "c");
};
The files and rc apps demonstrate how to transfer large files
without consuming massive server RAM or storing files centrally. This is achieved using proxy endpoints.
"/x-file/" + rc.room + "/" + rc.client + "/" + fileId + "/" + safeFileName
<a> or <video> tag pointing to this URL.
rc.sendFileChunk(fileId, start, url) on the host.
File object and issues a PUT request back to the server, which pipes it to the
requester.
// Using XMLHttpRequest to upload a chunk (from files.js)
var sendChunk = function (contentType, contentRange, chunk, url) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", url);
xhr.setRequestHeader("Content-Type", contentType);
xhr.setRequestHeader("Content-Range", contentRange);
xhr.send(chunk);
};
By relying on synchronized raw Javascript execution mediated by the RCWeb core framework, you bypass traditional rigid REST APIs and serialization overhead. This dynamic nature allows rapid prototyping and highly responsive, real-time distributed applications using minimal lines of code.