polfan-server-js-client 0.4.0 → 0.4.2
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/.idea/workspace.xml +71 -242
- package/build/index.cjs.js +219 -125
- 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/AbstractChatClient.d.ts +7 -1
- package/build/types/Permissions.d.ts +8 -0
- package/build/types/state-tracker/TopicHistoryWindow.d.ts +26 -1
- package/build/types/types/src/index.d.ts +9 -1
- 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/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/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/package.json +43 -43
- package/src/AbstractChatClient.ts +12 -0
- package/src/Permissions.ts +2 -0
- package/src/index.ts +31 -31
- package/src/state-tracker/TopicHistoryWindow.ts +121 -42
- package/src/state-tracker/UsersManager.ts +51 -51
- package/src/types/src/index.ts +333 -313
- 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/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/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
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
GetMessages,
|
|
3
|
+
Message,
|
|
4
|
+
MessageReaction,
|
|
5
|
+
ReactionUpdated,
|
|
6
|
+
MessagesRedacted,
|
|
7
|
+
NewMessage,
|
|
8
|
+
Reacted,
|
|
9
|
+
Topic,
|
|
10
|
+
UserReaction,
|
|
11
|
+
} from "../types/src";
|
|
2
12
|
import {ChatStateTracker} from "./ChatStateTracker";
|
|
3
13
|
import {CollectionEventMap, ObservableIndexedObjectCollection} from "../IndexedObjectCollection";
|
|
4
14
|
|
|
@@ -302,6 +312,8 @@ export class TopicHistoryWindow extends TraversableRemoteCollection<
|
|
|
302
312
|
|
|
303
313
|
declare protected internalState: typeof TraversableRemoteCollection<Message>['prototype']['internalState'] & {
|
|
304
314
|
traverseLock: boolean,
|
|
315
|
+
includeMyReactions: boolean,
|
|
316
|
+
myReactions: Record<string, UserReaction[]>,
|
|
305
317
|
};
|
|
306
318
|
|
|
307
319
|
public constructor(
|
|
@@ -313,10 +325,14 @@ export class TopicHistoryWindow extends TraversableRemoteCollection<
|
|
|
313
325
|
super('id');
|
|
314
326
|
|
|
315
327
|
this.internalState.traverseLock = false;
|
|
328
|
+
this.internalState.includeMyReactions = true;
|
|
329
|
+
this.internalState.myReactions = {};
|
|
316
330
|
|
|
317
331
|
if (bindEvents) {
|
|
318
332
|
this.tracker.client.on('NewMessage', ev => this.handleNewMessage(ev));
|
|
319
333
|
this.tracker.client.on('MessagesRedacted', ev => this.handleMessagesRedacted(ev));
|
|
334
|
+
this.tracker.client.on('ReactionUpdated', ev => this.handleReactionUpdated(ev));
|
|
335
|
+
this.tracker.client.on('Reacted', ev => this.handleReacted(ev));
|
|
320
336
|
}
|
|
321
337
|
}
|
|
322
338
|
|
|
@@ -332,6 +348,26 @@ export class TopicHistoryWindow extends TraversableRemoteCollection<
|
|
|
332
348
|
return this.internalState.traverseLock;
|
|
333
349
|
}
|
|
334
350
|
|
|
351
|
+
/**
|
|
352
|
+
* What the connected user has reacted with, keyed by message id. Kept apart from
|
|
353
|
+
* the messages, so the history itself is identical for every user.
|
|
354
|
+
*/
|
|
355
|
+
public get myReactions(): Readonly<Record<string, UserReaction[]>> {
|
|
356
|
+
return this.internalState.myReactions;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Whether the history is fetched together with the reactions of the connected
|
|
361
|
+
* user. Turn it off only where the active state of a reaction is never rendered.
|
|
362
|
+
*/
|
|
363
|
+
public get includeMyReactions(): boolean {
|
|
364
|
+
return this.internalState.includeMyReactions;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
public set includeMyReactions(value: boolean) {
|
|
368
|
+
this.internalState.includeMyReactions = value;
|
|
369
|
+
}
|
|
370
|
+
|
|
335
371
|
public async setTraverseLock(lock: boolean): Promise<void> {
|
|
336
372
|
this.internalState.traverseLock = lock;
|
|
337
373
|
|
|
@@ -384,70 +420,60 @@ export class TopicHistoryWindow extends TraversableRemoteCollection<
|
|
|
384
420
|
protected async fetchItemsAfter(): Promise<Message[] | null> {
|
|
385
421
|
const afterId = this.getAt(this.length - 1)?.id;
|
|
386
422
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
return null;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const result = await this.tracker.client.send('GetMessages', {
|
|
393
|
-
location: {roomId: this.roomId, topicId: this.topicId},
|
|
394
|
-
after: afterId,
|
|
395
|
-
limit: this.internalState.fetchLimit,
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
if (result.error) {
|
|
399
|
-
throw new Error(`Cannot fetch messages: ${result.error.message}`);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return result.data.messages;
|
|
423
|
+
// If there is no message to refer, fetch latest
|
|
424
|
+
return afterId ? this.fetchMessages({after: afterId}) : null;
|
|
403
425
|
}
|
|
404
426
|
|
|
405
427
|
protected async fetchItemsAround(id: string): Promise<Message[] | null> {
|
|
406
|
-
|
|
407
|
-
location: {roomId: this.roomId, topicId: this.topicId},
|
|
408
|
-
around: id,
|
|
409
|
-
limit: this.internalState.fetchLimit,
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
if (result.error) {
|
|
413
|
-
throw new Error(`Cannot fetch messages: ${result.error.message}`);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return result.data.messages;
|
|
428
|
+
return this.fetchMessages({around: id});
|
|
417
429
|
}
|
|
418
430
|
|
|
419
431
|
protected async fetchItemsBefore(): Promise<Message[] | null> {
|
|
420
432
|
const beforeId = this.getAt(0)?.id;
|
|
421
433
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
434
|
+
// If there is no message to refer, fetch latest
|
|
435
|
+
return beforeId ? this.fetchMessages({before: beforeId}) : null;
|
|
436
|
+
}
|
|
426
437
|
|
|
438
|
+
protected async fetchLatestItems(): Promise<Message[]> {
|
|
439
|
+
return this.fetchMessages({});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private async fetchMessages(criteria: Partial<GetMessages>): Promise<Message[]> {
|
|
427
443
|
const result = await this.tracker.client.send('GetMessages', {
|
|
428
444
|
location: {roomId: this.roomId, topicId: this.topicId},
|
|
429
|
-
before: beforeId,
|
|
430
445
|
limit: this.internalState.fetchLimit,
|
|
446
|
+
includeMyReactions: this.internalState.includeMyReactions,
|
|
447
|
+
...criteria,
|
|
431
448
|
});
|
|
432
449
|
|
|
433
450
|
if (result.error) {
|
|
434
451
|
throw new Error(`Cannot fetch messages: ${result.error.message}`);
|
|
435
452
|
}
|
|
436
453
|
|
|
454
|
+
this.storeMyReactions(result.data.messages, result.data.myReactions);
|
|
455
|
+
|
|
437
456
|
return result.data.messages;
|
|
438
457
|
}
|
|
439
458
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
throw new Error(`Cannot fetch messages: ${result.error.message}`);
|
|
459
|
+
/**
|
|
460
|
+
* The response is the truth for every message it covers, so a message it does not
|
|
461
|
+
* mention has no reaction of this user left on it.
|
|
462
|
+
*/
|
|
463
|
+
private storeMyReactions(messages: Message[], myReactions?: Record<string, UserReaction[]>): void {
|
|
464
|
+
if (! this.internalState.includeMyReactions) {
|
|
465
|
+
return;
|
|
448
466
|
}
|
|
449
467
|
|
|
450
|
-
|
|
468
|
+
for (const message of messages) {
|
|
469
|
+
const own = myReactions?.[message.id];
|
|
470
|
+
|
|
471
|
+
if (own?.length) {
|
|
472
|
+
this.internalState.myReactions[message.id] = own;
|
|
473
|
+
} else {
|
|
474
|
+
delete this.internalState.myReactions[message.id];
|
|
475
|
+
}
|
|
476
|
+
}
|
|
451
477
|
}
|
|
452
478
|
|
|
453
479
|
private async getTopic(): Promise<Topic | undefined> {
|
|
@@ -475,6 +501,59 @@ export class TopicHistoryWindow extends TraversableRemoteCollection<
|
|
|
475
501
|
}
|
|
476
502
|
}
|
|
477
503
|
|
|
504
|
+
/**
|
|
505
|
+
* The counter arrives as the global source of truth - only it is overwritten, and
|
|
506
|
+
* a reaction nobody holds any more (count 0) leaves the message.
|
|
507
|
+
*/
|
|
508
|
+
private handleReactionUpdated(ev: ReactionUpdated): void {
|
|
509
|
+
const message = this.get(ev.messageId);
|
|
510
|
+
|
|
511
|
+
if (! message) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const index = message.reactions.findIndex(
|
|
516
|
+
reaction => reaction.type === ev.reaction.type && reaction.value === ev.reaction.value);
|
|
517
|
+
|
|
518
|
+
if (index === -1 && ! ev.reaction.count) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const reactions: MessageReaction[] = [...message.reactions];
|
|
523
|
+
|
|
524
|
+
if (index === -1) {
|
|
525
|
+
reactions.push(ev.reaction);
|
|
526
|
+
} else if (ev.reaction.count) {
|
|
527
|
+
reactions[index] = ev.reaction; // In place - a pill must not jump around as its counter moves
|
|
528
|
+
} else {
|
|
529
|
+
reactions.splice(index, 1);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
this.set({...message, reactions});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private handleReacted(ev: Reacted): void {
|
|
536
|
+
if (! this.has(ev.messageId)) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const {type, value, isAdded} = ev.reaction;
|
|
541
|
+
const own = (this.internalState.myReactions[ev.messageId] ?? [])
|
|
542
|
+
.filter(reaction => reaction.type !== type || reaction.value !== value);
|
|
543
|
+
|
|
544
|
+
if (isAdded) {
|
|
545
|
+
own.push({type, value});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (own.length) {
|
|
549
|
+
this.internalState.myReactions[ev.messageId] = own;
|
|
550
|
+
} else {
|
|
551
|
+
delete this.internalState.myReactions[ev.messageId];
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
this.eventTarget.emit('change', {setItems: [ev.messageId]});
|
|
555
|
+
}
|
|
556
|
+
|
|
478
557
|
private async handleMessagesRedacted(ev: MessagesRedacted): Promise<void> {
|
|
479
558
|
if (ev.location.topicId !== this.topicId || ev.location.roomId !== this.roomId) {
|
|
480
559
|
return;
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import {ChatStateTracker} from "./ChatStateTracker";
|
|
2
|
-
import {ObservableIndexedObjectCollection} from "../IndexedObjectCollection";
|
|
3
|
-
import {RoomMember, Session, SpaceMember, User} from "../types/src";
|
|
4
|
-
import {extractUserFromMember} from "./functions";
|
|
5
|
-
import {EventTarget} from "../EventTarget";
|
|
6
|
-
|
|
7
|
-
export class UsersManager {
|
|
8
|
-
public readonly onlineStatus = new EventTarget<{ change: User }>();
|
|
9
|
-
|
|
10
|
-
private readonly users: ObservableIndexedObjectCollection<User> = new ObservableIndexedObjectCollection('id');
|
|
11
|
-
|
|
12
|
-
public constructor(private tracker: ChatStateTracker) {
|
|
13
|
-
// RoomMemberUpdated & SpaceMemberUpdated events are not contains user object
|
|
14
|
-
tracker.client.on('UserUpdated', event => this.handleUsers([event.user]));
|
|
15
|
-
tracker.client.on('RoomMembersJoined', event => this.handleMembers(event.members));
|
|
16
|
-
tracker.client.on('SpaceMemberJoined', event => this.handleMembers([event.member]));
|
|
17
|
-
tracker.client.on('SpaceMembers', event => this.handleMembers(event.members));
|
|
18
|
-
tracker.client.on('RoomMembers', event => this.handleMembers(event.members));
|
|
19
|
-
tracker.client.on('Messages', event => this.handleUsers(event.messages.map(message => message.author.user)));
|
|
20
|
-
tracker.client.on('NewMessage', event => this.handleUsers([event.message.author.user]));
|
|
21
|
-
tracker.client.on('Session', event => this.handleSession(event));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get all available (cached) user objects at once.
|
|
26
|
-
*/
|
|
27
|
-
public async getAvailable(): Promise<ObservableIndexedObjectCollection<User>> {
|
|
28
|
-
return this.users;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
private handleMembers(members: (RoomMember | SpaceMember)[]): void {
|
|
32
|
-
this.handleUsers(members.map(extractUserFromMember));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
private handleSession(session: Session): void {
|
|
36
|
-
// Keep the "seen users" cache across reconnects so bound user lists do
|
|
37
|
-
// not blank out; just ensure our own user is present/updated. Stale
|
|
38
|
-
// entries are refreshed as member/message collections refetch.
|
|
39
|
-
this.handleUsers([session.user]);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
private handleUsers(users: User[]): void {
|
|
43
|
-
users.forEach(newUser => {
|
|
44
|
-
const oldUser = this.users.get(newUser.id);
|
|
45
|
-
if (oldUser && oldUser.status !== newUser.status) {
|
|
46
|
-
this.onlineStatus.emit('change', newUser);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
this.users.set(...users);
|
|
51
|
-
}
|
|
1
|
+
import {ChatStateTracker} from "./ChatStateTracker";
|
|
2
|
+
import {ObservableIndexedObjectCollection} from "../IndexedObjectCollection";
|
|
3
|
+
import {RoomMember, Session, SpaceMember, User} from "../types/src";
|
|
4
|
+
import {extractUserFromMember} from "./functions";
|
|
5
|
+
import {EventTarget} from "../EventTarget";
|
|
6
|
+
|
|
7
|
+
export class UsersManager {
|
|
8
|
+
public readonly onlineStatus = new EventTarget<{ change: User }>();
|
|
9
|
+
|
|
10
|
+
private readonly users: ObservableIndexedObjectCollection<User> = new ObservableIndexedObjectCollection('id');
|
|
11
|
+
|
|
12
|
+
public constructor(private tracker: ChatStateTracker) {
|
|
13
|
+
// RoomMemberUpdated & SpaceMemberUpdated events are not contains user object
|
|
14
|
+
tracker.client.on('UserUpdated', event => this.handleUsers([event.user]));
|
|
15
|
+
tracker.client.on('RoomMembersJoined', event => this.handleMembers(event.members));
|
|
16
|
+
tracker.client.on('SpaceMemberJoined', event => this.handleMembers([event.member]));
|
|
17
|
+
tracker.client.on('SpaceMembers', event => this.handleMembers(event.members));
|
|
18
|
+
tracker.client.on('RoomMembers', event => this.handleMembers(event.members));
|
|
19
|
+
tracker.client.on('Messages', event => this.handleUsers(event.messages.map(message => message.author.user)));
|
|
20
|
+
tracker.client.on('NewMessage', event => this.handleUsers([event.message.author.user]));
|
|
21
|
+
tracker.client.on('Session', event => this.handleSession(event));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get all available (cached) user objects at once.
|
|
26
|
+
*/
|
|
27
|
+
public async getAvailable(): Promise<ObservableIndexedObjectCollection<User>> {
|
|
28
|
+
return this.users;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private handleMembers(members: (RoomMember | SpaceMember)[]): void {
|
|
32
|
+
this.handleUsers(members.map(extractUserFromMember));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private handleSession(session: Session): void {
|
|
36
|
+
// Keep the "seen users" cache across reconnects so bound user lists do
|
|
37
|
+
// not blank out; just ensure our own user is present/updated. Stale
|
|
38
|
+
// entries are refreshed as member/message collections refetch.
|
|
39
|
+
this.handleUsers([session.user]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private handleUsers(users: User[]): void {
|
|
43
|
+
users.forEach(newUser => {
|
|
44
|
+
const oldUser = this.users.get(newUser.id);
|
|
45
|
+
if (oldUser && oldUser.status !== newUser.status) {
|
|
46
|
+
this.onlineStatus.emit('change', newUser);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.users.set(...users);
|
|
51
|
+
}
|
|
52
52
|
}
|