newly-replay
v0.1.3
Published
Permissionless macOS session replay for OpenReplay — capture, encode and upload, with no Screen Recording grant.
Readme
newly-replay
Permissionless macOS session replay for OpenReplay.
Captures the host app's own window, encodes and batches frames, and uploads them to an OpenReplay mobile ingest — without the Screen Recording permission. An app captures its own pixels freely; only reading other apps' windows needs a TCC grant.
Install
React Native (macOS):
npm install newly-replay
cd macos && pod installAutolinking finds the podspec and registers the native module through the Objective-C
runtime. No AppDelegate.mm changes.
Native Swift, no React Native:
.package(path: "../newly-replay")Use
import { startReplay } from 'newly-replay';
startReplay({
ingestUrl: process.env.OPENREPLAY_INGEST_URL!,
projectKey: process.env.OPENREPLAY_PROJECT_KEY!,
fps: 1,
});NewlyReplay.start(ingestURL: url, projectKey: key)
// or, driven entirely by environment variables:
NewlyReplay.startFromEnvironment()That's the whole integration. Capture gating, session lifecycle, batching, retry and idle backoff are internal.
Optional timeline feeds:
recordNetworkCall({ method: 'POST', url, status: 500, durationMs: 42 });
recordLog('error', 'build failed');
recordEvent('build_started', JSON.stringify({ target: 'macos' }));How it works
| Stage | Where |
|---|---|
| Own-window capture, click + interaction monitor, process stats | WindowCapture.swift |
| Gating and backoff policy | CapturePolicy.swift |
| Downscale + JPEG encode | FrameEncoder.swift |
| OpenReplay mobile wire format | Messages.swift |
| tar + gzip for the image batch | TarGz.swift |
| Session lifecycle, batching, upload | Session.swift |
| Capture loop | ReplayController.swift |
Capture only runs when replay is on and the user has interacted recently, so an app sitting in the background never opens a session and never costs a frame.
Notes
No masking. Frames are captured as-is: whatever is on screen is uploaded, including
password fields, tokens and customer data. Fine when you control the app and the
backend. If you ship this to third-party developers, add view-level redaction first —
at minimum auto-masking NSSecureTextField — because their end users' data is not
yours to store.
trackerVersion must look like semver. The ingest parses it and requires
>=1.0.9, answering HTTP 426 otherwise — a bare product name or a 0.1.0 is rejected.
It validates only the part before the first -, so the pre-release tag is free for
identification. The default is 1.26.0-newly-replay, which passes and says who is
calling; prefer that over claiming a bare official version, which is a ToS problem
against instances you do not operate.
CGWindowListCreateImage is soft-deprecated. The replacement Apple points at,
ScreenCaptureKit, requires the Screen Recording permission even for your own window —
which would defeat the entire feature. If the call is ever removed, the path forward is
rendering the window's own layer tree (CALayer.render(in:)), also permission-free.
Licensing. This package is independent of the OpenReplay server. It speaks the
ingest's wire protocol over HTTP and links no OpenReplay code, so it is a separate work
and can stay proprietary while a modified OpenReplay server remains AGPL. Keep it that
way: do not vendor anything from backend/, player/ or frontend/ into this package.
Tests
swift testThe suite covers the wire format byte-for-byte against the original TypeScript encoder,
tar output against the system tar, and the session state machine against a fake
transport — no network, no window, no OpenReplay instance required.
