polfan-server-js-client 0.3.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.gitmodules +3 -3
  2. package/.idea/shelf/Uncommitted_changes_before_Checkout_at_05_08_2026_17_28_[Changes]/shelved.patch +447 -0
  3. package/.idea/shelf/Uncommitted_changes_before_Checkout_at_05_08_2026_17_28_[Changes]1/shelved.patch +0 -0
  4. package/.idea/shelf/Uncommitted_changes_before_Checkout_at_05_08_2026_17_28__Changes_.xml +4 -0
  5. package/.idea/workspace.xml +61 -52
  6. package/CODE_OF_CONDUCT.md +76 -76
  7. package/README.md +44 -44
  8. package/babel.config.js +5 -5
  9. package/build/index.cjs.js +398 -427
  10. package/build/index.cjs.js.map +1 -1
  11. package/build/index.umd.js +1 -1
  12. package/build/index.umd.js.map +1 -1
  13. package/build/types/index.d.ts +2 -1
  14. package/build/types/state-tracker/RoomMessagesHistory.d.ts +4 -15
  15. package/build/types/state-tracker/TopicHistoryWindow.d.ts +0 -48
  16. package/build/types/types/src/index.d.ts +11 -3
  17. package/build/types/types/src/schemes/Message.d.ts +5 -1
  18. package/build/types/types/src/schemes/MessagePoll.d.ts +10 -0
  19. package/build/types/types/src/schemes/Reaction.d.ts +13 -0
  20. package/build/types/types/src/schemes/SessionData.d.ts +6 -0
  21. package/build/types/types/src/schemes/User.d.ts +15 -0
  22. package/build/types/types/src/schemes/UserData.d.ts +5 -0
  23. package/build/types/types/src/schemes/commands/CreateMessage.d.ts +3 -0
  24. package/build/types/types/src/schemes/commands/GetMessages.d.ts +5 -0
  25. package/build/types/types/src/schemes/commands/GetReactionDetails.d.ts +8 -0
  26. package/build/types/types/src/schemes/commands/React.d.ts +6 -0
  27. package/build/types/types/src/schemes/commands/SetSessionData.d.ts +9 -1
  28. package/build/types/types/src/schemes/commands/SetUserData.d.ts +1 -0
  29. package/build/types/types/src/schemes/commands/Unreact.d.ts +6 -0
  30. package/build/types/types/src/schemes/events/Messages.d.ts +3 -0
  31. package/build/types/types/src/schemes/events/Reacted.d.ts +9 -0
  32. package/build/types/types/src/schemes/events/ReactionDetails.d.ts +11 -0
  33. package/build/types/types/src/schemes/events/ReactionUpdated.d.ts +8 -0
  34. package/jest.config.ts +199 -199
  35. package/package.json +1 -1
  36. package/scripts/getPackageJson.js +24 -24
  37. package/src/FilesClient.ts +42 -42
  38. package/src/index.ts +2 -0
  39. package/src/state-tracker/ChatStateTracker.ts +71 -71
  40. package/src/state-tracker/RoomMessagesHistory.ts +9 -27
  41. package/src/state-tracker/TopicHistoryWindow.ts +1 -133
  42. package/src/state-tracker/UsersManager.ts +1 -1
  43. package/src/state-tracker/functions.ts +28 -28
  44. package/src/types/src/index.ts +24 -2
  45. package/src/types/src/schemes/Message.ts +5 -1
  46. package/src/types/src/schemes/MessagePoll.ts +12 -0
  47. package/src/types/src/schemes/Reaction.ts +16 -0
  48. package/src/types/src/schemes/SessionData.ts +8 -1
  49. package/src/types/src/schemes/User.ts +17 -1
  50. package/src/types/src/schemes/UserData.ts +5 -0
  51. package/src/types/src/schemes/commands/CreateMessage.ts +3 -0
  52. package/src/types/src/schemes/commands/GetMessages.ts +5 -0
  53. package/src/types/src/schemes/commands/GetReactionDetails.ts +9 -0
  54. package/src/types/src/schemes/commands/React.ts +7 -0
  55. package/src/types/src/schemes/commands/SetSessionData.ts +11 -2
  56. package/src/types/src/schemes/commands/SetUserData.ts +1 -0
  57. package/src/types/src/schemes/commands/Unreact.ts +7 -0
  58. package/src/types/src/schemes/events/Messages.ts +3 -0
  59. package/src/types/src/schemes/events/Reacted.ts +10 -0
  60. package/src/types/src/schemes/events/ReactionDetails.ts +12 -0
  61. package/src/types/src/schemes/events/ReactionUpdated.ts +9 -0
  62. package/tests/async-utils.test.ts +29 -29
  63. package/tests/history-window.test.ts +0 -131
  64. package/tests/space-roles.test.ts +42 -42
  65. package/tests/state-reconnect.test.ts +0 -260
  66. package/.idea/shelf/Uncommitted_changes_before_Checkout_at_26_07_2026_21_26_[Changes]/shelved.patch +0 -174
  67. package/.idea/shelf/Uncommitted_changes_before_Checkout_at_26_07_2026_21_26__Changes_.xml +0 -4
  68. package/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/shelved.patch +0 -18
  69. package/.idea/shelf/Uncommitted_changes_before_rebase__Changes_.xml +0 -4
@@ -1,72 +1,72 @@
1
- import {WebSocketChatClient} from "../WebSocketChatClient";
2
- import {Session, User} from "../types/src";
3
- import {RoomsManager} from "./RoomsManager";
4
- import {SpacesManager} from "./SpacesManager";
5
- import {PermissionsManager} from "./PermissionsManager";
6
- import {DeferredTask} from "./AsyncUtils";
7
- import {EmoticonsManager} from "./EmoticonsManager";
8
- import {UsersManager} from "./UsersManager";
9
- import {RelationshipsManager} from "./RelationshipsManager";
10
-
11
- export class ChatStateTracker {
12
- public readonly client: WebSocketChatClient;
13
-
14
- /**
15
- * State of your permissions.
16
- */
17
- public readonly permissions: PermissionsManager;
18
-
19
- /**
20
- * State of the rooms you are in.
21
- */
22
- public readonly rooms: RoomsManager;
23
-
24
- /**
25
- * State of the spaces you are in.
26
- */
27
- public readonly spaces: SpacesManager;
28
-
29
- /**
30
- * State of the emoticons (global and space-related).
31
- */
32
- public readonly emoticons: EmoticonsManager;
33
-
34
- /**
35
- * Users related state.
36
- */
37
- public readonly users: UsersManager;
38
-
39
- /**
40
- * State of relationships with other users.
41
- */
42
- public readonly relationships: RelationshipsManager;
43
-
44
- private _me: User = null;
45
- private readonly deferredSession = new DeferredTask();
46
-
47
- public constructor(client: WebSocketChatClient) {
48
- this.client = client;
49
- this.client.on('Session', ev => this.handleSession(ev));
50
-
51
- this.permissions = new PermissionsManager(this);
52
- this.rooms = new RoomsManager(this);
53
- this.spaces = new SpacesManager(this);
54
- this.emoticons = new EmoticonsManager(this);
55
- this.users = new UsersManager(this);
56
- this.relationships = new RelationshipsManager(this);
57
- }
58
-
59
- public get me(): User | null {
60
- return this._me;
61
- }
62
-
63
- public async getMe(): Promise<User> {
64
- await this.deferredSession.promise;
65
- return this._me;
66
- }
67
-
68
- private handleSession(ev: Session): void {
69
- this._me = ev.user;
70
- this.deferredSession.resolve();
71
- }
1
+ import {WebSocketChatClient} from "../WebSocketChatClient";
2
+ import {Session, User} from "../types/src";
3
+ import {RoomsManager} from "./RoomsManager";
4
+ import {SpacesManager} from "./SpacesManager";
5
+ import {PermissionsManager} from "./PermissionsManager";
6
+ import {DeferredTask} from "./AsyncUtils";
7
+ import {EmoticonsManager} from "./EmoticonsManager";
8
+ import {UsersManager} from "./UsersManager";
9
+ import {RelationshipsManager} from "./RelationshipsManager";
10
+
11
+ export class ChatStateTracker {
12
+ public readonly client: WebSocketChatClient;
13
+
14
+ /**
15
+ * State of your permissions.
16
+ */
17
+ public readonly permissions: PermissionsManager;
18
+
19
+ /**
20
+ * State of the rooms you are in.
21
+ */
22
+ public readonly rooms: RoomsManager;
23
+
24
+ /**
25
+ * State of the spaces you are in.
26
+ */
27
+ public readonly spaces: SpacesManager;
28
+
29
+ /**
30
+ * State of the emoticons (global and space-related).
31
+ */
32
+ public readonly emoticons: EmoticonsManager;
33
+
34
+ /**
35
+ * Users related state.
36
+ */
37
+ public readonly users: UsersManager;
38
+
39
+ /**
40
+ * State of relationships with other users.
41
+ */
42
+ public readonly relationships: RelationshipsManager;
43
+
44
+ private _me: User = null;
45
+ private readonly deferredSession = new DeferredTask();
46
+
47
+ public constructor(client: WebSocketChatClient) {
48
+ this.client = client;
49
+ this.client.on('Session', ev => this.handleSession(ev));
50
+
51
+ this.permissions = new PermissionsManager(this);
52
+ this.rooms = new RoomsManager(this);
53
+ this.spaces = new SpacesManager(this);
54
+ this.emoticons = new EmoticonsManager(this);
55
+ this.users = new UsersManager(this);
56
+ this.relationships = new RelationshipsManager(this);
57
+ }
58
+
59
+ public get me(): User | null {
60
+ return this._me;
61
+ }
62
+
63
+ public async getMe(): Promise<User> {
64
+ await this.deferredSession.promise;
65
+ return this._me;
66
+ }
67
+
68
+ private handleSession(ev: Session): void {
69
+ this._me = ev.user;
70
+ this.deferredSession.resolve();
71
+ }
72
72
  }
@@ -6,7 +6,6 @@ import {TopicHistoryWindow, WindowState} from "./TopicHistoryWindow";
6
6
  export class RoomMessagesHistory {
7
7
  private historyWindows = new IndexedCollection<string, TopicHistoryWindow>();
8
8
  private traverseLock: boolean = false;
9
- private timeLimitedHistory: boolean = false;
10
9
 
11
10
  public constructor(
12
11
  private room: Room,
@@ -16,7 +15,7 @@ export class RoomMessagesHistory {
16
15
  this.tracker.client.on('NewTopic', ev => this.handleNewTopic(ev));
17
16
  this.tracker.client.on('TopicDeleted', ev => this.handleTopicDeleted(ev));
18
17
 
19
- this.updateHistoryMode(this.room);
18
+ this.updateTraverseLock(this.room);
20
19
 
21
20
  if (this.room.defaultTopic) {
22
21
  this.createHistoryWindowForTopic(this.room.defaultTopic);
@@ -47,23 +46,13 @@ export class RoomMessagesHistory {
47
46
  *
48
47
  * The window bindings are preserved; only windows that the application had
49
48
  * actually pulled to the latest page (state === LATEST) are refreshed, with
50
- * a single request instead of a chain of catch-up requests. Windows that
51
- * were never pulled (LIVE) or belong to an ephemeral room are left untouched
52
- * so their in-memory context survives the reconnect.
53
- *
54
- * How a refreshed window is rebuilt depends on the room history mode: rooms
55
- * keeping the full history are simply reset to the latest page (it can
56
- * always be traversed back on demand), while rooms with a time-limited
57
- * history (MaxAge) load the messages missed during the downtime on top of
58
- * the already loaded ones, with the messages returned in both deduplicated.
59
- * The maxAge retention applies to what the server serves - so that users
60
- * joining later do not see the older conversation - and never to what this
61
- * client already has: messages the user witnessed stay in the window until
62
- * they are pushed out by its own size limit.
49
+ * a single resetToLatest instead of a chain of catch-up requests. Windows
50
+ * that were never pulled (LIVE) or belong to an ephemeral room are left
51
+ * untouched so their in-memory context survives the reconnect.
63
52
  */
64
53
  public async resync(room: Room): Promise<void> {
65
54
  this.room = room;
66
- this.updateHistoryMode(room);
55
+ this.updateTraverseLock(room);
67
56
 
68
57
  if (this.room.defaultTopic) {
69
58
  this.createHistoryWindowForTopic(this.room.defaultTopic);
@@ -79,13 +68,7 @@ export class RoomMessagesHistory {
79
68
  continue;
80
69
  }
81
70
 
82
- if (window.state !== WindowState.LATEST) {
83
- continue;
84
- }
85
-
86
- if (this.timeLimitedHistory) {
87
- await window.resyncToLatest();
88
- } else {
71
+ if (window.state === WindowState.LATEST) {
89
72
  await window.resetToLatest(true);
90
73
  }
91
74
  } catch (_e) {
@@ -100,7 +83,7 @@ export class RoomMessagesHistory {
100
83
  if (this.room.id === ev.room.id) {
101
84
  this.room = ev.room;
102
85
 
103
- this.updateHistoryMode(ev.room);
86
+ this.updateTraverseLock(ev.room);
104
87
 
105
88
  if (ev.room.defaultTopic) {
106
89
  this.createHistoryWindowForTopic(ev.room.defaultTopic);
@@ -151,8 +134,7 @@ export class RoomMessagesHistory {
151
134
  }
152
135
  }
153
136
 
154
- private updateHistoryMode(room: Room): void {
155
- this.traverseLock = room.history?.mode === 'Ephemeral';
156
- this.timeLimitedHistory = room.history?.mode === 'MaxAge';
137
+ private updateTraverseLock(room: Room): void {
138
+ this.traverseLock = room.history.mode === 'Ephemeral';
157
139
  }
158
140
  }
@@ -45,7 +45,6 @@ export abstract class TraversableRemoteCollection<
45
45
  fetchLimit: number,
46
46
  lastFetchCount: number,
47
47
  oldestId: string | null,
48
- gaps: string[],
49
48
  } = {
50
49
  current: WindowState.LIVE,
51
50
  ongoing: undefined,
@@ -54,7 +53,6 @@ export abstract class TraversableRemoteCollection<
54
53
  fetchLimit: 50,
55
54
  lastFetchCount: 0,
56
55
  oldestId: null,
57
- gaps: [],
58
56
  };
59
57
 
60
58
  /**
@@ -105,21 +103,6 @@ export abstract class TraversableRemoteCollection<
105
103
  return [WindowState.LATEST, WindowState.LIVE].includes(this.state);
106
104
  }
107
105
 
108
- /**
109
- * IDs of the items the window could not stitch to the ones loaded before
110
- * them: there is a gap in front of each of them, i.e. the item right above
111
- * it in the window is not its real predecessor and an unknown number of
112
- * items in between was never fetched.
113
- *
114
- * Such a gap appears when the collection is resynchronised after a
115
- * reconnect (see resyncToLatest) and more items than a single page arrived
116
- * while the connection was down. The markers are kept in the window order
117
- * and disappear together with the items they point at.
118
- */
119
- public get gaps(): readonly string[] {
120
- return [...this.internalState.gaps];
121
- }
122
-
123
106
  public get hasOldest(): boolean {
124
107
  return this.state === WindowState.OLDEST
125
108
  || this.state === WindowState.LATEST && this.length < this.fetchLimit
@@ -145,68 +128,11 @@ export abstract class TraversableRemoteCollection<
145
128
  }
146
129
 
147
130
  this._items.deleteAll(); // Directly call deleteAll to prevent event emit.
148
- this.internalState.gaps = []; // Whole content is replaced, old markers mean nothing.
149
131
  this.addItems(result, 'tail');
150
132
  this.internalState.current = WindowState.LATEST;
151
133
  this.emitChangeWithDiff(true, originalState);
152
134
  }
153
135
 
154
- /**
155
- * Refresh the window with the latest page, keeping the already loaded items
156
- * instead of replacing them.
157
- *
158
- * This is the reconnect-friendly variant of resetToLatest: the items missed
159
- * while the connection was down are pulled with a single request and merged
160
- * on top of the loaded ones (items returned in both are deduplicated), so
161
- * the context the application already had does not disappear. The window
162
- * size limit is the only thing that pushes the oldest items out.
163
- *
164
- * An empty or partial page is not a reason to drop anything: it only means
165
- * the collection has little (or nothing) left on the remote side, while the
166
- * items loaded earlier are still valid. When the page is full and does not
167
- * reach the loaded items, an unknown number of items in between was never
168
- * fetched - both parts are still kept, and the seam between them is recorded
169
- * in `gaps` so the application can show where the history is not continuous.
170
- */
171
- public async resyncToLatest(): Promise<void> {
172
- if (this.internalState.ongoing) {
173
- return;
174
- }
175
-
176
- let result;
177
- const originalState = this.state;
178
- this.internalState.ongoing = WindowState.LATEST;
179
-
180
- try {
181
- result = await this.fetchLatestItems();
182
- this.internalState.lastFetchCount = result.length;
183
- } finally {
184
- this.internalState.ongoing = undefined;
185
- }
186
-
187
- const loaded = this.items;
188
- const fetchedIds = new Set(result.map(item => this.getId(item)));
189
- // Items present in the page are taken from it - the server copy is the
190
- // up-to-date one.
191
- const retained = loaded.filter(item => ! fetchedIds.has(this.getId(item)));
192
-
193
- // Nothing can be missing in between when there is nothing to stitch,
194
- // when the page is everything the remote side has (it is shorter than
195
- // the requested limit), or when the page reaches the newest loaded item.
196
- const isContinuous = ! retained.length
197
- || result.length < this.internalState.fetchLimit
198
- || fetchedIds.has(this.getId(loaded[loaded.length - 1]));
199
-
200
- if (! isContinuous) {
201
- this.markGapBefore(this.getId(result[0]));
202
- }
203
-
204
- this._items.deleteAll(); // Directly call deleteAll to prevent event emit.
205
- this.addItems([...retained, ...result], 'tail');
206
- this.internalState.current = WindowState.LATEST;
207
- this.emitChangeWithDiff(true, originalState);
208
- }
209
-
210
136
  public async fetchPrevious(): Promise<void> {
211
137
  if (this.internalState.ongoing || this.hasOldest) {
212
138
  return;
@@ -214,7 +140,6 @@ export abstract class TraversableRemoteCollection<
214
140
 
215
141
  let result;
216
142
  const originalState = this.state;
217
- const firstItem = this.getAt(0);
218
143
  this.internalState.ongoing = WindowState.PAST;
219
144
 
220
145
  try {
@@ -229,6 +154,7 @@ export abstract class TraversableRemoteCollection<
229
154
  }
230
155
 
231
156
  if (! result.length) {
157
+ const firstItem = this.getAt(0);
232
158
  this.internalState.oldestId = firstItem ? this.getId(firstItem) : null;
233
159
 
234
160
  await this.refreshFetchedState();
@@ -242,13 +168,6 @@ export abstract class TraversableRemoteCollection<
242
168
  return;
243
169
  }
244
170
 
245
- if (firstItem) {
246
- // The fetch asked for the items right before the one that was first,
247
- // so whatever came back is its real predecessor: a gap marked in
248
- // front of it (it used to be the top of the window) is closed now.
249
- this.clearGapBefore(this.getId(firstItem));
250
- }
251
-
252
171
  this.addItems(result, 'head');
253
172
  await this.refreshFetchedState();
254
173
  this.emitChangeWithDiff(true, originalState);
@@ -298,7 +217,6 @@ export abstract class TraversableRemoteCollection<
298
217
 
299
218
  if (result) {
300
219
  this._items.deleteAll(); // Directly call deleteAll to prevent event emit.
301
- this.internalState.gaps = []; // Whole content is replaced, old markers mean nothing.
302
220
  this.addItems(result, 'tail');
303
221
  await this.refreshFetchedState();
304
222
  }
@@ -309,16 +227,6 @@ export abstract class TraversableRemoteCollection<
309
227
  this.emitChangeWithDiff(!!result, originalState);
310
228
  }
311
229
 
312
- public delete(...ids: string[]): void {
313
- super.delete(...ids);
314
- this.dropDanglingGaps();
315
- }
316
-
317
- public deleteAll(): void {
318
- super.deleteAll();
319
- this.internalState.gaps = [];
320
- }
321
-
322
230
  protected abstract fetchLatestItems(): Promise<ItemT[]>;
323
231
 
324
232
  protected abstract fetchItemsBefore(): Promise<ItemT[] | null>;
@@ -347,8 +255,6 @@ export abstract class TraversableRemoteCollection<
347
255
  // Directly calls to prevent event emit.
348
256
  this._items.deleteAll();
349
257
  this._items.set(...(result.map(item => [this.getId(item), item] as [string, ItemT])));
350
-
351
- this.dropDanglingGaps();
352
258
  }
353
259
 
354
260
  protected emitChangeWithDiff(itemChanged: boolean, originalState: WindowState): void {
@@ -357,37 +263,6 @@ export abstract class TraversableRemoteCollection<
357
263
  }
358
264
  }
359
265
 
360
- /**
361
- * Record that the history is not continuous in front of the given item.
362
- */
363
- protected markGapBefore(id: string): void {
364
- if (! this.internalState.gaps.includes(id)) {
365
- this.internalState.gaps = [...this.internalState.gaps, id];
366
- }
367
- }
368
-
369
- /**
370
- * Forget the gap in front of the given item - the items before it are known
371
- * to be its real predecessors now.
372
- */
373
- protected clearGapBefore(id: string): void {
374
- if (this.internalState.gaps.includes(id)) {
375
- this.internalState.gaps = this.internalState.gaps.filter(gapId => gapId !== id);
376
- }
377
- }
378
-
379
- /**
380
- * Forget the gap markers pointing at items that are no longer in the window
381
- * (trimmed, deleted or replaced), so `gaps` never refers to nothing.
382
- */
383
- protected dropDanglingGaps(): void {
384
- if (! this.internalState.gaps.length) {
385
- return;
386
- }
387
-
388
- this.internalState.gaps = this.internalState.gaps.filter(id => this.has(id));
389
- }
390
-
391
266
  /**
392
267
  * Return array with messages trimmed using High/Low Watermark strategy.
393
268
  */
@@ -472,13 +347,6 @@ export class TopicHistoryWindow extends TraversableRemoteCollection<
472
347
  return super.resetToLatest(force);
473
348
  }
474
349
 
475
- public async resyncToLatest(): Promise<void> {
476
- if (this.internalState.traverseLock) {
477
- return;
478
- }
479
- return super.resyncToLatest();
480
- }
481
-
482
350
  public async fetchNext(): Promise<void> {
483
351
  if (this.internalState.traverseLock) {
484
352
  return;
@@ -42,7 +42,7 @@ export class UsersManager {
42
42
  private handleUsers(users: User[]): void {
43
43
  users.forEach(newUser => {
44
44
  const oldUser = this.users.get(newUser.id);
45
- if (oldUser && oldUser.online !== newUser.online) {
45
+ if (oldUser && oldUser.status !== newUser.status) {
46
46
  this.onlineStatus.emit('change', newUser);
47
47
  }
48
48
  });
@@ -1,29 +1,29 @@
1
- import {Role, RoomMember, SpaceMember, User} from "../types/src";
2
-
3
- export function reorderRolesOnPriorityUpdate(allRoles: Role[], oldRole: Role, updatedRole: Role): Role[] {
4
- // If the priority has changed, adjust the rest of roles
5
- const increased = (updatedRole.priority - oldRole.priority) > 0;
6
- const decreased = ! increased;
7
- const changedRoles: Role[] = [];
8
-
9
- allRoles.forEach(role => {
10
- if (role.id === updatedRole.id) {
11
- // Skip the updated role
12
- return;
13
- }
14
- if (increased && oldRole.priority <= role.priority) {
15
- role.priority--;
16
- changedRoles.push(role);
17
- }
18
- if (decreased && updatedRole.priority <= role.priority) {
19
- role.priority++;
20
- changedRoles.push(role);
21
- }
22
- });
23
-
24
- return changedRoles;
25
- }
26
-
27
- export function extractUserFromMember(member: RoomMember | SpaceMember): User | null {
28
- return member.user ?? (member as RoomMember).spaceMember?.user;
1
+ import {Role, RoomMember, SpaceMember, User} from "../types/src";
2
+
3
+ export function reorderRolesOnPriorityUpdate(allRoles: Role[], oldRole: Role, updatedRole: Role): Role[] {
4
+ // If the priority has changed, adjust the rest of roles
5
+ const increased = (updatedRole.priority - oldRole.priority) > 0;
6
+ const decreased = ! increased;
7
+ const changedRoles: Role[] = [];
8
+
9
+ allRoles.forEach(role => {
10
+ if (role.id === updatedRole.id) {
11
+ // Skip the updated role
12
+ return;
13
+ }
14
+ if (increased && oldRole.priority <= role.priority) {
15
+ role.priority--;
16
+ changedRoles.push(role);
17
+ }
18
+ if (decreased && updatedRole.priority <= role.priority) {
19
+ role.priority++;
20
+ changedRoles.push(role);
21
+ }
22
+ });
23
+
24
+ return changedRoles;
25
+ }
26
+
27
+ export function extractUserFromMember(member: RoomMember | SpaceMember): User | null {
28
+ return member.user ?? (member as RoomMember).spaceMember?.user;
29
29
  }
@@ -8,7 +8,7 @@ import {RoomSummary, RoomSummaryExtras} from "./schemes/RoomSummary";
8
8
  import {Space, SpaceDiscoverable} from "./schemes/Space";
9
9
  import {SpaceMember} from "./schemes/SpaceMember";
10
10
  import {Topic} from "./schemes/Topic";
11
- import {User} from "./schemes/User";
11
+ import {User, UserStatus} from "./schemes/User";
12
12
  import {UserState} from "./schemes/UserState";
13
13
  import {Bye} from "./schemes/events/Bye";
14
14
  import {Error} from "./schemes/events/Error";
@@ -134,7 +134,7 @@ import {GetUserInfo} from "./schemes/commands/GetUserInfo";
134
134
  import {UserInfo} from "./schemes/events/UserInfo";
135
135
  import {UserInformation} from "./schemes/UserInformation";
136
136
  import {SetSessionData, SessionPush} from "./schemes/commands/SetSessionData";
137
- import {SessionData} from "./schemes/SessionData";
137
+ import {SessionData, SessionPlatform} from "./schemes/SessionData";
138
138
  import {GetSessionData} from "./schemes/commands/GetSessionData";
139
139
  import {UserData, PrivateMessagePolicy} from "./schemes/UserData";
140
140
  import {SetUserData} from "./schemes/commands/SetUserData";
@@ -145,6 +145,14 @@ import {Uninvite} from "./schemes/commands/Uninvite";
145
145
  import {GetInvited} from "./schemes/commands/GetInvited";
146
146
  import {Invited} from "./schemes/events/Invited";
147
147
  import {TopicUnfollowed} from "./schemes/events/TopicUnfollowed";
148
+ import {MessageReaction, ReactionType, ToggledReaction, UserReaction} from "./schemes/Reaction";
149
+ import {MessagePoll, MessagePollOption} from "./schemes/MessagePoll";
150
+ import {React} from "./schemes/commands/React";
151
+ import {Unreact} from "./schemes/commands/Unreact";
152
+ import {GetReactionDetails} from "./schemes/commands/GetReactionDetails";
153
+ import {ReactionUpdated} from "./schemes/events/ReactionUpdated";
154
+ import {Reacted} from "./schemes/events/Reacted";
155
+ import {ReactionDetails} from "./schemes/events/ReactionDetails";
148
156
 
149
157
  export {
150
158
  // objects
@@ -168,6 +176,7 @@ export {
168
176
  FollowedTopic,
169
177
  NotificationLevel,
170
178
  User,
179
+ UserStatus,
171
180
  UserState,
172
181
  PermissionOverwritesValue,
173
182
  ChatLocation,
@@ -183,8 +192,15 @@ export {
183
192
  UserInformation,
184
193
  SessionPush,
185
194
  SessionData,
195
+ SessionPlatform,
186
196
  UserData,
187
197
  PrivateMessagePolicy,
198
+ ReactionType,
199
+ MessageReaction,
200
+ UserReaction,
201
+ ToggledReaction,
202
+ MessagePoll,
203
+ MessagePollOption,
188
204
  // events
189
205
  Bye,
190
206
  Error,
@@ -242,6 +258,9 @@ export {
242
258
  MessagesRedacted,
243
259
  UserInfo,
244
260
  Invited,
261
+ ReactionUpdated,
262
+ Reacted,
263
+ ReactionDetails,
245
264
  // commands
246
265
  AssignRole,
247
266
  GetMessages,
@@ -309,4 +328,7 @@ export {
309
328
  Invite,
310
329
  Uninvite,
311
330
  GetInvited,
331
+ React,
332
+ Unreact,
333
+ GetReactionDetails,
312
334
  };
@@ -1,7 +1,9 @@
1
1
  import {User} from "./User";
2
2
  import {ChatLocation} from "./ChatLocation";
3
+ import {MessageReaction} from "./Reaction";
4
+ import {MessagePoll} from "./MessagePoll";
3
5
 
4
- export type MessageType = 'Text'|'RoomJoin'|'RoomMemberAdd'|'RoomLeave'|'SpaceJoin'|'SpaceLeave'|'TopicChange'|'CustomNickChange'|'Ephemeral';
6
+ export type MessageType = 'Text'|'RoomJoin'|'RoomMemberAdd'|'RoomLeave'|'SpaceJoin'|'SpaceLeave'|'TopicChange'|'CustomNickChange'|'Ephemeral'|'Poll';
5
7
 
6
8
  export interface MessageAuthor {
7
9
  user: User;
@@ -18,4 +20,6 @@ export interface Message {
18
20
  content?: string;
19
21
  topicRef: string | null;
20
22
  attachments: string[] | null;
23
+ reactions: MessageReaction[];
24
+ poll?: MessagePoll;
21
25
  }
@@ -0,0 +1,12 @@
1
+ import {ReactionType} from "./Reaction";
2
+
3
+ export interface MessagePollOption {
4
+ type: ReactionType;
5
+ value: string;
6
+ label: string;
7
+ }
8
+
9
+ export interface MessagePoll {
10
+ multipleChoice: boolean;
11
+ options: MessagePollOption[];
12
+ }
@@ -0,0 +1,16 @@
1
+ export type ReactionType = 'Emoji' | 'Emoticon';
2
+
3
+ export interface MessageReaction {
4
+ type: ReactionType;
5
+ value: string;
6
+ count: number;
7
+ }
8
+
9
+ export interface UserReaction {
10
+ type: ReactionType;
11
+ value: string;
12
+ }
13
+
14
+ export interface ToggledReaction extends UserReaction {
15
+ isAdded: boolean;
16
+ }
@@ -1,5 +1,12 @@
1
1
  import {SessionPush} from "./commands/SetSessionData";
2
2
 
3
+ /**
4
+ * Platform of a client session. Reported on connect (the `platform` query
5
+ * parameter) and independent of the push registration.
6
+ */
7
+ export type SessionPlatform = 'web' | 'ios' | 'android' | 'desktop';
8
+
3
9
  export interface SessionData {
10
+ platform?: SessionPlatform;
4
11
  push?: SessionPush;
5
- }
12
+ }
@@ -1,9 +1,25 @@
1
1
  export type UserTags = 'bot' | 'temp' | string;
2
2
 
3
+ /**
4
+ * Availability of a user, aggregated over all of their sessions.
5
+ *
6
+ * - `Offline` - no session is connected and none can be reached asynchronously.
7
+ * - `Online` - at least one session holds a live connection.
8
+ * - `OnlineAsync` - no live connection, but a push-registered device was active
9
+ * recently, so a message will still reach the user.
10
+ */
11
+ export enum UserStatus {
12
+ Offline = 0,
13
+ Online = 1,
14
+ OnlineAsync = 2,
15
+ }
16
+
3
17
  export interface User {
4
18
  id: string;
5
19
  nick: string;
6
20
  avatar: string;
7
21
  tags: UserTags[];
22
+ status: UserStatus;
23
+ /** @deprecated Use {@link status}. Kept for clients older than the numeric status. */
8
24
  online: boolean;
9
- }
25
+ }