convokit_flutter 0.8.0
convokit_flutter: ^0.8.0 copied to clipboard
Flutter SDK for ConvoKit - real-time messaging integration.
0.8.0 #
- Add
ConvoKit.editMessage(messageId, {required String? text, required int revision})(PATCH /api/v1/messages/:id/own) returning the updatedMessage, andConvoKit.deleteMessage(messageId)(DELETE /api/v1/messages/:id/own) returning void. Both are author-tier calls for the current user's own messages, separate from the administrativePATCH/DELETE /api/v1/messages/:idyour server calls with the client secret. The edit body always carries both keys,{"text": ..., "revision": n}, with a null text serialised as JSONnullso a caption can be cleared on a message that has attachments (a text-only message cannot be emptied: a plain 400ConvoKitValidationExceptionwithout acode; only a malformedtextorrevisionbody is a 400 with codeINVALID_ARGUMENT); attachments are never changed by an edit.revisionmust be an integer in 0..2147483647 and throwsArgumentErrorbefore any request otherwise. - Add
Message.revision(trailing named constructor parameter, default 0) and the derivedMessage.isEdited(revision > 0). The backend sets 0 at creation and increments by one on every content edit, author or administrative (media-only administrative edits included); the value is carried by REST rows, inbox previews (InboxSummary.latestMessage) and Realtime UPDATE row images.updatedAtis never the edited signal. The parser treats a missing or nullrevision(a 0.7 backend) as 0 and fails on a present but malformed value like every other model field.Messagekeeps identity equality; existing literals are unchanged. - Conflicts: pass the
revisionof the row the user was shown. When it no longer matches, the backend answers 409 with codeREVISION_CONFLICT(ConvoKitValidationExceptionwithstatusCode == 409); reload the row withgetMessageand retry with its revision. A message that does not exist, was deleted, or belongs to a room the caller is not an active member of answers 404 with codeMESSAGE_NOT_FOUND(ConvoKitNotFoundException) for both calls; another member's message is a 403. Consumers that merge rows for one id should let the higherrevisionwin and fall back toupdatedAt ?? createdAtonly for equal revisions. - Deleting is unconditional and cannot be undone: other devices receive
onMessageDeletedand the inbox preview moves to the previous surviving message. Files already received or downloaded cannot be retracted; stored files are reclaimed by the existing user or app deletion cleanup, not by this call. Requires the coordinated 0.8 backend release: an older backend answers the/ownroutes with an unmatched-route 404 without a code, which must not be treated as "message gone".
0.7.0 #
- Add
ConvoKit.markConversationUnread(conversationId)(POST /api/v1/conversations/:id/unread) returning aConversationPrivateState(conversationId,unreadMarkedAt,privateStateVersion), andConvoKit.clearConversationUnread(conversationId, {int? ifVersion})(DELETE .../unread[?privateStateVersion=]) returning aClearUnreadResult(the same state pluscleared). The marker is private to the caller's own membership; every mark bumps the version, also a repeat mark. A clear whoseifVersionno longer matches, or with nothing marked, is a 200 withcleared == false, not an error.ifVersionmust be an integer in 0..2147483647 and throwsArgumentErrorbefore any request otherwise. - Add
privateStateVersiontoConvoKit.markConversationRead, serialised only when given: the acknowledgement clears the caller's marker only when the sent version equals the current one; the position rule and the exact-204 contract are unchanged, and the legacy body never clears. A version-only call clears the marker in an empty room; in a non-empty one it also acknowledges through the newest stored message, like the legacy form. Values outside 0..2147483647 throwArgumentError. - Add
ConversationMembership(role,lastReadAt,readPosition,unreadMarkedAt,privateStateVersion; value equality) and the nullableConversation.membership, parsed from the self-onlymembershipsibling ofGET /api/v1/conversations/:id.Conversation.fromJsonaccepts an optional namedmembership; list responses and existing literals are unchanged and the field is null against a 0.6 backend. Capturemembership?.privateStateVersionwhen a room opens and send it with every acknowledgement of that open. - Add
isUnread,unreadMarkedAtandprivateStateVersiontoInboxSummaryandInboxEntry(trailing named constructor parameters;isUnreadis derived asunreadCount > 0 || unreadCountCapped || unreadMarkedAt != nullwhen omitted,unreadMarkedAtdefaults to null andprivateStateVersionto 0, so existing literals keep their badges). The parser applies the same defaults for a 0.6 backend and fails on a present but malformed value.unreadCountis never inflated by a marker: render a numberless dot whenisUnreadis true with a zero count. Value equality,hashCodeandtoStringcover the new fields. onInboxActivityalso fires after the caller's own marker changes (a mark, a clear, or an acknowledgement that clears it), so other devices refetchlistInbox. No new realtime event. Requires the coordinated 0.7 backend release; an older backend answers the/unreadroutes with 404.
0.6.0 #
- Add
ConvoKit.listInbox({limit = 30, cursor, archived = false})returning anInboxPagefromGET /api/v1/inbox:entriesordered by activity time then conversation id (both descending) and an opaquenextCursor(null on the last page).limitmust be an integer in 1..100 and throwsArgumentErrorbefore any request otherwise;cursoris omitted from the query when null. A rejected cursor surfaces asConvoKitValidationExceptionwithcode == 'INVALID_CURSOR'.getConversations(creation order, offset paging) is unchanged. - Add
InboxEntry(conversationplussummary) andInboxSummary(latestMessagein thegetMessagesrow shape or null,unreadCount,unreadCountCapped, the caller'sreadPositionandlastReadAt, andactivityAt), parsed from camelCase or snake_case with value equality. The entry'sconversation.participantsis bounded to ten members and the preview'smediato four items. Unread counts follow thereadThroughrule (messages from others after the caller's position; own messages never count) over a 1,000-message window, withunreadCountCappedmarking a lower bound. - Add
ConvoKit.realtime.onInboxActivity(appId): an empty private app-hub signal after a message insert or edit and after a read-position advance. UnlikeonInboxChangedit is never synthesised on a verified join or rejoin; keep listening toonInboxChangedfor structural changes and reconnect reconciliation. Requires the coordinated backend release; an older backend answerslistInboxwith 404 and never broadcasts the event.
0.5.0 #
- Add
throughMessageIdtomarkConversationReadso a read acknowledges a concrete message. The backend stores that message's(createdAt, id)cursor as a monotonicReadPosition, separate from thelastReadAtacknowledgement time. The request body is now{ "throughMessageId": ... }or{}, the conversation id path segment is percent-encoded, and the exact-204 success check is unchanged. Requires the coordinated backend release; an older backend ignores the target. - Add
ReadPosition(messageId, UTCcreatedAt,covers(message), value equality),Participant.readPositionandReadEvent.readPosition(both nullable, parsed from camelCase or snake_case), and the top-levelreadThrough(message, readPosition:, lastReadAt:)rule shared by every ConvoKit client: position when present, otherwiselastReadAt >= createdAt. - Add optional
ConvoKitException.codeparsed from the error body, for exampleMESSAGE_NOT_FOUNDon a targeted read whose message is gone or foreign.ConvoKitSessionException.codeis unchanged. - Mixed fleet: precise receipts need sender and reader on 0.5. 0.4 readers keep timestamp semantics and still parse the additive payloads.
0.4.0 #
- Add private
onInboxChanged(appId)signals on mutations and verified subscription/reconnect, sharing the app presence channel. - Replace the unimplemented
onConversationUpdate()placeholder with that explicit inbox API. This requires the matching backend/UI release. - Generate and preserve
clientMessageIdthrough REST, history and live rows; exposecreateClientMessageId()for custom optimistic UIs and accept the backend's HTTP 200 result for an identical retry.
0.3.1 #
- Include the minimum-Flutter test lint correction. The 0.3.0 publishing run stopped at validation; its tag is retained without moving or replacing it.
-
Automatically discover rotating private Broadcast topics and rejoin remaining room/app listeners after membership removal or reconnect. Requires the matching backend cutover; no retired-topic fallback or customer Supabase configuration.
-
Preserve optional server
Message.updatedAtrevisions for consistent reconciliation of REST responses and Realtime edits. Creation timestamps and device-local display behavior are unchanged. -
Add value-based
beforeCreatedAt/beforeIdhistory cursors so pagination stays stable when rows are inserted or the previous page's last row is deleted. -
Own a dedicated Supabase client per connected user. Do not initialize or modify the host app's global Supabase instance.
-
Coordinate proactive and 401-triggered renewal of both user and Realtime tokens, with single-flight refresh, bounded backoff and independent expiry.
-
Reject pending work on logout/reconfiguration/user switching; bind all upload stages to their initiating session and never retry storage PUTs implicitly.
-
Share private room channels across typing/read listeners and release channels when their final listener cancels. Refresh credentials after delayed joins.
-
Serialize channel cleanup/re-entry and recover unexpected provider channel closes without losing listeners; discard paused events after session retirement.
-
Keep Realtime authentication explicitly owned by ConvoKit, avoiding a provider async-callback bug that resends pending joins with empty references. Preserve the Supabase HTTP user-token callback and never substitute a project key.
-
Expose sanitized session/Realtime errors and per-topic connection events for reconnect reconciliation. HTTP 403/5xx/network failures do not replay writes.
-
Breaking: replace
MessageChangeType.deletewith the separateonMessageDeleted()/MessageDeletedEvent(id, conversationId)contract. Receive only the backend's private ID-only deletion broadcast, validate its room, and share typing/read channel lifecycle, errors and session disposal. Never subscribe to raw Postgres DELETE or fabricate old Message records. Explicit message deletion requires the coordinated backend release; missed events need REST reconciliation; cascade inbox discovery requires refresh/reopen. -
Require
supabase_flutter >=2.14.0 <3.0.0, Flutter >=3.19, and Dart >=3.3. The former dependency floor admitted releases missing the SDK's auth APIs. -
Validate minimum and current Flutter/dependency resolutions in CI and before publishing, including loopback tests using the actual Supabase WebSocket client. Live security smoke tests still require explicit staging fixtures.
0.2.0 #
- Use the Supabase publishable key discovered from the ConvoKit token service.
- Authenticate Realtime before joining private message, typing, read-receipt, and presence channels.
- Keep
connectUser, token-provider, and managed API endpoint interfaces unchanged.
0.0.4 #
- Use the managed
https://api.convokit.appendpoint by default. - Keep
backendUrlas an optional override for local testing and self-hosting.
0.0.3 #
- Breaking:
RealtimeService.onMessage()now returnsStream<MessageEvent>instead ofStream<Message>, and deliversUPDATE/DELETEevents onMessagein addition toINSERT. Switch onMessageEvent.typeand readMessageEvent.message. - Added automatic session keep-alive: the SDK refreshes the session proactively ahead of token expiry, and reactively (refresh-and-retry once) on any REST call that comes back 401.
- Added a typed error hierarchy:
ConvoKitExceptionis now a base type withConvoKitAuthException,ConvoKitNotFoundException,ConvoKitValidationException,ConvoKitServerException, andConvoKitNetworkExceptionsubtypes. - Added
ConvoKit.getUser()/ConvoKit.getUsers()and a newAppUsermodel for looking up app user profiles, includinglastSeenAt. - Added
ConvoKit.getMessage()to fetch a single message by id.
0.0.2 #
- Finalize R2 uploads with the backend before returning media URLs.
- Validate signed-upload object keys and surface upload-completion failures.
0.0.1 #
- Initial release of the ConvoKit Flutter SDK.
- Added SDK configuration, user connection, and token exchange helpers.
- Added conversation, message, realtime, presence, read receipt, typing, and media upload APIs.