-
Notifications
You must be signed in to change notification settings - Fork 23
/
ExampleChatService.ts
191 lines (173 loc) · 6.69 KB
/
ExampleChatService.ts
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// This IChatService implementation is only an example and has no real business value.
// However, this is good start point to make your own implementation.
// Using this service it's possible to connects two or more chats in the same application for a demonstration purposes
import { IChatService } from "../interfaces/IChatService";
import { ChatEventType, MessageContentType, MessageDirection } from "../enums";
import {
ChatEventHandler,
SendMessageServiceParams,
SendTypingServiceParams,
UpdateState,
} from "../Types";
import { IStorage } from "../interfaces";
import { ChatEvent, MessageEvent, UserTypingEvent } from "../events";
import { ChatMessage } from "../ChatMessage";
type EventHandlers = {
onMessage: ChatEventHandler<
ChatEventType.Message,
ChatEvent<ChatEventType.Message>
>;
onConnectionStateChanged: ChatEventHandler<
ChatEventType.ConnectionStateChanged,
ChatEvent<ChatEventType.ConnectionStateChanged>
>;
onUserConnected: ChatEventHandler<
ChatEventType.UserConnected,
ChatEvent<ChatEventType.UserConnected>
>;
onUserDisconnected: ChatEventHandler<
ChatEventType.UserDisconnected,
ChatEvent<ChatEventType.UserDisconnected>
>;
onUserPresenceChanged: ChatEventHandler<
ChatEventType.UserPresenceChanged,
ChatEvent<ChatEventType.UserPresenceChanged>
>;
onUserTyping: ChatEventHandler<
ChatEventType.UserTyping,
ChatEvent<ChatEventType.UserTyping>
>;
[key: string]: any;
};
export class ExampleChatService implements IChatService {
storage?: IStorage;
updateState: UpdateState;
eventHandlers: EventHandlers = {
onMessage: () => {},
onConnectionStateChanged: () => {},
onUserConnected: () => {},
onUserDisconnected: () => {},
onUserPresenceChanged: () => {},
onUserTyping: () => {},
};
constructor(storage: IStorage, update: UpdateState) {
this.storage = storage;
this.updateState = update;
// For communication we use CustomEvent dispatched to the window object.
// It allows you to simulate sending and receiving data from the server.
// In a real application, instead of adding a listener to the window,
// you will implement here receiving data from your chat server.
window.addEventListener("chat-protocol", (evt: Event) => {
const event = evt as CustomEvent;
const {
detail: { type },
detail,
} = event;
if (type === "message") {
const message = detail.message as ChatMessage<MessageContentType.TextHtml>;
message.direction = MessageDirection.Incoming;
const { conversationId } = detail;
if (this.eventHandlers.onMessage && detail.sender !== this) {
// Running the onMessage callback registered by ChatProvider will cause:
// 1. Add a message to the conversation to which the message was sent
// 2. If a conversation with the given id exists and is not active,
// its unreadCounter will be incremented
// 3. Remove information about the sender who is writing from the conversation
// 4. Re-render
//
// Note!
// If a conversation with such id does not exist,
// the message will be added, but the conversation object will not be created.
// You have to take care of such a case yourself.
// You can check here if there is already a conversation in storage.
// If it is not there, you can create it before calling onMessage.
// After adding a conversation to the list, you don't need to manually run updateState
// because ChatProvider in onMessage will do it.
this.eventHandlers.onMessage(
new MessageEvent({ message, conversationId })
);
}
} else if (type === "typing") {
const { userId, isTyping, conversationId, content, sender } = detail;
if (this.eventHandlers.onUserTyping && sender !== this) {
// Running the onUserTyping callback registered by ChatProvider will cause:
// 1. Add the user to the list of users who are typing in the conversation
// 2. Debounce
// 3. Re-render
this.eventHandlers.onUserTyping(
new UserTypingEvent({
userId,
isTyping,
conversationId,
content,
})
);
}
}
});
}
sendMessage({ message, conversationId }: SendMessageServiceParams) {
// We send messages using a CustomEvent dispatched to the window object.
// They are received in the callback assigned in the constructor.
// In a real application, instead of dispatching the event here,
// you will implement sending messages to your chat server.
const messageEvent = new CustomEvent("chat-protocol", {
detail: {
type: "message",
message,
conversationId,
sender: this,
},
});
window.dispatchEvent(messageEvent);
return message;
}
sendTyping({
isTyping,
content,
conversationId,
userId,
}: SendTypingServiceParams) {
// We send the "typing" signalization using a CustomEvent dispatched to the window object.
// It is received in the callback assigned in the constructor
// In a real application, instead of dispatching the event here,
// you will implement sending signalization to your chat server.
const typingEvent = new CustomEvent("chat-protocol", {
detail: {
type: "typing",
isTyping,
content,
conversationId,
userId,
sender: this,
},
});
window.dispatchEvent(typingEvent);
}
// The ChatProvider registers callbacks with the service.
// These callbacks are necessary to notify the provider of the changes.
// For example, when your service receives a message, you need to run an onMessage callback,
// because the provider must know that the new message arrived.
// Here you need to implement callback registration in your service.
// You can do it in any way you like. It's important that you will have access to it elsewhere in the service.
on<T extends ChatEventType, H extends ChatEvent<T>>(
evtType: T,
evtHandler: ChatEventHandler<T, H>
) {
const key = `on${evtType.charAt(0).toUpperCase()}${evtType.substring(1)}`;
if (key in this.eventHandlers) {
this.eventHandlers[key] = evtHandler;
}
}
// The ChatProvider can unregister the callback.
// In this case remove it from your service to keep it clean.
off<T extends ChatEventType, H extends ChatEvent<T>>(
evtType: T,
eventHandler: ChatEventHandler<T, H>
) {
const key = `on${evtType.charAt(0).toUpperCase()}${evtType.substring(1)}`;
if (key in this.eventHandlers) {
this.eventHandlers[key] = () => {};
}
}
}