@builder6/rooms 3.0.4 → 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
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
WebSocketGateway,
|
|
3
|
-
WebSocketServer,
|
|
4
|
-
OnGatewayConnection,
|
|
5
|
-
SubscribeMessage,
|
|
6
|
-
} from '@nestjs/websockets';
|
|
7
|
-
import { WebSocket, Server } from 'ws';
|
|
8
|
-
import ws from 'ws';
|
|
9
|
-
import Redis from 'ioredis';
|
|
10
|
-
import url from 'url';
|
|
11
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
12
|
-
import { ServerMsgCode, ServerMsg } from './protocol/ServerMsg';
|
|
13
|
-
import { JwtService } from '@nestjs/jwt';
|
|
14
|
-
import { RoomsService } from './rooms.service';
|
|
15
|
-
|
|
16
|
-
interface RoomState {
|
|
17
|
-
roomId: string;
|
|
18
|
-
connections: {
|
|
19
|
-
[connectionId: number]: {
|
|
20
|
-
userId: string;
|
|
21
|
-
nonce: string;
|
|
22
|
-
roomId: string;
|
|
23
|
-
client: WebSocket; // 新增字段,保存客户端连接对象
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
@WebSocketGateway({
|
|
29
|
-
path: '/v7',
|
|
30
|
-
cors: {
|
|
31
|
-
origin: '*',
|
|
32
|
-
},
|
|
33
|
-
})
|
|
34
|
-
export class RoomsGateway implements OnGatewayConnection {
|
|
35
|
-
@WebSocketServer() server: Server;
|
|
36
|
-
|
|
37
|
-
private roomStates: Map<string, RoomState> = new Map();
|
|
38
|
-
private redis: Redis;
|
|
39
|
-
private pubRedis: Redis;
|
|
40
|
-
|
|
41
|
-
constructor(
|
|
42
|
-
private jwtService: JwtService,
|
|
43
|
-
private roomsService: RoomsService,
|
|
44
|
-
) {
|
|
45
|
-
this.redis = new Redis(process.env.B6_CLUSTER_TRANSPORTER);
|
|
46
|
-
this.pubRedis = new Redis(process.env.B6_CLUSTER_TRANSPORTER);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async afterInit() {
|
|
50
|
-
console.log('Rooms Gateway Initialized with Redis');
|
|
51
|
-
|
|
52
|
-
// Subscribe to Redis pub/sub channels for cluster-wide communication
|
|
53
|
-
this.redis.subscribe('rooms_channel', (err) => {
|
|
54
|
-
if (err) {
|
|
55
|
-
console.error('Failed to subscribe: ', err);
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
this.redis.on('message', (channel, message) => {
|
|
60
|
-
if (channel === 'rooms_channel') {
|
|
61
|
-
const parsedMessage = JSON.parse(message);
|
|
62
|
-
this.handleBroadcastedMessage(parsedMessage);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async handleConnection(client: WebSocket, req: any) {
|
|
68
|
-
const parsedUrl = url.parse(req.url, true);
|
|
69
|
-
|
|
70
|
-
const roomId = parsedUrl.query['roomId'] as string;
|
|
71
|
-
const token = parsedUrl.query['tok'] as string;
|
|
72
|
-
const jwt = (await this.jwtService.decode(token)) as any;
|
|
73
|
-
const userId = jwt.uid;
|
|
74
|
-
|
|
75
|
-
if (userId && roomId) {
|
|
76
|
-
client.on('message', (message: Buffer) => {
|
|
77
|
-
const messageStr = message.toString();
|
|
78
|
-
console.log('Received message from client: ', messageStr);
|
|
79
|
-
|
|
80
|
-
// Add ping/pong support to keep the connection alive
|
|
81
|
-
if (messageStr === 'ping') {
|
|
82
|
-
client.send('pong'); // 响应客户端的 ping,回复 pong 消息
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const parsedMessage = JSON.parse(messageStr);
|
|
87
|
-
// 判断如果是数组,则循环调用 handleClientMessage
|
|
88
|
-
if (Array.isArray(parsedMessage)) {
|
|
89
|
-
parsedMessage.forEach((msg) => {
|
|
90
|
-
this.handleClientMessage(client, msg);
|
|
91
|
-
});
|
|
92
|
-
} else if (typeof parsedMessage === 'object') {
|
|
93
|
-
this.handleClientMessage(client, parsedMessage);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// 监听客户端关闭事件,执行离开房间的操作
|
|
98
|
-
client.on('close', () => {
|
|
99
|
-
this.handleLeaveRoom(client, { roomId, userId });
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
await this.handleJoinRoom(client, { roomId, userId });
|
|
103
|
-
} else {
|
|
104
|
-
client.close();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async handleClientMessage(client: WebSocket, parsedMessage: any) {
|
|
109
|
-
console.log('Received message from client: ', parsedMessage);
|
|
110
|
-
if (parsedMessage.type === ServerMsgCode.UPDATE_PRESENCE) {
|
|
111
|
-
this.handleUpdatePresence(client, parsedMessage);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async handleJoinRoom(
|
|
116
|
-
client: WebSocket,
|
|
117
|
-
payload: { roomId: string; userId: string },
|
|
118
|
-
) {
|
|
119
|
-
const { roomId, userId } = payload;
|
|
120
|
-
|
|
121
|
-
const room = await this.roomsService.getRoom(roomId);
|
|
122
|
-
if (!room) {
|
|
123
|
-
throw new Error('Room not found');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
let roomState = this.roomStates.get(roomId);
|
|
127
|
-
|
|
128
|
-
if (!roomState) {
|
|
129
|
-
roomState = { roomId, connections: {} };
|
|
130
|
-
this.roomStates.set(roomId, roomState);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Increment connection ID using Redis to ensure consistency across instances
|
|
134
|
-
const connectionId = await this.pubRedis.incr(
|
|
135
|
-
`room:${roomId}:connectionId`,
|
|
136
|
-
);
|
|
137
|
-
const nonce = uuidv4();
|
|
138
|
-
roomState.connections[connectionId] = { userId, nonce, roomId, client }; // 保存 client
|
|
139
|
-
|
|
140
|
-
// Store client connection information in Redis for future communication
|
|
141
|
-
await this.pubRedis.hset(
|
|
142
|
-
`room:${roomId}:connections`,
|
|
143
|
-
connectionId.toString(),
|
|
144
|
-
JSON.stringify({ userId, nonce, roomId }),
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
// Attach connectionId to client for future reference
|
|
148
|
-
(client as any).connectionId = connectionId;
|
|
149
|
-
|
|
150
|
-
// Send initial ROOM_STATE to the user using RoomsService
|
|
151
|
-
const users = await Promise.all(
|
|
152
|
-
Object.keys(roomState.connections).map(async (connId) => {
|
|
153
|
-
const connection = roomState.connections[parseInt(connId)];
|
|
154
|
-
const roomId = connection.roomId;
|
|
155
|
-
const member = await this.roomsService.getRoomMember(
|
|
156
|
-
roomId,
|
|
157
|
-
connection.userId,
|
|
158
|
-
);
|
|
159
|
-
return {
|
|
160
|
-
connectionId: parseInt(connId),
|
|
161
|
-
userId: connection.userId,
|
|
162
|
-
scopes: member.scopes,
|
|
163
|
-
};
|
|
164
|
-
}),
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
const roomStateResponse = {
|
|
168
|
-
type: ServerMsgCode.ROOM_STATE, // 使用 ServerMsgCode 枚举
|
|
169
|
-
actor: connectionId,
|
|
170
|
-
nonce: nonce,
|
|
171
|
-
scopes: room.defaultScopes,
|
|
172
|
-
users: users.reduce(
|
|
173
|
-
(acc, user) => {
|
|
174
|
-
acc[user.connectionId] = {
|
|
175
|
-
id: user.userId,
|
|
176
|
-
scopes: user.scopes,
|
|
177
|
-
};
|
|
178
|
-
return acc;
|
|
179
|
-
},
|
|
180
|
-
{} as { [key: number]: { id: string; scopes: string[] } },
|
|
181
|
-
),
|
|
182
|
-
};
|
|
183
|
-
client.send(JSON.stringify(roomStateResponse));
|
|
184
|
-
|
|
185
|
-
// Notify other users in the room about the new user joined
|
|
186
|
-
this.broadcastToRoom(roomId, {
|
|
187
|
-
type: ServerMsgCode.USER_JOINED, // 使用 ServerMsgCode 枚举
|
|
188
|
-
actor: connectionId,
|
|
189
|
-
userId,
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
console.log(`User ${connectionId},${userId} has join room: ${roomId}`);
|
|
193
|
-
return connectionId;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async handleLeaveRoom(
|
|
197
|
-
client: WebSocket,
|
|
198
|
-
payload: { roomId: string; userId: string },
|
|
199
|
-
) {
|
|
200
|
-
const { roomId, userId } = payload;
|
|
201
|
-
const connectionId = (client as any).connectionId;
|
|
202
|
-
|
|
203
|
-
// 获取房间状态
|
|
204
|
-
const roomState = this.roomStates.get(roomId);
|
|
205
|
-
if (roomState && roomState.connections[connectionId]) {
|
|
206
|
-
// 移除内存中的连接信息
|
|
207
|
-
delete roomState.connections[connectionId];
|
|
208
|
-
|
|
209
|
-
// 同时从 Redis 中删除连接信息
|
|
210
|
-
await this.pubRedis.hdel(
|
|
211
|
-
`room:${roomId}:connections`,
|
|
212
|
-
connectionId.toString(),
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
// 如果房间中已经没有连接,清理房间状态
|
|
216
|
-
if (Object.keys(roomState.connections).length === 0) {
|
|
217
|
-
this.roomStates.delete(roomId);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// 通知房间内其他用户,该用户已经离开房间
|
|
221
|
-
this.broadcastToRoom(roomId, {
|
|
222
|
-
type: ServerMsgCode.USER_LEFT, // 使用 ServerMsgCode 枚举
|
|
223
|
-
actor: connectionId,
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
console.log(`User ${connectionId},${userId} has left room: ${roomId}`);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async handleUpdatePresence(
|
|
231
|
-
client: WebSocket,
|
|
232
|
-
payload: { roomId: string; connectionId: number; data: any },
|
|
233
|
-
) {
|
|
234
|
-
// const { roomId, connectionId, data } = payload;
|
|
235
|
-
// this.broadcastToRoom(roomId, {
|
|
236
|
-
// type: ServerMsgCode.UPDATE_PRESENCE, // 使用 ServerMsgCode 枚举
|
|
237
|
-
// actor: connectionId,
|
|
238
|
-
// data,
|
|
239
|
-
// });
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
public broadcastToRoom(roomId: string, message: any) {
|
|
243
|
-
// Publish the message to Redis so that other instances can receive it
|
|
244
|
-
this.pubRedis.publish('rooms_channel', JSON.stringify({ roomId, message }));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
private handleBroadcastedMessage(parsedMessage: {
|
|
248
|
-
roomId: string;
|
|
249
|
-
message: any;
|
|
250
|
-
}) {
|
|
251
|
-
const { roomId, message } = parsedMessage;
|
|
252
|
-
const roomState = this.roomStates.get(roomId);
|
|
253
|
-
|
|
254
|
-
if (roomState) {
|
|
255
|
-
Object.keys(roomState.connections).forEach((connectionId) => {
|
|
256
|
-
const client = roomState.connections[connectionId].client;
|
|
257
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
258
|
-
client.send(JSON.stringify(message));
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
async getUserScopes(roomId: string, userId: string) {
|
|
265
|
-
return ['room:write', 'comments:write'];
|
|
266
|
-
}
|
|
267
|
-
}
|
package/src/rooms/rooms.guard.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CanActivate,
|
|
3
|
-
ExecutionContext,
|
|
4
|
-
Injectable,
|
|
5
|
-
UnauthorizedException,
|
|
6
|
-
} from '@nestjs/common';
|
|
7
|
-
import { JwtService } from '@nestjs/jwt';
|
|
8
|
-
import { AuthGuard } from '@builder6/core';
|
|
9
|
-
|
|
10
|
-
@Injectable()
|
|
11
|
-
export class RoomsGuard implements CanActivate {
|
|
12
|
-
constructor(
|
|
13
|
-
// private roomsService: RoomsService,
|
|
14
|
-
private jwtService: JwtService,
|
|
15
|
-
private authGuard: AuthGuard,
|
|
16
|
-
) {}
|
|
17
|
-
|
|
18
|
-
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
19
|
-
const request = context.switchToHttp().getRequest();
|
|
20
|
-
try {
|
|
21
|
-
if ((await this.authGuard.canActivate(context)) === true) {
|
|
22
|
-
const user = request['user'];
|
|
23
|
-
request['jwt'] = {
|
|
24
|
-
k: 'acc',
|
|
25
|
-
pid: user.space,
|
|
26
|
-
sid: user.space,
|
|
27
|
-
uid: user._id,
|
|
28
|
-
mcpr: 10,
|
|
29
|
-
perms: {},
|
|
30
|
-
jti: 'S4EMiESTDe6k',
|
|
31
|
-
};
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
} catch {
|
|
35
|
-
const [type, bearerToken] =
|
|
36
|
-
request.headers.authorization?.split(' ') ?? [];
|
|
37
|
-
const token = type === 'Bearer' ? bearerToken : undefined;
|
|
38
|
-
try {
|
|
39
|
-
if (token && this.jwtService.verify(token)) {
|
|
40
|
-
// 💡 We're assigning the payload to the request object here
|
|
41
|
-
// so that we can access it in our route handlers
|
|
42
|
-
const jwt = await this.jwtService.decode(token);
|
|
43
|
-
request['jwt'] = jwt;
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
} catch {
|
|
47
|
-
throw new UnauthorizedException('Rooms token not valid.');
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
throw new UnauthorizedException('Rooms token not valid.');
|
|
51
|
-
}
|
|
52
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { Module } from '@nestjs/common';
|
|
2
|
-
import { RoomsController } from './rooms.controller';
|
|
3
|
-
import { RoomsService } from './rooms.service';
|
|
4
|
-
import { MongodbModule } from '@builder6/core';
|
|
5
|
-
import { JwtModule } from '@nestjs/jwt';
|
|
6
|
-
import { AuthModule } from '@builder6/core';
|
|
7
|
-
import { RoomsGateway } from './rooms.gateway';
|
|
8
|
-
import { FilesModule } from '@builder6/files';
|
|
9
|
-
import { PagesModule } from '@builder6/pages';
|
|
10
|
-
import { NotificationsService } from './notifications.service';
|
|
11
|
-
import { RoomsAppController } from './app.controller';
|
|
12
|
-
import { RoomsMoleculer } from './rooms.moleculer';
|
|
13
|
-
import { EmailNotificationService } from './emailNotification.service';
|
|
14
|
-
import { EmailModule } from '@builder6/email';
|
|
15
|
-
|
|
16
|
-
/* 按照 liveblocks.io 规范实现的API */
|
|
17
|
-
@Module({
|
|
18
|
-
imports: [
|
|
19
|
-
AuthModule,
|
|
20
|
-
MongodbModule,
|
|
21
|
-
EmailModule,
|
|
22
|
-
FilesModule,
|
|
23
|
-
PagesModule,
|
|
24
|
-
JwtModule.register({
|
|
25
|
-
secret: process.env.B6_JWT_SECRET || 'secret',
|
|
26
|
-
signOptions: { expiresIn: '60s' },
|
|
27
|
-
}),
|
|
28
|
-
],
|
|
29
|
-
controllers: [RoomsAppController, RoomsController],
|
|
30
|
-
providers: [
|
|
31
|
-
RoomsService,
|
|
32
|
-
RoomsGateway,
|
|
33
|
-
NotificationsService,
|
|
34
|
-
EmailNotificationService,
|
|
35
|
-
RoomsMoleculer,
|
|
36
|
-
],
|
|
37
|
-
})
|
|
38
|
-
export class RoomsModule {}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { Service, Context, ServiceBroker } from 'moleculer';
|
|
2
|
-
import { Injectable } from '@nestjs/common';
|
|
3
|
-
import { InjectBroker } from '@builder6/moleculer';
|
|
4
|
-
import { RoomsService } from './rooms.service';
|
|
5
|
-
|
|
6
|
-
@Injectable()
|
|
7
|
-
export class RoomsMoleculer extends Service {
|
|
8
|
-
constructor(
|
|
9
|
-
private readonly roomsService: RoomsService,
|
|
10
|
-
@InjectBroker() broker: ServiceBroker,
|
|
11
|
-
) {
|
|
12
|
-
super(broker);
|
|
13
|
-
|
|
14
|
-
this.parseServiceSchema({
|
|
15
|
-
name: '@builder6/rooms',
|
|
16
|
-
settings: {},
|
|
17
|
-
actions: {
|
|
18
|
-
createComment: this.createComment,
|
|
19
|
-
createThread: this.createThread,
|
|
20
|
-
},
|
|
21
|
-
created: this.serviceCreated,
|
|
22
|
-
started: this.serviceStarted,
|
|
23
|
-
stopped: this.serviceStopped,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
serviceCreated() {}
|
|
28
|
-
|
|
29
|
-
async serviceStarted() {}
|
|
30
|
-
|
|
31
|
-
async serviceStopped() {}
|
|
32
|
-
|
|
33
|
-
async createThread(ctx: Context) {
|
|
34
|
-
const { id, comment, metadata, resolved, roomId, userId, space } =
|
|
35
|
-
ctx.params as any;
|
|
36
|
-
try {
|
|
37
|
-
const result = this.roomsService.createThread({
|
|
38
|
-
id,
|
|
39
|
-
comment,
|
|
40
|
-
metadata,
|
|
41
|
-
resolved,
|
|
42
|
-
roomId,
|
|
43
|
-
userId,
|
|
44
|
-
space,
|
|
45
|
-
});
|
|
46
|
-
return result;
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error(error);
|
|
49
|
-
return {
|
|
50
|
-
error: {
|
|
51
|
-
message: error.message,
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async createComment(ctx: Context) {
|
|
58
|
-
const { id, attachmentIds, body, roomId, threadId, userId, space } =
|
|
59
|
-
ctx.params as any;
|
|
60
|
-
try {
|
|
61
|
-
const result = this.roomsService.createComment({
|
|
62
|
-
id,
|
|
63
|
-
attachmentIds,
|
|
64
|
-
body,
|
|
65
|
-
roomId,
|
|
66
|
-
threadId,
|
|
67
|
-
userId,
|
|
68
|
-
space,
|
|
69
|
-
});
|
|
70
|
-
return result;
|
|
71
|
-
} catch (error) {
|
|
72
|
-
console.error(error);
|
|
73
|
-
return {
|
|
74
|
-
error: {
|
|
75
|
-
message: error.message,
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|