Skip to content

dm807cam/amber

Repository files navigation

amber R ≥ 3.6

CI License: MIT Lifecycle: experimental

Build cross-platform desktop apps in R using Shiny and Electron.

amber is an R package that turns a Shiny app into a real, shippable desktop application. It scaffolds the Electron project, wires up an R runtime, applies a hardened security baseline, and produces native installers — .dmg on macOS, .exe on Windows, .deb / .rpm on Linux — all from a single init_project() call.

library(amber)

init_project(
  project_name        = "my-app",
  project_description = "My Desktop App",
  project_version     = "0.1.0",
  project_license     = "MIT",
  project_author      = "Your Name",
  project_email       = "you@example.com",
  window              = list(width = 1400, height = 900, title = "My App")
)

start_app("my-app", watch = TRUE)   # live-reload development
build_app("my-app")                 # platform-native installer

Table of contents


Supported platforms

Platform Status R runtime Installer artifact
macOS (arm64) Supported Bundled (CRAN) .dmg / .zip
macOS (x86_64) Supported Bundled (CRAN) .dmg / .zip
Windows (x86_64) Supported Bundled (silent NSIS install) Squirrel .exe
Linux (x86_64) Supported System R .deb / .rpm
Linux (arm64) Experimental System R .deb / .rpm

On macOS and Windows, init_project() downloads R from CRAN and unpacks it into the project (r-mac/ or r-win/), producing a self-contained distributable. On Linux, amber uses the end user's system R (bundling portable Linux R is on the roadmap).

Prerequisites

Tool Minimum version Where
R 3.6 https://cloud.r-project.org/
Node.js 18 https://nodejs.org/
npm 8 bundled with Node.js

Platform-specific extras:

  • macOS: Xcode Command Line Tools (xcode-select --install).
  • Windows: matching Rtools for your R version (only needed when building source packages); signtool.exe if you intend to sign releases.
  • Linux: dpkg-deb for .deb, rpm-build for .rpm; the libcurl4-openssl-dev/libssl-dev/libxml2-dev triplet for compiling common Shiny dependencies.

The check_nodejs(), check_npm() and check_electron_forge() helpers verify the toolchain from R and offer to install Electron Forge when it's missing.

Installation

# install.packages("remotes")
remotes::install_github("dm807cam/amber")

Or with devtools:

devtools::install_github("dm807cam/amber")

Quick start

library(amber)

# 1. Verify the toolchain (once, optional)
check_nodejs()
check_npm()
check_electron_forge()

# 2. Scaffold a project
init_project(
  project_name        = "my-app",
  project_description = "My Desktop App",
  project_version     = "0.1.0",
  project_license     = "MIT",
  project_author      = "Your Name",
  project_email       = "you@example.com"
)

# 3. Develop iteratively
start_app("my-app", watch = TRUE)   # live reload on changes under shiny/

# 4. Build native installer
build_app("my-app")

The starter Shiny app lives at my-app/shiny/app.R. Edit it, and with watch = TRUE the R subprocess restarts and the window reloads on save.

For supply-chain assurance on the bundled R installer, pin its SHA-256:

init_project(
  ...,
  expected_sha256 = "0123abcd...64-character-hex..."
)

amber aborts the project setup if the downloaded archive's hash does not match.

Customizing the window

init_project(
  ...,
  window = list(
    width            = 1400,
    height           = 900,
    title            = "My App",
    backgroundColor  = "#ffffff",
    minWidth         = 800,
    minHeight        = 600,
    autoHideMenuBar  = TRUE,
    resizable        = TRUE
  )
)

Settings flow into amber-app.json next to package.json; the Electron main process reads it at startup, so you never have to edit JavaScript. Unknown keys are rejected with the supported list.

Security model

Every app produced by amber ships with a hardened baseline:

  • sandbox: true, contextIsolation: true, nodeIntegration: false, webSecurity: true on every BrowserWindow.
  • Renderer ↔ main IPC goes through a minimal contextBridge surface (amberSplash), exposing only the events the splash screen needs.
  • Strict per-URL Content-Security-Policy: default-src 'none' for the local splash/error screens, and a Shiny-compatible CSP for the main window that still forbids any outbound network connection except to the local Shiny server.
  • Navigation is locked to the local Shiny origin via will-navigate, setWindowOpenHandler, and will-attach-webview handlers. External links route to the user's default browser via shell.openExternal.
  • A per-launch random 32-byte token is required as X-Amber-Token on every request reaching the Shiny server, so other local processes can't reach it even if they guess the random port.
  • setPermissionRequestHandler and setPermissionCheckHandler deny every Electron permission (geolocation, microphone, notifications…) by default.
  • X-Content-Type-Options: nosniff and X-Frame-Options: DENY are applied to every response.

Code signing & notarization

Signing is opt-in via environment variables — day-to-day development builds don't need any keys:

# macOS: sign + notarize
export AMBER_APPLE_IDENTITY="Developer ID Application: Your Org (TEAMID)"
export AMBER_APPLE_ID="you@example.com"
export AMBER_APPLE_ID_PASSWORD="app-specific-password"
export AMBER_APPLE_TEAM_ID="TEAMID"

# Windows: Squirrel signs the .exe with your .pfx
export AMBER_WIN_CERT_FILE="/path/to/cert.pfx"
export AMBER_WIN_CERT_PASSWORD="cert-password"

build_app("my-app") then produces signed (and on macOS, notarized) artifacts. forge.config.js reads these variables and silently skips signing when they're unset.

A ready-to-use entitlements.plist for macOS hardened-runtime is included in the template.

Project layout

After init_project("my-app"):

my-app/
├── package.json              # generated; pinned Electron 34
├── forge.config.js           # per-platform makers + signing config
├── entitlements.plist        # macOS hardened-runtime entitlements
├── r-info.json               # which R strategy this build uses
├── amber-app.json            # window customizations
├── r-mac/                    # bundled R framework (macOS only)
├── r-win/                    # bundled R install     (Windows only)
├── library/                  # project-local R library (Linux only)
├── shiny/
│   └── app.R                 # your Shiny app — edit this
├── start-shiny.R             # token-authenticated Shiny launcher
└── src/
    ├── main.js               # Electron main process
    ├── preload.js            # IPC bridge
    ├── loading.html / .css   # splash screen
    └── loading.js

Public API

Function Purpose
init_project() Scaffold a new Electron + R project
start_app(watch=TRUE) Run in development mode (with live reload)
build_app() Produce platform-native installer
check_nodejs() Verify Node.js installation
check_npm() Verify npm installation
check_electron_forge() Verify (and offer to install) Electron Forge

Browse ?amber after loading the package, or read the getting-started vignette:

vignette("getting-started", package = "amber")

Testing

# Run the unit-test suite (covers every pure function)
devtools::test()

# Or with testthat directly
testthat::test_dir("tests/testthat")

The suite has 87 tests covering: project-name validation, OS dispatch, window-config merging, install-spec selection, JSON quote safety, run_cmd exit semantics, project-path checks, and platform-aware install-hint formatting. Integration tests that need Node/network are skipped on CRAN.

Continuous integration

Job Runner
lint-js rosegold (self-hosted)
R-CMD-check macOS arm64 rosegold (self-hosted)
R-CMD-check Linux x86_64 ubuntu-latest hosted
R-CMD-check Windows x86_64 windows-latest hosted

The full pipeline is defined in .github/workflows/ci.yaml. See docs/CI.md for runner registration, label conventions, and the secret matrix for signing.

Roadmap

  • Bundled R on Linux (currently relies on system R)
  • electricShine-style asset minification pass for library/
  • First-class init_project(icon = "...") with per-OS conversion
  • vignette("packaging") covering full release pipelines
  • Optional GUI launcher (Tkinter / Electron itself) for non-RStudio users to scaffold projects

Contributing

Issues and pull requests are welcome. Before opening a PR:

  1. Run the unit tests: devtools::test().
  2. Run R CMD check clean: devtools::check().
  3. If you touch the Electron template, node --check every JS file you changed.

The CI workflow runs these checks for you on every push and PR.

License

MIT © Dennis Mayk. See LICENSE.md.

About

Build cross-platform desktop apps in R with Shiny and Electron. Native .dmg / .exe / .deb / .rpm installers, hardened security defaults, bundled R on macOS and Windows.

Topics

Resources

License

Unknown, MIT licenses found

Licenses found

Unknown
LICENSE
MIT
LICENSE.md

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors