@builder6/rooms 0.10.8 → 0.11.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/dist/emails/UnreadMention.d.ts +9 -0
- package/dist/emails/UnreadMention.js +54 -0
- package/dist/emails/UnreadMention.js.map +1 -0
- package/dist/emails/UnreadReplies.d.ts +9 -0
- package/dist/emails/UnreadReplies.js +68 -0
- package/dist/emails/UnreadReplies.js.map +1 -0
- package/dist/emails/_components/comment.d.ts +1 -0
- package/dist/emails/_components/comment.js +32 -0
- package/dist/emails/_components/comment.js.map +1 -0
- package/dist/emails/_components/header.d.ts +2 -0
- package/dist/emails/_components/header.js +15 -0
- package/dist/emails/_components/header.js.map +1 -0
- package/dist/emails/_components/headline.d.ts +5 -0
- package/dist/emails/_components/headline.js +17 -0
- package/dist/emails/_components/headline.js.map +1 -0
- package/dist/emails/_components/layout.d.ts +5 -0
- package/dist/emails/_components/layout.js +24 -0
- package/dist/emails/_components/layout.js.map +1 -0
- package/dist/emails/_lib/types.d.ts +9 -0
- package/dist/emails/_lib/types.js +3 -0
- package/dist/emails/_lib/types.js.map +1 -0
- package/dist/emails/_styles/colors.d.ts +2 -0
- package/dist/emails/_styles/colors.js +7 -0
- package/dist/emails/_styles/colors.js.map +1 -0
- package/dist/emails/_utils/cn.d.ts +2 -0
- package/dist/emails/_utils/cn.js +10 -0
- package/dist/emails/_utils/cn.js.map +1 -0
- package/dist/emails/_utils/comments.d.ts +7 -0
- package/dist/emails/_utils/comments.js +29 -0
- package/dist/emails/_utils/comments.js.map +1 -0
- package/dist/emails/_utils/getProps.d.ts +1 -0
- package/dist/emails/_utils/getProps.js +11 -0
- package/dist/emails/_utils/getProps.js.map +1 -0
- package/dist/rooms/app.controller.d.ts +3 -1
- package/dist/rooms/app.controller.js +61 -46
- package/dist/rooms/app.controller.js.map +1 -1
- package/dist/rooms/emailNotification.service.d.ts +17 -0
- package/dist/rooms/emailNotification.service.js +113 -0
- package/dist/rooms/emailNotification.service.js.map +1 -0
- package/dist/rooms/emails/comment-body.d.ts +44 -0
- package/dist/rooms/emails/comment-body.js +138 -0
- package/dist/rooms/emails/comment-body.js.map +1 -0
- package/dist/rooms/emails/comment-with-body.d.ts +6 -0
- package/dist/rooms/emails/comment-with-body.js +17 -0
- package/dist/rooms/emails/comment-with-body.js.map +1 -0
- package/dist/rooms/emails/index.d.ts +4 -0
- package/dist/rooms/emails/index.js +7 -0
- package/dist/rooms/emails/index.js.map +1 -0
- package/dist/rooms/emails/lib/batch-users-resolver.d.ts +9 -0
- package/dist/rooms/emails/lib/batch-users-resolver.js +67 -0
- package/dist/rooms/emails/lib/batch-users-resolver.js.map +1 -0
- package/dist/rooms/emails/lib/css-properties.d.ts +3 -0
- package/dist/rooms/emails/lib/css-properties.js +96 -0
- package/dist/rooms/emails/lib/css-properties.js.map +1 -0
- package/dist/rooms/emails/lib/warning.d.ts +1 -0
- package/dist/rooms/emails/lib/warning.js +20 -0
- package/dist/rooms/emails/lib/warning.js.map +1 -0
- package/dist/rooms/emails/thread-notification.d.ts +90 -0
- package/dist/rooms/emails/thread-notification.js +297 -0
- package/dist/rooms/emails/thread-notification.js.map +1 -0
- package/dist/rooms/globals/augmentation.d.ts +0 -5
- package/dist/rooms/lib/utils.js +21 -10
- package/dist/rooms/lib/utils.js.map +1 -1
- package/dist/rooms/liveblocks.service.d.ts +16 -0
- package/dist/rooms/liveblocks.service.js +54 -0
- package/dist/rooms/liveblocks.service.js.map +1 -0
- package/dist/rooms/notifications.service.d.ts +4 -0
- package/dist/rooms/notifications.service.js +107 -80
- package/dist/rooms/notifications.service.js.map +1 -1
- package/dist/rooms/rooms.controller.d.ts +4 -1
- package/dist/rooms/rooms.controller.js +321 -243
- package/dist/rooms/rooms.controller.js.map +1 -1
- package/dist/rooms/rooms.gateway.js +132 -109
- package/dist/rooms/rooms.gateway.js.map +1 -1
- package/dist/rooms/rooms.guard.js +31 -19
- package/dist/rooms/rooms.guard.js.map +1 -1
- package/dist/rooms/rooms.module.js +10 -1
- package/dist/rooms/rooms.module.js.map +1 -1
- package/dist/rooms/rooms.moleculer.js +85 -66
- package/dist/rooms/rooms.moleculer.js.map +1 -1
- package/dist/rooms/rooms.service.d.ts +17 -4
- package/dist/rooms/rooms.service.js +408 -340
- package/dist/rooms/rooms.service.js.map +1 -1
- package/package.json +18 -7
- package/src/emails/UnreadMention.tsx +76 -0
- package/src/emails/UnreadReplies.tsx +106 -0
- package/src/emails/_components/comment.tsx +70 -0
- package/src/emails/_components/header.tsx +27 -0
- package/src/emails/_components/headline.tsx +20 -0
- package/src/emails/_components/layout.tsx +45 -0
- package/src/emails/_lib/types.ts +10 -0
- package/src/emails/_styles/colors.ts +7 -0
- package/src/emails/_utils/cn.ts +6 -0
- package/src/emails/_utils/comments.ts +61 -0
- package/src/emails/_utils/getProps.ts +7 -0
- package/src/rooms/app.controller.ts +7 -2
- package/src/rooms/emailNotification.service.tsx +152 -0
- package/src/rooms/emails/comment-body.tsx +342 -0
- package/src/rooms/emails/comment-with-body.ts +24 -0
- package/src/rooms/emails/index.ts +25 -0
- package/src/rooms/emails/lib/batch-users-resolver.ts +120 -0
- package/src/rooms/emails/lib/css-properties.ts +123 -0
- package/src/rooms/emails/lib/warning.ts +25 -0
- package/src/rooms/emails/thread-notification.tsx +583 -0
- package/src/rooms/globals/augmentation.ts +8 -8
- package/src/rooms/liveblocks.service.ts +25 -0
- package/src/rooms/notifications.service.ts +22 -10
- package/src/rooms/rooms.controller.ts +22 -4
- package/src/rooms/rooms.module.ts +10 -1
- package/src/rooms/rooms.service.ts +35 -20
- package/tsconfig.json +2 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BaseRoomInfo,
|
|
3
|
+
BaseUserMeta,
|
|
4
|
+
CommentBody,
|
|
5
|
+
CommentData,
|
|
6
|
+
DRI,
|
|
7
|
+
DU,
|
|
8
|
+
InboxNotificationData,
|
|
9
|
+
OptionalPromise,
|
|
10
|
+
ResolveUsersArgs,
|
|
11
|
+
} from "@liveblocks/core";
|
|
12
|
+
import {
|
|
13
|
+
generateCommentUrl,
|
|
14
|
+
getMentionedIdsFromCommentBody,
|
|
15
|
+
} from "@liveblocks/core";
|
|
16
|
+
import type { Liveblocks, ThreadNotificationEvent } from "@liveblocks/node";
|
|
17
|
+
import type React from "react";
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
ConvertCommentBodyAsHtmlStyles,
|
|
21
|
+
ConvertCommentBodyAsReactComponents,
|
|
22
|
+
} from "./comment-body";
|
|
23
|
+
import {
|
|
24
|
+
convertCommentBodyAsHtml,
|
|
25
|
+
convertCommentBodyAsReact,
|
|
26
|
+
} from "./comment-body";
|
|
27
|
+
import type { CommentDataWithBody } from "./comment-with-body";
|
|
28
|
+
import { filterCommentsWithBody } from "./comment-with-body";
|
|
29
|
+
import { createBatchUsersResolver } from "./lib/batch-users-resolver";
|
|
30
|
+
|
|
31
|
+
/** @internal */
|
|
32
|
+
export const getUnreadComments = ({
|
|
33
|
+
comments,
|
|
34
|
+
inboxNotification,
|
|
35
|
+
userId,
|
|
36
|
+
}: {
|
|
37
|
+
comments: CommentData[];
|
|
38
|
+
inboxNotification: InboxNotificationData;
|
|
39
|
+
userId: string;
|
|
40
|
+
}): CommentDataWithBody[] => {
|
|
41
|
+
const commentsWithBody = filterCommentsWithBody(comments);
|
|
42
|
+
const readAt = inboxNotification.readAt;
|
|
43
|
+
|
|
44
|
+
return commentsWithBody
|
|
45
|
+
// .filter((c) => c.userId !== userId)
|
|
46
|
+
// .filter((c) =>
|
|
47
|
+
// readAt
|
|
48
|
+
// ? c.createdAt > readAt && c.createdAt <= inboxNotification.notifiedAt
|
|
49
|
+
// : c.createdAt <= inboxNotification.notifiedAt
|
|
50
|
+
// );
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** @internal */
|
|
54
|
+
export const getLastUnreadCommentWithMention = ({
|
|
55
|
+
comments,
|
|
56
|
+
mentionedUserId,
|
|
57
|
+
}: {
|
|
58
|
+
comments: CommentDataWithBody[];
|
|
59
|
+
mentionedUserId: string;
|
|
60
|
+
}): CommentDataWithBody | null => {
|
|
61
|
+
return (
|
|
62
|
+
Array.from(comments)
|
|
63
|
+
.reverse()
|
|
64
|
+
.filter((c) => c.userId !== mentionedUserId)
|
|
65
|
+
.find((c) => {
|
|
66
|
+
const mentionedUserIds = getMentionedIdsFromCommentBody(c.body);
|
|
67
|
+
return mentionedUserIds.includes(mentionedUserId);
|
|
68
|
+
}) ?? null
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type ThreadNotificationData =
|
|
73
|
+
| { type: "unreadMention"; comment: CommentDataWithBody }
|
|
74
|
+
| { type: "unreadReplies"; comments: CommentDataWithBody[] };
|
|
75
|
+
|
|
76
|
+
/** @internal */
|
|
77
|
+
export const extractThreadNotificationData = async ({
|
|
78
|
+
client,
|
|
79
|
+
event,
|
|
80
|
+
}: {
|
|
81
|
+
client: Liveblocks;
|
|
82
|
+
event: ThreadNotificationEvent;
|
|
83
|
+
}): Promise<ThreadNotificationData | null> => {
|
|
84
|
+
const { threadId, roomId, userId, inboxNotificationId } = event.data;
|
|
85
|
+
const [thread, inboxNotification] = await Promise.all([
|
|
86
|
+
client.getThread({ roomId, threadId }),
|
|
87
|
+
client.getInboxNotification({ inboxNotificationId, userId }),
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
const unreadComments = getUnreadComments({
|
|
91
|
+
comments: thread.comments,
|
|
92
|
+
inboxNotification,
|
|
93
|
+
userId,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (unreadComments.length <= 0) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const lastUnreadCommentWithMention = getLastUnreadCommentWithMention({
|
|
101
|
+
comments: unreadComments,
|
|
102
|
+
mentionedUserId: userId,
|
|
103
|
+
});
|
|
104
|
+
if (lastUnreadCommentWithMention !== null) {
|
|
105
|
+
return { type: "unreadMention", comment: lastUnreadCommentWithMention };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
type: "unreadReplies",
|
|
110
|
+
comments: unreadComments,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export type CommentEmailBaseData = {
|
|
115
|
+
id: string;
|
|
116
|
+
threadId: string;
|
|
117
|
+
roomId: string;
|
|
118
|
+
userId: string;
|
|
119
|
+
createdAt: Date;
|
|
120
|
+
url?: string;
|
|
121
|
+
rawBody: CommentBody;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export type ResolveRoomInfoArgs = {
|
|
125
|
+
/**
|
|
126
|
+
* The ID of the room to resolve
|
|
127
|
+
*/
|
|
128
|
+
roomId: string;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
type PrepareThreadNotificationEmailBaseDataOptions = {
|
|
132
|
+
/**
|
|
133
|
+
* A function that returns room info from room IDs.
|
|
134
|
+
*/
|
|
135
|
+
resolveRoomInfo?: (
|
|
136
|
+
args: ResolveRoomInfoArgs
|
|
137
|
+
) => OptionalPromise<DRI | undefined>;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export type ThreadNotificationEmailBaseData = (
|
|
141
|
+
| { type: "unreadMention"; comment: CommentEmailBaseData }
|
|
142
|
+
| { type: "unreadReplies"; comments: CommentEmailBaseData[] }
|
|
143
|
+
) & { roomInfo: DRI };
|
|
144
|
+
|
|
145
|
+
/** @internal */
|
|
146
|
+
export const makeCommentEmailBaseData = ({
|
|
147
|
+
roomInfo,
|
|
148
|
+
comment,
|
|
149
|
+
}: {
|
|
150
|
+
roomInfo: BaseRoomInfo | undefined;
|
|
151
|
+
comment: CommentDataWithBody;
|
|
152
|
+
}): CommentEmailBaseData => {
|
|
153
|
+
const url = roomInfo?.url
|
|
154
|
+
? generateCommentUrl({
|
|
155
|
+
roomUrl: roomInfo?.url,
|
|
156
|
+
commentId: comment.id,
|
|
157
|
+
})
|
|
158
|
+
: undefined;
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
id: comment.id,
|
|
162
|
+
userId: comment.userId,
|
|
163
|
+
threadId: comment.threadId,
|
|
164
|
+
roomId: comment.roomId,
|
|
165
|
+
createdAt: comment.createdAt,
|
|
166
|
+
url,
|
|
167
|
+
rawBody: comment.body,
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/** @internal */
|
|
172
|
+
export const prepareThreadNotificationEmailBaseData = async ({
|
|
173
|
+
client,
|
|
174
|
+
event,
|
|
175
|
+
options = {},
|
|
176
|
+
}: {
|
|
177
|
+
client: Liveblocks;
|
|
178
|
+
event: ThreadNotificationEvent;
|
|
179
|
+
options?: PrepareThreadNotificationEmailBaseDataOptions;
|
|
180
|
+
}): Promise<ThreadNotificationEmailBaseData | null> => {
|
|
181
|
+
const { roomId } = event.data;
|
|
182
|
+
|
|
183
|
+
const roomInfo = options.resolveRoomInfo
|
|
184
|
+
? await options.resolveRoomInfo({ roomId })
|
|
185
|
+
: undefined;
|
|
186
|
+
const resolvedRoomInfo: DRI = {
|
|
187
|
+
...roomInfo,
|
|
188
|
+
name: roomInfo?.name ?? roomId,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const data = await extractThreadNotificationData({ client, event });
|
|
192
|
+
if (data === null) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
switch (data.type) {
|
|
197
|
+
case "unreadMention":
|
|
198
|
+
return {
|
|
199
|
+
type: "unreadMention",
|
|
200
|
+
comment: makeCommentEmailBaseData({
|
|
201
|
+
roomInfo,
|
|
202
|
+
comment: data.comment,
|
|
203
|
+
}),
|
|
204
|
+
roomInfo: resolvedRoomInfo,
|
|
205
|
+
};
|
|
206
|
+
case "unreadReplies": {
|
|
207
|
+
return {
|
|
208
|
+
type: "unreadReplies",
|
|
209
|
+
comments: data.comments.map((comment) =>
|
|
210
|
+
makeCommentEmailBaseData({ roomInfo, comment })
|
|
211
|
+
),
|
|
212
|
+
roomInfo: resolvedRoomInfo,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/** @internal */
|
|
219
|
+
const resolveAuthorsInfo = async <U extends BaseUserMeta>({
|
|
220
|
+
comments,
|
|
221
|
+
resolveUsers,
|
|
222
|
+
}: {
|
|
223
|
+
comments: CommentEmailBaseData[];
|
|
224
|
+
resolveUsers?: (
|
|
225
|
+
args: ResolveUsersArgs
|
|
226
|
+
) => OptionalPromise<(U["info"] | undefined)[] | undefined>;
|
|
227
|
+
}): Promise<Map<string, U["info"]>> => {
|
|
228
|
+
const resolvedAuthors = new Map<string, U["info"]>();
|
|
229
|
+
if (!resolveUsers) {
|
|
230
|
+
return resolvedAuthors;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const userIds = comments.map((c) => c.userId);
|
|
234
|
+
const users = await resolveUsers({ userIds });
|
|
235
|
+
|
|
236
|
+
for (const [index, userId] of userIds.entries()) {
|
|
237
|
+
const user = users?.[index];
|
|
238
|
+
if (user) {
|
|
239
|
+
resolvedAuthors.set(userId, user);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return resolvedAuthors;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
export type CommentEmailAsHtmlData<U extends BaseUserMeta = DU> = Omit<
|
|
247
|
+
CommentEmailBaseData,
|
|
248
|
+
"userId" | "rawBody"
|
|
249
|
+
> & {
|
|
250
|
+
author: U;
|
|
251
|
+
htmlBody: string;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
export type CommentEmailAsReactData<U extends BaseUserMeta = DU> = Omit<
|
|
255
|
+
CommentEmailBaseData,
|
|
256
|
+
"userId" | "rawBody"
|
|
257
|
+
> & {
|
|
258
|
+
author: U;
|
|
259
|
+
reactBody: React.ReactNode;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
type ThreadNotificationEmailUnreadRepliesData<
|
|
263
|
+
U extends BaseUserMeta,
|
|
264
|
+
C extends CommentEmailAsHtmlData<U> | CommentEmailAsReactData<U>,
|
|
265
|
+
> = {
|
|
266
|
+
type: "unreadReplies";
|
|
267
|
+
comments: C[];
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
type ThreadNotificationEmailUnreadMentionsData<
|
|
271
|
+
U extends BaseUserMeta,
|
|
272
|
+
C extends CommentEmailAsHtmlData<U> | CommentEmailAsReactData<U>,
|
|
273
|
+
> = {
|
|
274
|
+
type: "unreadMention";
|
|
275
|
+
comment: C;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// Note: export for testing helpers
|
|
279
|
+
export type ThreadNotificationEmailData<
|
|
280
|
+
U extends BaseUserMeta,
|
|
281
|
+
C extends CommentEmailAsHtmlData<U> | CommentEmailAsReactData<U>,
|
|
282
|
+
> = (
|
|
283
|
+
| ThreadNotificationEmailUnreadRepliesData<U, C>
|
|
284
|
+
| ThreadNotificationEmailUnreadMentionsData<U, C>
|
|
285
|
+
) & { roomInfo: DRI };
|
|
286
|
+
|
|
287
|
+
export type PrepareThreadNotificationEmailAsHtmlOptions<
|
|
288
|
+
U extends BaseUserMeta = DU,
|
|
289
|
+
> = PrepareThreadNotificationEmailBaseDataOptions & {
|
|
290
|
+
/**
|
|
291
|
+
* A function that returns info from user IDs.
|
|
292
|
+
*/
|
|
293
|
+
resolveUsers?: (
|
|
294
|
+
args: ResolveUsersArgs
|
|
295
|
+
) => OptionalPromise<(U["info"] | undefined)[] | undefined>;
|
|
296
|
+
/**
|
|
297
|
+
* The styles used to customize the html elements in the resulting html safe string inside a comment body.
|
|
298
|
+
* Each styles has priority over the base styles inherited.
|
|
299
|
+
*/
|
|
300
|
+
styles?: Partial<ConvertCommentBodyAsHtmlStyles>;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
export type ThreadNotificationEmailDataAsHtml = ThreadNotificationEmailData<
|
|
304
|
+
BaseUserMeta,
|
|
305
|
+
CommentEmailAsHtmlData
|
|
306
|
+
>;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Prepares data from a `ThreadNotificationEvent` and convert comment bodies as an html safe string.
|
|
310
|
+
*
|
|
311
|
+
* @param client The `Liveblocks` node client
|
|
312
|
+
* @param event The `ThreadNotificationEvent` received in the webhook handler
|
|
313
|
+
* @param options The optional options to provide to resolve users, resolve room info
|
|
314
|
+
* and customize comment bodies html elements styles with inline CSS.
|
|
315
|
+
*
|
|
316
|
+
* It returns a `ThreadNotificationEmailDataAsHtml` or `null` if there are no unread comments (mention or replies).
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* import { Liveblocks} from "@liveblocks/node"
|
|
320
|
+
* import { prepareThreadNotificationEmailAsHtml } from "@liveblocks/emails"
|
|
321
|
+
*
|
|
322
|
+
* const liveblocks = new Liveblocks({ secret: "sk_..." })
|
|
323
|
+
* const emailData = prepareThreadNotificationEmailAsHtml(
|
|
324
|
+
* liveblocks,
|
|
325
|
+
* event,
|
|
326
|
+
* {
|
|
327
|
+
* resolveUsers,
|
|
328
|
+
* resolveRoomInfo,
|
|
329
|
+
* styles,
|
|
330
|
+
* }
|
|
331
|
+
* )
|
|
332
|
+
*
|
|
333
|
+
*/
|
|
334
|
+
export async function prepareThreadNotificationEmailAsHtml(
|
|
335
|
+
client: Liveblocks,
|
|
336
|
+
event: ThreadNotificationEvent,
|
|
337
|
+
options: PrepareThreadNotificationEmailAsHtmlOptions<BaseUserMeta> = {}
|
|
338
|
+
): Promise<ThreadNotificationEmailDataAsHtml | null> {
|
|
339
|
+
const data = await prepareThreadNotificationEmailBaseData({
|
|
340
|
+
client,
|
|
341
|
+
event,
|
|
342
|
+
options: { resolveRoomInfo: options.resolveRoomInfo },
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
if (data === null) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const batchUsersResolver = createBatchUsersResolver<BaseUserMeta>({
|
|
350
|
+
resolveUsers: options.resolveUsers,
|
|
351
|
+
callerName: "prepareThreadNotificationEmailAsHtml",
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
switch (data.type) {
|
|
355
|
+
case "unreadMention": {
|
|
356
|
+
const { comment } = data;
|
|
357
|
+
|
|
358
|
+
const authorsInfoPromise = resolveAuthorsInfo({
|
|
359
|
+
comments: [comment],
|
|
360
|
+
resolveUsers: batchUsersResolver.resolveUsers,
|
|
361
|
+
});
|
|
362
|
+
const commentBodyPromise = convertCommentBodyAsHtml(comment.rawBody, {
|
|
363
|
+
resolveUsers: batchUsersResolver.resolveUsers,
|
|
364
|
+
styles: options.styles,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
await batchUsersResolver.resolve();
|
|
368
|
+
|
|
369
|
+
const [authorsInfo, commentBodyHtml] = await Promise.all([
|
|
370
|
+
authorsInfoPromise,
|
|
371
|
+
commentBodyPromise,
|
|
372
|
+
]);
|
|
373
|
+
const authorInfo = authorsInfo.get(comment.userId);
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
type: "unreadMention",
|
|
377
|
+
comment: {
|
|
378
|
+
id: comment.id,
|
|
379
|
+
threadId: comment.threadId,
|
|
380
|
+
roomId: comment.roomId,
|
|
381
|
+
author: authorInfo
|
|
382
|
+
? { id: comment.userId, info: authorInfo }
|
|
383
|
+
: { id: comment.userId, info: { name: comment.userId } },
|
|
384
|
+
createdAt: comment.createdAt,
|
|
385
|
+
url: comment.url,
|
|
386
|
+
htmlBody: commentBodyHtml,
|
|
387
|
+
},
|
|
388
|
+
roomInfo: data.roomInfo,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
case "unreadReplies": {
|
|
392
|
+
const { comments } = data;
|
|
393
|
+
|
|
394
|
+
const authorsInfoPromise = resolveAuthorsInfo({
|
|
395
|
+
comments,
|
|
396
|
+
resolveUsers: batchUsersResolver.resolveUsers,
|
|
397
|
+
});
|
|
398
|
+
const commentBodiesPromises = comments.map((c) =>
|
|
399
|
+
convertCommentBodyAsHtml(c.rawBody, {
|
|
400
|
+
resolveUsers: batchUsersResolver.resolveUsers,
|
|
401
|
+
styles: options.styles,
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
await batchUsersResolver.resolve();
|
|
406
|
+
|
|
407
|
+
const [authorsInfo, ...commentBodies] = await Promise.all([
|
|
408
|
+
authorsInfoPromise,
|
|
409
|
+
...commentBodiesPromises,
|
|
410
|
+
]);
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
type: "unreadReplies",
|
|
414
|
+
comments: comments.map((comment, index) => {
|
|
415
|
+
const authorInfo = authorsInfo.get(comment.userId);
|
|
416
|
+
const commentBodyHtml = commentBodies[index];
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
id: comment.id,
|
|
420
|
+
threadId: comment.threadId,
|
|
421
|
+
roomId: comment.roomId,
|
|
422
|
+
author: authorInfo
|
|
423
|
+
? { id: comment.userId, info: authorInfo }
|
|
424
|
+
: { id: comment.userId, info: { name: comment.userId } },
|
|
425
|
+
createdAt: comment.createdAt,
|
|
426
|
+
url: comment.url,
|
|
427
|
+
htmlBody: commentBodyHtml ?? "",
|
|
428
|
+
};
|
|
429
|
+
}),
|
|
430
|
+
roomInfo: data.roomInfo,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export type PrepareThreadNotificationEmailAsReactOptions<
|
|
437
|
+
U extends BaseUserMeta = DU,
|
|
438
|
+
> = PrepareThreadNotificationEmailBaseDataOptions & {
|
|
439
|
+
/**
|
|
440
|
+
* A function that returns info from user IDs.
|
|
441
|
+
*/
|
|
442
|
+
resolveUsers?: (
|
|
443
|
+
args: ResolveUsersArgs
|
|
444
|
+
) => OptionalPromise<(U["info"] | undefined)[] | undefined>;
|
|
445
|
+
/**
|
|
446
|
+
* The components used to customize the resulting React nodes inside a comment body.
|
|
447
|
+
* Each components has priority over the base components inherited internally defined.
|
|
448
|
+
*/
|
|
449
|
+
components?: Partial<ConvertCommentBodyAsReactComponents<U>>;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
export type ThreadNotificationEmailDataAsReact = ThreadNotificationEmailData<
|
|
453
|
+
BaseUserMeta,
|
|
454
|
+
CommentEmailAsReactData
|
|
455
|
+
>;
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Prepares data from a `ThreadNotificationEvent` and convert comment bodies as React nodes.
|
|
459
|
+
*
|
|
460
|
+
* @param client The `Liveblocks` node client
|
|
461
|
+
* @param event The `ThreadNotificationEvent` received in the webhook handler
|
|
462
|
+
* @param options The optional options to provide to resolve users, resolve room info and customize comment bodies React components.
|
|
463
|
+
*
|
|
464
|
+
* It returns a `ThreadNotificationEmailDataAsReact` or `null` if there are no unread comments (mention or replies).
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* import { Liveblocks} from "@liveblocks/node"
|
|
468
|
+
* import { prepareThreadNotificationEmailAsReact } from "@liveblocks/emails"
|
|
469
|
+
*
|
|
470
|
+
* const liveblocks = new Liveblocks({ secret: "sk_..." })
|
|
471
|
+
* const emailData = prepareThreadNotificationEmailAsReact(
|
|
472
|
+
* liveblocks,
|
|
473
|
+
* event,
|
|
474
|
+
* {
|
|
475
|
+
* resolveUsers,
|
|
476
|
+
* resolveRoomInfo,
|
|
477
|
+
* components,
|
|
478
|
+
* }
|
|
479
|
+
* )
|
|
480
|
+
*
|
|
481
|
+
*/
|
|
482
|
+
export async function prepareThreadNotificationEmailAsReact(
|
|
483
|
+
client: Liveblocks,
|
|
484
|
+
event: ThreadNotificationEvent,
|
|
485
|
+
options: PrepareThreadNotificationEmailAsReactOptions<BaseUserMeta> = {}
|
|
486
|
+
): Promise<ThreadNotificationEmailDataAsReact | null> {
|
|
487
|
+
const data = await prepareThreadNotificationEmailBaseData({
|
|
488
|
+
client,
|
|
489
|
+
event,
|
|
490
|
+
options: { resolveRoomInfo: options.resolveRoomInfo },
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
if (data === null) {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const batchUsersResolver = createBatchUsersResolver<BaseUserMeta>({
|
|
498
|
+
resolveUsers: options.resolveUsers,
|
|
499
|
+
callerName: "prepareThreadNotificationEmailAsReact",
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
switch (data.type) {
|
|
503
|
+
case "unreadMention": {
|
|
504
|
+
const { comment } = data;
|
|
505
|
+
|
|
506
|
+
const authorsInfoPromise = resolveAuthorsInfo({
|
|
507
|
+
comments: [comment],
|
|
508
|
+
resolveUsers: batchUsersResolver.resolveUsers,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
const commentBodyPromise = convertCommentBodyAsReact(comment.rawBody, {
|
|
512
|
+
resolveUsers: batchUsersResolver.resolveUsers,
|
|
513
|
+
components: options.components,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
await batchUsersResolver.resolve();
|
|
517
|
+
|
|
518
|
+
const [authorsInfo, commentBodyReact] = await Promise.all([
|
|
519
|
+
authorsInfoPromise,
|
|
520
|
+
commentBodyPromise,
|
|
521
|
+
]);
|
|
522
|
+
const authorInfo = authorsInfo.get(comment.userId);
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
type: "unreadMention",
|
|
526
|
+
comment: {
|
|
527
|
+
id: comment.id,
|
|
528
|
+
threadId: comment.threadId,
|
|
529
|
+
roomId: comment.roomId,
|
|
530
|
+
author: authorInfo
|
|
531
|
+
? { id: comment.userId, info: authorInfo }
|
|
532
|
+
: { id: comment.userId, info: { name: comment.userId } },
|
|
533
|
+
createdAt: comment.createdAt,
|
|
534
|
+
url: comment.url,
|
|
535
|
+
reactBody: commentBodyReact,
|
|
536
|
+
},
|
|
537
|
+
roomInfo: data.roomInfo,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
case "unreadReplies": {
|
|
541
|
+
const { comments } = data;
|
|
542
|
+
const authorsInfoPromise = resolveAuthorsInfo({
|
|
543
|
+
comments,
|
|
544
|
+
resolveUsers: batchUsersResolver.resolveUsers,
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
const commentBodiesPromises = comments.map((c) =>
|
|
548
|
+
convertCommentBodyAsReact(c.rawBody, {
|
|
549
|
+
resolveUsers: batchUsersResolver.resolveUsers,
|
|
550
|
+
components: options.components,
|
|
551
|
+
})
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
await batchUsersResolver.resolve();
|
|
555
|
+
|
|
556
|
+
const [authorsInfo, ...commentBodies] = await Promise.all([
|
|
557
|
+
authorsInfoPromise,
|
|
558
|
+
...commentBodiesPromises,
|
|
559
|
+
]);
|
|
560
|
+
|
|
561
|
+
return {
|
|
562
|
+
type: "unreadReplies",
|
|
563
|
+
comments: comments.map((comment, index) => {
|
|
564
|
+
const authorInfo = authorsInfo.get(comment.userId);
|
|
565
|
+
const commentBodyReact = commentBodies[index];
|
|
566
|
+
|
|
567
|
+
return {
|
|
568
|
+
id: comment.id,
|
|
569
|
+
threadId: comment.threadId,
|
|
570
|
+
roomId: comment.roomId,
|
|
571
|
+
author: authorInfo
|
|
572
|
+
? { id: comment.userId, info: authorInfo }
|
|
573
|
+
: { id: comment.userId, info: { name: comment.userId } },
|
|
574
|
+
createdAt: comment.createdAt,
|
|
575
|
+
url: comment.url,
|
|
576
|
+
reactBody: commentBodyReact ?? null,
|
|
577
|
+
};
|
|
578
|
+
}),
|
|
579
|
+
roomInfo: data.roomInfo,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
@@ -5,14 +5,14 @@ import type { BaseRoomInfo } from '../protocol/BaseRoomInfo';
|
|
|
5
5
|
import type { BaseUserMeta } from '../protocol/BaseUserMeta';
|
|
6
6
|
import type { BaseMetadata } from '../protocol/Comments';
|
|
7
7
|
|
|
8
|
-
declare global {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
8
|
+
// declare global {
|
|
9
|
+
// /**
|
|
10
|
+
// * Namespace for user-defined Liveblocks types.
|
|
11
|
+
// */
|
|
12
|
+
// export interface Liveblocks {
|
|
13
|
+
// [key: string]: unknown;
|
|
14
|
+
// }
|
|
15
|
+
// }
|
|
16
16
|
|
|
17
17
|
// NOTE: When extending this list, make sure to also add respective error
|
|
18
18
|
// message docs (in ../../docs/pages/errors/*.mdx).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
|
+
import { RoomsService } from './rooms.service';
|
|
3
|
+
import { NotificationsService } from './notifications.service';
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class LiveblocksService {
|
|
7
|
+
constructor(
|
|
8
|
+
private roomsService: RoomsService,
|
|
9
|
+
private notificationService: NotificationsService,
|
|
10
|
+
) {}
|
|
11
|
+
private readonly logger = new Logger(LiveblocksService.name);
|
|
12
|
+
|
|
13
|
+
async getThread({ roomId, threadId }) {
|
|
14
|
+
const thread = await this.roomsService.getThread({ roomId, threadId });
|
|
15
|
+
return thread;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async getInboxNotification({ inboxNotificationId, userId }) {
|
|
19
|
+
const notification = await this.notificationService.getInboxNotification({
|
|
20
|
+
inboxNotificationId,
|
|
21
|
+
userId,
|
|
22
|
+
});
|
|
23
|
+
return notification;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -66,6 +66,13 @@ export class NotificationsService {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
async getInboxNotification({ inboxNotificationId, userId }) {
|
|
70
|
+
return this.mongodbService.findOne('b6_inbox_notifications', {
|
|
71
|
+
_id: inboxNotificationId,
|
|
72
|
+
// userId,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
69
76
|
async createInboxNotification({
|
|
70
77
|
_id,
|
|
71
78
|
userId,
|
|
@@ -75,16 +82,21 @@ export class NotificationsService {
|
|
|
75
82
|
roomId,
|
|
76
83
|
threadId,
|
|
77
84
|
}: InboxNotificationDTO) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
const result = await this.mongodbService.insertOne(
|
|
86
|
+
'b6_inbox_notifications',
|
|
87
|
+
{
|
|
88
|
+
_id,
|
|
89
|
+
id: _id,
|
|
90
|
+
userId,
|
|
91
|
+
kind,
|
|
92
|
+
notifiedAt,
|
|
93
|
+
readAt,
|
|
94
|
+
roomId,
|
|
95
|
+
threadId,
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return result;
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
async findNotifications({
|