REMAPWRAP DOCS

v0.1.0 ALPHA — © 2026 F-KEYS

Overview

RemapWrap serves a phone's browser a grid of keys that fire OS-level keystrokes on a PC. All traffic stays on the local network: no cloud, no account, no app installed on the phone.

Four parts: remapwrap-server.js, a Node.js process that serves the pages, relays WebSocket messages and injects keystrokes; dashboard.html, the PC side, where layouts are built; controller.html, the phone side, the keypad itself; and store.js, which keeps settings and profiles on disk.

RemapWrap began under the name F-Keys. The name moved to the company; the product kept the code.

ALPHA — v0.1.0 Runs from source. A packaged Windows build exists in the repository but is unsigned and not yet distributed.
Requirements
PC
RequirementDetails
Node.jsv16.0.0 or higher. Download at nodejs.org
npmBundled with Node.js. v7+ recommended.
OSWindows 10/11, macOS 11+, Ubuntu 20.04+
BrowserChrome or Edge (latest) for the dashboard
Ports7331 (HTTP) and 7332 (WebSocket) must be open
Phone
RequirementDetails
BrowserChrome for Android, Safari for iOS, Firefox. Any modern mobile browser.
NetworkSame Wi-Fi network as PC, or hotspot to PC
App installNone required
Native Build Tools (for keystroke injection)

The @nut-tree-fork/nut-js package compiles native bindings. Without it, RemapWrap runs in relay-only mode: presses reach the dashboard's live feed but no OS keystrokes fire. See nut-js install.

OSRequired Tools
WindowsVisual Studio Build Tools 2019+ with "Desktop development with C++" workload + Python 3
macOSXcode Command Line Tools: xcode-select --install
Linuxsudo apt install build-essential libxtst-dev
Quick-Start
Step 1 — Get the source

RemapWrap runs from source, from the remapwrap/ directory of the f-keys repository:

1git clone https://github.com/vince-gonzalez/f-keys
2cd f-keys/remapwrap
Step 2 — Install dependencies
1npm install
IF NUT-JS FAILS TO COMPILE The server still runs without it, in relay-only mode. Fix the native build separately — see nut-js install.
Step 3 — Start the server
1npm start

The server prints its URLs on startup:

╔══════════════════════════════════════════════╗
║ RemapWrap SERVER v0.1.0 RUNNING ║
╠══════════════════════════════════════════════╣
║ Dashboard: http://192.168.1.42:7331/
║ Controller: http://192.168.1.42:7331/controller
║ QR Code: http://192.168.1.42:7331/qr
║ WebSocket: ws://192.168.1.42:7332
╚══════════════════════════════════════════════╝
Step 4 — Open the dashboard

Open the Dashboard URL on the PC running the server. The dashboard is served to that machine only; a request for / from any other device is redirected to the controller.

Step 5 — Pair the phone

Scan the QR code shown on the dashboard. The encoded URL carries the pairing secret, so a scanned phone is paired with nothing to type. A phone whose camera cannot scan opens the Controller URL directly and enters the six-digit PIN displayed on the dashboard. Five wrong PINs lock that address out for five minutes.

Step 6 — Press a key

A tap on the phone fires the bound action as an OS-level keystroke on the PC. The dashboard's live feed records the key, the action fired, and the timestamp.

File Reference
FileRoleRuns On
remapwrap-server.jsHTTP server + WebSocket bridge + keystroke injectorPC (Node.js)
dashboard.htmlLayout builder, pairing QR + PIN, live feedPC browser, localhost only
controller.htmlThe keypad, served at /controllerPhone browser
store.jsSettings and profiles on diskInside the server
system.jsCommand catalogueInside the server
licence.jsTier check for the paid featuresInside the server

The server injects the local IP and WebSocket port into controller.html at serve time; the dashboard reads them from /ip. Neither page carries a hand-edited address.

On disk: %APPDATA%\RemapWrap\settings.json holds pairing and the active profile; %APPDATA%\RemapWrap\profiles\*.json holds one file per profile. The folder is the import path — a profile file placed there appears in the dashboard.

Config Block

The server carries one CONFIG block at the top of remapwrap-server.js. It is the only place a value needs changing: the dashboard and controller take their ports from the running server.

remapwrap-server.js
VariableDefaultDescription
HTTP_PORT7331Port for the HTTP server. Change if 7331 is in use.
WS_PORT7332Port for the WebSocket bridge. Must match dashboard + controller CFG.
DEV_MODEtrueVerbose console logging. Set false for silent production mode.
KEY_DELAY_MS30Delay between keydown and keyup in milliseconds. Increase to 60–80 if keystrokes miss in games.
MAX_CLIENTS8Max simultaneous WebSocket connections. Covers multiple phones + dashboards.
dashboard.html and controller.html

No configuration. The controller is served with the server's IP and WebSocket port injected; the dashboard asks the server's /ip endpoint. Changing HTTP_PORT or WS_PORT in the server config is sufficient for all three parts.

Action Strings

The Action field in the key editor accepts a +-separated keystroke string. Each token resolves to an OS key via nut-js. The key table is generated from the nut-js enum at startup — all 137 keys the library can press are available — with aliases layered on top for the names people actually type.

Format
# Single key
f5

# Key combo
ctrl+c

# Three-key combo
ctrl+shift+s

# Media / system key
mute
Token Aliases
FamilyTokens
Modifiersctrl / control / lctrl / rctrl · shift / lshift / rshift · alt / lalt / ralt / altgr · win / cmd / super / meta
Navigationesc, enter, tab, backspace, ins, del, home, end, pgup, pgdn, up, down, left, right, caps, prtsc, break
Function keysf1 … f12
Mediavolumeup / volup, volumedown / voldown, mute, playpause / play, next, prev, stop
Punctuation= plus - _ , . / ; ' [ ] \ ` ~ space
Numpadnumpadplus, numpadminus, numpadtimes, numpaddivide, numpaddot, numpadenter
Letters, digitsa–z, 0–9
UNRESOLVED TOKENS A token the map cannot resolve is logged and dropped; the remaining keys in the combo still fire. A combo with no resolvable keys fires nothing.
Profile JSON

Profiles are plain JSON files, one per file, in %APPDATA%\RemapWrap\profiles\. Exporting, sharing and importing a profile is moving that file. The format is versioned from the first byte: every profile carries "schema": 1 and holds pages, even when there is only one page.

Structure
{
"schema": 1,
"name": "Streaming",
"pages": [
{ "name": "Main", "cols": 12, "rows": 21,
"keys": [ // id, label, sub, action, color per key ] }
]
}

The dashboard's key editor writes every field a key can carry; a saved profile is its own reference for the full key shape.

Dashboard

Served at / to the machine running the server, and to that machine only — any other device requesting it is redirected to the controller. Pairing, licence and profile routes refuse non-local requests outright.

Pairing Panel

Shows the QR code and the six-digit PIN. The server detects its own LAN address; the QR re-renders when the address changes.

Layout Builder

Clicking a key selects it; the key editor edits its label, subtitle, action string and background colour, and the canvas re-renders per edit. + ADD KEY appends, − REMOVE deletes the selection. Grid dimensions are per page.

Live Feed

Each press from the phone is appended with the key label, the action fired, and a timestamp. The feed is the loop's confirmation surface: a press that reaches the feed but moves nothing on screen is a nut-js problem, not a network one.

▶ PUSH TO PHONE

Sends the current layout to connected controllers over WebSocket. Edits do not reach the phone until pushed.

Phone Controller

Served at /controller. Renders the pushed layout scaled to the phone's screen. The status badge reports connection state: LIVE, OFFLINE, CONNECTING….

A press animates, vibrates on Android, and sends the action over WebSocket. On a dropped connection the controller reconnects on its own; a reload is never required.

Pairing

Two paths, one secret:

PathMechanism
Scan the QRThe encoded URL carries the pairing secret. Nothing to type. The comparison is constant-time.
Type the PINFor a phone that cannot scan: open http://[server-ip]:7331/controller and enter the six digits shown on the dashboard.

Five wrong PINs lock the requesting address out for five minutes. A QR scan that reaches an error page is a firewall or network problem — see Firewall / Ports.

nut-js Install

@nut-tree-fork/nut-js is a native module that requires compilation. If it fails to install, follow the steps for your OS:

Windows
# 1. Install Visual Studio Build Tools
# Download: https://visualstudio.microsoft.com/visual-cpp-build-tools/
# Select: "Desktop development with C++"

# 2. Install Python 3 (from microsoft store or python.org)

# 3. Configure npm
npm config set msvs_version 2019

# 4. Re-install
npm install @nut-tree-fork/nut-js
macOS
xcode-select --install
npm install @nut-tree-fork/nut-js
Linux (Ubuntu / Debian)
sudo apt install build-essential libxtst-dev python3
npm install @nut-tree-fork/nut-js
RELAY-ONLY MODE Without nut-js, the server still runs and keypresses appear in the dashboard live feed — but no OS keystrokes are fired. The server logs: Keystroke injection: INACTIVE on startup.
Firewall / Ports

RemapWrap uses two ports. Both must be reachable from the phone:

PortProtocolPurpose
7331TCP (HTTP)Serves dashboard + controller pages, QR endpoint
7332TCP (WebSocket)Real-time keypress relay
Windows Defender Firewall

On first run, Windows may prompt you to allow Node.js through the firewall. Click Allow for private networks. If you missed the prompt, open Windows Defender Firewall → Allow an app → add node.exe with Private checked.

Router AP Isolation

Some routers block device-to-device traffic on the same Wi-Fi (AP isolation / client isolation). A phone that cannot reach the PC on an otherwise working network is usually this. The router setting is the fix; a phone hotspot with the PC joined to it bypasses the router entirely.

Changing Ports

Edit HTTP_PORT and WS_PORT in the CONFIG BLOCK at the top of fkeys-server.js. Update the matching WS_PORT and HTTP_PORT values in fkeys-dashboard.html's CFG block.

iOS Notes
FeatureiOS SafariiOS Chrome
WebSocket✓ Supported✓ Supported
Touch input✓ Supported✓ Supported
Haptic vibration✗ Not available✗ Not available
Add to Home Screen✓ Works✓ Works
Background WebSocketDrops after ~60sDrops after ~60s

iOS suspends a backgrounded WebSocket after roughly a minute. The controller reconnects on its own when the page returns to the foreground.

Common Errors
Dashboard shows "DISCONNECTED" immediately

The server is not running, or it crashed on a port error. The terminal running npm start names the cause.

Phone QR scan fails / "Site can't be reached"

Firewall blocking port 7331, or the phone is on a different network. See Firewall / Ports.

Pairing locked

Five wrong PINs lock that address for five minutes. Scanning the QR pairs without a PIN and is not subject to the lockout.

Keystrokes fire on the wrong window

nut-js injects into whichever window has OS focus. The window to be controlled must be focused on the PC before keys are pressed on the phone.

Presses reach the live feed but nothing happens on screen

Relay-only mode: @nut-tree-fork/nut-js did not load. The startup log prints Keystroke injection: INACTIVE. See nut-js install.

Port already in use

On EADDRINUSE the server prints the address of the instance that is probably already running, and exits. One server per machine; a genuinely occupied port means changing HTTP_PORT or WS_PORT in the CONFIG block.

State of the Build
AreaState
Profiles on disk, multi-page layoutsBuilt. schema: 1, one JSON file per profile.
Pairing: QR secret + PIN with lockoutBuilt.
Command catalogueBuilt. Everything offered runs; a test enforces both directions.
Packaged Windows buildBuilds from source; unsigned, not yet distributed.
OBS / Twitch integrations (obs.*, stream.*)Planned. Named in the catalogue, outside it until the integrations exist.
VU metersNot built.
Row-column scanningNot built; scanning is linear.

Nothing in this table is aspirational. A feature listed as built has a test; a feature listed as planned is absent from the running product.

Changelog
2026-08 — RemapWrap

The product F-Keys was renamed RemapWrap; the F-Keys name became the company. Same alpha, carried forward:

+ Files renamed: remapwrap-server.js, dashboard.html, controller.html
+ Pairing secret in the QR URL; six-digit PIN fallback with lockout
+ Profiles on disk: %APPDATA%\RemapWrap, schema 1, pages
+ Dashboard restricted to localhost
+ Command catalogue with a two-way test
v0.1.0 — 2026-03-19

Initial alpha, under the working name F-Keys.

+ HTTP + WebSocket bridge, nut-js keystroke injection
+ Layout builder, QR pairing, live feed
+ Touch keypad served to the phone
+ Auto-reconnect on both dashboard and controller
+ Layout export / import as JSON
+ Push layout from dashboard to phone over WebSocket
+ Haptic feedback on Android