Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions packages/react-native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,55 @@ on Bugsee's production API. Set, it reaches every upload and the runtime:
Each of these is the plugin's own, so a later prebuild with a new value
replaces it, and one without `endpoint` removes it again.

### Auto-launch: what is recorded before JavaScript runs (Android)

With `autoLaunch` the Android SDK starts at process start, from manifest
meta-data, before any JavaScript exists. Two things follow:

- **Reports say which wrapper they came from, with context.** The native side
registers only the wrapper type and version at process start; `Bugsee.attach()`
registers the rest (React Native version, JS engine, build configuration), as
`launch()` and `relaunch()` do. Call `attach()` once at startup.
- **Filters are not installed yet.** The JS log, network and breadcrumb filters
are installed natively only when `setLogFilter`, `setNetworkFilter` and
`setBreadcrumbFilter` run, so what the SDK captures before then is recorded
unfiltered. To close that window, opt in per filter:

```json
["@bugsee/react-native", { "autoLaunch": true, "appToken": "<token>", "holdFilters": ["network", "log", "breadcrumb"] }]
```

`true` holds all three. A held filter is installed natively at process start
and drops every event of its kind (log lines, network events, breadcrumbs)
until JS calls its setter, with a function (your filter applies) or with
`null` (no filter: events pass through). Call each setter unconditionally,
early, on every start of the JS runtime. Only list the filters your app
installs: an app that lists one and never calls its setter records none of
that kind, and the wrapper says so in the log (`BugseeRN`, warning) 30 s after
process start. It is off by default for that reason, and an app that never uses
filters is unaffected. It writes `com.bugsee.reactnative.hold-filters`
application meta-data, removed again by a prebuild without the option.
A JS reload is held only if the previous runtime had a filter function
installed (it stays dropped until the new runtime calls the setter again). If
the previous runtime called `setXFilter(null)`, that kind passes through again
from the reload until the new runtime calls the setter. The 30 s warning is
given once per process, at 30 s after process start. The hold is meant
for `autoLaunch`; it was not
verified with a JS `launch()`, where the SDK may replace a filter set before
launch. In a process that never starts React Native (a service or push
process), a held kind drops everything native code records, for the life of
that process.
Two interactions to know about: a filter you declare in the manifest
(`com.bugsee.filter.log-event`, `network-event` or `breadcrumb`) is not
applied for a kind that is held, because the SDK lets a filter set from code
take its place, so do not list a kind you filter from the manifest; and
`setXFilter(null)` then leaves that kind with no filter at all.
- **Masking is not held.** Nothing from `<BugseeSecure>` or `setSecureRectangles`
is masked until those mount, and the wrapper cannot hold the SDK's capture
from JS-side state. If the first frames must not show a sensitive screen, do
not auto-launch: call `Bugsee.launch()` from JavaScript (or keep `autoLaunch`
off), after your first secure view is in place.

The plugin's Gradle edits never remove or change a line of your own code, on a
clean or a `--no-clean` prebuild, however often it runs: it adds its own
marked lines, replaces only those, and rewrites exactly one of yours,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.LongSupplier;

/**
Expand Down Expand Up @@ -108,7 +109,7 @@ private static final class Pending {
}
}

private static final BreadcrumbFilterBridge SHARED = new BreadcrumbFilterBridge(new DaemonScheduler());
private static final BreadcrumbFilterBridge SHARED = new BreadcrumbFilterBridge(new DaemonScheduler(), System::nanoTime, Bugsee::setBreadcrumbFilter);

@NonNull
static BreadcrumbFilterBridge shared() {
Expand Down Expand Up @@ -147,8 +148,64 @@ static BreadcrumbFilterBridge shared() {

/** Tests also pass a clock, to pass the deadline without the timer having run. */
BreadcrumbFilterBridge(@NonNull final Scheduler scheduler, @NonNull final LongSupplier nanoClock) {
this(scheduler, nanoClock, installed -> { });
}

/** As above, and the call that installs (or, with null, removes) the SDK's filter. */
BreadcrumbFilterBridge(
@NonNull final Scheduler scheduler,
@NonNull final LongSupplier nanoClock,
@NonNull final Consumer<EventFilter<Breadcrumb>> installer
) {
this.scheduler = scheduler;
this.nanoClock = nanoClock;
this.installer = installer;
}

private final Consumer<EventFilter<Breadcrumb>> installer;

/** True from {@link #holdUntilJs} until JS first calls {@link #setEnabled}. For the watchdog only. */
private final AtomicBoolean held = new AtomicBoolean();

/** Whether the startup hold is still waiting for JS to call {@code setBreadcrumbFilter}. */
boolean isHeld() {
return held.get();
}

/**
* Installs the filter with the SDK. A throw (the SDK not ready that early)
* leaves {@code installed} false, so a later {@code setEnabled(true)}
* installs it instead of believing it already is, and is rethrown for the
* caller to log.
*/
private void install() {
if (installed.compareAndSet(false, true)) {
boolean ok = false;
try {
installer.accept(filter);
ok = true;
} finally {
if (!ok) {
installed.set(false);
}
}
}
}

/**
* Installs the filter before JS has asked for it, so a manifest
* auto-launch does not record breadcrumbs unfiltered until
* {@code setBreadcrumbFilter} runs (see {@link StartupHold}). With no module
* attached yet the filter drops everything, and so does one whose JS has
* not armed it (the armed gate), so nothing waits on a reply that cannot
* come. {@code setEnabled(false)}, which {@code setBreadcrumbFilter(null)} sends, removes
* it again. A second call, or one after {@code setEnabled(true)}, is a no-op.
*/
void holdUntilJs() {
withSdkLock(() -> {
install();
held.set(true);
});
}

private final EventFilter<Breadcrumb> filter = new EventFilter<Breadcrumb>() {
Expand Down Expand Up @@ -243,17 +300,16 @@ void setEnabled(final boolean enabled, final Sink caller) {
// A superseded module must neither arm nor disable the process-wide filter.
return;
}
held.set(false);
if (enabled) {
if (caller != null) {
arm(caller);
}
if (installed.compareAndSet(false, true)) {
Bugsee.setBreadcrumbFilter(filter);
}
install();
return;
}
if (installed.compareAndSet(true, false)) {
Bugsee.setBreadcrumbFilter(null);
installer.accept(null);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ static LogFilterBridge shared() {
*/
private final AtomicReference<Sink> armedFor = new AtomicReference<>();

/** True from {@link #holdUntilJs} until JS first calls {@link #setEnabled}. For the watchdog only. */
private final AtomicBoolean held = new AtomicBoolean();

/** Whether the startup hold is still waiting for JS to call {@code setLogFilter}. */
boolean isHeld() {
return held.get();
}

/** Production: one daemon thread forgets pending requests at {@link #BORROW_MS}. */
private LogFilterBridge() {
this(new DaemonScheduler(), System::currentTimeMillis, Bugsee::setLogEventFilter);
Expand All @@ -147,7 +155,8 @@ private LogFilterBridge() {
this(scheduler, clock, filter -> { });
}

private LogFilterBridge(
/** Tests pass the call that installs the SDK's filter. */
LogFilterBridge(
@NonNull final Scheduler scheduler,
@NonNull final ConsoleEchoDedup.Clock clock,
@NonNull final Installer installer
Expand Down Expand Up @@ -197,6 +206,39 @@ void attachModule(@NonNull final Sink next) {
ensureInstalled();
}

/**
* Installs the filter before JS has asked for it, so a manifest
* auto-launch does not record log lines unfiltered until
* {@code setLogFilter} runs (see {@link StartupHold}). It is the state of
* an app whose filter is on but whose runtime has not armed it: lines are
* dropped. {@link #attachModule} keeps it, so it also holds while the
* module is constructed ahead of its JS. {@code setEnabled(true)} arms
* the filter, and {@code setEnabled(false)} (what {@code setLogFilter(null)}
* sends) releases it to the pass-through.
*/
void holdUntilJs() {
// userFilter first: once the filter is installed no line may pass
// through before it is on. If the install throws it is rolled back
// (the throw goes on to StartupHold, which logs it) and the hold is
// not reported, as in the other two bridges; attachModule installs
// again when the module is built.
synchronized (stateLock) {
userFilter.set(true);
}
boolean ok = false;
try {
ensureInstalled();
ok = true;
} finally {
if (!ok) {
synchronized (stateLock) {
userFilter.set(false);
}
}
}
held.set(true);
}

/**
* The module {@code caller}'s JS now answers requests. Ignored unless
* {@code caller} is still the attached module: a stale module's late
Expand Down Expand Up @@ -263,6 +305,7 @@ void setEnabled(final boolean enabled, final Sink caller) {
// A superseded module must neither arm nor disable the process-wide filter.
return;
}
held.set(false);
if (enabled && caller != null) {
arm(caller);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ static NetworkFilterBridge shared() {

/** Production: one daemon thread forgets pending requests at {@link #BORROW_MS}. */
private NetworkFilterBridge() {
this(new DaemonScheduler(), NetworkFilterBridge::readDefaultSanitizerOption);
this(new DaemonScheduler(), NetworkFilterBridge::readDefaultSanitizerOption, Bugsee::setNetworkEventFilter);
}

/**
Expand All @@ -136,8 +136,64 @@ private NetworkFilterBridge() {
* {@link Bugsee#getLaunchOptions()} needs an Android looper.
*/
NetworkFilterBridge(@NonNull final Scheduler scheduler, @NonNull final BooleanSupplier defaultSanitizer) {
this(scheduler, defaultSanitizer, installed -> { });
}

/** As above, and the call that installs (or, with null, removes) the SDK's filter. */
NetworkFilterBridge(
@NonNull final Scheduler scheduler,
@NonNull final BooleanSupplier defaultSanitizer,
@NonNull final Consumer<EventFilter<NetworkEvent>> installer
) {
this.scheduler = scheduler;
this.defaultSanitizer = defaultSanitizer;
this.installer = installer;
}

private final Consumer<EventFilter<NetworkEvent>> installer;

/** True from {@link #holdUntilJs} until JS first calls {@link #setEnabled}. For the watchdog only. */
private final AtomicBoolean held = new AtomicBoolean();

/** Whether the startup hold is still waiting for JS to call {@code setNetworkFilter}. */
boolean isHeld() {
return held.get();
}

/**
* Installs the filter with the SDK. A throw (the SDK not ready that early)
* leaves {@code installed} false, so a later {@code setEnabled(true)}
* installs it instead of believing it already is, and is rethrown for the
* caller to log.
*/
private void install() {
if (installed.compareAndSet(false, true)) {
boolean ok = false;
try {
installer.accept(filter);
ok = true;
} finally {
if (!ok) {
installed.set(false);
}
}
}
}

/**
* Installs the filter before JS has asked for it, so a manifest
* auto-launch does not record network events unfiltered until
* {@code setNetworkFilter} runs (see {@link StartupHold}). With no module
* attached yet the filter drops everything, and so does one whose JS has
* not armed it (the armed gate), so nothing waits on a reply that cannot
* come. {@code setEnabled(false)}, which {@code setNetworkFilter(null)} sends, removes
* it again. A second call, or one after {@code setEnabled(true)}, is a no-op.
*/
void holdUntilJs() {
synchronized (stateLock) {
install();
held.set(true);
}
}

private final EventFilter<NetworkEvent> filter = new EventFilter<NetworkEvent>() {
Expand Down Expand Up @@ -221,17 +277,16 @@ void setEnabled(final boolean enabled, final Sink caller) {
// A superseded module must neither arm nor disable the process-wide filter.
return;
}
held.set(false);
if (enabled) {
if (caller != null) {
arm(caller);
}
if (installed.compareAndSet(false, true)) {
Bugsee.setNetworkEventFilter(filter);
}
install();
return;
}
if (installed.compareAndSet(true, false)) {
Bugsee.setNetworkEventFilter(null);
installer.accept(null);
}
}
}
Expand Down
Loading
Loading