Fliplet.Chat
Build chat features (one-to-one conversations, group chats, and public channels) on top of Fliplet Data Sources via the fliplet-chat package. The package exposes Fliplet.Chat, Fliplet.Chat.connect(), Fliplet.Chat.init(), Fliplet.Chat.get(), and Fliplet.Chat.subscribe().
The chat is a thin wrapper over the Fliplet Data Sources APIs: each conversation is backed by a data source, messages are entries in that data source, and the contacts list is a data source you nominate when you connect. All methods return Promises.
Install
Add the fliplet-chat dependency to your screen or app resources. In V3 apps, add it via Studio’s library picker — required dependencies (fliplet-datasources, fliplet-utils) are pulled in automatically.
When using the chat instance from screens that aren’t the chat screen itself, prefer Fliplet.Chat.init() (reads settings from the chat widget on the page) or Fliplet.Chat.get() (resolves once another screen has called connect()).
Fliplet.Chat.connect()
(Returns Promise<Chat>)
Connects to a chat backend and returns the chat instance you use for everything else. This is the entry point for the rest of the public API.
Usage
const chat = await Fliplet.Chat.connect({
dataSourceId: 123, // required — the Contacts data source ID
primaryKey: 'email', // optional — column to identify users
encryptMessages: true, // optional — AES-encrypt message bodies at rest
pushNotifications: true, // optional — subscribe device for push
crossLogin: false, // optional — share session across screens
crossLoginDataSourceId: 456, // optional — separate cross-login DS
crossLoginEmailKey: 'fl-chat-auth-email',
crossLoginContactsKey: 'fl-chat-source-id'
});
- connectionOptions (Object)
- dataSourceId (Number, required) The ID of the contacts data source. Each row is a user; messages reference users by their ID in this data source.
- primaryKey (String) Optional column name used to identify users (e.g.
'email'). When set, the chat uses the encryptedflUserId/flUserTokenflow instead of the legacy random-token flow. - encryptMessages (Boolean) When
true, message bodies sent viachat.message()andchat.updateMessage()are AES-encrypted before being stored. Defaultfalse. - pushNotifications (Boolean) When
true, the device is subscribed for push notifications and new conversations are wired with apush-messagehook. Defaultfalse. - crossLogin / crossLoginDataSourceId / crossLoginEmailKey / crossLoginContactsKey Cross-login options used in conjunction with
Fliplet.Chat.subscribe()for screens that need push without rendering the chat UI.
The promise resolves with the chat instance; subsequent calls to Fliplet.Chat.get() will resolve with the same instance.
Fliplet.Chat.init()
(Returns Promise<Chat>)
Bootstraps the chat from the chat widget’s exported settings on the current screen, then logs the current Fliplet user in automatically using the configured primaryKey. Use this when you want the chat to follow the user across screens without re-passing the data source ID.
const chat = await Fliplet.Chat.init();
// chat is connected, encryption + push are enabled by default,
// and the current Fliplet user is already logged in.
- options (Object) Optional overrides merged on top of the widget’s exported settings. Defaults applied when the widget is found:
encryptMessages: true,pushNotifications: true.
The promise rejects if no chat widget is exported on the screen, or if the chat widget hasn’t been upgraded to use a primaryKey.
Fliplet.Chat.get()
(Returns Promise<Chat>)
Returns a promise that resolves with the chat instance once any screen has called Fliplet.Chat.connect() or Fliplet.Chat.init(). Useful for screens that depend on the chat instance but don’t own its setup.
const chat = await Fliplet.Chat.get();
const conversations = await chat.conversations();
Fliplet.Chat.subscribe()
(Returns Promise<Chat>)
Connects to the chat in push-only mode using cross-login keys stored in app storage (fl-chat-auth-email and fl-chat-source-id). Use this on screens that should subscribe a user for push notifications without rendering the chat UI.
// In your screen's custom code:
Fliplet.Chat.subscribe()
.then((chat) => {
// user is logged in and subscribed for push
})
.catch((err) => {
// missing widget, missing storage keys, or push disabled
});
The promise rejects if the screen has no Push Notifications widget, or if the cross-login keys aren’t set in Fliplet.App.Storage.
chat.login()
(Returns Promise<DataSourceEntry>)
Logs a user into the chat by matching against the contacts data source. After login, the user’s flUserId and flUserToken are generated (or read) and cached.
// Log in by primary key (when configured)
await chat.login({ email: '[email protected]' });
// Offline-first login: try cache, fall back to network
await chat.login({ email: '[email protected]' }, { offline: true });
- query (Object)
where-style query against the contacts data source. - options (Object)
- offline (Boolean) When
true, attempts a cache-only login first; falls back to online login when the device is online.
- offline (Boolean) When
The promise rejects with { code: 404 } if no contact matches, or { code: 400 } if the device is offline and no cached user exists.
chat.logout()
(Returns Promise)
Clears the user’s token in the contacts data source and stops polling.
await chat.logout();
chat.conversations()
(Returns Promise<Conversation[]>)
Fetches the conversations the logged-in user participates in, ordered by updatedAt descending. Each returned conversation is decorated with instance helpers (participants, notifications, messages, getInviteCode, getEncryptionKey).
const conversations = await chat.conversations();
conversations.forEach((c) => {
console.log(c.id, c.name, c.definition.participants);
});
- options (Object)
- offline (Boolean) When
true, returns the cached conversations list instead of hitting the network.
- offline (Boolean) When
chat.conversations.join()
(Returns Promise<Conversation>)
Joins a private conversation using an invite code shared by an existing participant.
const conversation = await chat.conversations.join('abc123');
chat.create()
(Returns Promise<Conversation>)
Creates a new conversation, or returns the existing conversation if one already exists with the same set of participants. The returned object is decorated with the same instance helpers as conversations returned by chat.conversations().
const conversation = await chat.create({
participants: [42, 57], // contact IDs in the contacts data source
name: 'Project Phoenix', // optional
group: { public: false }, // optional — pass to mark as group
metadata: { topic: 'launch' }, // optional — arbitrary object
allowInvite: true // optional — enables invite codes
});
// `isNew` tells you whether a conversation was created or matched an existing one.
console.log(conversation.isNew);
- options (Object)
- participants (Array, required) IDs of the participants in the contacts data source. The current user is added automatically.
- name (String) Optional conversation name; defaults to a generated GUID.
- group (Object) Pass any object (typically
{ public: false }) to mark the conversation as a group chat. - metadata (Object) Arbitrary metadata stored on the conversation definition.
- allowInvite (Boolean) When
true, an invite code is generated and retrievable viaconversation.getInviteCode().
chat.contacts()
(Returns Promise<Contact[]>)
Fetches users from the contacts data source.
const contacts = await chat.contacts();
const offlineContacts = await chat.contacts({ offline: true });
- options (Object)
- offline (Boolean) When
true, returns the cached contacts list. - Any other option supported by
connection.find()(e.g.where,limit).
- offline (Boolean) When
chat.message()
(Returns Promise)
Posts a new message to a conversation. The current user is set as fromUserId automatically, and the message is added to their readBy list. After the message is inserted, chat.poll() runs to publish the message to all chat.stream() listeners.
await chat.message(conversation.id, {
body: 'Hello team!',
conversationId: conversation.id
});
// Send a message with a file attachment
await chat.message(conversation.id, {
body: 'See attached',
file: ['https://example.org/uploads/spec.pdf']
});
- conversationId (Number) The conversation’s data source ID.
- message (Object) The message payload.
bodyis the most common field;fileis an array of file URLs for attachments. Custom fields are stored as-is on the message entry.
If encryptMessages was set on connect(), the body is AES-encrypted before being inserted.
chat.updateMessage()
(Returns Promise<DataSourceEntry>)
Updates an existing message — typically used for edits.
await chat.updateMessage(conversation.id, message.id, {
body: 'Updated message body',
isEdited: true
});
- conversationId (Number) The conversation ID.
- messageId (Number) The message entry ID.
- data (Object) Fields to update. If
encryptMessageswas set onconnect(), thebodyis encrypted before saving.
chat.messages()
(Returns Promise<Message[]>)
Manually fetches messages, sorted oldest-to-newest. Use this for “load older messages” interactions; for live updates, use chat.stream() instead.
// Latest 50 messages across all conversations
const recent = await chat.messages({ limit: 50 });
// Messages for a specific conversation
const messages = await chat.messages({
conversations: [conversation.id],
limit: 100
});
// Messages matching a custom where clause
const fromUser = await chat.messages({
where: { data: { $contains: { fromUserId: 'id-abc-123' } } }
});
- options (Object)
- where (Object) Optional MongoDB-style filter applied to message entries.
- limit (Number) Page size. Defaults to the batch size returned by
chat.getBatchSize()(200). - conversations (Array) Optional list of conversation (data source) IDs to scope the fetch to.
chat.markMessagesAsRead()
(Returns Promise)
Marks an array of messages as read by the current user. Already-read messages are filtered out automatically; if nothing needs marking, the promise resolves immediately.
await chat.markMessagesAsRead(visibleMessages);
- messages (Array) Messages from
chat.stream()orchat.messages(). Each must includeidanddata.readBy.
chat.unread
Helpers for the unread badge.
// Full list of unread messages for the current user
const { entries } = await chat.unread.get();
// Just the count (cheaper than .get().length)
const count = await chat.unread.count();
chat.stream()
(Returns Promise)
Subscribes a callback to new messages. The callback fires once per message as messages arrive. Internally stream() triggers polling on a 20-second cadence (with exponential backoff when the conversation is quiet).
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
const messages = ref([]);
let chat;
onMounted(async () => {
chat = await Fliplet.Chat.connect({
dataSourceId: Fliplet.Env.get('appSettings').contactsDataSourceId,
primaryKey: 'email',
encryptMessages: true
});
await chat.login({ email: Fliplet.Env.get('user').email });
await chat.stream((message) => {
if (message.isDeleted) {
messages.value = messages.value.filter((m) => m.id !== message.id);
return;
}
if (message.isUpdate) {
const idx = messages.value.findIndex((m) => m.id === message.id);
if (idx !== -1) messages.value.splice(idx, 1, message);
return;
}
messages.value.push(message);
});
});
onUnmounted(() => {
if (chat) chat.logout();
});
</script>
- fn (Function) Listener invoked once per message. The message is annotated with
isReadByCurrentUser,isUpdate(edited), andisDeletedflags. - options (Object)
- offline (Boolean) When
true, the listener is first invoked with cached messages from the previous session.
- offline (Boolean) When
chat.poll()
(Returns Promise)
Manually triggers a poll for new messages. Most apps don’t need to call this — chat.stream() and chat.message() already poll. Use it after operations that bypass chat.message() (e.g. server-side inserts) or to reset the poll cursor.
// Force a fresh poll from the start
await chat.poll({ reset: true });
- options (Object)
- reset (Boolean) When
true, clears the internal “newest message timestamp” cursor so the next poll re-fetches from the beginning.
- reset (Boolean) When
chat.getBatchSize()
(Returns Number)
Returns the page size used when polling for messages. Useful when implementing “has more” logic against chat.messages().
const batchSize = chat.getBatchSize(); // 200
Channels
Public channels are conversations marked with definition.group.public = true. They live on the master app’s data sources so multiple app instances can share them.
chat.channels.create()
(Returns Promise<DataSource>)
Creates a public channel.
const channel = await chat.channels.create('General');
chat.channels.get()
(Returns Promise<DataSource[]>)
Lists all public channels for the master app.
const channels = await chat.channels.get();
chat.channels.join()
(Returns Promise<Conversation>)
Joins a public channel. The returned object is decorated with the same instance helpers as a conversation, plus isChannel: true and nParticipants.
const channel = await chat.channels.join(channelId);
console.log(`Joined — ${channel.nParticipants} members`);
chat.channels.delete()
(Returns Promise)
Deletes a public channel.
await chat.channels.delete(channelId);
Conversation instance methods
Conversations returned by chat.conversations(), chat.create(), chat.conversations.join(), and chat.channels.join() come decorated with the helpers below.
conversation.participants
// Get participant IDs (flUserIds) stored on the conversation definition
const ids = conversation.participants.get();
// Resolve participant IDs to full contact entries
const people = await conversation.participants.fetch();
// Add or remove participants by their contact-data-source ID
await conversation.participants.add([contactId1, contactId2]);
await conversation.participants.remove([contactId1]);
conversation.notifications
Mute or unmute push notifications for the current user on this conversation.
await conversation.notifications.mute();
await conversation.notifications.unmute();
// Computed flag
console.log(conversation.isMuted);
conversation.messages.fetch()
(Returns Promise<Message[]>)
Fetches messages directly from the conversation’s data source. The same options as Fliplet.DataSources connection.find() are accepted (e.g. where, limit, order).
const messages = await conversation.messages.fetch({
limit: 50,
order: [['createdAt', 'DESC']]
});
conversation.getInviteCode()
(Returns Promise<String>)
Resolves with the conversation’s invite code if one exists (i.e. allowInvite: true was set at creation).
const code = await conversation.getInviteCode();
The promise rejects if no invite code is set on the conversation.
conversation.getEncryptionKey()
(Returns String)
Returns the per-conversation encryption key used when downloading attachments tagged with hasMediaFileWithPrivateEncryptionKey. Most apps don’t need this directly; it’s appended automatically by the chat when parsing messages.