feat(webview): support flutter_inappwebview and add session-only mode - #6
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 iswebview_flutter's channel shape.flutter_inappwebviewroutes JS→Dart throughwindow.flutter_inappwebview.callHandler(...), so the bridge silently delivered nothing on that plugin — while the docstring claimed it "works the same way forflutter_inappwebview— just adapt the controller calls."What
flutter_inappwebviewsupportThe shim now detects either transport at runtime rather than being configured, since the two plugins are unambiguously distinguishable (
window.<channel>.postMessagevswindow.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
flutterInAppWebViewPlatformReadytiming for free — on WebViews withoutDOCUMENT_START_SCRIPT,callHandlersimply isn't there yet, and the existing poll loop covers it.ScoutWebViewModeSelects 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 noattach()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-react0.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.relaystays 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
injectShimcheap 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_msis now carried through aswebview.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-reactv0.2.0+, which does not exist (setWebViewBridgehas 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 forflutter_inappwebviewandwebview_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 passflutter analyze lib test— cleanCompatibility
Non-breaking.
modedefaults torelay, matching current behaviour, andattach()is unchanged. Pairs with base-14/scout-react#5.