Integrate Meeting Place for Dart

Learn how to integrate Affinidi Meeting Place into your Dart or Flutter applications to enable secure, private, and trusted messaging powered by DIDComm v2.1.

The Meeting Place integration guide provides a step-by-step overview for developers to integrate DIDComm-based communication into their applications. DIDComm enables secure, privacy-preserving, and verifiable messaging between decentralised identifiers (DIDs), allowing users to exchange messages securely and privately without relying on centralised intermediaries.

Meeting Place App

Sample screens from Affinidi Meeting Place Open-sourced App

Key Features

  • Set up a main profile and create aliases for different roles or contexts.

  • Share secure, customisable invites to connect individually or in groups.

  • Chat privately in peer or group settings with built-in security and privacy.

  • Prove your identity using verifiable credentials in conversations.

  • Use default messaging servers or set up your own for full control.

Requirements

  • Dart SDK version 3.9.0 or later.

Required Packages

The following Dart packages are required to get started with the integration:

PackageDescription
Affinidi Meeting Place - Core SDK for DartThe core package implementing the Control Plane and Mediator SDK, including managing multiple digital identities for better privacy and anonymity in digital interactions.
Affinidi Meeting Place - Chat SDK for DartThis package is built on top of the core Meeting Place SDK that utilises Decentralised Identifier (DID) and DIDComm Messaging v2.1 protocol to send secure and private messages with end-to-end encryption.
SSIA package that provides libraries and tools for implementing Self-Sovereign Identity (SSI). This is used to create the DID to represent a user’s identity.

Sample Usage

Initialising the SDK

The integration starts with creating an Core SDK instance.

// create a Persistent wallet using SSI final wallet = PersistentWallet(InMemoryKeyStore()); // create a RepositoryConfig final storage = InMemoryStorage(); final repositoryConfig = RepositoryConfig( connectionOfferRepository: ConnectionOfferRepositoryImpl(storage: storage), groupRepository: GroupRepositoryImpl(storage: storage), channelRepository: ChannelRepositoryImpl(storage: storage), keyRepository: KeyRepositoryImpl(storage: storage), ); // Define the mediator did final mediatorDid = '<YOUR-MEDIATOR-DID>:.well-known'; // Define the serviceDID (control plane DID) final serviceDid = '<YOUR-CONTROL-PLANE-DID>'; // Create an instance of Core SDK final aliceSDK = await CoreSDK.create( wallet: wallet, repositoryConfig: repositoryConfig, mediatorDid: mediatorDid, serviceDid: serviceDid ); // Register the device to receive push notification await aliceSDK.registerForPushNotifications(const Uuid().v4());

Publish an Offer

An offer pertains to an invitation for others to connect with you on Meeting Place.

The example below shows how to publish an offer to make you discoverable. You have an option to either provide a custom phrase or automatically generate random phrases in your offer.

// Publish an offer using Core SDK final publishOfferResult = await aliceSDK.publishOffer( offerName: '<YOUR-OFFER-NAME>', offerDescription: '<YOUR-OFFER-DESC>', customPhrase: '<YOUR-CUSTOM-PHRASE>', // if not provided, it will generate a random mnemonic vCard: VCard(values: {}), publishAsGroup: false, validUntil: DateTime.now().toUtc().add(const Duration(minutes: 5)), ); // Listen to Discovery Events Stream to receive updates about the published offer final waitForInvitationAccept = Completer<DiscoveryStreamEvent>(); final waitForChannelActivity = Completer<DiscoveryStreamEvent>(); aliceSDK.discoveryEventsStream.listen((event) { if (event.type == DiscoveryEventType.InvitationAccept) { waitForInvitationAccept.complete(event); } if (event.type == DiscoveryEventType.ChannelActivity) { if (!waitForChannelActivity.isCompleted) { waitForChannelActivity.complete(event); } } }); // Create a Mediator Channel to listen to discovery events about the published offer final publishOfferMediatorChannel = await aliceSDK.subscribeToMediator( publishOfferResult.connectionOffer.publishOfferDid, deleteOnMediator: false, ); // Use the Mediator Channel to listen to discovery events about the published offer publishOfferMediatorChannel.stream.listen((data) async { if (data.type.toString() == MeetingPlaceProtocol.connectionSetup.value) { await aliceSDK.processDiscoveryEvents(); } });

Find an Offer

When an offer is published, other users can find and request to connect with you on Meeting Place by providing the passphrase of your offer. The example below shows how to find an offer by providing the mnemonic.

Note: It is required to initialise the Core SDK calling the findOffer method.

// Find the offer using Alice's mnemonic final findOfferResult = await bobSDK.findOffer(mnemonic: '<ALICE-OFFER>');

Accept an Offer

After finding an offer, a user can request to connect with another user by accepting the offer. When a user accepts an offer, it sends a notification requesting a connection, along with the requestor’s vCard.

The example below uses the Core SDK to send a connection request to the owner of the offer. It also sends a notification about the connection offer.

// Accept the offer to accept the invite final acceptOfferResult = await bobSDK.acceptOffer( connectionOffer: findOfferResult.connectionOffer!, vCard: VCard(values: {}), ); // Listen on discovery events stream to receive update on the accepted offer final waitForOfferFinalised = Completer<DiscoveryStreamEvent>(); bobSDK.discoveryEventsStream.listen((event) { if (event.type == DiscoveryEventType.OfferFinalised) { waitForOfferFinalised.complete(event); } }); // Create a Mediator Channel to listen to discovery events about the accepted offer final mediatorChannel = await bobSDK.subscribeToMediator( acceptOfferResult.connectionOffer.acceptOfferDid!, deleteOnMediator: false, ); // Use the Mediator Channel to listen to discovery events about the accepted offer mediatorChannel.stream.listen((data) async { if (data.type.toString() == MeetingPlaceProtocol.connectionAccepted.value) { await Future.delayed(Duration(seconds: 3)); await bobSDK.processDiscoveryEvents(); } }); // Send a notification to Alice that Bob had requested to connect await bobSDK.notifyAcceptance( connectionOffer: acceptOfferResult.connectionOffer);

Approve Connection Request

When a user sends a request to connect from an accepted offer, the offer owner should receive a notification to approve the request. This finalises the connection request, which signifies that both parties can start sending and receiving messages from each other.

The example below shows how a connection request is approved using Core SDK on the offerer side. It also establishes the permanent channel to listen to a channel inauguration event from the requrestor.

// Get the connection request event from Bob final receivedEvent = await waitForInvitationAccept.future; // Alice approves/accepts the connection request from Bob final channel = await aliceSDK.approveConnectionRequest( connectionOffer: publishOfferResult.connectionOffer, channel: receivedEvent.channel, ); // Setup the permanent channel final permanentChannelDidMediatorChannel = await aliceSDK.subscribeToMediator( channel.permanentChannelDid!, deleteOnMediator: false, ); // Listen to discovery events about channel inauguration from Bob permanentChannelDidMediatorChannel.stream.listen((data) async { if (data.type.toString() == MeetingPlaceProtocol.channelInauguration.value) { await aliceSDK.processDiscoveryEvents(); } });

Start a Chat Session

Starting a chat session is a pre-requisite for both parties before they can start sending and receiving messages.

The example below uses the Chat SDK to create and start a chat session. The Chat SDK also provides event streaming to get events on messages sent and receive from the other party.

// Create an instance of ChatSDK final aliceChatSDK = await ChatSDK.initialiseFromChannel( channel, coreSDK: aliceSDK, chatRepository: ChatRepositoryImpl(storage: InMemoryStorage()), options: ChatSDKOptions(chatPresenceSendInterval: 60), ); // Start the chat session using the created ChatSDK await aliceChatSDK.start(); // Setup the channel stream to get message events from the chat await aliceChatSDK.channelSubscription.then((stream) { stream!.listen((data) { if (data.plainTextMessage != null) { prettyJsonPrintYellow( 'Received message on chat stream', data.plainTextMessage!.toJson(), ); } if (data.chatItem != null) { prettyJsonPrintYellow( 'Received chat item on chat stream', data.chatItem!.toJson(), ); } }); });

Sending a Message

After initialising a chat session, a user can start sending messages to the other party.

The example below shows how to send a message using the ChatSDK.

// Send a plain text message to Alice await bobChatSDK.sendTextMessage('Hi Alice!');

Optionally, you could use the Core SDK to send a message to another user.

Note: The sendMessage() method can be used to send other types of messages not limited to plain text but also commands or operations required by your application.

final bobSDK = await CoreSDK.create(...); await bobSDK.sendMessage( PlainTextMessage( id: Uuid().v4(), type: Uri.parse(ChatProtocol.chatMessage.value), from: offerFinalisedEvent.channel.permanentChannelDid, to: [offerFinalisedEvent.channel.otherPartyPermanentChannelDid!], body: {'text': 'This is a custom message', 'seqNo': 100}, ), senderDid: offerFinalisedEvent.channel.permanentChannelDid!, recipientDid: offerFinalisedEvent.channel.otherPartyPermanentChannelDid!, );

What’s Next

  Checkout the Meeting Place Reference App for Flutter

  Explore the Meeting Place SDK

  Explore different deployment options of DIDComm Mediator