btx 0.0.24 copy "btx: ^0.0.24" to clipboard
btx: ^0.0.24 copied to clipboard

BTX Flutter SDK for customer app telemetry, feature flags, messaging, knowledge base, and native integrations.

BTX Flutter SDK #

btx is the BTX Flutter SDK for customer-app telemetry, feature flags, messaging, knowledge base, and native integrations.

Install #

dependencies:
  btx: ^0.0.24

Quickstart #

import 'package:btx/btx.dart';

await Btx.configure(
  BtxConfiguration(
    publishableClientKey: 'cfk_...',
  ),
);

await Btx.identify(
  const BtxCustomer(
    externalId: 'customer_123',
    name: 'Taylor',
    email: 'taylor@example.com',
  ),
);

Btx.log(
  'checkout_started',
  level: BtxLogLevel.info,
  message: 'Customer started checkout.',
  properties: <String, Object?>{'cartId': 'cart_123'},
);

Btx.messenger.present();

To expose the SDK-owned knowledge base viewer, opt in to messenger and the knowledge base, then identify the customer:

await Btx.configure(
  BtxConfiguration(
    publishableClientKey: 'cfk_...',
    features: const <BtxFeature>{
      BtxFeature.logs,
      BtxFeature.messenger,
      BtxFeature.knowledgeBase,
    },
  ),
);

await Btx.identify(
  const BtxCustomer(externalId: 'customer_123'),
);

Btx.knowledgeBase.present();

The default feature set is logs plus messenger. Btx.knowledgeBase.* throws a StateError until the host app enables both BtxFeature.knowledgeBase and BtxFeature.messenger, then identifies a customer. The SDK resolves the project from the publishable client key through messenger bootstrap.

BTX derives app version, build number, platform, bundle ID/package name, OS name and version, client-device model/manufacturer, physical-device status, and BTX SDK name/version from the host app by default. Android also reports the API level and security-patch version when available.

The SDK does not collect persistent phone identifiers, user-assigned device names, phone serial numbers, storage readings, or memory readings. Runtime metadata collection is best effort and never blocks SDK configuration.

Mount BtxHost inside your MaterialApp so the SDK can present messenger UI and foreground notification surfaces:

MaterialApp(
  home: BtxHost(
    child: MyAppHome(),
  ),
);

Facade Surface #

  • Btx.configure(BtxConfiguration) configures the SDK once.
  • Btx.sdkName and Btx.sdkVersion expose the SDK identity reported in app context for support diagnostics.
  • Btx.identify(BtxCustomer?) binds or clears the active customer.
  • Btx.ready() waits for pending configure/identity work to settle.
  • Btx.log(...) enqueues host telemetry. The SDK flushes logs automatically after enqueue, on app lifecycle transitions, and when batches fill.
  • Btx.refreshFeatureFlags() refreshes the active customer flag snapshot.
  • Btx.isFeatureEnabled(...) reads the current flag snapshot synchronously.
  • Btx.featureFlagEnabled(...) waits for pending SDK work and optionally refreshes before reading a flag.
  • Btx.featureFlagsListenable lets host UI rebuild when refreshed flag values change.
  • Btx.status and Btx.statusListenable expose whether telemetry is active, inactive, disabled, or disposed. Disabled states include a typed reason such as invalidPublishableClientKey or appContextMismatch.
  • Btx.messenger.present(...), presentThread(...), presentCompose(...), createThread(...), and dismiss() own messenger presentation and thread creation.
  • Btx.knowledgeBase.present(...), openArticle(...), search(...), and dismiss() own knowledge base viewer presentation after the host opts in with messenger and knowledge base features and identifies a customer.
  • Btx.push.bindAndroidSource(...), setAndroidToken(...), handleAndroidNotificationOpen(...), and unregisterAndroidDevice() own Android BTX push lifecycle once the host supplies a Firebase-neutral source.
  • Btx.flush() is available for rare lifecycle/debug waits; normal host apps should not call it for routine telemetry.
  • Btx.dispose() supports teardown and test cleanup.

Foreground reply transport #

The default facade starts messenger synchronization after Btx.identify, even before the messenger opens. With messenger and realtime enabled, one SSE connection listens while the host app is foregrounded, including while the messenger is hidden. autoStart: false and enableRealtime: false remain explicit host opt-outs.

Flutter uses the native iOS connection contract: bootstrap advertises only its four supported event kinds, and SSE requests send the session ID and acknowledged cursor without a visibility parameter. No transport acknowledgement, profiles, or heartbeat metadata are required. The events URL is resolved against the configured API base URL.

The client waits 1.5 seconds after clean-rotation reconciliation before reconnecting. The backend still controls stream duration through existing project policies: the default is 240 seconds, while a project override can close a stream after five seconds. Flutter does not adopt an advertised 60-second retry delay.

Connection headers time out after 15 seconds. No valid event or heartbeat for 45 seconds triggers recovery. Failures retry with exponential backoff and jitter, capped at 30 seconds. Heartbeats never acknowledge unapplied message cursors. Healthy idle connections do not poll /sync.

For a known thread, a complete streamed reply updates the message state and foreground notification before reconciliation finishes. Reconciliation still refreshes thread metadata and controls cursor acknowledgement. Incomplete events and unknown threads use the reconciliation path.

Backgrounding, logout, customer replacement, and disposal cancel realtime work. Foreground resume reconciles missed events. Background notification delivery continues through APNs or FCM. The foreground smoke-check target is 5 seconds with a 1-second tolerance (a 6-second upper bound), not a production percentile guarantee.

Messenger Behavior #

The packaged messenger renders URLs in message bodies as tappable links and opens them with the platform's external URL handler. Customers can long-press a message bubble to copy the message text.

Knowledge base articles #

The packaged knowledge base viewer renders the hydrated article HTML returned by BTX. Host apps must opt in with BtxFeature.knowledgeBase and BtxFeature.messenger, then identify a customer before calling Btx.knowledgeBase.*. The SDK authenticates the publishable client key through messenger bootstrap and uses the returned project context for public KB reads. The default feature set remains logs plus messenger only. KB stays in the main btx package for a single host-app install path, so WebView and video-player dependencies are present at build time even when the runtime feature is disabled.

The host integration path is:

await Btx.configure(
  BtxConfiguration(
    publishableClientKey: 'cfk_...',
    features: const <BtxFeature>{
      BtxFeature.logs,
      BtxFeature.messenger,
      BtxFeature.knowledgeBase,
    },
  ),
);

await Btx.identify(
  const BtxCustomer(externalId: 'customer_123'),
);

Btx.knowledgeBase.present();

Supported media includes:

  • Images with alt text, dimensions, and captions.
  • MP4 videos with play, pause, scrubbing, and playback-speed controls. MP4 metadata and native controllers are created only after the user taps Play video. The first tap queues playback during initialization and starts the video once metadata is ready. Each block owns and disposes its own controller; the viewer does not prewarm videos or coordinate controllers across blocks.
  • Embedded YouTube videos in a narrow in-app WebView adapter. The viewer uses the privacy-enhanced www.youtube-nocookie.com host, the official YouTube IFrame Player API, autoplay=0, playsinline=1, and sanitized start offsets. The WebView shell uses the SDK-owned HTTPS origin https://btx.secondcontext.com for both origin and widget_referrer. This nonempty client identity is required by YouTube mobile WebViews and avoids the configuration error 153 path. See the YouTube IFrame Player API reference.

Media blocks use themed surfaces. Opening an article creates no MP4 controller or YouTube WebView. MP4 activation initializes and queues playback on its first tap, so one tap is enough to start the video. YouTube's idle surface uses a validated video ID to load the thumbnail directly from i.ytimg.com; it does not create a WebView until activation. Media state and teardown are local to each mounted block, with generation checks protecting against late native callbacks after replacement or dismissal. Backgrounding pauses local MP4 playback and does not trigger automatic reinitialization.

Images, MP4 videos, and attachment links are rendered only when their linked asset publicUrl points to the BTX public asset route on the configured API origin: /apps/btx/knowledge/v1/assets/<assetId>. Raw src values in hydrated HTML are ignored when they disagree with the linked asset. YouTube remains a separate narrow exception for validated /embed/<11-character-id> URLs. Project logos are rendered only through the BTX public project-logo route on the configured API origin: /apps/btx/knowledge/v1/projects/<projectId>/logo. During backend rollout, the SDK also accepts existing uploaded Supabase Storage object URLs for the exact project-icons/<projectId>/icon object. Development HTTP is accepted only when it is the configured apiBaseUrl origin and the URL still matches one of these BTX public KB routes.

MP4 videos provide a fullscreen action through a local SDK route that reuses the same Chewie and native video controller. This does not create another media controller or backend request. The route closes before the widget disposes its controllers, including when the article is dismissed or replaced. The SDK does not call SystemChrome, force portrait or landscape orientation, hide system UI, or change the host app's navigation policy. The host app remains authoritative for orientation, system UI, and platform navigation behavior. YouTube remains inline in v1 and does not expose an external browser/app fallback.

Loading uses a themed skeleton. A real image, video, or WebView failure moves that block to a local fallback with a bounded, user-triggered retry action; there are no retry timers. Media failures do not remove article text, headings, lists, or other media. The hydrated HTML field is authoritative when it contains renderable content, and plainText is used only as a fallback. Linked assets marked attachment are kept in the separate attachments section and are not rendered inline when referenced by article media. Linked inline images must use the inline or hero asset use and an image MIME type; linked MP4 media must use the video asset use and video/mp4.

KB media and project-logo requests are limited to BTX public KB routes on the configured API origin. On Android, declare the host app's internet permission:

<uses-permission android:name="android.permission.INTERNET" />

For local HTTP development, add only the narrowly scoped App Transport Security exception required for the configured local API origin. Production should use HTTPS so no ATS exception is needed. The SDK's iOS podspec requires iOS 13.0 or newer; WebView-based media also requires Android API 21 or newer. The SDK keeps its declared Dart 3.5 and Flutter 3.24 minimums.

MP4 playback depends on the native platform codec. Unsupported codecs, broken files, and offline media produce an inline fallback instead of an app-level error and can be retried manually. Article dismissal disposes mounted media resources. YouTube playback depends on an available platform WebView, JavaScript, network access, and YouTube's embed availability.

The viewer coalesces identical in-flight article and search requests and ignores responses that belong to an older article, search query, cleared search, dismissed viewer, or disposed controller. It does not add a persistent response cache. Logo loading makes one validated BTX-route attempt and never blocks article or category rendering. A real failure resolves to the existing initials fallback without retry timers.

Host-owned feedback forms can create a customer message thread directly without opening the SDK-owned messenger UI:

final thread = await Btx.messenger.createThread(
  subject: 'App feedback',
  body: feedbackText,
  launchContext: const BtxLaunchContext(
    conversationPurpose: BtxConversationPurpose.feedback,
    entryPoint: 'feedback',
    sourceType: 'host_feedback_form',
  ),
);

BtxConversationPurpose.support is the default for normal messenger threads. Feedback threads remain replyable, but BTX operator surfaces can label and prioritize them separately.

Messenger Realtime Lifecycle #

The public host API is unchanged. Stream ownership depends on the foreground lifecycle, session, and controller generation. UI presentation state controls rendering and foreground notification eligibility, not connection policy.

Presenting the messenger or returning to the foreground performs explicit reconciliation. Opening, hiding, or switching messenger surfaces reuses the customer's foreground SSE connection and preserves any pending reconnect delay. Flutter sends no visibility query parameter. Backend visibility logs reflect the server's inferred policy, not whether the Flutter messenger is visible. A session change replaces the stream; backgrounding or disposal closes it.

Clean stream rotation retains one single-flight reconciliation, followed by the client's 1.5-second retry wait. HTTP reconciliation adds time before that wait. Neither hidden nor visible messenger surfaces run a periodic sync watchdog. State-changing events, explicit actions, and recovery still reconcile through /sync. Foreground replies arrive through SSE whether the messenger is open or hidden. Push provides background alerts when configured and permitted; returning to the foreground catches up even without push.

Bootstrap validates session credentials but does not require transport profiles or an sse_v1 acknowledgement. This policy uses the existing backend contract.

Configuration #

await Btx.configure(
  BtxConfiguration(
    apiBaseUrl: Uri.parse('http://localhost:3000'),
    publishableClientKey: 'cfk_...',
    features: const <BtxFeature>{
      BtxFeature.logs,
      BtxFeature.messenger,
      BtxFeature.knowledgeBase,
    },
  ),
);

For apps with multiple build variants, provide the key set once and let BTX resolve the current platform/package key:

await Btx.configure(
  BtxConfiguration.withPublishableClientKeys(
    publishableClientKeys: const BtxPublishableClientKeys(
      defaultKey: 'cfk_ios_prod',
      iosBundleIds: <String, String>{
        'com.example.app.beta': 'cfk_ios_beta',
      },
      androidPackageNames: <String, String>{
        'com.example.app': 'cfk_android',
      },
    ),
    features: const <BtxFeature>{BtxFeature.logs},
  ),
);

Use appContext only for app-specific low-cardinality attributes or explicit app-version/build-number overrides. Platform, OS, client-device, and BTX SDK attributes are SDK-owned so one host cannot accidentally report stale runtime metadata:

await Btx.configure(
  BtxConfiguration(
    publishableClientKey: 'cfk_...',
    appContext: const BtxAppContext(
      attributes: <String, String>{
        'releaseRing': 'beta',
      },
    ),
    features: const <BtxFeature>{
      BtxFeature.logs,
      BtxFeature.messenger,
    },
  ),
);

Use features: {BtxFeature.logs} for telemetry-only integrations such as the current Captify use case. Omit BtxFeature.knowledgeBase unless the host app intentionally exposes the KB viewer.

Telemetry delivery preserves queued logs across runtime recreation. A rejected publishable key or app-context mismatch pauses delivery without retrying every two seconds; update the platform/package key configuration and recreate the runtime to resume delivery. Network, rate-limit, and server failures remain queued and retry with bounded exponential backoff.

Android #

Android messenger UI is supported. Android push is Firebase-neutral in the core package: host apps own Firebase initialization and dependencies, then bind plain Dart token/open callbacks into BTX. Android push is scoped to the messenger feature; BtxFeature.messenger and enablePushBridge=true are both required for BTX registration and tap routing.

final messaging = FirebaseMessaging.instance;

await Btx.push.bindAndroidSource(
  BtxAndroidPushSource(
    firebaseProjectId: messaging.app.options.projectId,
    getToken: messaging.getToken,
    tokenRefreshes: messaging.onTokenRefresh,
    getInitialNotification: () async {
      final message = await messaging.getInitialMessage();
      return message == null
          ? null
          : BtxAndroidNotificationOpen(
              data: Map<String, Object?>.from(message.data),
              handleIfUnhandled: () => handleHostNotification(message),
            );
    },
    notificationOpens: FirebaseMessaging.onMessageOpenedApp.map(
      (message) => BtxAndroidNotificationOpen(
        data: Map<String, Object?>.from(message.data),
        handleIfUnhandled: () => handleHostNotification(message),
      ),
    ),
  ),
);

The btx package does not depend on FlutterFire, declare a Firebase receiver or service, or delete/change host Firebase tokens. Android notification permission is requested by BTX on first messenger presentation when an Android source or token is available.

Advanced Surfaces #

The package exposes lower-level non-knowledge-base controller and runtime types for tests, custom embedded views, and advanced host ownership. Knowledge-base controller, mount, and view implementation types are intentionally internal; new app integrations should use the Btx.knowledgeBase facade. See doc/advanced.md for worker-mode telemetry, direct controller ownership, and native push details.

0
likes
110
points
442
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

BTX Flutter SDK for customer app telemetry, feature flags, messaging, knowledge base, and native integrations.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

chewie, device_info_plus, flutter, flutter_linkify, html, http, image, image_picker, package_info_plus, path, path_provider, shadcn_ui, shared_preferences, url_launcher, uuid, video_player, webview_flutter

More

Packages that depend on btx

Packages that implement btx