karam_messenger 0.1.0
karam_messenger: ^0.1.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.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:karam_messenger/karam_messenger.dart';
/// Your messenger app id (Karam portal → Settings → Developers).
const karamAppId = 'app_replace_with_your_app_id';
/// YOUR backend endpoint that returns a Karam identity token for the signed-in
/// user (signed with the app's identity secret — never ship the secret in the
/// app). See the README for the token format.
final identityTokenEndpoint = Uri.parse(
'https://your-backend.example.com/karam/identity-token',
);
void main() {
KaramMessenger.instance.init(
const KaramMessengerConfig(
appId: karamAppId,
colorScheme: MessengerColorScheme.system,
),
);
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
title: 'Karam Messenger',
theme: ThemeData(colorSchemeSeed: Colors.indigo),
darkTheme: ThemeData(
colorSchemeSeed: Colors.indigo,
brightness: Brightness.dark,
),
// One host, around the app's navigator.
builder: (context, child) => KaramMessengerHost(child: child!),
home: const HomeScreen(),
);
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _messenger = KaramMessenger.instance;
final _conversationId = TextEditingController();
late final StreamSubscription<MessengerEvent> _events;
String? _userId;
int? _unread;
String? _status;
@override
void initState() {
super.initState();
_events = _messenger.events.listen((event) {
// The user has probably read something: refresh the badge.
if (event is MessengerDismissedEvent) unawaited(_refreshUnread());
if (event is MessengerErrorEvent) {
setState(() => _status = 'Messenger error: ${event.code}');
}
});
}
@override
void dispose() {
unawaited(_events.cancel());
_conversationId.dispose();
super.dispose();
}
/// Asks the host backend for a fresh token. The SDK calls this only when it
/// needs one and never stores the result.
Future<String> _fetchIdentityToken(String userId) async {
final response = await http.post(
identityTokenEndpoint,
headers: {'content-type': 'application/json'},
// A real app authenticates this request with ITS OWN session instead
// of sending a user id.
body: jsonEncode({'userId': userId}),
);
if (response.statusCode != 200) {
throw Exception('token endpoint answered ${response.statusCode}');
}
return (jsonDecode(response.body) as Map<String, Object?>)['token']!
as String;
}
void _signIn() {
const userId = 'demo-user-42';
_messenger.identify(tokenProvider: () => _fetchIdentityToken(userId));
setState(() {
_userId = userId;
_status = null;
});
unawaited(_refreshUnread());
}
Future<void> _signOut() async {
await _messenger.logout();
setState(() {
_userId = null;
_unread = null;
_status = null;
});
}
Future<void> _refreshUnread() async {
if (_userId == null) return;
try {
final count = await _messenger.getUnreadCount();
if (mounted) setState(() => _unread = count);
} on KaramMessengerException catch (error) {
// `cancelled`: the user changed while the request was in flight.
if (error.code == KaramMessengerErrorCode.cancelled || !mounted) return;
setState(() => _status = 'Unread count: ${error.code.wireName}');
}
}
void _openConversation() {
try {
_messenger.present(conversationId: _conversationId.text.trim());
} on KaramMessengerException catch (error) {
setState(() => _status = error.message);
}
}
@override
Widget build(BuildContext context) {
final signedIn = _userId != null;
return Scaffold(
appBar: AppBar(
title: const Text('Karam Messenger'),
actions: [
IconButton(
tooltip: 'Refresh unread count',
onPressed: signedIn ? _refreshUnread : null,
icon: Badge(
isLabelVisible: (_unread ?? 0) > 0,
label: Text('${_unread ?? 0}'),
child: const Icon(Icons.chat_bubble_outline),
),
),
],
),
body: ListView(
padding: const EdgeInsets.all(24),
children: [
SwitchListTile(
title: const Text('Signed in'),
subtitle: Text(signedIn ? 'As $_userId' : 'Anonymous visitor'),
value: signedIn,
onChanged: (on) => on ? _signIn() : _signOut(),
),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: _messenger.present,
icon: const Icon(Icons.support_agent),
label: const Text('Open messenger'),
),
const SizedBox(height: 32),
TextField(
controller: _conversationId,
decoration: const InputDecoration(
labelText: 'Conversation id',
hintText: '3f0c7a2e-5b1d-4c8e-9a7f-2d6b8e1c4a90',
),
),
const SizedBox(height: 12),
OutlinedButton(
onPressed: _openConversation,
child: const Text('Open conversation'),
),
if (_status != null) ...[
const SizedBox(height: 24),
Text(
_status!,
style: TextStyle(color: Theme.of(context).colorScheme.error),
),
],
],
),
);
}
}