karam_messenger 0.2.0 copy "karam_messenger: ^0.2.0" to clipboard
karam_messenger: ^0.2.0 copied to clipboard

Karam messenger for Flutter: presents Karam's hosted customer-support messenger in a secured WebView, with identity, deep links and unread counts.

karam_messenger (Flutter) #

Karam's customer-support messenger for Flutter apps. The SDK shows Karam's hosted messenger in a locked-down WebView and handles presentation, dismissal, loading and error states, the keyboard, safe areas, Android back, attachments, RTL and dark mode. You call a handful of methods.

  • Android (minSdk 24) and iOS (13+; Flutter 3.47 projects default to 15). Web and desktop are not supported.
  • Dart ^3.13, Flutter >=3.47.
  • No custom platform channels. It is built on webview_flutter, shared_preferences, http, url_launcher, file_selector and image_picker.

Building a Zid merchant app with AppsBunches? Use package:karam_messenger/appsbunches.dart and follow the AppsBunches integration guide instead of the steps below.

Install #

flutter pub add karam_messenger

1. Place the host once #

The host widget draws the messenger. Put it around your app's navigator, in the app's builder:

MaterialApp(
  builder: (context, child) => KaramMessengerHost(child: child!),
  home: const HomeScreen(),
)

This works the same with MaterialApp.router (go_router and similar), CupertinoApp and WidgetsApp. When you call present(), the host pushes a full-screen route onto your root navigator. That route covers nested navigators' tab bars and any open dialogs, gets Android back and predictive back, and hides your screens from screen readers while it is open. Back closes the messenger. It never pops your screen underneath.

Without a host, pass a BuildContext that has a Navigator above it: KaramMessenger.instance.present(context: context). The messenger is pushed onto that context's root navigator and behaves the same way.

2. Initialise #

void main() {
  KaramMessenger.instance.init(const KaramMessengerConfig(
    appId: 'app_…',                             // Karam portal → Settings → Developers
    locale: MessengerLocale.ar,                  // optional: ar | en (default: the page decides)
    colorScheme: MessengerColorScheme.system,    // optional: light | dark | system
  ));
  runApp(const MyApp());
}

messengerUrl and apiUrl override the Karam endpoints for staging or local development only. They must be https. Plain http is accepted only for localhost and 127.0.0.1. Calling init again closes the messenger and switches to the new configuration. The current identity is kept.

3. Identify signed-in users #

Karam identifies users with an identity token: a JWS (HS256) that your backend signs with the app's identity secret. Never put the secret in the app.

claim
sub required: your stable user id (1–255 chars)
aud required: the appId
exp required: at most 1 hour ahead
iat optional
name, email, phone optional profile enrichment. Karam never matches users by these.
KaramMessenger.instance.identify(tokenProvider: () async {
  // Call YOUR backend, authenticated with YOUR session.
  final response = await myApi.post('/karam/identity-token');
  return response.data['token'] as String;
});

The SDK calls tokenProvider only when it needs a token: when the messenger opens, when Karam's session expires, and for getUnreadCount(). It never stores the token and never puts it in a URL. A provider that throws, returns an empty or oversized token (over 8192 chars), or takes longer than 15 seconds shows the native error view with Try again.

Calling identify for a different user while the messenger is open reloads the messenger. Nothing from the previous user carries over, and a late answer meant for the previous user is discarded.

Visitors who never call identify are anonymous. Their conversation is tied to this installation.

KaramMessenger.instance.present();                                   // open
KaramMessenger.instance.present(conversationId: '3f0c7a2e-…');       // open a conversation
KaramMessenger.instance.dismiss();                                   // close

conversationId must be a UUID, otherwise present throws KaramMessengerException(invalid_options). If the messenger is already open, the conversation opens inside it. If it is still loading, the conversation opens as soon as it is ready. A conversation id from a push notification only opens that user's own conversations.

5. Unread count #

try {
  final unread = await KaramMessenger.instance.getUnreadCount();
} on KaramMessengerException catch (e) {
  switch (e.code) {
    case KaramMessengerErrorCode.cancelled: break;       // identify/logout ran meanwhile: ignore
    case KaramMessengerErrorCode.notIdentified: break;   // anonymous visitors have no count
    default: /* identity_unavailable, identity_rejected, network */
  }
}

The messenger does not need to be open. The count is never reported for a user who has since logged out or been replaced.

6. Logout #

await KaramMessenger.instance.logout();

Logout closes the messenger, forgets the identity and starts a new anonymous visitor. It rotates the installation id: karam.messenger.installationId in SharedPreferences is the only key the SDK writes. If the messenger page is open, it also clears that page's own localStorage/sessionStorage. It does not touch any other app data, cookies or WebViews.

If the messenger was closed at logout, the previous visitor's anonymous session can remain in the messenger origin's localStorage until the next time the messenger opens. The page then purges every key that does not belong to the new installation id before it renders anything. Identified sessions are never persisted by the page.

Events and state #

KaramMessenger.instance.events.listen((event) {
  switch (event) {
    case MessengerPresentedEvent(): …
    case MessengerDismissedEvent(): …   // a good moment to refresh the unread badge
    case MessengerErrorEvent(:final code, :final retryable): …
  }
});
KaramMessenger.instance.presentation; // ValueListenable<PresentationState>

MessengerErrorEvent is one of two kinds:

  • MessengerPageErrorEvent: the page reported config_unavailable, identity_rejected, conversation_not_found, network or internal and draws its own UI.
  • MessengerPresentationFailedEvent: the SDK shows its native error view, for load_failed, timeout or identity_unavailable.

Attachments: platform setup #

iOS: add these keys to Info.plist. WKWebView presents the system picker and camera itself.

<key>NSCameraUsageDescription</key>
<string>Take a photo to send to support.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Choose a photo to send to support.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Record a video to send to support.</string>

Android: the SDK answers <input type="file"> with the system document picker (file_selector), or with the camera (image_picker) when the input asks for a capture. It needs no storage permission. In AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"/>
<queries>
  <intent><action android:name="android.intent.action.VIEW"/><data android:scheme="https"/></intent>
  <intent><action android:name="android.intent.action.SENDTO"/><data android:scheme="mailto"/></intent>
  <intent><action android:name="android.intent.action.DIAL"/><data android:scheme="tel"/></intent>
  <intent><action android:name="android.media.action.IMAGE_CAPTURE"/></intent>
</queries>

INTERNET is only merged into debug builds by default, so release builds need it declared explicitly. If your app declares the CAMERA permission, Android requires you to have been granted it at runtime before any app, including this SDK, can open the camera.

Layout behaviour #

  • Safe areas. On iOS the WebView runs edge to edge and the page pads itself with env(safe-area-inset-*). On Android the SDK lays the WebView out inside the safe area and tells it to ignore system-bar, cutout and IME insets, so nothing is padded twice. The native loading and error views use SafeArea on both platforms.
  • Keyboard. The page area shrinks above the keyboard, which is equivalent to resizeToAvoidBottomInset. Android needs android:windowSoftInputMode="adjustResize" on the activity, which is Flutter's default.
  • RTL and dark mode. The native views follow locale, or your app's locale when locale is not set, and colorScheme, where system follows the platform brightness.

Security model #

  • The WebView loads only the messenger origin. Other https, mailto: and tel: links open in the system browser or handler. Every other scheme, including javascript:, data:, file:, intent: and blob:, is refused. Foreign iframes are refused on iOS.
  • The page URL carries only appId, platform, protocol and sdkVersion. Tokens travel only over the bridge, only after the page announces ready, and only to the messenger origin.
  • webview_flutter JavaScript channels don't report the sending frame. The SDK attributes each bridge message to the origin of the WebView's current URL, which the navigation policy restricts to the messenger origin. Every message is parsed by the protocol v1 parser. Anything malformed or unknown is dropped.
  • Web permission requests (camera, microphone, geolocation) are denied. File access is off on Android. The WebView is inspectable only in debug builds.

Troubleshooting #

Symptom Cause
Assertion: present() needs a KaramMessengerHost No host in the tree and no context passed. See step 1.
KaramMessengerHost found no Navigator below it The host was placed below your navigator or in a subtree without one. Use the app builder.
invalid_options from init Blank appId, or a non-https messengerUrl/apiUrl.
Error view "Couldn't sign you in" tokenProvider threw, timed out (15 s) or returned an empty or oversized token. Check your backend.
Error view "This is taking too long" The page didn't answer within 15 s: very slow network, or a messengerUrl that isn't the Karam messenger.
Page shows "identity rejected" Karam refused the token: wrong secret, aud not the appId, or exp more than 1 hour ahead.
Links do nothing on Android 11+ Missing <queries> entries (see above).
Blank page on Android release builds Missing INTERNET permission.

Debug builds print operational logs prefixed [KaramMessenger]. They never contain tokens or message contents.

Development #

cd sdks/flutter
flutter test      # conformance fixtures, controller scenarios, host widget tests
flutter analyze

test/protocol_conformance_test.dart reads the shared conformance suite from the monorepo at ../../packages/messenger-bridge/fixtures/protocol-v1.json, relative to this package. flutter test runs from the package root. The Dart parser (lib/src/protocol.dart) and controller (lib/src/controller.dart) are ports of packages/messenger-bridge/src/{protocol,controller}.ts. The TypeScript is normative. Keep the ports in step with it, method for method.

example/ is a small app with a placeholder appId and token endpoint. Replace both constants in example/lib/main.dart.

0
likes
140
points
22
downloads

Documentation

API reference

Publisher

verified publisherkaramai.co

Weekly Downloads

Karam messenger for Flutter: presents Karam's hosted customer-support messenger in a secured WebView, with identity, deep links and unread counts.

Homepage
Repository (GitHub)
View/report issues

Topics

#chat #customer-support #messenger #webview

License

MIT (license)

Dependencies

file_selector, flutter, http, image_picker, shared_preferences, url_launcher, webview_flutter, webview_flutter_android, webview_flutter_platform_interface, webview_flutter_wkwebview

More

Packages that depend on karam_messenger