-
Notifications
You must be signed in to change notification settings - Fork 73
feat: add primitive models and handler for Simplified Sliding Sync #2198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
TheOneWithTheBraid
wants to merge
1
commit into
famedly:main
Choose a base branch
from
TheOneWithTheBraid:braid/msc4186
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
lib/msc_extensions/msc_4186_simplified_sliding_sync/models/extension_config.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| /// Different extensions have different configuration formats. | ||
| abstract class ExtensionConfig { | ||
| Map<String, Object?> toJson(); | ||
| } |
21 changes: 21 additions & 0 deletions
21
lib/msc_extensions/msc_4186_simplified_sliding_sync/models/maybe.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| /// Used to depict the difference between null and undefined in JavaScript | ||
| abstract class Maybe<T> {} | ||
|
|
||
| class Some<T> extends Maybe<T> { | ||
| Some(this.data); | ||
|
|
||
| final T data; | ||
|
|
||
| @override | ||
| int get hashCode => data.hashCode; | ||
|
|
||
| @override | ||
| String toString() => data.toString(); | ||
|
|
||
| @override | ||
| bool operator ==(Object other) { | ||
| return data == other; | ||
| } | ||
| } | ||
|
|
||
| class Undefined<T> extends Maybe<T> {} | ||
16 changes: 16 additions & 0 deletions
16
lib/msc_extensions/msc_4186_simplified_sliding_sync/models/required_state_request.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| class RequiredStateRequest { | ||
| /// The event type to match. If omitted then matches all types. | ||
| final String? type; | ||
|
|
||
| /// The event state key to match. If omitted then matches all state keys. | ||
| /// | ||
| /// Note: it is possible to match a specific state key, for all event types, by specifying [stateKey] but leaving [type] unset. | ||
| final String? stateKey; | ||
|
|
||
| const RequiredStateRequest({required this.type, required this.stateKey}); | ||
|
|
||
| Map<String, Object?> toJson() => { | ||
| if (type != null) 'type': type, | ||
| if (stateKey != null) 'state_key': stateKey, | ||
| }; | ||
| } |
152 changes: 152 additions & 0 deletions
152
lib/msc_extensions/msc_4186_simplified_sliding_sync/models/room_result.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| import 'package:matrix/matrix.dart'; | ||
|
|
||
| class RoomResult { | ||
| /// An integer that can be used to sort rooms based on the last "proper" activity in the room. Greater means more recent. | ||
| /// | ||
| /// "Proper" activity is defined as an event being received is one of the following types: m.room.create, m.room.message, m.room.encrypted, m.sticker, m.call.invite, m.poll.start, m.beacon_info. | ||
| /// | ||
| /// For rooms that the user is not currently joined to, this instead represents when the relevant membership happened, e.g. when the user left the room. | ||
| /// | ||
| /// The exact value of bump_stamp is opaque to the client, a server may use e.g. an auto-incrementing integer, a timestamp, etc. | ||
| /// | ||
| /// The bump_stamp may decrease in subsequent responses, if e.g. an event was redacted/removed (or purged in cases of retention policies). | ||
| final int bumpStamp; | ||
|
|
||
| /// The current membership of the user, or omitted if user not in room (for peeking). | ||
| // TODO: migrate to enum | ||
| final String? membership; | ||
|
|
||
| /// The name of the lists that match this room. The field is omitted if it doesn't match any list and is included only due to a subscription. | ||
| final List<String>? lists; | ||
|
|
||
| // Currently or previously joined rooms | ||
| // When a user is or has been in the room, the following field are also returned: | ||
|
|
||
| /// Room name or calculated room name. | ||
| final String? name; | ||
|
|
||
| /// Room avatar | ||
| // TODO: migrate to Uri | ||
| final String? avatar; | ||
|
|
||
| /// A truncated list of users in the room that can be used to calculate the room name. Will first include joined users, then invited users, and then finally left users. The same as the m.heroes section in the /v3/sync specification | ||
| final List<StrippedHero>? heroes; | ||
|
|
||
| /// Flag to specify whether the room is a direct-message room (according to account data). If absent the room is not a DM room. | ||
| final bool? isDm; | ||
|
|
||
| /// Flag which is set when this is the first time the server is sending this data on this connection, or if the client should replace all room data with what is returned. Clients can use this flag to replace or update their local state. The absence of this flag means false. | ||
| final bool? initial; | ||
|
|
||
| /// Flag which is set if we're returning more historic events due to the timeline limit having increased. See "Changing room configs" section. | ||
| final bool? expandedTimeline; | ||
|
|
||
| /// Changes in the current state of the room. | ||
| /// | ||
| /// To handle state being deleted, the list may include a StateStub type (c.f. schema below) that only has type and state_key fields. The presence or absence of content field can be used to differentiate between the two cases. | ||
| final List<BasicEvent>? requiredState; | ||
|
|
||
| /// The latest events in the room. May not include all events if e.g. there were more events than the configured timeline_limit, c.f. the limited field. | ||
| /// | ||
| /// If limited is true then we include bundle aggregations for the event, as per /v3/sync. | ||
| /// | ||
| /// The last event in the list is the most recent. | ||
| final List<MatrixEvent>? timelineEvents; | ||
|
|
||
| /// A token that can be passed as a start parameter to the /rooms/<room_id>/messages API to retrieve earlier messages. | ||
| final String? prevBatch; | ||
|
|
||
| /// True if there are more events since the previous sync than were included in the timeline_events field, or that the client should paginate to fetch more events. | ||
| /// | ||
| /// Note that server may return fewer than the requested number of events and still set limited to true, e.g. because there is a gap in the history the server has for the room. | ||
| /// | ||
| /// Absence means false | ||
| final bool limited; | ||
|
|
||
| /// The number of timeline events which have "just occurred" and are not historical, i.e. that have happened since the previous sync request. The last N events are 'live' and should be treated as such. | ||
| /// | ||
| /// This is mostly useful to e.g. determine whether a given @mention event should make a noise or not. Clients cannot rely solely on the absence of initial: true to determine live events because if a room not in the sliding window bumps into the window because of an @mention it will have initial: true yet contain a single live event (with potentially other old events in the timeline). | ||
| final int? numLive; | ||
|
|
||
| /// The number of users with membership of join, including the client's own user ID. (same as /v3/sync m.joined_member_count) | ||
| final int? joinedCount; | ||
|
|
||
| /// The number of users with membership of invite. (same as /v3/sync m.invited_member_count) | ||
| final int? invitedCount; | ||
|
|
||
| /// The total number of unread notifications for this room. (same as /v3/sync). | ||
| /// | ||
| /// Does not included threaded notifications, which are returned in an extension. | ||
| final int? notificationCount; | ||
|
|
||
| /// The number of unread notifications for this room with the highlight flag set. (same as /v3/sync) | ||
| /// | ||
| /// Does not included threaded notifications, which are returned in an extension. | ||
| final int? highlightCount; | ||
|
|
||
| // Invite/knock/rejections | ||
| // For rooms the user has not been joined to the client also gets the stripped state events. This is commonly the case for invites or knocks, but can also be for when the user has rejected an invite. | ||
|
|
||
| /// Stripped state events (for rooms where the user is invited). Same as rooms.invite.$room_id.invite_state for invites in /v3/sync. | ||
| final StrippedStateEvent? stripped_state; | ||
|
|
||
| const RoomResult({ | ||
| required this.bumpStamp, | ||
| this.membership, | ||
| this.lists, | ||
| this.name, | ||
| this.avatar, | ||
| this.heroes, | ||
| this.isDm, | ||
| this.initial, | ||
| this.expandedTimeline, | ||
| this.requiredState, | ||
| this.timelineEvents, | ||
| this.prevBatch, | ||
| this.limited = false, | ||
| this.numLive, | ||
| this.joinedCount, | ||
| this.invitedCount, | ||
| this.notificationCount, | ||
| this.highlightCount, | ||
| this.stripped_state, | ||
| }); | ||
|
|
||
| factory RoomResult.fromJson(Map<String, Object?> json) => RoomResult( | ||
| bumpStamp: json['bump_stamp'] as int, | ||
| membership: json['membership'] as String?, | ||
| lists: (json['lists'] as List?)?.cast<String>(), | ||
| name: json['name'] as String?, | ||
| avatar: json['avatar'] as String?, | ||
| heroes: json.containsKey('heroes') | ||
| ? (json['heroes'] as List) | ||
| .map((v) => StrippedHero.fromJson(v)) | ||
| .toList() | ||
| : null, | ||
| isDm: json['is_dm'] as bool?, | ||
| initial: json['initial'] as bool?, | ||
| expandedTimeline: json['expanded_timeline'] as bool?, | ||
| requiredState: json.containsKey('required_state') | ||
| ? (json['required_state'] as List) | ||
| .map((v) => BasicEvent.fromJson(v)) | ||
| .toList() | ||
| : null, | ||
| timelineEvents: json.containsKey('timeline_events') | ||
| ? (json['timeline_events'] as List) | ||
| .map((v) => MatrixEvent.fromJson(v)) | ||
| .toList() | ||
| : null, | ||
| prevBatch: json['prev_batch'] as String?, | ||
| limited: json['limited'] as bool? ?? false, | ||
| numLive: json['num_live'] as int?, | ||
| joinedCount: json['joined_count'] as int?, | ||
| invitedCount: json['invited_count'] as int?, | ||
| notificationCount: json['notification_count'] as int?, | ||
| highlightCount: json['highlight_count'] as int?, | ||
| stripped_state: json.containsKey('stripped_state') | ||
| ? StrippedStateEvent.fromJson( | ||
| (json['stripped_state'] as Map).cast<String, Object?>(), | ||
| ) | ||
| : null, | ||
| ); | ||
| } |
19 changes: 19 additions & 0 deletions
19
lib/msc_extensions/msc_4186_simplified_sliding_sync/models/room_subscription.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import 'package:matrix/matrix.dart'; | ||
|
|
||
| class RoomSubscription { | ||
| /// The maximum number of timeline events to return per response. The server may cap this number. | ||
| final int timelineLimit; | ||
|
|
||
| /// Required state for each room returned. | ||
| final RequiredStateRequest requiredState; | ||
|
|
||
| const RoomSubscription({ | ||
| required this.timelineLimit, | ||
| required this.requiredState, | ||
| }); | ||
|
|
||
| Map<String, Object?> toJson() => { | ||
| 'timeline_limit': timelineLimit, | ||
| 'required_state': requiredState.toJson(), | ||
| }; | ||
| } |
65 changes: 65 additions & 0 deletions
65
lib/msc_extensions/msc_4186_simplified_sliding_sync/models/sliding_room_filter.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import 'package:matrix/matrix.dart'; | ||
|
|
||
| class SlidingRoomFilter { | ||
| /// Flag which only returns rooms present (or not) in the m.direct entry in account data. | ||
| /// | ||
| /// If unset, both DM rooms and non-DM rooms are returned. If False, only non-DM rooms are returned. If True, only DM rooms are returned. | ||
| final bool? isDm; | ||
|
|
||
| /// Filter the room based on the space they belong to according to m.space.child state events. | ||
| /// | ||
| /// If multiple spaces are present, a room can be part of any one of the listed spaces (OR'd). The server will inspect the m.space.child state events for the JOINED space room IDs given. Servers MUST NOT navigate subspaces. It is up to the client to give a complete list of spaces to navigate. Only rooms directly mentioned as m.space.child events in these spaces will be returned. Unknown spaces or spaces the user is not joined to will be ignored. | ||
| final List<String>? spaces; | ||
|
|
||
| /// Flag which only returns rooms which have an m.room.encryption state event. | ||
| /// | ||
| /// If unset, both encrypted and unencrypted rooms are returned. If false, only unencrypted rooms are returned. If True, only encrypted rooms are returned. | ||
| final bool? isEncrypted; | ||
|
|
||
| /// Flag which only returns rooms the user is currently invited to. | ||
| /// | ||
| /// If unset, both invited and joined rooms are returned. If false, no invited rooms are returned. If true, only invited rooms are returned. | ||
| final bool? isInvited; | ||
|
|
||
| /// If specified, only rooms where the m.room.create event has a type matching one of the strings in this array will be returned. | ||
| /// | ||
| /// If this field is unset, all rooms are returned regardless of type. This can be used to get the initial set of spaces for an account. For rooms which do not have a room type, use null to include them. | ||
| final List<Maybe<String>>? roomTypes; | ||
|
|
||
| /// Same as [roomTypes] but inverted. | ||
| /// | ||
| /// This can be used to filter out spaces from the room list. If a type is in both room_types and not_room_types, then not_room_types wins and they are not included in the result. | ||
| final List<Maybe<String>>? notRoomTypes; | ||
|
|
||
| /// Filter the room based on its [room tags](https://spec.matrix.org/v1.16/client-server-api/#room-tagging). | ||
| /// | ||
| /// If multiple tags are present, a room can have any one of the listed tags (OR'd). | ||
| final List<String>? tags; | ||
|
|
||
| /// Filter the room based on its [room tags](https://spec.matrix.org/v1.16/client-server-api/#room-tagging). | ||
| /// | ||
| /// If multiple tags are present, a room can have any one of the listed tags (OR'd). | ||
| final List<String>? notTags; | ||
|
|
||
| const SlidingRoomFilter({ | ||
| required this.isDm, | ||
| required this.spaces, | ||
| required this.isEncrypted, | ||
| required this.isInvited, | ||
| required this.roomTypes, | ||
| required this.notRoomTypes, | ||
| required this.tags, | ||
| required this.notTags, | ||
| }); | ||
|
|
||
| Map<String, Object?> toJson() => { | ||
| if (isDm != null) 'is_dm': isDm, | ||
| if (spaces != null) 'spaces': spaces, | ||
| if (isEncrypted != null) 'is_encrypted': isEncrypted, | ||
| if (isInvited != null) 'is_invited': isInvited, | ||
| if (roomTypes is Some) 'room_types': roomTypes, | ||
| if (notRoomTypes is Some) 'not_room_types': notRoomTypes, | ||
| if (tags != null) 'tags': tags, | ||
| if (notTags != null) 'not_tags': notTags, | ||
| }; | ||
| } |
17 changes: 17 additions & 0 deletions
17
lib/msc_extensions/msc_4186_simplified_sliding_sync/models/state_stub.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import 'package:matrix/matrix.dart'; | ||
|
|
||
| /// The StateStub is used in required_state to indicate that a piece of state has been deleted. | ||
| class StateStub extends BasicEvent { | ||
| /// The state_key of the state entry that was deleted | ||
| final String stateKey; | ||
|
|
||
| StateStub({ | ||
| required super.type, | ||
| required super.content, | ||
| required this.stateKey, | ||
| }); | ||
|
|
||
| StateStub.fromJson(super.json) | ||
| : stateKey = json['state_key'] as String, | ||
| super.fromJson(); | ||
| } |
19 changes: 19 additions & 0 deletions
19
lib/msc_extensions/msc_4186_simplified_sliding_sync/models/stripped_hero.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| class StrippedHero { | ||
| /// The user ID of the hero. | ||
| final String user_id; | ||
|
|
||
| /// The display name of the user from the membership event, if set | ||
| final String? displayName; | ||
|
|
||
| /// The avatar url from the membership event, if set | ||
| // TODO: migrate to Uri | ||
| final String? avatarUrl; | ||
|
|
||
| const StrippedHero({required this.user_id, this.displayName, this.avatarUrl}); | ||
|
|
||
| factory StrippedHero.fromJson(Map<String, Object?> json) => StrippedHero( | ||
| user_id: json['user_id'] as String, | ||
| displayName: json['displayname'] as String?, | ||
| avatarUrl: json['avatar_url'] as String?, | ||
| ); | ||
| } |
29 changes: 29 additions & 0 deletions
29
lib/msc_extensions/msc_4186_simplified_sliding_sync/models/sync_list_config.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import 'package:matrix/matrix.dart'; | ||
|
|
||
| class SyncListConfig { | ||
| /// The maximum number of timeline events to return per response. The server may cap this number. | ||
| final int timeline_limit; | ||
|
|
||
| /// Required state for each room returned. | ||
| final RequiredStateRequest required_state; | ||
|
|
||
| /// Sliding window range. If this field is missing, no sliding window is used and all rooms are returned in this list. Integers are inclusive, and are 0-indexed. (This is a 2-tuple.) | ||
| final (int, int)? range; | ||
|
|
||
| /// Filters to apply to the list. | ||
| final SlidingRoomFilter? filters; | ||
|
|
||
| const SyncListConfig({ | ||
| required this.timeline_limit, | ||
| required this.required_state, | ||
| required this.range, | ||
| required this.filters, | ||
| }); | ||
|
|
||
| Map<String, Object?> toJson() => { | ||
| 'timeline_limit': timeline_limit, | ||
| 'required_state': required_state.toJson(), | ||
| if (range != null) 'range': [range!.$1, range!.$2], | ||
| if (filters != null) 'filters': filters!.toJson(), | ||
| }; | ||
| } |
9 changes: 9 additions & 0 deletions
9
lib/msc_extensions/msc_4186_simplified_sliding_sync/models/sync_list_result.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| class SyncListResult { | ||
| /// The total number of entries in the list. | ||
| final int count; | ||
|
|
||
| const SyncListResult({required this.count}); | ||
|
|
||
| factory SyncListResult.fromJson(Map<String, Object?> json) => | ||
| SyncListResult(count: json['count'] as int); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting concept. What do you think about we make this a
sealedclass? Then we could even useswitchlike: