@webinex/chatify 2.0.0-alpha13 → 2.0.0-alpha14

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 (143) hide show
  1. package/dist/{Chatify-CwAfN1wu.js → Chatify-CP2jgo8d.js} +191 -118
  2. package/dist/Chatify-CP2jgo8d.js.map +1 -0
  3. package/dist/Chatify.css +28 -0
  4. package/dist/audit.js +3 -2
  5. package/dist/audit.js.map +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/types/audit/index.d.ts +17 -15
  8. package/dist/types/ui/Chat/ChatContext.d.ts +2 -0
  9. package/dist/types/ui/Chat/ChatPanel.d.ts +7 -0
  10. package/dist/types/ui/Chat/ChatView.d.ts +4 -2
  11. package/dist/types/ui/ChatGroup/ChatGroupContext.d.ts +4 -1
  12. package/dist/types/ui/ChatGroup/ChatGroupPanel.d.ts +21 -2
  13. package/dist/types/ui/Conversation/Conversation.d.ts +1 -0
  14. package/dist/types/ui/Conversation/ConversationContext.d.ts +1 -0
  15. package/dist/types/ui/Conversation/ConversationHeader.d.ts +4 -1
  16. package/dist/types/ui/localizer.d.ts +4 -1
  17. package/dist/types/ui/useCompact.d.ts +4 -0
  18. package/package.json +81 -81
  19. package/src/audit/AuditChatList.tsx +25 -25
  20. package/src/audit/AuditConversationPanel.tsx +49 -48
  21. package/src/audit/AuditPanel.tsx +76 -76
  22. package/src/audit/AuditPanelCustomizeValue.ts +11 -11
  23. package/src/audit/AuditSearch.tsx +89 -89
  24. package/src/audit/AuditSearchValue.tsx +42 -42
  25. package/src/audit/auditApi.tsx +113 -113
  26. package/src/audit/index.d.ts +18 -18
  27. package/src/audit/index.tsx +28 -28
  28. package/src/audit/styles.scss +72 -72
  29. package/src/core/api/api.ts +675 -675
  30. package/src/core/api/baseApi.ts +38 -38
  31. package/src/core/api/index.ts +1 -1
  32. package/src/core/client.ts +298 -298
  33. package/src/core/constants.ts +1 -1
  34. package/src/core/debounce.ts +90 -90
  35. package/src/core/index.ts +4 -4
  36. package/src/core/types.tsx +182 -182
  37. package/src/index.ts +2 -2
  38. package/src/styles/aside.scss +34 -26
  39. package/src/styles/chat-list.scss +138 -138
  40. package/src/styles/chat.scss +11 -11
  41. package/src/styles/conversation.scss +199 -200
  42. package/src/styles/index.scss +227 -220
  43. package/src/styles/input.scss +148 -148
  44. package/src/styles/keyframes.scss +14 -14
  45. package/src/styles/members.scss +30 -13
  46. package/src/styles/mixins.scss +38 -25
  47. package/src/styles/variables.scss +13 -13
  48. package/src/ui/Chat/ChatContext.tsx +32 -30
  49. package/src/ui/Chat/ChatEditableHeaderName.tsx +63 -63
  50. package/src/ui/Chat/ChatHeaderActions.tsx +19 -19
  51. package/src/ui/Chat/ChatPanel.tsx +46 -30
  52. package/src/ui/Chat/ChatView.tsx +122 -115
  53. package/src/ui/Chat/Members/ChatMembersButton.tsx +9 -9
  54. package/src/ui/Chat/Members/ChatMembersPanel.tsx +128 -112
  55. package/src/ui/Chat/Members/ChatMembersPreview.tsx +25 -25
  56. package/src/ui/Chat/Members/index.ts +13 -13
  57. package/src/ui/Chat/index.ts +6 -6
  58. package/src/ui/ChatGroup/AutoReply/AutoReplyButton.tsx +21 -21
  59. package/src/ui/ChatGroup/AutoReply/AutoReplyForm.tsx +50 -50
  60. package/src/ui/ChatGroup/AutoReply/AutoReplyPanel.tsx +94 -94
  61. package/src/ui/ChatGroup/AutoReply/AutoReplyPeriodInput.tsx +22 -22
  62. package/src/ui/ChatGroup/AutoReply/AutoReplyTextInput.tsx +22 -22
  63. package/src/ui/ChatGroup/AutoReply/index.ts +19 -19
  64. package/src/ui/ChatGroup/ChatGroupContext.tsx +63 -61
  65. package/src/ui/ChatGroup/ChatGroupPanel.tsx +110 -75
  66. package/src/ui/ChatGroup/Create/CreateChatButton.tsx +15 -15
  67. package/src/ui/ChatGroup/Create/CreateChatForm.tsx +36 -36
  68. package/src/ui/ChatGroup/Create/CreateChatMemberInput.tsx +65 -65
  69. package/src/ui/ChatGroup/Create/CreateChatNameInput.tsx +25 -25
  70. package/src/ui/ChatGroup/Create/CreateChatPanel.tsx +45 -45
  71. package/src/ui/ChatGroup/Create/index.ts +19 -19
  72. package/src/ui/ChatGroup/Layout/ChatGroupActions.tsx +15 -15
  73. package/src/ui/ChatGroup/Layout/ChatGroupAside.tsx +12 -12
  74. package/src/ui/ChatGroup/Layout/ChatGroupBody.tsx +17 -10
  75. package/src/ui/ChatGroup/Layout/ChatGroupFooter.tsx +3 -3
  76. package/src/ui/ChatGroup/Layout/ChatGroupHeader.tsx +5 -5
  77. package/src/ui/ChatGroup/Layout/ChatGroupMain.tsx +21 -19
  78. package/src/ui/ChatGroup/Layout/index.ts +22 -22
  79. package/src/ui/ChatGroup/List/ChatListPanel.tsx +28 -28
  80. package/src/ui/ChatGroup/List/index.ts +8 -8
  81. package/src/ui/ChatGroup/index.ts +6 -6
  82. package/src/ui/Chatify.tsx +15 -15
  83. package/src/ui/Conversation/Conversation.tsx +96 -92
  84. package/src/ui/Conversation/ConversationActions.tsx +12 -12
  85. package/src/ui/Conversation/ConversationBody.tsx +28 -28
  86. package/src/ui/Conversation/ConversationContext.ts +31 -30
  87. package/src/ui/Conversation/ConversationHeader.tsx +35 -19
  88. package/src/ui/Conversation/ConversationName.tsx +7 -7
  89. package/src/ui/Conversation/InputBox/InputBox.tsx +126 -126
  90. package/src/ui/Conversation/InputBox/InputBoxFile.tsx +38 -38
  91. package/src/ui/Conversation/InputBox/InputBoxFileList.tsx +21 -21
  92. package/src/ui/Conversation/InputBox/InputFilesBox.tsx +78 -78
  93. package/src/ui/Conversation/InputBox/InputSubmitButtonBox.tsx +26 -26
  94. package/src/ui/Conversation/InputBox/InputTextBox.tsx +63 -61
  95. package/src/ui/Conversation/InputBox/index.ts +23 -23
  96. package/src/ui/Conversation/Message/MessageAuthor.tsx +13 -13
  97. package/src/ui/Conversation/Message/MessageBox.tsx +20 -20
  98. package/src/ui/Conversation/Message/MessageContent.tsx +15 -15
  99. package/src/ui/Conversation/Message/MessageFile.tsx +54 -54
  100. package/src/ui/Conversation/Message/MessageFileList.tsx +14 -14
  101. package/src/ui/Conversation/Message/MessageInfoBox.tsx +28 -28
  102. package/src/ui/Conversation/Message/MessageRow.tsx +28 -28
  103. package/src/ui/Conversation/Message/MessageText.tsx +7 -7
  104. package/src/ui/Conversation/Message/index.ts +28 -28
  105. package/src/ui/Conversation/NextObserver/NextMessagesObserver.tsx +47 -47
  106. package/src/ui/Conversation/NextObserver/index.ts +1 -1
  107. package/src/ui/Conversation/ReadObserver/MessageReadObserver.tsx +49 -49
  108. package/src/ui/Conversation/ReadObserver/index.ts +1 -1
  109. package/src/ui/Conversation/SendingMessage/SendingMessageBox.tsx +16 -16
  110. package/src/ui/Conversation/SendingMessage/SendingMessageContent.tsx +13 -13
  111. package/src/ui/Conversation/SendingMessage/SendingMessageInfoBox.tsx +13 -13
  112. package/src/ui/Conversation/SendingMessage/SendingMessageText.tsx +7 -7
  113. package/src/ui/Conversation/SendingMessage/index.ts +16 -16
  114. package/src/ui/Conversation/SystemMessage/SystemMessageBox.tsx +20 -20
  115. package/src/ui/Conversation/SystemMessage/SystemMessageRow.tsx +28 -28
  116. package/src/ui/Conversation/SystemMessage/index.ts +10 -10
  117. package/src/ui/Conversation/index.ts +13 -13
  118. package/src/ui/Thread/ThreadActions.tsx +22 -22
  119. package/src/ui/Thread/ThreadContext.ts +13 -13
  120. package/src/ui/Thread/ThreadPanel.tsx +128 -127
  121. package/src/ui/Thread/index.tsx +3 -3
  122. package/src/ui/color.ts +20 -20
  123. package/src/ui/common/Avatar.tsx +20 -20
  124. package/src/ui/common/ChatList/ChatList.tsx +83 -84
  125. package/src/ui/common/ChatList/ChatListItemAvatar.tsx +22 -22
  126. package/src/ui/common/ChatList/ChatListItemBox.tsx +32 -32
  127. package/src/ui/common/ChatList/ChatListItemLastMessage.tsx +15 -15
  128. package/src/ui/common/ChatList/ChatListItemLastMessageAuthor.tsx +15 -15
  129. package/src/ui/common/ChatList/ChatListItemLastMessageContent.tsx +21 -21
  130. package/src/ui/common/ChatList/ChatListItemLastMessageSentAt.tsx +22 -22
  131. package/src/ui/common/ChatList/ChatListItemName.tsx +8 -8
  132. package/src/ui/common/ChatList/ChatListItemUnreadCount.tsx +12 -12
  133. package/src/ui/common/ChatList/ChatListValue.ts +9 -9
  134. package/src/ui/common/ChatList/index.ts +32 -32
  135. package/src/ui/common/Icon.tsx +48 -48
  136. package/src/ui/common/MessageSkeleton.tsx +20 -20
  137. package/src/ui/common/index.ts +14 -14
  138. package/src/ui/customize.tsx +51 -51
  139. package/src/ui/index.ts +11 -11
  140. package/src/ui/localizer.tsx +148 -137
  141. package/src/ui/useCompact.tsx +32 -0
  142. package/src/ui/useFormatter.tsx +17 -17
  143. package/dist/Chatify-CwAfN1wu.js.map +0 -1
@@ -1,38 +1,38 @@
1
- import { BaseQueryFn, EndpointBuilder, createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react';
2
- import { ChatifyClient } from '../client';
3
- import { Debounce } from '../debounce';
4
- import { chatId } from '../types';
5
-
6
- export const DEFAULT_MESSAGE_LIST_TAKE = 20;
7
-
8
- export const CHAT_MESSAGE_READ_QUEUE = new Debounce<string>({
9
- callback: (id) => __settings.client.readChatMessage({ id }),
10
- compare: (a, b) => -a.localeCompare(b),
11
- groupBy: (id) => chatId(id),
12
- timeout: 500,
13
- });
14
-
15
- export interface ChatifyApiSettings {
16
- me: () => string;
17
- client: ChatifyClient;
18
- }
19
-
20
- export const __settings: ChatifyApiSettings = {
21
- me: null!,
22
- client: null!,
23
- };
24
-
25
- const TAG_TYPES = ['chat', 'account', 'message', 'thread', 'thread-message'] as const;
26
-
27
- export interface BaseApiEndpointBuilder
28
- extends EndpointBuilder<BaseQueryFn, (typeof TAG_TYPES)[number], 'chatify'> {}
29
-
30
- const baseApi: ReturnType<typeof createApi<BaseQueryFn, {}, 'chatify', (typeof TAG_TYPES)[number]>> =
31
- createApi({
32
- baseQuery: fakeBaseQuery(),
33
- reducerPath: 'chatify',
34
- tagTypes: TAG_TYPES,
35
- endpoints: (_) => ({}),
36
- });
37
-
38
- export const __baseApi = baseApi;
1
+ import { BaseQueryFn, EndpointBuilder, createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react';
2
+ import { ChatifyClient } from '../client';
3
+ import { Debounce } from '../debounce';
4
+ import { chatId } from '../types';
5
+
6
+ export const DEFAULT_MESSAGE_LIST_TAKE = 20;
7
+
8
+ export const CHAT_MESSAGE_READ_QUEUE = new Debounce<string>({
9
+ callback: (id) => __settings.client.readChatMessage({ id }),
10
+ compare: (a, b) => -a.localeCompare(b),
11
+ groupBy: (id) => chatId(id),
12
+ timeout: 500,
13
+ });
14
+
15
+ export interface ChatifyApiSettings {
16
+ me: () => string;
17
+ client: ChatifyClient;
18
+ }
19
+
20
+ export const __settings: ChatifyApiSettings = {
21
+ me: null!,
22
+ client: null!,
23
+ };
24
+
25
+ const TAG_TYPES = ['chat', 'account', 'message', 'thread', 'thread-message'] as const;
26
+
27
+ export interface BaseApiEndpointBuilder
28
+ extends EndpointBuilder<BaseQueryFn, (typeof TAG_TYPES)[number], 'chatify'> {}
29
+
30
+ const baseApi: ReturnType<typeof createApi<BaseQueryFn, {}, 'chatify', (typeof TAG_TYPES)[number]>> =
31
+ createApi({
32
+ baseQuery: fakeBaseQuery(),
33
+ reducerPath: 'chatify',
34
+ tagTypes: TAG_TYPES,
35
+ endpoints: (_) => ({}),
36
+ });
37
+
38
+ export const __baseApi = baseApi;
@@ -1 +1 @@
1
- export * from './api';
1
+ export * from './api';
@@ -1,298 +1,298 @@
1
- import { AxiosInstance } from 'axios';
2
- import * as SignalR from '@microsoft/signalr';
3
- import {
4
- Account,
5
- AddChatRequest,
6
- AddChatMemberRequest,
7
- Chat,
8
- ChatListItem,
9
- ChatMessage,
10
- ReadChatMessageEvent,
11
- ReadChatMessageRequest,
12
- RemoveChatMemberRequest,
13
- SendChatMessageRequest,
14
- UpdateChatNameRequest,
15
- SendThreadMessageRequest,
16
- Thread,
17
- ThreadMessage,
18
- ThreadWatchListItem as ThreadListItem,
19
- UpdateAccountRequest,
20
- } from './types';
21
-
22
- export interface ChatifySignalRConfig {
23
- hubUri?: string;
24
- accessTokenFactory?: () => Promise<string>;
25
- headersFactory?: () => Promise<Record<string, string>>;
26
- }
27
-
28
- const DEFAULT_SIGNALR_CONFIG: ChatifySignalRConfig = {
29
- hubUri: '/api/chatify/hub',
30
- headersFactory: () => Promise.resolve({}),
31
- };
32
-
33
- export interface ChatifyClientConfig {
34
- axios: AxiosInstance;
35
- signalR: ChatifySignalRConfig;
36
- }
37
-
38
- export const CHATIFY_SIGNALR_METHODS = [
39
- 'chatify://chat-new-message',
40
- 'chatify://chat-created',
41
- 'chatify://chat-message-read',
42
- 'chatify://chat-member-added',
43
- 'chatify://chat-member-removed',
44
- 'chatify://chat-name-changed',
45
-
46
- 'chatify://thread-created',
47
- 'chatify://thread-message-new',
48
- 'chatify://thread-message-read',
49
- 'chatify://thread-watch-added',
50
- 'chatify://thread-watch-removed',
51
- 'chatify://thread-updated',
52
- ] as const;
53
-
54
- export type ChatifySignalRMethod = (typeof CHATIFY_SIGNALR_METHODS)[number];
55
-
56
- export type ChatifySignalRArgsByMethod = {
57
- 'chatify://chat-new-message': [message: ChatMessage];
58
- 'chatify://chat-created': [chat: ChatListItem];
59
- 'chatify://chat-message-read': [events: ReadChatMessageEvent[]];
60
- 'chatify://chat-member-added': [
61
- chatId: string,
62
- chatName: string,
63
- account: Account,
64
- message: ChatMessage,
65
- withHistory: boolean,
66
- read: boolean,
67
- ];
68
- 'chatify://chat-member-removed': [
69
- chatId: string,
70
- accountId: string,
71
- deleteHistory: boolean,
72
- message: ChatMessage,
73
- read: boolean,
74
- ];
75
- 'chatify://chat-name-changed': [chatId: string, newName: string, message: ChatMessage, read: boolean];
76
-
77
- 'chatify://thread-message-new': [threadId: string, message: ThreadMessage, readForId: string | null];
78
- 'chatify://thread-message-read': [threadId: string, messageId: string];
79
- 'chatify://thread-watch-added': [thread: ThreadListItem];
80
- 'chatify://thread-watch-removed': [id: string];
81
- 'chatify://thread-created': [thread: ThreadListItem, watch: boolean];
82
- 'chatify://thread-updated': [threadId: string, threadName: string];
83
- };
84
-
85
- export class ChatifyClient {
86
- private _config: ChatifyClientConfig;
87
- private _connection: SignalR.HubConnection = null!;
88
- private _connectionPromise: Promise<SignalR.HubConnection> | null = null!;
89
- private _subscribers: Record<string, ((args: any[]) => void)[]> = {};
90
- private _reconnectSubscribers: Array<() => void> = [];
91
- private _threadConnectionByThreadId = new Map<string, number>();
92
-
93
- constructor(config: ChatifyClientConfig) {
94
- this._config = {
95
- ...config,
96
- signalR: {
97
- ...DEFAULT_SIGNALR_CONFIG,
98
- ...config.signalR,
99
- },
100
- };
101
- }
102
-
103
- public connect = async () => {
104
- if (this._connection) {
105
- return;
106
- }
107
-
108
- if (this._connectionPromise) {
109
- return await this._connectionPromise;
110
- }
111
-
112
- this._connectionPromise = this.createAndStartConnection();
113
- this._connection = await this._connectionPromise;
114
- };
115
-
116
- private createAndStartConnection = async () => {
117
- const headers = await this._config.signalR!.headersFactory!();
118
-
119
- const connection = new SignalR.HubConnectionBuilder()
120
- .withUrl(this._config.signalR!.hubUri!, {
121
- accessTokenFactory: this._config.signalR!.accessTokenFactory
122
- ? this._config.signalR!.accessTokenFactory
123
- : undefined,
124
- headers,
125
- })
126
- .withAutomaticReconnect()
127
- .build();
128
-
129
- CHATIFY_SIGNALR_METHODS.forEach((x) => {
130
- connection.on(x, (...args) => {
131
- this._subscribers[x]?.forEach((s) => {
132
- try {
133
- s(args);
134
- } catch (ex) {
135
- console.error(`Failed to notify subscriber`, ex);
136
- }
137
- });
138
- });
139
- });
140
-
141
- connection.onreconnected(() => this._reconnectSubscribers.forEach((subscriber) => subscriber()));
142
-
143
- await connection.start();
144
- return connection;
145
- };
146
-
147
- public subscribe = <TMethod extends ChatifySignalRMethod>(
148
- method: TMethod,
149
- subscriber: (args: ChatifySignalRArgsByMethod[TMethod]) => void,
150
- ) => {
151
- this.connect().then(() => {
152
- this._subscribers[method] = this._subscribers[method] ?? [];
153
- this._subscribers[method].push(subscriber as any);
154
- });
155
- return () => this._subscribers[method].filter((x) => x !== subscriber);
156
- };
157
-
158
- public subscribeReconnect = (subscriber: () => any) => {
159
- this._reconnectSubscribers.push(subscriber);
160
- return () => this._reconnectSubscribers.filter((x) => x !== subscriber);
161
- };
162
-
163
- public get axios() {
164
- return this._config.axios;
165
- }
166
-
167
- public accounts = async (args: { ids?: string[] }) => {
168
- const searchParams = new URLSearchParams();
169
-
170
- args?.ids?.forEach((id) => {
171
- searchParams.append('ids', encodeURIComponent(id));
172
- });
173
-
174
- const { data } = await this.axios.get<Account[]>(`account?${searchParams.toString()}`);
175
- return data;
176
- };
177
-
178
- public currentUserAccount = async () => {
179
- const { data } = await this.axios.get<Account>('account/me');
180
- return data;
181
- };
182
-
183
- public updateAccount = async (request: UpdateAccountRequest) => {
184
- await this.axios.put(`account/${encodeURIComponent(request.id)}`, request);
185
- };
186
-
187
- public chats = async () => {
188
- const { data } = await this.axios.get<ChatListItem[]>('chat');
189
- return data;
190
- };
191
-
192
- public chat = async (id: string) => {
193
- const { data } = await this.axios.get<Chat>(`chat/${encodeURIComponent(id)}`);
194
- return data;
195
- };
196
-
197
- public addChat = async (args: AddChatRequest) => {
198
- const { data } = await this.axios.post<string>(`chat`, args);
199
- return data;
200
- };
201
-
202
- public chatMessages = async (args: { chatId: string; skip?: number; take?: number }) => {
203
- const { chatId, skip = 0, take = 20 } = args;
204
- const pagingRule = encodeURIComponent(JSON.stringify({ skip, take }));
205
- const { data } = await this.axios.get<ChatMessage[]>(
206
- `chat/${encodeURIComponent(chatId)}/message?pagingRule=${pagingRule}`,
207
- );
208
- return data;
209
- };
210
-
211
- public sendChatMessage = async (request: SendChatMessageRequest) => {
212
- const { chatId, ...rest } = request;
213
- await this.axios.post(`chat/${encodeURIComponent(chatId)}/message`, rest);
214
- };
215
-
216
- public readChatMessage = async (request: ReadChatMessageRequest) => {
217
- await this.axios.put(`chat/message/read`, request);
218
- };
219
-
220
- public removeChatMember = async (request: RemoveChatMemberRequest) => {
221
- await this.axios.delete(`chat/${encodeURIComponent(request.chatId)}/member`, { data: request });
222
- };
223
-
224
- public updateChatName = async (request: UpdateChatNameRequest) => {
225
- await this.axios.put(`chat/${encodeURIComponent(request.id)}/name`, request);
226
- };
227
-
228
- public addChatMember = async (request: AddChatMemberRequest) => {
229
- await this.axios.post(`chat/${encodeURIComponent(request.chatId)}/member`, request);
230
- };
231
-
232
- public sendThreadMessage = async ({ body, threadId }: SendThreadMessageRequest) => {
233
- const { data: id } = await this.axios.post<string>(`thread/${encodeURIComponent(threadId)}/message`, {
234
- body,
235
- });
236
- return id;
237
- };
238
-
239
- public readThreadMessage = async (id: string) => {
240
- await this.axios.put(`thread/message/${encodeURIComponent(id)}/read`);
241
- };
242
-
243
- public watchThreads = async () => {
244
- const { data } = await this.axios.get<ThreadListItem[]>(`thread/watch`);
245
- return data;
246
- };
247
-
248
- public thread = async (id: string) => {
249
- const { data } = await this.axios.get<Thread>(`thread/${encodeURIComponent(id)}`);
250
- return data;
251
- };
252
-
253
- public threadMessages = async ({
254
- threadId,
255
- skip,
256
- take,
257
- }: {
258
- threadId: string;
259
- skip: number;
260
- take: number;
261
- }) => {
262
- const searchParams = new URLSearchParams();
263
- searchParams.append('skip', skip.toString());
264
- searchParams.append('take', take.toString());
265
- const { data } = await this.axios.get<ThreadMessage[]>(
266
- `thread/${encodeURIComponent(threadId)}/message?${searchParams.toString()}`,
267
- );
268
- return data;
269
- };
270
-
271
- public watchThread = async (id: string, watch: boolean) => {
272
- await this.axios.put(`thread/${encodeURIComponent(id)}/watch`, { watch });
273
- };
274
-
275
- public connectToThread = async (id: string) => {
276
- const connectionCount = this._threadConnectionByThreadId.get(id) ?? 0;
277
- this._threadConnectionByThreadId.set(id, connectionCount + 1);
278
-
279
- if (connectionCount === 0) {
280
- this.connect().then(() => this._connection.invoke('ConnectToThread', id));
281
- }
282
- };
283
-
284
- public disconnectFromThread = async (id: string) => {
285
- const connectionCount = this._threadConnectionByThreadId.get(id) ?? 0;
286
- this._threadConnectionByThreadId.set(id, Math.max(0, connectionCount - 1));
287
-
288
- if (connectionCount === 1) {
289
- this.connect().then(() => this._connection.invoke('DisconnectFromThread', id));
290
- }
291
- };
292
- }
293
-
294
- export function unsubscribe(...subscribers: Array<() => void>) {
295
- const result = () => subscribers.forEach((x) => x());
296
- result.when = (promise: Promise<any>) => promise.then(result);
297
- return result;
298
- }
1
+ import { AxiosInstance } from 'axios';
2
+ import * as SignalR from '@microsoft/signalr';
3
+ import {
4
+ Account,
5
+ AddChatRequest,
6
+ AddChatMemberRequest,
7
+ Chat,
8
+ ChatListItem,
9
+ ChatMessage,
10
+ ReadChatMessageEvent,
11
+ ReadChatMessageRequest,
12
+ RemoveChatMemberRequest,
13
+ SendChatMessageRequest,
14
+ UpdateChatNameRequest,
15
+ SendThreadMessageRequest,
16
+ Thread,
17
+ ThreadMessage,
18
+ ThreadWatchListItem as ThreadListItem,
19
+ UpdateAccountRequest,
20
+ } from './types';
21
+
22
+ export interface ChatifySignalRConfig {
23
+ hubUri?: string;
24
+ accessTokenFactory?: () => Promise<string>;
25
+ headersFactory?: () => Promise<Record<string, string>>;
26
+ }
27
+
28
+ const DEFAULT_SIGNALR_CONFIG: ChatifySignalRConfig = {
29
+ hubUri: '/api/chatify/hub',
30
+ headersFactory: () => Promise.resolve({}),
31
+ };
32
+
33
+ export interface ChatifyClientConfig {
34
+ axios: AxiosInstance;
35
+ signalR: ChatifySignalRConfig;
36
+ }
37
+
38
+ export const CHATIFY_SIGNALR_METHODS = [
39
+ 'chatify://chat-new-message',
40
+ 'chatify://chat-created',
41
+ 'chatify://chat-message-read',
42
+ 'chatify://chat-member-added',
43
+ 'chatify://chat-member-removed',
44
+ 'chatify://chat-name-changed',
45
+
46
+ 'chatify://thread-created',
47
+ 'chatify://thread-message-new',
48
+ 'chatify://thread-message-read',
49
+ 'chatify://thread-watch-added',
50
+ 'chatify://thread-watch-removed',
51
+ 'chatify://thread-updated',
52
+ ] as const;
53
+
54
+ export type ChatifySignalRMethod = (typeof CHATIFY_SIGNALR_METHODS)[number];
55
+
56
+ export type ChatifySignalRArgsByMethod = {
57
+ 'chatify://chat-new-message': [message: ChatMessage];
58
+ 'chatify://chat-created': [chat: ChatListItem];
59
+ 'chatify://chat-message-read': [events: ReadChatMessageEvent[]];
60
+ 'chatify://chat-member-added': [
61
+ chatId: string,
62
+ chatName: string,
63
+ account: Account,
64
+ message: ChatMessage,
65
+ withHistory: boolean,
66
+ read: boolean,
67
+ ];
68
+ 'chatify://chat-member-removed': [
69
+ chatId: string,
70
+ accountId: string,
71
+ deleteHistory: boolean,
72
+ message: ChatMessage,
73
+ read: boolean,
74
+ ];
75
+ 'chatify://chat-name-changed': [chatId: string, newName: string, message: ChatMessage, read: boolean];
76
+
77
+ 'chatify://thread-message-new': [threadId: string, message: ThreadMessage, readForId: string | null];
78
+ 'chatify://thread-message-read': [threadId: string, messageId: string];
79
+ 'chatify://thread-watch-added': [thread: ThreadListItem];
80
+ 'chatify://thread-watch-removed': [id: string];
81
+ 'chatify://thread-created': [thread: ThreadListItem, watch: boolean];
82
+ 'chatify://thread-updated': [threadId: string, threadName: string];
83
+ };
84
+
85
+ export class ChatifyClient {
86
+ private _config: ChatifyClientConfig;
87
+ private _connection: SignalR.HubConnection = null!;
88
+ private _connectionPromise: Promise<SignalR.HubConnection> | null = null!;
89
+ private _subscribers: Record<string, ((args: any[]) => void)[]> = {};
90
+ private _reconnectSubscribers: Array<() => void> = [];
91
+ private _threadConnectionByThreadId = new Map<string, number>();
92
+
93
+ constructor(config: ChatifyClientConfig) {
94
+ this._config = {
95
+ ...config,
96
+ signalR: {
97
+ ...DEFAULT_SIGNALR_CONFIG,
98
+ ...config.signalR,
99
+ },
100
+ };
101
+ }
102
+
103
+ public connect = async () => {
104
+ if (this._connection) {
105
+ return;
106
+ }
107
+
108
+ if (this._connectionPromise) {
109
+ return await this._connectionPromise;
110
+ }
111
+
112
+ this._connectionPromise = this.createAndStartConnection();
113
+ this._connection = await this._connectionPromise;
114
+ };
115
+
116
+ private createAndStartConnection = async () => {
117
+ const headers = await this._config.signalR!.headersFactory!();
118
+
119
+ const connection = new SignalR.HubConnectionBuilder()
120
+ .withUrl(this._config.signalR!.hubUri!, {
121
+ accessTokenFactory: this._config.signalR!.accessTokenFactory
122
+ ? this._config.signalR!.accessTokenFactory
123
+ : undefined,
124
+ headers,
125
+ })
126
+ .withAutomaticReconnect()
127
+ .build();
128
+
129
+ CHATIFY_SIGNALR_METHODS.forEach((x) => {
130
+ connection.on(x, (...args) => {
131
+ this._subscribers[x]?.forEach((s) => {
132
+ try {
133
+ s(args);
134
+ } catch (ex) {
135
+ console.error(`Failed to notify subscriber`, ex);
136
+ }
137
+ });
138
+ });
139
+ });
140
+
141
+ connection.onreconnected(() => this._reconnectSubscribers.forEach((subscriber) => subscriber()));
142
+
143
+ await connection.start();
144
+ return connection;
145
+ };
146
+
147
+ public subscribe = <TMethod extends ChatifySignalRMethod>(
148
+ method: TMethod,
149
+ subscriber: (args: ChatifySignalRArgsByMethod[TMethod]) => void,
150
+ ) => {
151
+ this.connect().then(() => {
152
+ this._subscribers[method] = this._subscribers[method] ?? [];
153
+ this._subscribers[method].push(subscriber as any);
154
+ });
155
+ return () => this._subscribers[method].filter((x) => x !== subscriber);
156
+ };
157
+
158
+ public subscribeReconnect = (subscriber: () => any) => {
159
+ this._reconnectSubscribers.push(subscriber);
160
+ return () => this._reconnectSubscribers.filter((x) => x !== subscriber);
161
+ };
162
+
163
+ public get axios() {
164
+ return this._config.axios;
165
+ }
166
+
167
+ public accounts = async (args: { ids?: string[] }) => {
168
+ const searchParams = new URLSearchParams();
169
+
170
+ args?.ids?.forEach((id) => {
171
+ searchParams.append('ids', encodeURIComponent(id));
172
+ });
173
+
174
+ const { data } = await this.axios.get<Account[]>(`account?${searchParams.toString()}`);
175
+ return data;
176
+ };
177
+
178
+ public currentUserAccount = async () => {
179
+ const { data } = await this.axios.get<Account>('account/me');
180
+ return data;
181
+ };
182
+
183
+ public updateAccount = async (request: UpdateAccountRequest) => {
184
+ await this.axios.put(`account/${encodeURIComponent(request.id)}`, request);
185
+ };
186
+
187
+ public chats = async () => {
188
+ const { data } = await this.axios.get<ChatListItem[]>('chat');
189
+ return data;
190
+ };
191
+
192
+ public chat = async (id: string) => {
193
+ const { data } = await this.axios.get<Chat>(`chat/${encodeURIComponent(id)}`);
194
+ return data;
195
+ };
196
+
197
+ public addChat = async (args: AddChatRequest) => {
198
+ const { data } = await this.axios.post<string>(`chat`, args);
199
+ return data;
200
+ };
201
+
202
+ public chatMessages = async (args: { chatId: string; skip?: number; take?: number }) => {
203
+ const { chatId, skip = 0, take = 20 } = args;
204
+ const pagingRule = encodeURIComponent(JSON.stringify({ skip, take }));
205
+ const { data } = await this.axios.get<ChatMessage[]>(
206
+ `chat/${encodeURIComponent(chatId)}/message?pagingRule=${pagingRule}`,
207
+ );
208
+ return data;
209
+ };
210
+
211
+ public sendChatMessage = async (request: SendChatMessageRequest) => {
212
+ const { chatId, ...rest } = request;
213
+ await this.axios.post(`chat/${encodeURIComponent(chatId)}/message`, rest);
214
+ };
215
+
216
+ public readChatMessage = async (request: ReadChatMessageRequest) => {
217
+ await this.axios.put(`chat/message/read`, request);
218
+ };
219
+
220
+ public removeChatMember = async (request: RemoveChatMemberRequest) => {
221
+ await this.axios.delete(`chat/${encodeURIComponent(request.chatId)}/member`, { data: request });
222
+ };
223
+
224
+ public updateChatName = async (request: UpdateChatNameRequest) => {
225
+ await this.axios.put(`chat/${encodeURIComponent(request.id)}/name`, request);
226
+ };
227
+
228
+ public addChatMember = async (request: AddChatMemberRequest) => {
229
+ await this.axios.post(`chat/${encodeURIComponent(request.chatId)}/member`, request);
230
+ };
231
+
232
+ public sendThreadMessage = async ({ body, threadId }: SendThreadMessageRequest) => {
233
+ const { data: id } = await this.axios.post<string>(`thread/${encodeURIComponent(threadId)}/message`, {
234
+ body,
235
+ });
236
+ return id;
237
+ };
238
+
239
+ public readThreadMessage = async (id: string) => {
240
+ await this.axios.put(`thread/message/${encodeURIComponent(id)}/read`);
241
+ };
242
+
243
+ public watchThreads = async () => {
244
+ const { data } = await this.axios.get<ThreadListItem[]>(`thread/watch`);
245
+ return data;
246
+ };
247
+
248
+ public thread = async (id: string) => {
249
+ const { data } = await this.axios.get<Thread>(`thread/${encodeURIComponent(id)}`);
250
+ return data;
251
+ };
252
+
253
+ public threadMessages = async ({
254
+ threadId,
255
+ skip,
256
+ take,
257
+ }: {
258
+ threadId: string;
259
+ skip: number;
260
+ take: number;
261
+ }) => {
262
+ const searchParams = new URLSearchParams();
263
+ searchParams.append('skip', skip.toString());
264
+ searchParams.append('take', take.toString());
265
+ const { data } = await this.axios.get<ThreadMessage[]>(
266
+ `thread/${encodeURIComponent(threadId)}/message?${searchParams.toString()}`,
267
+ );
268
+ return data;
269
+ };
270
+
271
+ public watchThread = async (id: string, watch: boolean) => {
272
+ await this.axios.put(`thread/${encodeURIComponent(id)}/watch`, { watch });
273
+ };
274
+
275
+ public connectToThread = async (id: string) => {
276
+ const connectionCount = this._threadConnectionByThreadId.get(id) ?? 0;
277
+ this._threadConnectionByThreadId.set(id, connectionCount + 1);
278
+
279
+ if (connectionCount === 0) {
280
+ this.connect().then(() => this._connection.invoke('ConnectToThread', id));
281
+ }
282
+ };
283
+
284
+ public disconnectFromThread = async (id: string) => {
285
+ const connectionCount = this._threadConnectionByThreadId.get(id) ?? 0;
286
+ this._threadConnectionByThreadId.set(id, Math.max(0, connectionCount - 1));
287
+
288
+ if (connectionCount === 1) {
289
+ this.connect().then(() => this._connection.invoke('DisconnectFromThread', id));
290
+ }
291
+ };
292
+ }
293
+
294
+ export function unsubscribe(...subscribers: Array<() => void>) {
295
+ const result = () => subscribers.forEach((x) => x());
296
+ result.when = (promise: Promise<any>) => promise.then(result);
297
+ return result;
298
+ }