@builder6/rooms 3.0.5 → 3.0.6
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/package.json +10 -7
- package/.prettierrc +0 -4
- package/src/emails/UnreadMention.tsx +0 -37
- package/src/emails/UnreadReplies.tsx +0 -49
- package/src/emails/_components/comment.tsx +0 -70
- package/src/emails/_components/header.tsx +0 -26
- package/src/emails/_components/headline.tsx +0 -20
- package/src/emails/_components/layout.tsx +0 -45
- package/src/emails/_lib/types.ts +0 -10
- package/src/emails/_styles/colors.ts +0 -7
- package/src/emails/_utils/cn.ts +0 -6
- package/src/emails/_utils/comments.ts +0 -61
- package/src/emails/_utils/getProps.ts +0 -7
- package/src/plugin.module.ts +0 -3
- package/src/rooms/app.controller.ts +0 -89
- package/src/rooms/dtos/inbox_notifications.dto.ts +0 -13
- package/src/rooms/dtos/notifications.dto.ts +0 -14
- package/src/rooms/dtos/room_members.dto.ts +0 -32
- package/src/rooms/dtos/rooms.dto.ts +0 -51
- package/src/rooms/emailNotification.service.tsx +0 -126
- package/src/rooms/emails/comment-body.tsx +0 -342
- package/src/rooms/emails/comment-with-body.ts +0 -24
- package/src/rooms/emails/index.ts +0 -25
- package/src/rooms/emails/lib/batch-users-resolver.ts +0 -120
- package/src/rooms/emails/lib/css-properties.ts +0 -123
- package/src/rooms/emails/lib/warning.ts +0 -25
- package/src/rooms/emails/thread-notification.tsx +0 -583
- package/src/rooms/globals/augmentation.ts +0 -89
- package/src/rooms/index.ts +0 -5
- package/src/rooms/lib/DateToString.ts +0 -9
- package/src/rooms/lib/Json.ts +0 -34
- package/src/rooms/lib/utils.ts +0 -240
- package/src/rooms/liveblocks.service.ts +0 -25
- package/src/rooms/notifications.service.ts +0 -235
- package/src/rooms/protocol/AuthToken.ts +0 -126
- package/src/rooms/protocol/Authentication.ts +0 -18
- package/src/rooms/protocol/BaseActivitiesData.ts +0 -5
- package/src/rooms/protocol/BaseRoomInfo.ts +0 -15
- package/src/rooms/protocol/BaseUserMeta.ts +0 -28
- package/src/rooms/protocol/ClientMsg.ts +0 -94
- package/src/rooms/protocol/Comments.ts +0 -202
- package/src/rooms/protocol/InboxNotifications.ts +0 -75
- package/src/rooms/protocol/Op.ts +0 -143
- package/src/rooms/protocol/SerializedCrdt.ts +0 -61
- package/src/rooms/protocol/ServerMsg.ts +0 -307
- package/src/rooms/protocol/VersionHistory.ts +0 -9
- package/src/rooms/rooms.controller.ts +0 -587
- package/src/rooms/rooms.gateway.ts +0 -267
- package/src/rooms/rooms.guard.ts +0 -52
- package/src/rooms/rooms.module.ts +0 -38
- package/src/rooms/rooms.moleculer.ts +0 -80
- package/src/rooms/rooms.service.ts +0 -723
- package/tsconfig.json +0 -10
- package/yarn-error.log +0 -17218
package/src/rooms/lib/Json.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Represents an indefinitely deep arbitrary JSON data structure. There are
|
|
3
|
-
* four types that make up the Json family:
|
|
4
|
-
*
|
|
5
|
-
* - Json any legal JSON value
|
|
6
|
-
* - JsonScalar any legal JSON leaf value (no lists or objects)
|
|
7
|
-
* - JsonArray a JSON value whose outer type is an array
|
|
8
|
-
* - JsonObject a JSON value whose outer type is an object
|
|
9
|
-
*
|
|
10
|
-
*/
|
|
11
|
-
export type Json = JsonScalar | JsonArray | JsonObject;
|
|
12
|
-
export type JsonScalar = string | number | boolean | null;
|
|
13
|
-
export type JsonArray = Json[];
|
|
14
|
-
/**
|
|
15
|
-
* Any valid JSON object.
|
|
16
|
-
*/
|
|
17
|
-
export type JsonObject = { [key: string]: Json | undefined };
|
|
18
|
-
|
|
19
|
-
export function isJsonScalar(data: Json): data is JsonScalar {
|
|
20
|
-
return (
|
|
21
|
-
data === null ||
|
|
22
|
-
typeof data === 'string' ||
|
|
23
|
-
typeof data === 'number' ||
|
|
24
|
-
typeof data === 'boolean'
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function isJsonArray(data: Json): data is JsonArray {
|
|
29
|
-
return Array.isArray(data);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function isJsonObject(data: Json): data is JsonObject {
|
|
33
|
-
return !isJsonScalar(data) && !isJsonArray(data);
|
|
34
|
-
}
|
package/src/rooms/lib/utils.ts
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import type { Json } from './Json';
|
|
2
|
-
|
|
3
|
-
declare const brand: unique symbol;
|
|
4
|
-
export type Brand<T, TBrand extends string> = T & { [brand]: TBrand };
|
|
5
|
-
|
|
6
|
-
export type DistributiveOmit<T, K extends PropertyKey> = T extends any
|
|
7
|
-
? Omit<T, K>
|
|
8
|
-
: never;
|
|
9
|
-
|
|
10
|
-
// export type DistributivePick<T, K extends keyof T> = T extends any
|
|
11
|
-
// ? Pick<T, K>
|
|
12
|
-
// : never;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Throw an error, but as an expression instead of a statement.
|
|
16
|
-
*/
|
|
17
|
-
export function raise(msg: string): never {
|
|
18
|
-
throw new Error(msg);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function isPlainObject(
|
|
22
|
-
blob: unknown,
|
|
23
|
-
): blob is { [key: string]: unknown } {
|
|
24
|
-
// Implementation borrowed from pojo decoder, see
|
|
25
|
-
// https://github.com/nvie/decoders/blob/78849f843193647eb6b5307240387bdcff7161fb/src/lib/objects.js#L10-L41
|
|
26
|
-
return (
|
|
27
|
-
blob !== null &&
|
|
28
|
-
typeof blob === 'object' &&
|
|
29
|
-
Object.prototype.toString.call(blob) === '[object Object]'
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Drop-in replacement for Object.entries() that retains better types.
|
|
35
|
-
*/
|
|
36
|
-
export function entries<
|
|
37
|
-
O extends { [key: string]: unknown },
|
|
38
|
-
K extends keyof O,
|
|
39
|
-
>(obj: O): [K, O[K]][] {
|
|
40
|
-
return Object.entries(obj) as [K, O[K]][];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Drop-in replacement for Object.keys() that retains better types.
|
|
45
|
-
*/
|
|
46
|
-
export function keys<O extends { [key: string]: unknown }, K extends keyof O>(
|
|
47
|
-
obj: O,
|
|
48
|
-
): K[] {
|
|
49
|
-
return Object.keys(obj) as K[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Drop-in replacement for Object.values() that retains better types.
|
|
54
|
-
*/
|
|
55
|
-
export function values<O extends Record<string, unknown>>(
|
|
56
|
-
obj: O,
|
|
57
|
-
): O[keyof O][] {
|
|
58
|
-
return Object.values(obj) as O[keyof O][];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Creates a new object by mapping a function over all values. Keys remain the
|
|
63
|
-
* same. Think Array.prototype.map(), but for values in an object.
|
|
64
|
-
*/
|
|
65
|
-
export function mapValues<V, O extends Record<string, unknown>>(
|
|
66
|
-
obj: O,
|
|
67
|
-
mapFn: (value: O[keyof O], key: keyof O) => V,
|
|
68
|
-
): { [K in keyof O]: V } {
|
|
69
|
-
const result = {} as { [K in keyof O]: V };
|
|
70
|
-
for (const pair of Object.entries(obj)) {
|
|
71
|
-
const key: keyof O = pair[0];
|
|
72
|
-
if (key === '__proto__') {
|
|
73
|
-
// Avoid setting dangerous __proto__ keys
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
const value = pair[1] as O[keyof O];
|
|
77
|
-
result[key] = mapFn(value, key);
|
|
78
|
-
}
|
|
79
|
-
return result;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Alternative to JSON.parse() that will not throw in production. If the passed
|
|
84
|
-
* string cannot be parsed, this will return `undefined`.
|
|
85
|
-
*/
|
|
86
|
-
export function tryParseJson(rawMessage: string): Json | undefined {
|
|
87
|
-
try {
|
|
88
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
89
|
-
return JSON.parse(rawMessage) as Json;
|
|
90
|
-
} catch (e) {
|
|
91
|
-
return undefined;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Deep-clones a JSON-serializable value.
|
|
97
|
-
*
|
|
98
|
-
* NOTE: We should be able to replace `deepClone` by `structuredClone` once
|
|
99
|
-
* we've upgraded to Node 18.
|
|
100
|
-
*/
|
|
101
|
-
export function deepClone<T extends Json>(value: T): T {
|
|
102
|
-
// NOTE: In this case, the combination of JSON.parse() and JSON.stringify
|
|
103
|
-
// won't lead to type unsafety, so this use case is okay.
|
|
104
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
105
|
-
return JSON.parse(JSON.stringify(value)) as T;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Decode base64 string.
|
|
110
|
-
*/
|
|
111
|
-
export function b64decode(b64value: string): string {
|
|
112
|
-
try {
|
|
113
|
-
const formattedValue = b64value.replace(/-/g, '+').replace(/_/g, '/');
|
|
114
|
-
const decodedValue = decodeURIComponent(
|
|
115
|
-
atob(formattedValue)
|
|
116
|
-
.split('')
|
|
117
|
-
.map(function (c) {
|
|
118
|
-
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
119
|
-
})
|
|
120
|
-
.join(''),
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
return decodedValue;
|
|
124
|
-
} catch (err) {
|
|
125
|
-
return atob(b64value);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Mutates the array in-place by removing the first occurrence of `item` from
|
|
131
|
-
* the array.
|
|
132
|
-
*/
|
|
133
|
-
export function remove<T>(array: T[], item: T): void {
|
|
134
|
-
for (let i = 0; i < array.length; i++) {
|
|
135
|
-
if (array[i] === item) {
|
|
136
|
-
array.splice(i, 1);
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Removes null and undefined values from the array, and reflects this in the
|
|
144
|
-
* output type.
|
|
145
|
-
*/
|
|
146
|
-
export function compact<T>(items: readonly T[]): NonNullable<T>[] {
|
|
147
|
-
return items.filter(
|
|
148
|
-
(item: T): item is NonNullable<T> => item !== null && item !== undefined,
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export type RemoveUndefinedValues<T> = {
|
|
153
|
-
[K in keyof T]-?: Exclude<T[K], undefined>;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Returns a new object instance where all explictly-undefined values are
|
|
158
|
-
* removed.
|
|
159
|
-
*/
|
|
160
|
-
export function compactObject<O extends Record<string, unknown>>(
|
|
161
|
-
obj: O,
|
|
162
|
-
): RemoveUndefinedValues<O> {
|
|
163
|
-
const newObj = { ...obj };
|
|
164
|
-
Object.keys(obj).forEach((k) => {
|
|
165
|
-
const key = k as keyof O;
|
|
166
|
-
if (newObj[key] === undefined) {
|
|
167
|
-
delete newObj[key];
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
return newObj as RemoveUndefinedValues<O>;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Returns a promise that resolves after the given number of milliseconds.
|
|
175
|
-
*/
|
|
176
|
-
export function wait(millis: number): Promise<void> {
|
|
177
|
-
return new Promise((res) => setTimeout(res, millis));
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Returns whatever the given promise returns, but will be rejected with
|
|
182
|
-
* a "Timed out" error if the given promise does not return or reject within
|
|
183
|
-
* the given timeout period (in milliseconds).
|
|
184
|
-
*/
|
|
185
|
-
export async function withTimeout<T>(
|
|
186
|
-
promise: Promise<T>,
|
|
187
|
-
millis: number,
|
|
188
|
-
errmsg: string,
|
|
189
|
-
): Promise<T> {
|
|
190
|
-
let timerID: ReturnType<typeof setTimeout> | undefined;
|
|
191
|
-
const timer$ = new Promise<never>((_, reject) => {
|
|
192
|
-
timerID = setTimeout(() => {
|
|
193
|
-
reject(new Error(errmsg));
|
|
194
|
-
}, millis);
|
|
195
|
-
});
|
|
196
|
-
return (
|
|
197
|
-
Promise
|
|
198
|
-
// Race the given promise against the timer. Whichever one finishes
|
|
199
|
-
// first wins the race.
|
|
200
|
-
.race([promise, timer$])
|
|
201
|
-
|
|
202
|
-
// Either way, clear the timeout, no matter who won
|
|
203
|
-
.finally(() => clearTimeout(timerID))
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Memoize a promise factory, so that each subsequent call will return the same
|
|
209
|
-
* pending or success promise. If the promise rejects, will retain that failed
|
|
210
|
-
* promise for a small time period, after which the next attempt will reset the
|
|
211
|
-
* memoized value.
|
|
212
|
-
*/
|
|
213
|
-
export function memoizeOnSuccess<T>(
|
|
214
|
-
factoryFn: () => Promise<T>,
|
|
215
|
-
): () => Promise<T> {
|
|
216
|
-
let cached: Promise<T> | null = null;
|
|
217
|
-
return () => {
|
|
218
|
-
if (cached === null) {
|
|
219
|
-
cached = factoryFn().catch((err) => {
|
|
220
|
-
//
|
|
221
|
-
// Keep returning the failed promise for any calls to the memoized
|
|
222
|
-
// promise for the next 5 seconds. This time period is a bit arbitrary,
|
|
223
|
-
// but exists to make this play nicely with frameworks like React.
|
|
224
|
-
//
|
|
225
|
-
// In React, after a component is suspended and its promise is
|
|
226
|
-
// rejected, React will re-render the component, and expect the next
|
|
227
|
-
// call to this function to return the rejected promise, so its error
|
|
228
|
-
// can be shown. If we immediately reset this value, then such next
|
|
229
|
-
// render would instantly trigger a new promise which would trigger an
|
|
230
|
-
// infinite loop and keeping the component in loading state forever.
|
|
231
|
-
//
|
|
232
|
-
setTimeout(() => {
|
|
233
|
-
cached = null;
|
|
234
|
-
}, 5_000);
|
|
235
|
-
throw err;
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
return cached;
|
|
239
|
-
};
|
|
240
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { MongodbService } from '@builder6/core';
|
|
2
|
-
import { InjectBroker } from '@builder6/moleculer';
|
|
3
|
-
import { Injectable, Logger } from '@nestjs/common';
|
|
4
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
-
import { ServiceBroker } from 'moleculer';
|
|
6
|
-
import { InboxNotificationDTO } from './dtos/inbox_notifications.dto';
|
|
7
|
-
import { isArray } from 'lodash';
|
|
8
|
-
import { EmailNotificationService } from './emailNotification.service';
|
|
9
|
-
|
|
10
|
-
@Injectable()
|
|
11
|
-
export class NotificationsService {
|
|
12
|
-
private readonly logger = new Logger(NotificationsService.name);
|
|
13
|
-
constructor(
|
|
14
|
-
private mongodbService: MongodbService,
|
|
15
|
-
private emailNotificationService: EmailNotificationService,
|
|
16
|
-
@InjectBroker() private readonly broker: ServiceBroker,
|
|
17
|
-
) {}
|
|
18
|
-
|
|
19
|
-
async onThreadNotification({
|
|
20
|
-
roomId,
|
|
21
|
-
userId,
|
|
22
|
-
fromUserId,
|
|
23
|
-
threadId,
|
|
24
|
-
type = 'mention',
|
|
25
|
-
spaceId,
|
|
26
|
-
}: {
|
|
27
|
-
roomId: string;
|
|
28
|
-
userId: string;
|
|
29
|
-
fromUserId: string;
|
|
30
|
-
threadId: string;
|
|
31
|
-
type: 'mention' | 'reply';
|
|
32
|
-
spaceId: string;
|
|
33
|
-
}) {
|
|
34
|
-
// Create Inbox Notification
|
|
35
|
-
const notification = await this.createOrUpdateInboxNotification({
|
|
36
|
-
userId,
|
|
37
|
-
kind: 'thread',
|
|
38
|
-
roomId,
|
|
39
|
-
threadId,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// Create Email Notification
|
|
43
|
-
await this.emailNotificationService.sendThreadNotificationEmail({
|
|
44
|
-
roomId,
|
|
45
|
-
threadId,
|
|
46
|
-
userId,
|
|
47
|
-
fromUserId,
|
|
48
|
-
inboxNotificationId: notification._id,
|
|
49
|
-
spaceId,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Emitting Moleculer Message and Steedos Notification
|
|
53
|
-
if (roomId && roomId.split(':').length === 3) {
|
|
54
|
-
const [objects, objectName, recordId] = roomId.split(':');
|
|
55
|
-
if (objects === 'objects') {
|
|
56
|
-
if (type === 'mention') {
|
|
57
|
-
this.logger.log(
|
|
58
|
-
`Emitting ${objectName}.mentioned, recordId:${recordId} userId:${userId}`,
|
|
59
|
-
);
|
|
60
|
-
this.broker.emit(`${objectName}.mentioned`, {
|
|
61
|
-
recordId,
|
|
62
|
-
userId,
|
|
63
|
-
fromUserId,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async getInboxNotification({ inboxNotificationId, userId }) {
|
|
71
|
-
return this.mongodbService.findOne('b6_inbox_notifications', {
|
|
72
|
-
_id: inboxNotificationId,
|
|
73
|
-
// userId,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
deleteNotifications({ userId }: { userId: string }) {
|
|
78
|
-
return this.mongodbService.deleteMany('b6_inbox_notifications', {
|
|
79
|
-
userId,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async createOrUpdateInboxNotification({
|
|
84
|
-
kind = 'thread',
|
|
85
|
-
userId,
|
|
86
|
-
roomId,
|
|
87
|
-
threadId,
|
|
88
|
-
}: any) {
|
|
89
|
-
let notification = await this.mongodbService.findOne(
|
|
90
|
-
'b6_inbox_notifications',
|
|
91
|
-
{
|
|
92
|
-
roomId,
|
|
93
|
-
threadId,
|
|
94
|
-
userId,
|
|
95
|
-
},
|
|
96
|
-
);
|
|
97
|
-
if (!notification) {
|
|
98
|
-
const _id = uuidv4();
|
|
99
|
-
notification = await this.mongodbService.insertOne(
|
|
100
|
-
'b6_inbox_notifications',
|
|
101
|
-
{
|
|
102
|
-
_id,
|
|
103
|
-
id: _id,
|
|
104
|
-
userId,
|
|
105
|
-
kind,
|
|
106
|
-
notifiedAt: new Date(),
|
|
107
|
-
readAt: null,
|
|
108
|
-
roomId,
|
|
109
|
-
threadId,
|
|
110
|
-
},
|
|
111
|
-
);
|
|
112
|
-
} else {
|
|
113
|
-
await this.mongodbService.findOneAndUpdateData(
|
|
114
|
-
'b6_inbox_notifications',
|
|
115
|
-
{
|
|
116
|
-
_id: notification._id,
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
notifiedAt: new Date(),
|
|
120
|
-
},
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return notification;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async findNotifications({
|
|
128
|
-
userId,
|
|
129
|
-
unread,
|
|
130
|
-
limit,
|
|
131
|
-
sinceDate,
|
|
132
|
-
roomId,
|
|
133
|
-
threadId,
|
|
134
|
-
}: {
|
|
135
|
-
userId: string;
|
|
136
|
-
unread?: boolean;
|
|
137
|
-
limit?: number;
|
|
138
|
-
sinceDate?: Date;
|
|
139
|
-
roomId?: string;
|
|
140
|
-
threadId?: string;
|
|
141
|
-
}) {
|
|
142
|
-
const queryOptions = { userId } as {
|
|
143
|
-
userId: string;
|
|
144
|
-
readAt: null | undefined;
|
|
145
|
-
notifiedAt: { $gt: Date } | undefined;
|
|
146
|
-
roomId?: string;
|
|
147
|
-
threadId?: string;
|
|
148
|
-
id: { $in: string[] } | undefined;
|
|
149
|
-
};
|
|
150
|
-
if (unread) queryOptions.readAt = null;
|
|
151
|
-
if (sinceDate) {
|
|
152
|
-
queryOptions.notifiedAt = { $gt: sinceDate };
|
|
153
|
-
}
|
|
154
|
-
if (roomId) queryOptions.roomId = roomId;
|
|
155
|
-
if (threadId) queryOptions.threadId = threadId;
|
|
156
|
-
const collection = (await this.mongodbService.getCollection(
|
|
157
|
-
'b6_inbox_notifications',
|
|
158
|
-
)) as any;
|
|
159
|
-
const query = collection.find(queryOptions).sort({ notifiedAt: 1 });
|
|
160
|
-
if (limit) query.limit(limit);
|
|
161
|
-
return query.toArray();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async markAsRead({
|
|
165
|
-
roomId,
|
|
166
|
-
userId,
|
|
167
|
-
inboxNotificationIds,
|
|
168
|
-
}: {
|
|
169
|
-
roomId?: string;
|
|
170
|
-
userId: string;
|
|
171
|
-
inboxNotificationIds: string[] | 'all';
|
|
172
|
-
}) {
|
|
173
|
-
const queryOptions = { userId } as {
|
|
174
|
-
roomId?: string;
|
|
175
|
-
userId: string;
|
|
176
|
-
_id: { $in: string[] };
|
|
177
|
-
};
|
|
178
|
-
if (roomId) queryOptions.roomId = roomId;
|
|
179
|
-
if (isArray(inboxNotificationIds)) {
|
|
180
|
-
queryOptions._id = { $in: inboxNotificationIds as string[] };
|
|
181
|
-
}
|
|
182
|
-
const collection = (await this.mongodbService.getCollection(
|
|
183
|
-
'b6_inbox_notifications',
|
|
184
|
-
)) as any;
|
|
185
|
-
return collection.updateMany(queryOptions, {
|
|
186
|
-
$set: { readAt: new Date() },
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async createSteedosNotification({
|
|
191
|
-
objectName,
|
|
192
|
-
recordId,
|
|
193
|
-
name,
|
|
194
|
-
body,
|
|
195
|
-
fromUserId,
|
|
196
|
-
userId,
|
|
197
|
-
}: {
|
|
198
|
-
objectName: string;
|
|
199
|
-
recordId: string;
|
|
200
|
-
name?: string;
|
|
201
|
-
body: string;
|
|
202
|
-
fromUserId: string;
|
|
203
|
-
userId: string;
|
|
204
|
-
}) {
|
|
205
|
-
const record = await this.mongodbService.findOne(
|
|
206
|
-
objectName,
|
|
207
|
-
{
|
|
208
|
-
_id: recordId,
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
projection: {
|
|
212
|
-
name: 1,
|
|
213
|
-
space: 1,
|
|
214
|
-
owner: 1,
|
|
215
|
-
},
|
|
216
|
-
},
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
return await this.mongodbService.insertOne('notifications', {
|
|
220
|
-
name: name || record.name,
|
|
221
|
-
body,
|
|
222
|
-
related_to: {
|
|
223
|
-
o: objectName,
|
|
224
|
-
ids: [recordId],
|
|
225
|
-
},
|
|
226
|
-
related_name: record.name,
|
|
227
|
-
from: fromUserId,
|
|
228
|
-
space: record.space,
|
|
229
|
-
owner: userId,
|
|
230
|
-
is_read: false,
|
|
231
|
-
created: new Date(),
|
|
232
|
-
created_by: fromUserId,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import type { Json } from '../lib/Json';
|
|
2
|
-
import { b64decode, isPlainObject, tryParseJson } from '../lib/utils';
|
|
3
|
-
import type { IUserInfo } from './BaseUserMeta';
|
|
4
|
-
|
|
5
|
-
export enum Permission {
|
|
6
|
-
Read = 'room:read',
|
|
7
|
-
Write = 'room:write',
|
|
8
|
-
PresenceWrite = 'room:presence:write',
|
|
9
|
-
CommentsWrite = 'comments:write',
|
|
10
|
-
CommentsRead = 'comments:read',
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type LiveblocksPermissions = Record<string, Permission[]>;
|
|
14
|
-
|
|
15
|
-
export enum TokenKind {
|
|
16
|
-
SECRET_LEGACY = 'sec-legacy',
|
|
17
|
-
ACCESS_TOKEN = 'acc',
|
|
18
|
-
ID_TOKEN = 'id',
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Infers from the given scopes whether the user can write the document (e.g.
|
|
23
|
-
* Storage and/or YDoc).
|
|
24
|
-
*/
|
|
25
|
-
export function canWriteStorage(scopes: readonly string[]): boolean {
|
|
26
|
-
return scopes.includes(Permission.Write);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function canComment(scopes: readonly string[]): boolean {
|
|
30
|
-
return (
|
|
31
|
-
scopes.includes(Permission.CommentsWrite) ||
|
|
32
|
-
scopes.includes(Permission.Write)
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type JwtMeta = {
|
|
37
|
-
iat: number;
|
|
38
|
-
exp: number;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Legacy Secret Token.
|
|
43
|
-
*/
|
|
44
|
-
export type LegacySecretToken = {
|
|
45
|
-
k: TokenKind.SECRET_LEGACY;
|
|
46
|
-
roomId: string;
|
|
47
|
-
scopes: string[];
|
|
48
|
-
|
|
49
|
-
// Extra payload as defined by the customer's own authorization
|
|
50
|
-
id?: string;
|
|
51
|
-
info?: IUserInfo;
|
|
52
|
-
|
|
53
|
-
// IMPORTANT: All other fields on the JWT token are deliberately treated as
|
|
54
|
-
// opaque, and not relied on by the client.
|
|
55
|
-
[other: string]: Json | undefined;
|
|
56
|
-
} & JwtMeta;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* New authorization Access Token.
|
|
60
|
-
*/
|
|
61
|
-
export type AccessToken = {
|
|
62
|
-
k: TokenKind.ACCESS_TOKEN;
|
|
63
|
-
pid: string; // project id
|
|
64
|
-
uid: string; // user id
|
|
65
|
-
perms: LiveblocksPermissions; // permissions
|
|
66
|
-
ui?: IUserInfo; // user info
|
|
67
|
-
} & JwtMeta;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* New authorization ID Token.
|
|
71
|
-
*/
|
|
72
|
-
export type IDToken = {
|
|
73
|
-
k: TokenKind.ID_TOKEN;
|
|
74
|
-
pid: string; // project id
|
|
75
|
-
uid: string; // user id
|
|
76
|
-
gids?: string[]; // group ids
|
|
77
|
-
ui?: IUserInfo; // user info
|
|
78
|
-
} & JwtMeta;
|
|
79
|
-
|
|
80
|
-
export type AuthToken = AccessToken | IDToken | LegacySecretToken;
|
|
81
|
-
|
|
82
|
-
// The "rich" token is data we obtain by parsing the JWT token and making all
|
|
83
|
-
// metadata on it accessible. It's done right after hitting the backend, but
|
|
84
|
-
// before the promise will get returned, so it's an inherent part of the
|
|
85
|
-
// authentication step.
|
|
86
|
-
export type ParsedAuthToken = {
|
|
87
|
-
readonly raw: string; // The raw JWT value, unchanged
|
|
88
|
-
readonly parsed: AuthToken; // Rich data on the JWT value
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
function isValidAuthTokenPayload(
|
|
92
|
-
data: Json,
|
|
93
|
-
): data is AccessToken | IDToken | LegacySecretToken {
|
|
94
|
-
return (
|
|
95
|
-
isPlainObject(data) &&
|
|
96
|
-
(data.k === TokenKind.ACCESS_TOKEN ||
|
|
97
|
-
data.k === TokenKind.ID_TOKEN ||
|
|
98
|
-
data.k === TokenKind.SECRET_LEGACY)
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Parses a raw JWT token string, which allows reading the metadata/payload of
|
|
104
|
-
* the token.
|
|
105
|
-
*
|
|
106
|
-
* NOTE: Doesn't do any validation, so always treat the metadata as other user
|
|
107
|
-
* input: never trust these values for anything important.
|
|
108
|
-
*/
|
|
109
|
-
export function parseAuthToken(rawTokenString: string): ParsedAuthToken {
|
|
110
|
-
const tokenParts = rawTokenString.split('.');
|
|
111
|
-
if (tokenParts.length !== 3) {
|
|
112
|
-
throw new Error('Authentication error: invalid JWT token');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const payload = tryParseJson(b64decode(tokenParts[1]));
|
|
116
|
-
if (!(payload && isValidAuthTokenPayload(payload))) {
|
|
117
|
-
throw new Error(
|
|
118
|
-
'Authentication error: expected a valid token but did not get one. Hint: if you are using a callback, ensure the room is passed when creating the token. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientCallback',
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
raw: rawTokenString,
|
|
124
|
-
parsed: payload,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export type CustomAuthenticationResult =
|
|
2
|
-
| { token: string; error?: never }
|
|
3
|
-
| { token?: never; error: 'forbidden'; reason: string } // Will stop retrying and disconnect
|
|
4
|
-
| { token?: never; error: string; reason: string }; // Will log the error and keep retrying
|
|
5
|
-
|
|
6
|
-
export type Authentication =
|
|
7
|
-
| {
|
|
8
|
-
type: 'public';
|
|
9
|
-
publicApiKey: string;
|
|
10
|
-
}
|
|
11
|
-
| {
|
|
12
|
-
type: 'private';
|
|
13
|
-
url: string;
|
|
14
|
-
}
|
|
15
|
-
| {
|
|
16
|
-
type: 'custom';
|
|
17
|
-
callback: (room?: string) => Promise<CustomAuthenticationResult>;
|
|
18
|
-
};
|