Skip to content

Commit 963d5f9

Browse files
committed
✨(y-provider) add endpoint returning document connection state
We need a new endpoint in the y-provider server allowing the backend to retrieve the number of active connections on a document and if a session key exists.
1 parent 1e76e6e commit 963d5f9

File tree

7 files changed

+238
-0
lines changed

7 files changed

+238
-0
lines changed
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import request from 'supertest';
2+
import { v4 as uuid } from 'uuid';
3+
4+
const port = 5555;
5+
const origin = 'http://localhost:3000';
6+
7+
jest.mock('../src/env', () => {
8+
return {
9+
PORT: port,
10+
COLLABORATION_SERVER_ORIGIN: origin,
11+
COLLABORATION_SERVER_SECRET: 'test-secret-api-key',
12+
};
13+
});
14+
15+
console.error = jest.fn();
16+
17+
import { hocusPocusServer } from '@/servers/hocusPocusServer';
18+
19+
import { initServer } from '../src/servers/appServer';
20+
21+
const { app, server } = initServer();
22+
const apiEndpoint = '/collaboration/api/get-connections/';
23+
24+
describe('Server Tests', () => {
25+
afterAll(() => {
26+
server.close();
27+
});
28+
29+
test('POST /collaboration/api/get-connections?room=[ROOM_ID] with incorrect API key should return 403', async () => {
30+
const response = await request(app as any)
31+
.get(`${apiEndpoint}?room=test-room`)
32+
.set('Origin', origin)
33+
.set('Authorization', 'wrong-api-key');
34+
35+
expect(response.status).toBe(403);
36+
expect(response.body.error).toBe('Forbidden: Invalid API Key');
37+
});
38+
39+
test('POST /collaboration/api/get-connections?room=[ROOM_ID] failed if room not indicated', async () => {
40+
const response = await request(app as any)
41+
.get(`${apiEndpoint}`)
42+
.set('Origin', origin)
43+
.set('Authorization', 'test-secret-api-key')
44+
.send({ document_id: 'test-document' });
45+
46+
expect(response.status).toBe(400);
47+
expect(response.body.error).toBe('Room name not provided');
48+
});
49+
50+
test('POST /collaboration/api/get-connections?room=[ROOM_ID] failed if session key not indicated', async () => {
51+
const response = await request(app as any)
52+
.get(`${apiEndpoint}?room=test-room`)
53+
.set('Origin', origin)
54+
.set('Authorization', 'test-secret-api-key')
55+
.send({ document_id: 'test-document' });
56+
57+
expect(response.status).toBe(400);
58+
expect(response.body.error).toBe('Session key not provided');
59+
});
60+
61+
test('POST /collaboration/api/get-connections?room=[ROOM_ID] return a 404 if room not found', async () => {
62+
const response = await request(app as any)
63+
.get(`${apiEndpoint}?room=test-room&sessionKey=test-session-key`)
64+
.set('Origin', origin)
65+
.set('Authorization', 'test-secret-api-key');
66+
67+
expect(response.status).toBe(404);
68+
expect(response.body.error).toBe('Room not found');
69+
});
70+
71+
test('POST /collaboration/api/get-connections?room=[ROOM_ID] returns connection info, session key existing', async () => {
72+
const document = await hocusPocusServer.createDocument(
73+
'test-room',
74+
{},
75+
uuid(),
76+
{ isAuthenticated: true, readOnly: false, requiresAuthentication: true },
77+
{},
78+
);
79+
80+
document.addConnection({
81+
webSocket: 1,
82+
context: { sessionKey: 'test-session-key' },
83+
document: document,
84+
pongReceived: false,
85+
request: null,
86+
timeout: 0,
87+
socketId: uuid(),
88+
lock: null,
89+
} as any);
90+
document.addConnection({
91+
webSocket: 2,
92+
context: { sessionKey: 'other-session-key' },
93+
document: document,
94+
pongReceived: false,
95+
request: null,
96+
timeout: 0,
97+
socketId: uuid(),
98+
lock: null,
99+
} as any);
100+
document.addConnection({
101+
webSocket: 3,
102+
context: { sessionKey: 'last-session-key' },
103+
document: document,
104+
pongReceived: false,
105+
request: null,
106+
timeout: 0,
107+
socketId: uuid(),
108+
lock: null,
109+
} as any);
110+
111+
const response = await request(app as any)
112+
.get(`${apiEndpoint}?room=test-room&sessionKey=test-session-key`)
113+
.set('Origin', origin)
114+
.set('Authorization', 'test-secret-api-key');
115+
116+
expect(response.status).toBe(200);
117+
expect(response.body).toEqual({
118+
count: 3,
119+
exists: true,
120+
});
121+
});
122+
123+
test('POST /collaboration/api/get-connections?room=[ROOM_ID] returns connection info, session key not existing', async () => {
124+
const document = await hocusPocusServer.createDocument(
125+
'test-room',
126+
{},
127+
uuid(),
128+
{ isAuthenticated: true, readOnly: false, requiresAuthentication: true },
129+
{},
130+
);
131+
132+
document.addConnection({
133+
webSocket: 1,
134+
context: { sessionKey: 'test-session-key' },
135+
document: document,
136+
pongReceived: false,
137+
request: null,
138+
timeout: 0,
139+
socketId: uuid(),
140+
lock: null,
141+
} as any);
142+
document.addConnection({
143+
webSocket: 2,
144+
context: { sessionKey: 'other-session-key' },
145+
document: document,
146+
pongReceived: false,
147+
request: null,
148+
timeout: 0,
149+
socketId: uuid(),
150+
lock: null,
151+
} as any);
152+
document.addConnection({
153+
webSocket: 3,
154+
context: { sessionKey: 'last-session-key' },
155+
document: document,
156+
pongReceived: false,
157+
request: null,
158+
timeout: 0,
159+
socketId: uuid(),
160+
lock: null,
161+
} as any);
162+
163+
const response = await request(app as any)
164+
.get(`${apiEndpoint}?room=test-room&sessionKey=non-existing-session-key`)
165+
.set('Origin', origin)
166+
.set('Authorization', 'test-secret-api-key');
167+
168+
expect(response.status).toBe(200);
169+
expect(response.body).toEqual({
170+
count: 3,
171+
exists: false,
172+
});
173+
});
174+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Request, Response } from 'express';
2+
3+
import { hocusPocusServer } from '@/servers/hocusPocusServer';
4+
import { logger } from '@/utils';
5+
6+
type getDocumentConnectionInfoRequestQuery = {
7+
room?: string;
8+
sessionKey?: string;
9+
};
10+
11+
export const getDocumentConnectionInfoHandler = (
12+
req: Request<object, object, object, getDocumentConnectionInfoRequestQuery>,
13+
res: Response,
14+
) => {
15+
const room = req.query.room;
16+
const sessionKey = req.query.sessionKey;
17+
18+
if (!room) {
19+
res.status(400).json({ error: 'Room name not provided' });
20+
return;
21+
}
22+
23+
if (!req.query.sessionKey) {
24+
res.status(400).json({ error: 'Session key not provided' });
25+
return;
26+
}
27+
28+
logger('Getting document connection info for room:', room);
29+
30+
const roomInfo = hocusPocusServer.documents.get(room);
31+
32+
if (!roomInfo) {
33+
logger('Room not found:', room);
34+
res.status(404).json({ error: 'Room not found' });
35+
return;
36+
}
37+
const connections = roomInfo.getConnections();
38+
39+
res.status(200).json({
40+
count: connections.length,
41+
exists: connections.some(
42+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
43+
(connection) => connection.context.sessionKey === sessionKey,
44+
),
45+
});
46+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './collaborationResetConnectionsHandler';
22
export * from './collaborationWSHandler';
33
export * from './convertMarkdownHandler';
4+
export * from './getDocumentConnectionInfoHandler';

src/frontend/servers/y-provider/src/middlewares.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const httpSecurity = (
2828
// Note: Changing this header to Bearer token format will break backend compatibility with this microservice.
2929
const apiKey = req.headers['authorization'];
3030
if (!apiKey || !VALID_API_KEYS.includes(apiKey)) {
31+
logger('Forbidden: Invalid API Key', apiKey);
3132
res.status(403).json({ error: 'Forbidden: Invalid API Key' });
3233
return;
3334
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export const routes = {
22
COLLABORATION_WS: '/collaboration/ws/',
33
COLLABORATION_RESET_CONNECTIONS: '/collaboration/api/reset-connections/',
4+
COLLABORATION_GET_CONNECTIONS: '/collaboration/api/get-connections/',
45
CONVERT_MARKDOWN: '/api/convert-markdown/',
56
};

src/frontend/servers/y-provider/src/servers/appServer.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
collaborationResetConnectionsHandler,
1010
collaborationWSHandler,
1111
convertMarkdownHandler,
12+
getDocumentConnectionInfoHandler,
1213
} from '../handlers';
1314
import { corsMiddleware, httpSecurity, wsSecurity } from '../middlewares';
1415
import { routes } from '../routes';
@@ -46,6 +47,12 @@ export const initServer = () => {
4647
collaborationResetConnectionsHandler,
4748
);
4849

50+
app.get(
51+
routes.COLLABORATION_GET_CONNECTIONS,
52+
httpSecurity,
53+
getDocumentConnectionInfoHandler,
54+
);
55+
4956
/**
5057
* Route to convert markdown
5158
*/

src/frontend/servers/y-provider/src/servers/hocusPocusServer.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ export const hocusPocusServer = Server.configure({
6161

6262
connection.readOnly = !can_edit;
6363

64+
const session = requestHeaders['cookie']
65+
?.split('; ')
66+
.find((cookie) => cookie.startsWith('docs_sessionid='));
67+
if (session) {
68+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
69+
context.sessionKey = session.split('=')[1];
70+
}
71+
6472
/*
6573
* Unauthenticated users can be allowed to connect
6674
* so we flag only authenticated users

0 commit comments

Comments
 (0)