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().
Every RCWeb app lives in its own folder under src/main/apps/app/. New apps should normally
contain these files:
index.html — Defines the server-injected rc object and loads scripts.style.css — Contains app-specific styling.script.js — Contains app state, event listeners, remote callback APIs, and
rc.connect().appinfo.md — Markdown description used by app information pages. The runtime looks
for this exact filename.Immutable resources that should not change during normal app editing may be placed under
src/main/immutable/app/<app-name>/.
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, app wildcards, 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."spacewar*" — Apps whose names start with
spacewar."*-control" — Apps whose names end with
-control.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 by finishing the allow list before the deny separator. For
example, "chat,!12345678" targets
all clients with rc.app == "chat" except the client with ID
"12345678".
For example, "!c,*-control" targets every client except the default controller app and apps
ending in -control.
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().
Remote functions invoked through rc.sendFunctionCall must be globally reachable from
window. The usual pattern is to expose a global app object, such as
var myApp = (function () { ... return { doAction: doAction }; })();. Avoid
type="module" for app scripts unless the remote API is explicitly attached to
window.
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: app/chat/script.js, app/notepad/script.js,
app/gallery/script.js, app/files/script.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. Include the sending client ID and a message/event ID where useful so clients can ignore echoes and deduplicate repeated updates.
// 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);
};
Use encodeURIComponent(file.name) when building proxy URLs, keep the source
File or Blob in memory while peers may request it, and handle missing file IDs
gracefully. Closing the hosting tab stops the active stream.
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.