Skip to content

feat(webview): support flutter_inappwebview and add session-only mode - #6

Merged
nitinstp23 merged 2 commits into
mainfrom
feat/webview-bridge-inappwebview
Aug 12, 2026
Merged

feat(webview): support flutter_inappwebview and add session-only mode#6
nitinstp23 merged 2 commits into
mainfrom
feat/webview-bridge-inappwebview

Conversation

@nitinstp23

Copy link
Copy Markdown
Contributor

Why

Reviewing the scout-flutter ↔ scout-react seam for a customer embedding a React flow in their Flutter app turned up four problems in the WebView bridge, all in code that has never had a test.

The headline one: the injected shim posts only through window.<channel>.postMessage, which is webview_flutter's channel shape. flutter_inappwebview routes JS→Dart through window.flutter_inappwebview.callHandler(...), so the bridge silently delivered nothing on that plugin — while the docstring claimed it "works the same way for flutter_inappwebview — just adapt the controller calls."

What

flutter_inappwebview support

The shim now detects either transport at runtime rather than being configured, since the two plugins are unambiguously distinguishable (window.<channel>.postMessage vs window.flutter_inappwebview.callHandler). In relay mode it waits for a transport to exist before binding, so the page is never told to stop exporting while it has nowhere to send.

That also handles Android's flutterInAppWebViewPlatformReady timing for free — on WebViews without DOCUMENT_START_SCRIPT, callHandler simply isn't there yet, and the existing poll loop covers it.

ScoutWebViewMode

Selects who delivers the page's spans:

  • sessionOnly — the page adopts the native session/anon id and keeps exporting to the collector itself. One copy of every signal, and the page's logs, metrics and web vitals survive (the relay carries spans only). Needs no attach() and no JS channel. Recommended whenever the WebView can reach the collector.
  • relay — the page hands its spans to the native SDK. Requires @base-14/scout-react 0.1.16+ (see feat(webview): add relay mode so bridged spans are not duplicated scout-react#5); older versions ignore the relay flag and export the span themselves as well, landing it in the backend twice.

relay stays the default so existing integrations are unchanged.

Fixes

  • Re-injection after a session rotation was a no-op. The sentinel was a bare boolean, so the first inject latched it and a page outliving a rotation stayed pinned to the stale session id for the rest of its life. It now stores the session id it bound to — idempotent per session rather than per page, which makes injectShim cheap to call on app resume as well as page load.

  • The page's own event timestamp was discarded. The tracer offers no way to backdate a span, so bridged spans are necessarily stamped on arrival; timestamp_ms is now carried through as webview.timestamp_ms. The gap between the two is bridge latency — milliseconds normally, seconds when a backgrounded WebView is throttled.

  • Docs corrected. They required @base14/scout-react v0.2.0+, which does not exist (setWebViewBridge has shipped since 0.1.6), and claimed the page "stops POSTing to its own OTLP endpoint" once bridged, which was never true — it exported and forwarded. Both modes and their tradeoffs are now documented, with worked examples for flutter_inappwebview and webview_flutter.

Tests

First coverage for the bridge — 15 tests in test/webview_bridge_test.dart: shim shape per mode, transport detection for both plugins, session-keyed re-injection (including asserting the sentinel is only marked after a successful bind), custom channel names, JS-injection escaping, and malformed-payload resilience on the inbound channel.

Verification

  • flutter test — 213 pass
  • flutter analyze lib test — clean

Compatibility

Non-breaking. mode defaults to relay, matching current behaviour, and attach() is unchanged. Pairs with base-14/scout-react#5.

The injected shim posted only through `window.<channel>.postMessage`,
which is webview_flutter's channel shape. flutter_inappwebview routes
JS to Dart through `window.flutter_inappwebview.callHandler(...)`, so
the bridge silently delivered nothing there despite the docs claiming it
worked the same way. The shim now detects either transport at runtime,
and in relay mode waits for one to exist before binding, so the page is
never told to stop exporting while it has nowhere to send.

Adds ScoutWebViewMode to choose who delivers the page's spans.
`sessionOnly` has the page adopt the native session and keep exporting
to the collector itself, which keeps its logs, metrics and web vitals
(the relay carries spans only). `relay` stays the default, so existing
integrations are unchanged.

Fixes re-injection: the sentinel was a bare boolean, so the first inject
latched it and a page outliving a session rotation stayed pinned to the
stale session id for the rest of its life. It now stores the session id
it bound to, making injectShim cheap to call on resume as well as load.

Fixes the discarded page timestamp: bridged spans are stamped on
arrival because the tracer cannot backdate a span, so `timestamp_ms` is
now carried through as `webview.timestamp_ms`.

Corrects the bridge docs, which required a scout-react v0.2.0 that does
not exist (setWebViewBridge has shipped since 0.1.6) and claimed the
page stops POSTing once bridged, which was never true.

Adds the first test coverage for the bridge.
@nitinstp23
nitinstp23 merged commit 4ba11bc into main Aug 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant