leancode_cyberware_contract_base

leancode_cyberware_contract_base

leancode_cyberware_contract_base pub.dev badge leancode_cyberware_contract_base continuous integration badge

Base package for Flutter web apps running inside an iframe that need bidirectional RPC with a host page. Provides Cubit-based connection state management, semver contract versioning, and type-safe Dart wrappers over a Penpal-powered postMessage bridge.

This package is the Flutter side of the @leancodepl/cyberware-contract ecosystem. For the full end-to-end setup guide (contract schema, Dart code generation, React host, and Flutter remote), see the readme.

How it fits together

A shared contract package contains a Zod schema — the single source of truth. TypeScript types are inferred for the React host; Dart extension types are generated by @leancodepl/cyberware-contract-generator-dart. The Flutter remote uses this package to manage the connection lifecycle via ConnectToHostCubit.

Features

  • Bidirectional method calls between iframe (Flutter) and host page
  • Semver-based contract version negotiation
  • Ready-to-use ConnectToHostCubit for managing the connection lifecycle
  • Equatable states for reliable BlocBuilder rebuilds
  • Type-safe Dart wrappers over the JS interop layer
  • Works with generated Dart types from @leancodepl/cyberware-contract-generator-dart

Setup

1. Install the package

flutter pub add leancode_cyberware_contract_base

2. Load the JS asset

Add the bundled script to your Flutter web app's web/index.html before the Flutter bootstrap script:

<head>
  <!-- ... -->
</head>
<body>
  <script src="assets/packages/leancode_cyberware_contract_base/assets/connect_to_host.js"></script>
  <script src="flutter_bootstrap.js" async></script>
</body>

Usage

The typical workflow is:

  1. Define a Zod contract schema in a shared contract package
  2. Generate Dart types with npx cyberware-contract-generator-dart
  3. Implement RemoteMethodsBase (the methods the host can call on the Flutter app)
  4. Create a ConnectToHostCubit wrapper wiring the generated connectToHost and contract version
  5. Use BlocProvider + BlocBuilder to react to connection states

The generated code provides HostMethods, RemoteMethodsBase, JSRemoteMethods, JSHostMethods, RemoteUrlParams, and a typed connectToHost function. You write a thin ConnectToHostCubit subclass and a RemoteMethodsBase implementation on top.

Reacting to connection states

BlocBuilder<ConnectToHostCubit, ConnectToHostState>(
  builder: (context, state) {
    return switch (state) {
      ConnectToHostStateIdle() =>
        const Center(child: Text('Connecting...')),
      ConnectToHostStateConnected(:final host) => Column(
          children: [
            Text('Connected!'),
            ElevatedButton(
              onPressed: () => host.showNotification(
                HostShowNotificationParams(
                  message: 'Hello from Flutter!',
                ),
              ),
              child: const Text('Show notification'),
            ),
          ],
        ),
      ConnectToHostStateError(:final error) =>
        Center(child: Text('Error: $error')),
      ConnectToHostStateIncompatible(
        :final hostVersion,
        :final remoteVersion,
      ) =>
        Center(
          child: Text(
            'Incompatible: host $hostVersion, remote $remoteVersion',
          ),
        ),
    };
  },
)

Using the raw API

For more control, use connectToHostRaw directly instead of through the generated wrapper:

import 'package:web/web.dart' as web;

if (!identical(web.window.parent, web.window)) {
  final result = await connectToHostRaw<JSHostMethods>(myMethods);

  switch (result) {
    case RawConnectToHostResultConnected(:final host):
      // use host methods
    case RawConnectToHostResultError(:final error):
      // handle error
  }
}

Note

connectToHostRaw must only be called once. Subsequent calls throw a StateError. Call disconnectHost() to tear down the connection.

Contract versioning

The host page passes a contractVersion query parameter in the iframe URL. ConnectToHostCubit reads it and checks it against the contractVersionRange you provide (parsed as a semver VersionConstraint). If the versions are incompatible, the cubit emits ConnectToHostStateIncompatible instead of attempting to connect.

API overview

Symbol Description
connectToHostRaw() Establishes a raw JS-level connection to the host
disconnectHost() Tears down the active connection
parseConnectToHostResult() Parses the raw JS result into a typed result
ConnectToHostCubit Cubit managing the full connection lifecycle
ConnectToHostCubitOptions Configuration for ConnectToHostCubit
ConnectToHostState Sealed class with Idle, Connected, Incompatible, Error states
UrlParamsBase Abstract class with static accessors for URL query parameters (e.g. contractVersion)
Package Description
@leancodepl/cyberware-contract TypeScript contract definition, React hooks, and Penpal connection management. See its readme for the full end-to-end setup guide.
@leancodepl/cyberware-contract-generator-dart Generates Dart extension types from the Zod contract schema

Building the JS asset

The TypeScript source lives in js/. To rebuild the bundled JS asset:

Note

Node.js >= 22.0.0 is required.

cd js
npm install
npx vite build

The output is written to assets/connect_to_host.js.


🛠️ Maintained by LeanCode

LeanCode Logo

This package is built with 💙 by LeanCode. We are top-tier experts focused on Flutter Enterprise solutions.

Why LeanCode?

  • Creators of Patrol – the next-gen testing framework for Flutter.

  • Production-Ready – We use this package in apps with millions of users.

  • Full-Cycle Product Development – We take your product from scratch to long-term maintenance.


Need help with your Flutter project?

👉 Hire our team   •   Check our other packages