When a session is recorded
Replay is off until your app turns it on:ApsioReplayOptions(enabled: true) on
iOS, replay.enabled = true on Android, and the
replay option on React Native. Then a session is
recorded when all of these hold:
- the remote configuration has not turned replay off;
- the session is kept by sampling: replay is part of a session’s detail.
GET /v1/projects/{projectId}/sessions/{sessionId}/replay/segments and
each one with GET /v1/projects/{projectId}/attachments/{attachmentId}.
What is masked by default
Masking fails closed. With no setting at all:- All text is masked, not only inputs: every character becomes
x, and spaces and line breaks stay, so a masked text keeps its length and shape. You can opt down to masking text inputs only (textMaskinginputs). - Secure fields (passwords, one-time codes, card numbers) are always masked. No setting shows them.
- Images are boxes.
- What the SDK cannot read is a hidden box, with nothing inside: web views, maps, video, camera previews, and views that draw their own content. On iOS, SwiftUI content is drawn into plain layers the SDK cannot read, so it is recorded as boxes without content.
- Touches are recorded only over what is shown: never over masked text, an image, a secure
field, the keyboard or a hidden box. One masked or hidden view under a touch is enough to drop
it, whatever is drawn above, so taps on a PIN pad of plain buttons do not give the PIN away.
You can also turn touches off (
toucheshide).
Your settings
Each view can be given one of three settings:
You set them on a view instance, on a class of views, or for the whole app (the text level), and
the remote configuration can add its own. The rules are the same on every platform:
- Secure fields are always masked.
- Nothing is shown inside a hidden view.
- At one level,
hidebeatsmask, which beatsunmask. - The nearest setting wins: a view’s own setting beats its class’s, and its class’s beats the app-wide text level. A view without one takes its nearest ancestor’s.
Per view
Tokens in an identifier
A view whose accessibility identifier (iOS), view tag or Compose test tag (Android) ortestID
(React Native) holds the word apsio-mask, apsio-unmask or apsio-hide gets that setting,
as if set on the instance. This is how React Native sets them, and it works in any app.
A token counts only as a whole word, exactly as written. A word is a run of ASCII letters,
digits and hyphens; every other character, the underscore, the dot and white space included,
separates words.
So a test identifier never unmasks a view by accident.
Per class
maskClasses, unmaskClasses and hideClasses apply a setting to every view of a class or its
subclasses, such as a view a library draws. The SDK matches classes by their name at run time:
- iOS: the name
NSStringFromClassgives. A Swift class carries its module:Checkout.CardField, notCardField. In Swift, pass the class itself (hideClasses: [CardField.self]) and the SDK takes its name. - Android: the fully qualified name. R8 renames classes in a minified release build, so keep
the name of every class you name, with
-keepnames class com.example.checkout.CardViewin your R8 rules, or pass the class itself (replay.hideClass(CardView::class.java)), which needs no rule. The SDK keeps the names of the platform, AndroidX, Material and React Native classes it recognizes. See Class names and R8. - React Native: the native class names above, one list for both platforms.
Strict mode
A name in a hide or mask list, yours or the remote configuration’s, that matches no class of the app would fail open: the views it was meant to hide would be shown. So a name that does not resolve, whether a typo, a Swift class without its module or a class R8 renamed, puts the session in strict mode for the rest of its replay:- every text and input is masked and every image hidden, whatever unmasks them;
- every view the SDK knows only as its own drawing is hidden;
- the device logs a warning naming the classes, and the session gets one
apsio.replay.strict_moderecord whoseapsio.replay.unresolved_classeslists them.
Remote tightening
The remote configuration can mask more without a release: mask all text, which voids every unmask the app set, and add mask and hide classes. Its classes win over every setting of the app, on the view and on everything inside it. It can never loosen masking.Check what is recorded
The mask preview draws over your app what replay records, from each capture: red where it masks, grey where it hides, and a green outline where touches are recorded. It works whether or not the session records replay, and only in a debug build:
Check every screen with the preview before you turn replay on in a release.