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.
- package/.gitmodules +3 -3
- package/.idea/shelf/Uncommitted_changes_before_Checkout_at_05_08_2026_17_28_[Changes]/shelved.patch +447 -0
- package/.idea/shelf/Uncommitted_changes_before_Checkout_at_05_08_2026_17_28_[Changes]1/shelved.patch +0 -0
- package/.idea/shelf/Uncommitted_changes_before_Checkout_at_05_08_2026_17_28__Changes_.xml +4 -0
- package/.idea/workspace.xml +61 -52
- package/CODE_OF_CONDUCT.md +76 -76
- package/README.md +44 -44
- package/babel.config.js +5 -5
- package/build/index.cjs.js +398 -427
- package/build/index.cjs.js.map +1 -1
- package/build/index.umd.js +1 -1
- package/build/index.umd.js.map +1 -1
- package/build/types/index.d.ts +2 -1
- package/build/types/state-tracker/RoomMessagesHistory.d.ts +4 -15
- package/build/types/state-tracker/TopicHistoryWindow.d.ts +0 -48
- package/build/types/types/src/index.d.ts +11 -3
- package/build/types/types/src/schemes/Message.d.ts +5 -1
- package/build/types/types/src/schemes/MessagePoll.d.ts +10 -0
- package/build/types/types/src/schemes/Reaction.d.ts +13 -0
- package/build/types/types/src/schemes/SessionData.d.ts +6 -0
- package/build/types/types/src/schemes/User.d.ts +15 -0
- package/build/types/types/src/schemes/UserData.d.ts +5 -0
- package/build/types/types/src/schemes/commands/CreateMessage.d.ts +3 -0
- package/build/types/types/src/schemes/commands/GetMessages.d.ts +5 -0
- package/build/types/types/src/schemes/commands/GetReactionDetails.d.ts +8 -0
- package/build/types/types/src/schemes/commands/React.d.ts +6 -0
- package/build/types/types/src/schemes/commands/SetSessionData.d.ts +9 -1
- package/build/types/types/src/schemes/commands/SetUserData.d.ts +1 -0
- package/build/types/types/src/schemes/commands/Unreact.d.ts +6 -0
- package/build/types/types/src/schemes/events/Messages.d.ts +3 -0
- package/build/types/types/src/schemes/events/Reacted.d.ts +9 -0
- package/build/types/types/src/schemes/events/ReactionDetails.d.ts +11 -0
- package/build/types/types/src/schemes/events/ReactionUpdated.d.ts +8 -0
- package/jest.config.ts +199 -199
- package/package.json +1 -1
- package/scripts/getPackageJson.js +24 -24
- package/src/FilesClient.ts +42 -42
- package/src/index.ts +2 -0
- package/src/state-tracker/ChatStateTracker.ts +71 -71
- package/src/state-tracker/RoomMessagesHistory.ts +9 -27
- package/src/state-tracker/TopicHistoryWindow.ts +1 -133
- package/src/state-tracker/UsersManager.ts +1 -1
- package/src/state-tracker/functions.ts +28 -28
- package/src/types/src/index.ts +24 -2
- package/src/types/src/schemes/Message.ts +5 -1
- package/src/types/src/schemes/MessagePoll.ts +12 -0
- package/src/types/src/schemes/Reaction.ts +16 -0
- package/src/types/src/schemes/SessionData.ts +8 -1
- package/src/types/src/schemes/User.ts +17 -1
- package/src/types/src/schemes/UserData.ts +5 -0
- package/src/types/src/schemes/commands/CreateMessage.ts +3 -0
- package/src/types/src/schemes/commands/GetMessages.ts +5 -0
- package/src/types/src/schemes/commands/GetReactionDetails.ts +9 -0
- package/src/types/src/schemes/commands/React.ts +7 -0
- package/src/types/src/schemes/commands/SetSessionData.ts +11 -2
- package/src/types/src/schemes/commands/SetUserData.ts +1 -0
- package/src/types/src/schemes/commands/Unreact.ts +7 -0
- package/src/types/src/schemes/events/Messages.ts +3 -0
- package/src/types/src/schemes/events/Reacted.ts +10 -0
- package/src/types/src/schemes/events/ReactionDetails.ts +12 -0
- package/src/types/src/schemes/events/ReactionUpdated.ts +9 -0
- package/tests/async-utils.test.ts +29 -29
- package/tests/history-window.test.ts +0 -131
- package/tests/space-roles.test.ts +42 -42
- package/tests/state-reconnect.test.ts +0 -260
- package/.idea/shelf/Uncommitted_changes_before_Checkout_at_26_07_2026_21_26_[Changes]/shelved.patch +0 -174
- package/.idea/shelf/Uncommitted_changes_before_Checkout_at_26_07_2026_21_26__Changes_.xml +0 -4
- package/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/shelved.patch +0 -18
- 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.
|
|
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
|
|
51
|
-
* were never pulled (LIVE) or belong to an ephemeral room are left
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
155
|
-
this.traverseLock = room.history
|
|
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.
|
|
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
|
}
|
package/src/types/src/index.ts
CHANGED
|
@@ -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,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
|
+
}
|