1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text.Json.Nodes;
using ArcaneLibs.Extensions;
using LibMatrix.HomeserverEmulator.Services;
using LibMatrix.Responses;
using LibMatrix.Services;
using Microsoft.AspNetCore.Mvc;
namespace LibMatrix.HomeserverEmulator.Controllers;
[ApiController]
[Route("/_matrix/client/{version}/")]
public class SyncController(ILogger<SyncController> logger, TokenService tokenService, UserStore userStore, RoomStore roomStore, HSEConfiguration cfg) : ControllerBase {
[HttpGet("sync")]
[SuppressMessage("ReSharper.DPA", "DPA0011: High execution time of MVC action", Justification = "Endpoint is expected to wait until data is available or timeout.")]
public async Task<SyncResponse> Sync([FromQuery] string? since = null, [FromQuery] int? timeout = 5000) {
var sw = Stopwatch.StartNew();
var token = tokenService.GetAccessToken(HttpContext);
if (token == null)
throw new MatrixException() {
ErrorCode = "M_MISSING_TOKEN",
Error = "Missing token"
};
var user = await userStore.GetUserByToken(token);
if (user == null)
throw new MatrixException() {
ErrorCode = "M_UNKNOWN_TOKEN",
Error = "No such user"
};
var session = user.AccessTokens[token];
if (string.IsNullOrWhiteSpace(since))
return InitialSync(user, session);
if (!session.SyncStates.TryGetValue(since, out var syncState))
if (!cfg.UnknownSyncTokenIsInitialSync)
throw new MatrixException() {
ErrorCode = "M_UNKNOWN",
Error = "Unknown sync token."
};
else
return InitialSync(user, session);
var response = new SyncResponse() {
NextBatch = Guid.NewGuid().ToString(),
DeviceOneTimeKeysCount = new()
};
session.SyncStates.Add(response.NextBatch, new() {
RoomPositions = syncState.RoomPositions.ToDictionary(x => x.Key, x => new UserStore.User.SessionInfo.UserSyncState.SyncRoomPosition() {
StatePosition = roomStore._rooms.First(y => y.RoomId == x.Key).State.Count,
TimelinePosition = roomStore._rooms.First(y => y.RoomId == x.Key).Timeline.Count,
AccountDataPosition = roomStore._rooms.First(y => y.RoomId == x.Key).AccountData[user.UserId].Count
})
});
if (!string.IsNullOrWhiteSpace(since)) {
while (sw.ElapsedMilliseconds < timeout && response.Rooms?.Join is not { Count: > 0 }) {
await Task.Delay(100);
var rooms = roomStore._rooms.Where(x => x.State.Any(y => y.Type == "m.room.member" && y.StateKey == user.UserId)).ToList();
foreach (var room in rooms) {
var roomPositions = syncState.RoomPositions[room.RoomId];
response.Rooms ??= new();
response.Rooms.Join ??= new();
response.Rooms.Join[room.RoomId] = new() {
State = new(room.State.Skip(roomPositions.StatePosition).ToList()),
Timeline = new(events: room.Timeline.Skip(roomPositions.TimelinePosition).ToList(), limited: false),
AccountData = new(room.AccountData.GetOrCreate(user.UserId, _ => []).Skip(roomPositions.AccountDataPosition).ToList())
};
session.SyncStates[response.NextBatch].RoomPositions[room.RoomId] = new() {
StatePosition = room.State.Count,
TimelinePosition = room.Timeline.Count,
AccountDataPosition = room.AccountData[user.UserId].Count
};
if (response.Rooms.Join[room.RoomId].State.Events.Count == 0 &&
response.Rooms.Join[room.RoomId].Timeline.Events.Count == 0 &&
response.Rooms.Join[room.RoomId].AccountData.Events.Count == 0
)
response.Rooms.Join.Remove(room.RoomId);
}
}
}
return response;
}
private SyncResponse InitialSync(UserStore.User user, UserStore.User.SessionInfo session) {
var response = new SyncResponse() {
NextBatch = Guid.NewGuid().ToString(),
DeviceOneTimeKeysCount = new(),
AccountData = new(events: user.AccountData.ToList())
};
session.SyncStates.Add(response.NextBatch, new());
var rooms = roomStore._rooms.Where(x => x.State.Any(y => y.Type == "m.room.member" && y.StateKey == user.UserId)).ToList();
foreach (var room in rooms) {
response.Rooms ??= new();
response.Rooms.Join ??= new();
response.Rooms.Join[room.RoomId] = new() {
State = new(room.State.ToList()),
Timeline = new(events: room.Timeline.ToList(), limited: false),
AccountData = new(room.AccountData.GetOrCreate(user.UserId, _ => []).ToList())
};
session.SyncStates[response.NextBatch].RoomPositions[room.RoomId] = new() {
StatePosition = room.State.Count,
TimelinePosition = room.Timeline.Count,
AccountDataPosition = room.AccountData[user.UserId].Count
};
}
return response;
}
}
|