# Matrix SDK

> API reference for MeetingPlaceMatrixSDK, MeetingPlaceMatrixChatSDK, and MatrixConfig: the Matrix transport implementation for Meeting Place.

The Matrix SDK provides the Matrix transport implementation for the Meeting Place SDK. It extends MeetingPlaceCoreSDK to route individual and group chats over a [Matrix](https://matrix.org/) homeserver rather than the default DIDComm transport. Use it as a drop-in replacement for MeetingPlaceCoreSDK wherever you need Matrix room features.

Use meeting_place_matrix when you need any of the following:

- Individual or group Matrix chats with room history.

- Native Matrix end-to-end encryption backed by vodozemac.

- Rich messaging: image, video, file, and voice attachments, reactions, message editing and deletion, typing indicators, and delivery receipts.

- Optional audio/video calling via a LiveKit SFU.

For applications that need only DIDComm-based messaging without Matrix room features, use meeting_place_chat directly.

## Install Dependency

Package: meeting_place_matrix

```bash
dart pub add meeting_place_matrix
```

Check the latest version on the [pub.dev registry](https://pub.dev/packages/meeting_place_matrix) or view the source on [GitHub](https://github.com/affinidi/affinidi-meetingplace-sdk-dart/tree/main/packages/meeting_place_matrix).

## Requirements

- Dart SDK ^3.8.0.

- A running Matrix homeserver.

- The vodozemac encryption runtime initialised before the first Matrix login. See [Initialise the encryption runtime](#initialise-the-encryption-runtime).

- For audio/video calling: a LiveKit JWT service and a LiveKit SFU.

## Initialise the encryption runtime

The Matrix SDK uses native end-to-end encryption backed by the [vodozemac](https://pub.dev/packages/vodozemac) library. Initialise vodozemac once at application startup, before creating a MeetingPlaceMatrixSDK instance. Matrix client creation will fail if vodozemac has not been initialised first.

Flutter apps: add [flutter_vodozemac](https://pub.dev/packages/flutter_vodozemac) to your pubspec.yaml and call init() from main():

```dart
import 'package:flutter/widgets.dart';
import 'package:flutter_vodozemac/flutter_vodozemac.dart' as fvod;

Future main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await fvod.init();
  // ... create MeetingPlaceMatrixSDK
}
```

Pure-Dart apps: build the vodozemac native library for your target platform (see the [vodozemac README](https://pub.dev/packages/vodozemac)) and initialise it directly:

```dart
import 'package:vodozemac/vodozemac.dart' as vod;

Future main() async {
  await vod.init(libraryPath: '/path/to/vodozemac/dylib/dir');
  // ... create MeetingPlaceMatrixSDK
}
```

## Classes and Methods

### MatrixConfig

MatrixConfig carries the configuration required to connect MeetingPlaceMatrixSDK to a Matrix homeserver. Pass an instance to MeetingPlaceMatrixSDK.create().

#### Constructor

    mediatorDid
    String
    Required
  DIDComm mediator DID used for discovery and connection flows.

    controlPlaneDid
    String
    Required
  Control Plane DID used for discovery and Matrix JWT login.

    homeserver
    Uri
    Required
  URL of the Matrix homeserver (for example, https://matrix.example.com).

    databaseFactory
    MatrixDatabaseFactory
    Required
  Opens the local Matrix database for sessions, sync state, and encryption data.

    deviceId
    String
    Required
  Device identifier used for Matrix device binding.

    serverName
    String
    Optional
  Matrix server name for user ID derivation (@hash:<serverName>). Defaults to homeserver.host. Set this explicitly when a tunnel or reverse proxy hostname differs from the Synapse server_name, to ensure all clients derive consistent user IDs.

    livekitServiceUrl
    Uri?
    Optional
  URL of the LiveKit JWT service for issuing call tokens. Required to enable audio/video calling. When omitted, the call plugin is not created.

    livekitSfuUrl
    Uri?
    Optional
  WebSocket URL of the LiveKit SFU. Required when enabling audio/video calling: MeetingPlaceMatrixSDK.create() throws MeetingPlaceLiveKitCallMisconfiguredException when this is omitted and livekitServiceUrl is set. Also overrides the SFU URL from the JWT token response, which is useful in local development when the container-internal hostname is not reachable from the device.

    outgoingCallTimeout
    Duration
    Optional
  How long the caller waits for the remote party to answer before the call ends and is reported as missed. Defaults to 60 seconds.

Example

```dart
import 'package:meeting_place_matrix/meeting_place_matrix.dart';

final config = MatrixConfig(
  mediatorDid: 'did:web:mediator.example.com:.well-known',
  controlPlaneDid: 'did:web:controlplane.example.com',
  homeserver: Uri.parse('https://matrix.example.com'),
  databaseFactory: matrixDatabaseFactory,
  deviceId: 'device-id-1',
);
```

### MeetingPlaceMatrixSDK

MeetingPlaceMatrixSDK implements MeetingPlaceCoreSDK backed by a Matrix homeserver. It delegates all core behaviour to an inner MeetingPlaceCoreSDK instance and intercepts Matrix-specific transport calls. Use it anywhere a MeetingPlaceCoreSDK instance is accepted. The additional properties and methods on this class expose audio/video call management.

#### create

A static factory that creates an instance of MeetingPlaceMatrixSDK. Audio/video calling is enabled when livekitServiceUrl, rtcDelegate, and roomFactory are all provided.

Parameters

    wallet
    Wallet
    Required

A digital wallet to manage cryptographic keys for signing and verification.

Requires the [ssi package](https://pub.dev/packages/ssi).

    repositoryConfig
    RepositoryConfig
    Required
  Storage configuration providing connection offer, group, channel, and key repositories.

    config
    MatrixConfig
    Required
  Matrix connection configuration. See [MatrixConfig](#matrixconfig).

    options
    MeetingPlaceMatrixSdkOptions
    Optional
  SDK-level options such as retry behaviour, timeouts, and DID resolver address. Defaults apply when omitted.

    logger
    MeetingPlaceCoreSDKLogger
    Optional
  Optional logger for custom logging behaviour.

    rtcDelegate
    WebRTCDelegate
    Optional
  WebRTC delegate required for audio/video calling. Provide together with roomFactory and MatrixConfig.livekitServiceUrl. Flutter apps should use FlutterMatrixRTCDelegate from meeting_place_livekit_flutter.

    roomFactory
    LiveKitRoomFactory
    Optional
  LiveKit room factory required for audio/video calling. Provide together with rtcDelegate and MatrixConfig.livekitServiceUrl. Flutter apps should use FlutterLiveKitRoom from meeting_place_livekit_flutter.

Example

```dart
import 'package:meeting_place_matrix/meeting_place_matrix.dart';
import 'package:ssi/ssi.dart';

final matrixSDK = await MeetingPlaceMatrixSDK.create(
  wallet: Bip32Wallet.fromSeed(seed),
  repositoryConfig: RepositoryConfig(
    connectionOfferRepository: ConnectionOfferRepositoryImpl(storage: storage),
    groupRepository: GroupRepositoryImpl(storage: storage),
    channelRepository: ChannelRepositoryImpl(storage: storage),
    keyRepository: KeyRepositoryImpl(storage: storage),
  ),
  config: MatrixConfig(
    mediatorDid: 'did:web:mediator.example.com:.well-known',
    controlPlaneDid: 'did:web:controlplane.example.com',
    homeserver: Uri.parse('https://matrix.example.com'),
    databaseFactory: matrixDatabaseFactory,
    deviceId: 'device-id-1',
  ),
);
```

#### Properties

    isCallSupported
    bool
    Optional
  Whether audio/video calling is available. Returns false when livekitServiceUrl was not set, or rtcDelegate and roomFactory were not passed to create().

    incomingCalls
    Stream<IncomingAudioVideoCallEvent>
    Optional
  Stream of incoming call events. Empty when no call plugin is configured.

    cancelledCalls
    Stream<IncomingAudioVideoCallEvent>
    Optional
  Stream of cancelled incoming-call events. Empty when no call plugin is configured.

    activeCallSession
    LiveKitCallSession?
    Optional
  The currently active call session, or null when no call is in progress.

#### startCall

Starts an outbound audio or video call to another participant. Returns a live AudioVideoCallSession handle.

Parameters

    otherPartyChannelDid
    String
    Required
  The channel DID of the participant to call.

    mediaType
    CallMediaType
    Required
  The type of call: CallMediaType.audio or CallMediaType.audioVideo.

Throws MeetingPlaceLiveKitCallOperationException when no call plugin is configured. Check isCallSupported before calling.

Example

```dart
if (matrixSDK.isCallSupported) {
  final session = await matrixSDK.startCall(
    otherPartyChannelDid: 'did:example:bob-channel',
    mediaType: CallMediaType.audioVideo,
  );
}
```

#### acceptCall

Accepts an incoming call.

Parameters

    callId
    String
    Required
  The ID of the incoming call to accept.

Throws MeetingPlaceLiveKitCallOperationException when no call plugin is configured.

Example

```dart
matrixSDK.incomingCalls.listen((event) async {
  await matrixSDK.acceptCall(callId: event.callId);
});
```

#### declineCall

Declines an incoming call.

Parameters

    callId
    String
    Required
  The ID of the incoming call to decline.

Throws MeetingPlaceLiveKitCallOperationException when no call plugin is configured.

Example

```dart
matrixSDK.incomingCalls.listen((event) async {
  await matrixSDK.declineCall(callId: event.callId);
});
```

#### leaveCurrentCall

Leaves the currently active call. No-op when no call is in progress or no call plugin is configured.

Parameters

No parameters required.

Example

```dart
await matrixSDK.leaveCurrentCall();
```

#### ringGroupMember

Sends a targeted call-invite notification to a single group member, leaving other members undisturbed.

Parameters

    groupChannelDid
    String
    Required
  The channel DID of the group.

    memberDid
    String
    Required
  The DID of the group member to ring.

    mediaType
    CallMediaType
    Required
  The type of call: CallMediaType.audio or CallMediaType.audioVideo.

Throws MeetingPlaceLiveKitCallOperationException when no call plugin is configured.

Example

```dart
await matrixSDK.ringGroupMember(
  groupChannelDid: 'did:example:group-channel',
  memberDid: 'did:example:member-alice',
  mediaType: CallMediaType.audio,
);
```

### MeetingPlaceMatrixChatSDK

MeetingPlaceMatrixChatSDK is the Matrix-backed chat SDK for individual and group conversations. It implements MeetingPlaceChatSDK and provides rich messaging features: attachments, reactions, message editing and deletion, typing indicators, and delivery receipts.

This class only supports channels with transport == ChannelTransport.matrix. For DIDComm channels, use ChatSDK.initialiseFromChannel() from meeting_place_chat.

#### initialiseFromChannel

A static factory that creates a MeetingPlaceChatSDK instance from a Matrix channel. Returns a group or individual Matrix chat implementation based on the channel type.

Parameters

    channel
    Channel
    Required
  The channel to create the chat from. Must have transport == ChannelTransport.matrix.

    coreSDK
    MeetingPlaceCoreSDK
    Required
  An instance of MeetingPlaceMatrixSDK or any MeetingPlaceCoreSDK backed by the Matrix transport.

    chatRepository
    ChatRepository
    Required
  The repository used for persisting chat messages and sync state.

    options
    MeetingPlaceChatSDKOptions
    Required
  Chat session configuration, including activity expiry, delete window, and presence intervals.

    card
    ContactCard
    Optional
  Optional contact card representing the local user’s profile.

    logger
    MeetingPlaceMatrixSDKLogger
    Optional
  Optional logger for custom logging behaviour.

Example

```dart
import 'package:meeting_place_matrix/meeting_place_matrix.dart';

final chatSDK = await MeetingPlaceMatrixChatSDK.initialiseFromChannel(
  channel,
  coreSDK: matrixSDK,
  chatRepository: chatRepository,
  options: const MeetingPlaceMatrixChatSdkOptions(),
  card: myContactCard,
);
```

#### startChatSession

Starts a Matrix chat session. Subscribes to the Matrix room, loads persisted messages, and fetches missed room history since the last session marker. Returns a Chat object immediately with the locally persisted snapshot. Matrix auth and history backfill run in the background and push updates through chat.stream.

Parameters

No parameters required.

Example

```dart
final chat = await chatSDK.startChatSession();

// Render the initial snapshot
for (final message in chat.messages) {
  print(message);
}

// Listen for new messages and events
chat.stream.listen((data) {
  print('New event: ${data.chatItem}');
});
```

#### sendTextMessage

Sends a text message, optionally with attachments.

Parameters

    text
    String
    Required
  The message text to send.

    attachments
    List<ChatAttachment>
    Optional
  Optional list of attachments (image, video, file, or voice). Defaults to an empty list.

Example

```dart
// Plain text
await chatSDK.sendTextMessage('Hello via Matrix!');

// With an image attachment
await chatSDK.sendTextMessage(
  'Check this out',
  attachments: [
    ChatAttachment(
      data: imageBytes,
      contentType: 'image/jpeg',
      filename: 'photo.jpg',
    ),
  ],
);
```

#### reactOnMessage

Toggles a reaction on a message. Adds the reaction if the local user has not already reacted with that emoji. Removes it if they have.

Parameters

    message
    Message
    Required
  The message to react to.

    reaction
    String
    Required
  The emoji to add or remove as a reaction.

Example

```dart
await chatSDK.reactOnMessage(message, reaction: '👍');
```

#### editTextMessage

Edits the text of a previously sent message. Only the original sender can edit. The message must have been delivered to the server (has a transportId).

Parameters

    message
    Message
    Required
  The message to edit. Must be sent by the local user.

    newText
    String
    Required
  The replacement text. Must be non-empty and different from the current text.

Example

```dart
await chatSDK.editTextMessage(message, 'Corrected message text');
```

#### deleteMessage

Deletes a previously sent message. Only the original sender can delete. The deletion window is configured via MeetingPlaceChatSDKOptions.deleteMessageWindow.

Parameters

    message
    Message
    Required
  The message to delete. Must be sent by the local user.

    localOnly
    bool
    Optional
  When true, hides the message locally only without sending a Matrix redaction event to the room. Defaults to false.

Example

```dart
// Delete for all participants
await chatSDK.deleteMessage(message);

// Hide locally only
await chatSDK.deleteMessage(message, localOnly: true);
```

#### sendChatActivity

Sends a typing indicator to the Matrix room. The indicator clears automatically after MeetingPlaceChatSDKOptions.chatActivityExpiry elapses without a new call.

Parameters

No parameters required.

Example

```dart
// Call when the local user starts typing
await chatSDK.sendChatActivity();
```

#### sendChatDeliveredMessage

Sends an m.read receipt to the Matrix room, marking a message as delivered.

Parameters

    messageId
    String
    Required
  The Matrix event ID of the message to mark as delivered.

Example

```dart
await chatSDK.sendChatDeliveredMessage(message.transportId!);
```

#### downloadMedia

Downloads a media attachment hosted on the Matrix homeserver. The attachment must have a non-null transportId.

Parameters

    attachment
    ChatAttachment
    Required
  The attachment to download.

Example

```dart
final bytes = await chatSDK.downloadMedia(attachment);
```

#### sendCustomEvent

Dispatches an arbitrary Matrix room event. The SDK does not persist a ChatItem or push to chatStream for the sender. Receivers handle the event through their incoming routers based on the event type. Use this as a low-level escape hatch for event types not supported by the built-in methods.

Parameters

    type
    String
    Required
  The Matrix event type string (for example, com.example.my_event).

    payload
    Map<String, dynamic>
    Required
  The event content payload.

Example

```dart
await chatSDK.sendCustomEvent(
  type: 'com.example.read_status',
  payload: {'status': 'online'},
);
```

#### end

Ends the current chat session, cancels the Matrix room subscription, and releases resources.

Parameters

No parameters required.

Example

```dart
await chatSDK.end();
```

## Flutter call rendering

Flutter applications require the companion [LiveKit Flutter](../livekit-flutter/) plugin to render audio and video call tracks. The plugin provides FlutterMatrixRTCDelegate and FlutterLiveKitRoom: the concrete implementations of the rtcDelegate and roomFactory parameters accepted by MeetingPlaceMatrixSDK.create().

## Samples

### Initialise the SDK and publish a Matrix offer

The following example initialises vodozemac, creates the MeetingPlaceMatrixSDK, and publishes a connection offer using the Matrix transport.

```dart
import 'package:meeting_place_core/meeting_place_core.dart';
import 'package:meeting_place_matrix/meeting_place_matrix.dart';
import 'package:ssi/ssi.dart';
import 'package:vodozemac/vodozemac.dart' as vod;

Future main() async {
  // 1. Initialise the vodozemac encryption runtime before any Matrix login.
  await vod.init(libraryPath: '/path/to/libvodozemac');

  // 2. Create the Matrix SDK.
  final matrixSDK = await MeetingPlaceMatrixSDK.create(
    wallet: Bip32Wallet.fromSeed(seed),
    repositoryConfig: RepositoryConfig(
      connectionOfferRepository: ConnectionOfferRepositoryImpl(storage: storage),
      groupRepository: GroupRepositoryImpl(storage: storage),
      channelRepository: ChannelRepositoryImpl(storage: storage),
      keyRepository: KeyRepositoryImpl(storage: storage),
    ),
    config: MatrixConfig(
      mediatorDid: 'did:web:mediator.example.com:.well-known',
      controlPlaneDid: 'did:web:controlplane.example.com',
      homeserver: Uri.parse('https://matrix.example.com'),
      databaseFactory: matrixDatabaseFactory,
      deviceId: 'device-id-1',
    ),
  );

  // 3. Register for DIDComm notifications.
  await matrixSDK.registerForDIDCommNotifications();

  // 4. Publish a connection offer with Matrix transport.
  final result = await matrixSDK.publishOffer(
    offerName: 'My Matrix offer',
    offerDescription: 'Connect with me over Matrix.',
    contactCard: ContactCard(
      did: 'did:example:alice',
      type: 'individual',
      contactInfo: const {},
    ),
    type: SDKConnectionOfferType.invitation,
    validUntil: DateTime.now().toUtc().add(const Duration(hours: 24)),
    transport: ChannelTransport.matrix,
  );

  print('Share this mnemonic to connect: ${result.connectionOffer.mnemonic}');
}
```

### Start a Matrix chat session

Once a channel has been established with ChannelTransport.matrix, use MeetingPlaceMatrixChatSDK to open a chat session, send messages, and listen for incoming events.

```dart
import 'package:meeting_place_matrix/meeting_place_matrix.dart';

// matrixSDK and channel (ChannelTransport.matrix) must already be created.
final chatSDK = await MeetingPlaceMatrixChatSDK.initialiseFromChannel(
  channel,
  coreSDK: matrixSDK,
  chatRepository: chatRepository,
  options: const MeetingPlaceMatrixChatSdkOptions(),
);

final chat = await chatSDK.startChatSession();

// Render the initial persisted snapshot
for (final message in chat.messages) {
  print(message);
}

// Listen for incoming messages and events
chat.stream.listen((data) {
  print('Event: ${data.chatItem}');
});

// Send a plain text message
await chatSDK.sendTextMessage('Hello via Matrix!');

// React to a received message
await chatSDK.reactOnMessage(message, reaction: '👍');

// End the session and release resources when done
await chatSDK.end();
```

### Enable audio/video calling with LiveKit

Pass LiveKit configuration to MatrixConfig and provide rtcDelegate and roomFactory to MeetingPlaceMatrixSDK.create(). The call plugin is only created when all three are present. For Flutter apps, use FlutterMatrixRTCDelegate and FlutterLiveKitRoom from [meeting_place_livekit_flutter](../livekit-flutter/).

```dart
import 'package:meeting_place_matrix/meeting_place_matrix.dart';
import 'package:ssi/ssi.dart';

final matrixSDK = await MeetingPlaceMatrixSDK.create(
  wallet: wallet,
  repositoryConfig: repositoryConfig,
  config: MatrixConfig(
    mediatorDid: 'did:web:mediator.example.com:.well-known',
    controlPlaneDid: 'did:web:controlplane.example.com',
    homeserver: Uri.parse('https://matrix.example.com'),
    databaseFactory: matrixDatabaseFactory,
    deviceId: 'device-id-1',
    livekitServiceUrl: Uri.parse('https://livekit-jwt.example.com'),
    livekitSfuUrl: Uri.parse('wss://livekit.example.com'),
  ),
  rtcDelegate: webRtcDelegate,
  roomFactory: liveKitRoomFactory,
);

// Listen for incoming calls
matrixSDK.incomingCalls.listen((event) async {
  print('Incoming call: ${event.callId}');
  await matrixSDK.acceptCall(callId: event.callId);
});

// Start an outbound audio/video call
if (matrixSDK.isCallSupported) {
  final session = await matrixSDK.startCall(
    otherPartyChannelDid: 'did:example:bob-channel',
    mediaType: CallMediaType.audioVideo,
  );
}

// Ring a single group member for a group call
await matrixSDK.ringGroupMember(
  groupChannelDid: 'did:example:group-channel',
  memberDid: 'did:example:member-alice',
  mediaType: CallMediaType.audio,
);

// Leave the active call
await matrixSDK.leaveCurrentCall();
```
