mcp-excalidraw-server 1.0.0
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/LICENSE +21 -0
- package/README.md +424 -0
- package/dist/assets/Assistant-Bold-gm-uSS1B.woff2 +0 -0
- package/dist/assets/Assistant-Medium-DrcxCXg3.woff2 +0 -0
- package/dist/assets/Assistant-Regular-DVxZuzxb.woff2 +0 -0
- package/dist/assets/Assistant-SemiBold-SCI4bEL9.woff2 +0 -0
- package/dist/assets/Tableau10-B-NsZVaP.js +1 -0
- package/dist/assets/_commonjs-dynamic-modules-TDtrdbi3.js +1 -0
- package/dist/assets/ar-SA-G6X2FPQ2-BS3fMnev.js +10 -0
- package/dist/assets/arc-0Fwkaye8.js +1 -0
- package/dist/assets/array-BKyUJesY.js +1 -0
- package/dist/assets/az-AZ-76LH7QW2-DNgzWA8S.js +1 -0
- package/dist/assets/bg-BG-XCXSNQG7-CULoJKdI.js +5 -0
- package/dist/assets/blockDiagram-38ab4fdb-CVOkr1Ma.js +118 -0
- package/dist/assets/bn-BD-2XOGV67Q-9DmWphrb.js +5 -0
- package/dist/assets/c4Diagram-3d4e48cf-BwpfSRgq.js +10 -0
- package/dist/assets/ca-ES-6MX7JW3Y-gYoEYP36.js +8 -0
- package/dist/assets/channel-BgX0NHoH.js +1 -0
- package/dist/assets/classDiagram-70f12bd4-ftK2tRy5.js +2 -0
- package/dist/assets/classDiagram-v2-f2320105--Nu78BDB.js +2 -0
- package/dist/assets/clone-sj1RjrIX.js +1 -0
- package/dist/assets/createText-2e5e7dd3-DPYwmS_z.js +7 -0
- package/dist/assets/cs-CZ-2BRQDIVT-DgwhrQZi.js +11 -0
- package/dist/assets/da-DK-5WZEPLOC-BL1Ng5As.js +5 -0
- package/dist/assets/de-DE-XR44H4JA-Cw-ySDmq.js +8 -0
- package/dist/assets/directory-open-01563666-DWU9wJ6I.js +1 -0
- package/dist/assets/directory-open-4ed118d0-BzWybGaI.js +1 -0
- package/dist/assets/edges-e0da2a9e-CB2w3jS2.js +4 -0
- package/dist/assets/el-GR-BZB4AONW-BPJAfWZm.js +10 -0
- package/dist/assets/erDiagram-9861fffd-VwqM-D3G.js +51 -0
- package/dist/assets/es-ES-U4NZUMDT-oMrzPWSn.js +9 -0
- package/dist/assets/eu-ES-A7QVB2H4-DFshkDl1.js +11 -0
- package/dist/assets/fa-IR-HGAKTJCU-DVU2rysM.js +8 -0
- package/dist/assets/fi-FI-Z5N7JZ37-BFKGqZcw.js +6 -0
- package/dist/assets/file-open-002ab408-DIuFHtCF.js +1 -0
- package/dist/assets/file-open-7c801643-684qeFg4.js +1 -0
- package/dist/assets/file-save-3189631c-x92wctJd.js +1 -0
- package/dist/assets/file-save-745eba88-Bb9F9Kg7.js +1 -0
- package/dist/assets/flowDb-956e92f1-CZj2LmRK.js +10 -0
- package/dist/assets/flowDiagram-66a62f08-D_vhln5h.js +4 -0
- package/dist/assets/flowDiagram-v2-96b9c2cf-CShVLez9.js +1 -0
- package/dist/assets/flowchart-elk-definition-4a651766-BJYKnLZA.js +139 -0
- package/dist/assets/fr-FR-RHASNOE6-M3Leevu1.js +9 -0
- package/dist/assets/ganttDiagram-c361ad54-Dl0RXJiR.js +257 -0
- package/dist/assets/gitGraphDiagram-72cf32ee-Cckr93z5.js +70 -0
- package/dist/assets/gl-ES-HMX3MZ6V-CM77q-gv.js +10 -0
- package/dist/assets/graph-CsvWB58Y.js +1 -0
- package/dist/assets/he-IL-6SHJWFNN-B3UI_ebx.js +10 -0
- package/dist/assets/hi-IN-IWLTKZ5I-DUAO_Nd9.js +4 -0
- package/dist/assets/hu-HU-A5ZG7DT2-Dm7uZbxs.js +7 -0
- package/dist/assets/id-ID-SAP4L64H-sn247bNe.js +10 -0
- package/dist/assets/image-blob-reduce.esm-B6b2_-a4.js +7 -0
- package/dist/assets/index-3862675e-pSTifqE7.js +1 -0
- package/dist/assets/index-CMTjDsOp.js +97 -0
- package/dist/assets/infoDiagram-f8f76790-Cy0QfHmF.js +7 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/it-IT-JPQ66NNP-C3cx3eLC.js +11 -0
- package/dist/assets/ja-JP-DBVTYXUO-P2fPONfc.js +8 -0
- package/dist/assets/journeyDiagram-49397b02-AWYgjzgS.js +139 -0
- package/dist/assets/kaa-6HZHGXH3-wmoL7do0.js +1 -0
- package/dist/assets/kab-KAB-ZGHBKWFO-DNNu0Xkz.js +8 -0
- package/dist/assets/katex-ChWnQ-fc.js +261 -0
- package/dist/assets/kk-KZ-P5N5QNE5-BZEEoSCw.js +1 -0
- package/dist/assets/km-KH-HSX4SM5Z-4Sf3SjAI.js +11 -0
- package/dist/assets/ko-KR-MTYHY66A-DTzTxHW5.js +9 -0
- package/dist/assets/ku-TR-6OUDTVRD-eWqCXim6.js +9 -0
- package/dist/assets/layout-ErVDAsYA.js +1 -0
- package/dist/assets/line-Cvp4skCK.js +1 -0
- package/dist/assets/linear-DlBkj_hR.js +1 -0
- package/dist/assets/lt-LT-XHIRWOB4-BzqmXLnL.js +3 -0
- package/dist/assets/lv-LV-5QDEKY6T-alH_HCjW.js +7 -0
- package/dist/assets/main-B9Rh8YyQ.css +1 -0
- package/dist/assets/main-C1TkgC3p.js +254 -0
- package/dist/assets/mindmap-definition-fc14e90a-DQB0p-81.js +425 -0
- package/dist/assets/mr-IN-CRQNXWMA-Cq2VIpVa.js +13 -0
- package/dist/assets/my-MM-5M5IBNSE-CBPHDdNv.js +1 -0
- package/dist/assets/nb-NO-T6EIAALU-BUNp_A07.js +10 -0
- package/dist/assets/nl-NL-IS3SIHDZ-CNz8gWhx.js +8 -0
- package/dist/assets/nn-NO-6E72VCQL-BFz6U-AT.js +8 -0
- package/dist/assets/oc-FR-POXYY2M6-BjY1Oz8X.js +8 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pa-IN-N4M65BXN-bLY9e9o3.js +4 -0
- package/dist/assets/path-CbwjOpE9.js +1 -0
- package/dist/assets/pica-JUO0Loj6.js +7 -0
- package/dist/assets/pieDiagram-8a3498a8-BD6Rsa6P.js +35 -0
- package/dist/assets/pl-PL-T2D74RX3-CpLUuJZ4.js +9 -0
- package/dist/assets/pt-BR-5N22H2LF-DXSmPIb5.js +9 -0
- package/dist/assets/pt-PT-UZXXM6DQ-CtilzVM5.js +9 -0
- package/dist/assets/quadrantDiagram-120e2f19-CvG-FK5I.js +7 -0
- package/dist/assets/requirementDiagram-deff3bca-CVh-bBIY.js +52 -0
- package/dist/assets/ro-RO-JPDTUUEW-4SIE29UH.js +11 -0
- package/dist/assets/roundRect-0PYZxl1G.js +1 -0
- package/dist/assets/ru-RU-B4JR7IUQ-yWKAM4lo.js +9 -0
- package/dist/assets/sankeyDiagram-04a897e0-Bb-ywaQF.js +8 -0
- package/dist/assets/sequenceDiagram-704730f1-BZu5e-R7.js +122 -0
- package/dist/assets/si-LK-N5RQ5JYF-D-XE0eOh.js +1 -0
- package/dist/assets/sk-SK-C5VTKIMK-DTdiYCrX.js +6 -0
- package/dist/assets/sl-SI-NN7IZMDC-qcTJE3hs.js +6 -0
- package/dist/assets/stateDiagram-587899a1-CoklS1cy.js +1 -0
- package/dist/assets/stateDiagram-v2-d93cdb3a-JBfFG9Hu.js +1 -0
- package/dist/assets/styles-6aaf32cf-Cg7CSuxw.js +207 -0
- package/dist/assets/styles-9a916d00-Ny9GZRfC.js +160 -0
- package/dist/assets/styles-c10674c1-BMpWBEge.js +116 -0
- package/dist/assets/subset-shared.chunk-tHzjs2ro.js +84 -0
- package/dist/assets/subset-worker.chunk-CuKy9sB1.js +1 -0
- package/dist/assets/sv-SE-XGPEYMSR-Bi6tm-oG.js +10 -0
- package/dist/assets/svgDrawCommon-08f97a94-CEEsVKXL.js +1 -0
- package/dist/assets/ta-IN-2NMHFXQM-C7Igaqv2.js +9 -0
- package/dist/assets/th-TH-HPSO5L25-BdAfwagC.js +2 -0
- package/dist/assets/timeline-definition-85554ec2-DsgJKruU.js +61 -0
- package/dist/assets/tr-TR-DEFEU3FU-BPBRoOZh.js +7 -0
- package/dist/assets/uk-UA-QMV73CPH-CeJGl_H3.js +6 -0
- package/dist/assets/vi-VN-M7AON7JQ-D537-quZ.js +5 -0
- package/dist/assets/xychartDiagram-e933f94c-DqfSpZR2.js +7 -0
- package/dist/assets/zh-CN-LNUGB5OW-Bd8rr_cr.js +10 -0
- package/dist/assets/zh-HK-E62DVLB3-Y1MdNiXR.js +1 -0
- package/dist/assets/zh-TW-RAJ6MFWO-BAzsCe6B.js +9 -0
- package/dist/frontend/index.html +241 -0
- package/package.json +78 -0
- package/src/index.js +1002 -0
- package/src/server.js +377 -0
- package/src/types.js +36 -0
- package/src/utils/logger.js +27 -0
package/src/server.js
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import { WebSocketServer } from 'ws';
|
|
4
|
+
import { createServer } from 'http';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import dotenv from 'dotenv';
|
|
8
|
+
import logger from './utils/logger.js';
|
|
9
|
+
import {
|
|
10
|
+
elements,
|
|
11
|
+
generateId,
|
|
12
|
+
EXCALIDRAW_ELEMENT_TYPES
|
|
13
|
+
} from './types.js';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
|
|
16
|
+
// Load environment variables
|
|
17
|
+
dotenv.config();
|
|
18
|
+
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = path.dirname(__filename);
|
|
21
|
+
|
|
22
|
+
const app = express();
|
|
23
|
+
const server = createServer(app);
|
|
24
|
+
const wss = new WebSocketServer({ server });
|
|
25
|
+
|
|
26
|
+
// Middleware
|
|
27
|
+
app.use(cors());
|
|
28
|
+
app.use(express.json());
|
|
29
|
+
|
|
30
|
+
// Serve static files from the build directory
|
|
31
|
+
const staticDir = path.join(__dirname, '../dist');
|
|
32
|
+
app.use(express.static(staticDir));
|
|
33
|
+
|
|
34
|
+
// WebSocket connections
|
|
35
|
+
const clients = new Set();
|
|
36
|
+
|
|
37
|
+
// Broadcast to all connected clients
|
|
38
|
+
function broadcast(message) {
|
|
39
|
+
const data = JSON.stringify(message);
|
|
40
|
+
clients.forEach(client => {
|
|
41
|
+
if (client.readyState === client.OPEN) {
|
|
42
|
+
client.send(data);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// WebSocket connection handling
|
|
48
|
+
wss.on('connection', (ws) => {
|
|
49
|
+
clients.add(ws);
|
|
50
|
+
logger.info('New WebSocket connection established');
|
|
51
|
+
|
|
52
|
+
// Send current elements to new client
|
|
53
|
+
ws.send(JSON.stringify({
|
|
54
|
+
type: 'initial_elements',
|
|
55
|
+
elements: Array.from(elements.values())
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
ws.on('close', () => {
|
|
59
|
+
clients.delete(ws);
|
|
60
|
+
logger.info('WebSocket connection closed');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
ws.on('error', (error) => {
|
|
64
|
+
logger.error('WebSocket error:', error);
|
|
65
|
+
clients.delete(ws);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Schema validation
|
|
70
|
+
const CreateElementSchema = z.object({
|
|
71
|
+
type: z.enum(Object.values(EXCALIDRAW_ELEMENT_TYPES)),
|
|
72
|
+
x: z.number(),
|
|
73
|
+
y: z.number(),
|
|
74
|
+
width: z.number().optional(),
|
|
75
|
+
height: z.number().optional(),
|
|
76
|
+
backgroundColor: z.string().optional(),
|
|
77
|
+
strokeColor: z.string().optional(),
|
|
78
|
+
strokeWidth: z.number().optional(),
|
|
79
|
+
roughness: z.number().optional(),
|
|
80
|
+
opacity: z.number().optional(),
|
|
81
|
+
text: z.string().optional(),
|
|
82
|
+
fontSize: z.number().optional(),
|
|
83
|
+
fontFamily: z.string().optional()
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const UpdateElementSchema = z.object({
|
|
87
|
+
id: z.string(),
|
|
88
|
+
type: z.enum(Object.values(EXCALIDRAW_ELEMENT_TYPES)).optional(),
|
|
89
|
+
x: z.number().optional(),
|
|
90
|
+
y: z.number().optional(),
|
|
91
|
+
width: z.number().optional(),
|
|
92
|
+
height: z.number().optional(),
|
|
93
|
+
backgroundColor: z.string().optional(),
|
|
94
|
+
strokeColor: z.string().optional(),
|
|
95
|
+
strokeWidth: z.number().optional(),
|
|
96
|
+
roughness: z.number().optional(),
|
|
97
|
+
opacity: z.number().optional(),
|
|
98
|
+
text: z.string().optional(),
|
|
99
|
+
fontSize: z.number().optional(),
|
|
100
|
+
fontFamily: z.string().optional()
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// API Routes
|
|
104
|
+
|
|
105
|
+
// Get all elements
|
|
106
|
+
app.get('/api/elements', (req, res) => {
|
|
107
|
+
try {
|
|
108
|
+
const elementsArray = Array.from(elements.values());
|
|
109
|
+
res.json({
|
|
110
|
+
success: true,
|
|
111
|
+
elements: elementsArray,
|
|
112
|
+
count: elementsArray.length
|
|
113
|
+
});
|
|
114
|
+
} catch (error) {
|
|
115
|
+
logger.error('Error fetching elements:', error);
|
|
116
|
+
res.status(500).json({
|
|
117
|
+
success: false,
|
|
118
|
+
error: error.message
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Create new element
|
|
124
|
+
app.post('/api/elements', (req, res) => {
|
|
125
|
+
try {
|
|
126
|
+
const params = CreateElementSchema.parse(req.body);
|
|
127
|
+
logger.info('Creating element via API', { type: params.type });
|
|
128
|
+
|
|
129
|
+
const id = generateId();
|
|
130
|
+
const element = {
|
|
131
|
+
id,
|
|
132
|
+
...params,
|
|
133
|
+
createdAt: new Date().toISOString(),
|
|
134
|
+
updatedAt: new Date().toISOString(),
|
|
135
|
+
version: 1
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
elements.set(id, element);
|
|
139
|
+
|
|
140
|
+
// Broadcast to all connected clients
|
|
141
|
+
broadcast({
|
|
142
|
+
type: 'element_created',
|
|
143
|
+
element: element
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
res.json({
|
|
147
|
+
success: true,
|
|
148
|
+
element: element
|
|
149
|
+
});
|
|
150
|
+
} catch (error) {
|
|
151
|
+
logger.error('Error creating element:', error);
|
|
152
|
+
res.status(400).json({
|
|
153
|
+
success: false,
|
|
154
|
+
error: error.message
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Update element
|
|
160
|
+
app.put('/api/elements/:id', (req, res) => {
|
|
161
|
+
try {
|
|
162
|
+
const { id } = req.params;
|
|
163
|
+
const updates = UpdateElementSchema.parse({ id, ...req.body });
|
|
164
|
+
|
|
165
|
+
const existingElement = elements.get(id);
|
|
166
|
+
if (!existingElement) {
|
|
167
|
+
return res.status(404).json({
|
|
168
|
+
success: false,
|
|
169
|
+
error: `Element with ID ${id} not found`
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const updatedElement = {
|
|
174
|
+
...existingElement,
|
|
175
|
+
...updates,
|
|
176
|
+
updatedAt: new Date().toISOString(),
|
|
177
|
+
version: existingElement.version + 1
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
elements.set(id, updatedElement);
|
|
181
|
+
|
|
182
|
+
// Broadcast to all connected clients
|
|
183
|
+
broadcast({
|
|
184
|
+
type: 'element_updated',
|
|
185
|
+
element: updatedElement
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
res.json({
|
|
189
|
+
success: true,
|
|
190
|
+
element: updatedElement
|
|
191
|
+
});
|
|
192
|
+
} catch (error) {
|
|
193
|
+
logger.error('Error updating element:', error);
|
|
194
|
+
res.status(400).json({
|
|
195
|
+
success: false,
|
|
196
|
+
error: error.message
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Delete element
|
|
202
|
+
app.delete('/api/elements/:id', (req, res) => {
|
|
203
|
+
try {
|
|
204
|
+
const { id } = req.params;
|
|
205
|
+
|
|
206
|
+
if (!elements.has(id)) {
|
|
207
|
+
return res.status(404).json({
|
|
208
|
+
success: false,
|
|
209
|
+
error: `Element with ID ${id} not found`
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
elements.delete(id);
|
|
214
|
+
|
|
215
|
+
// Broadcast to all connected clients
|
|
216
|
+
broadcast({
|
|
217
|
+
type: 'element_deleted',
|
|
218
|
+
elementId: id
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
res.json({
|
|
222
|
+
success: true,
|
|
223
|
+
message: `Element ${id} deleted successfully`
|
|
224
|
+
});
|
|
225
|
+
} catch (error) {
|
|
226
|
+
logger.error('Error deleting element:', error);
|
|
227
|
+
res.status(500).json({
|
|
228
|
+
success: false,
|
|
229
|
+
error: error.message
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Get element by ID
|
|
235
|
+
app.get('/api/elements/:id', (req, res) => {
|
|
236
|
+
try {
|
|
237
|
+
const { id } = req.params;
|
|
238
|
+
const element = elements.get(id);
|
|
239
|
+
|
|
240
|
+
if (!element) {
|
|
241
|
+
return res.status(404).json({
|
|
242
|
+
success: false,
|
|
243
|
+
error: `Element with ID ${id} not found`
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
res.json({
|
|
248
|
+
success: true,
|
|
249
|
+
element: element
|
|
250
|
+
});
|
|
251
|
+
} catch (error) {
|
|
252
|
+
logger.error('Error fetching element:', error);
|
|
253
|
+
res.status(500).json({
|
|
254
|
+
success: false,
|
|
255
|
+
error: error.message
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Query elements with filters
|
|
261
|
+
app.get('/api/elements/search', (req, res) => {
|
|
262
|
+
try {
|
|
263
|
+
const { type, ...filters } = req.query;
|
|
264
|
+
let results = Array.from(elements.values());
|
|
265
|
+
|
|
266
|
+
// Filter by type if specified
|
|
267
|
+
if (type) {
|
|
268
|
+
results = results.filter(element => element.type === type);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Apply additional filters
|
|
272
|
+
if (Object.keys(filters).length > 0) {
|
|
273
|
+
results = results.filter(element => {
|
|
274
|
+
return Object.entries(filters).every(([key, value]) => {
|
|
275
|
+
return element[key] === value;
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
res.json({
|
|
281
|
+
success: true,
|
|
282
|
+
elements: results,
|
|
283
|
+
count: results.length
|
|
284
|
+
});
|
|
285
|
+
} catch (error) {
|
|
286
|
+
logger.error('Error querying elements:', error);
|
|
287
|
+
res.status(500).json({
|
|
288
|
+
success: false,
|
|
289
|
+
error: error.message
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Batch create elements
|
|
295
|
+
app.post('/api/elements/batch', (req, res) => {
|
|
296
|
+
try {
|
|
297
|
+
const { elements: elementsToCreate } = req.body;
|
|
298
|
+
|
|
299
|
+
if (!Array.isArray(elementsToCreate)) {
|
|
300
|
+
return res.status(400).json({
|
|
301
|
+
success: false,
|
|
302
|
+
error: 'Expected an array of elements'
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const createdElements = [];
|
|
307
|
+
|
|
308
|
+
elementsToCreate.forEach(elementData => {
|
|
309
|
+
const params = CreateElementSchema.parse(elementData);
|
|
310
|
+
const id = generateId();
|
|
311
|
+
const element = {
|
|
312
|
+
id,
|
|
313
|
+
...params,
|
|
314
|
+
createdAt: new Date().toISOString(),
|
|
315
|
+
updatedAt: new Date().toISOString(),
|
|
316
|
+
version: 1
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
elements.set(id, element);
|
|
320
|
+
createdElements.push(element);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Broadcast to all connected clients
|
|
324
|
+
broadcast({
|
|
325
|
+
type: 'elements_batch_created',
|
|
326
|
+
elements: createdElements
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
res.json({
|
|
330
|
+
success: true,
|
|
331
|
+
elements: createdElements,
|
|
332
|
+
count: createdElements.length
|
|
333
|
+
});
|
|
334
|
+
} catch (error) {
|
|
335
|
+
logger.error('Error batch creating elements:', error);
|
|
336
|
+
res.status(400).json({
|
|
337
|
+
success: false,
|
|
338
|
+
error: error.message
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Serve the frontend
|
|
344
|
+
app.get('/', (req, res) => {
|
|
345
|
+
const htmlFile = path.join(__dirname, '../dist/index.html');
|
|
346
|
+
res.sendFile(htmlFile);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Health check endpoint
|
|
350
|
+
app.get('/health', (req, res) => {
|
|
351
|
+
res.json({
|
|
352
|
+
status: 'healthy',
|
|
353
|
+
timestamp: new Date().toISOString(),
|
|
354
|
+
elements_count: elements.size,
|
|
355
|
+
websocket_clients: clients.size
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Error handling middleware
|
|
360
|
+
app.use((err, req, res, next) => {
|
|
361
|
+
logger.error('Unhandled error:', err);
|
|
362
|
+
res.status(500).json({
|
|
363
|
+
success: false,
|
|
364
|
+
error: 'Internal server error'
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Start server
|
|
369
|
+
const PORT = process.env.PORT || 3000;
|
|
370
|
+
const HOST = process.env.HOST || 'localhost';
|
|
371
|
+
|
|
372
|
+
server.listen(PORT, HOST, () => {
|
|
373
|
+
logger.info(`POC server running on http://${HOST}:${PORT}`);
|
|
374
|
+
logger.info(`WebSocket server running on ws://${HOST}:${PORT}`);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
export default app;
|
package/src/types.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Excalidraw element types
|
|
2
|
+
export const EXCALIDRAW_ELEMENT_TYPES = {
|
|
3
|
+
RECTANGLE: 'rectangle',
|
|
4
|
+
ELLIPSE: 'ellipse',
|
|
5
|
+
DIAMOND: 'diamond',
|
|
6
|
+
ARROW: 'arrow',
|
|
7
|
+
TEXT: 'text',
|
|
8
|
+
LABEL: 'label',
|
|
9
|
+
FREEDRAW: 'freedraw',
|
|
10
|
+
LINE: 'line',
|
|
11
|
+
ARROW_LABEL: 'arrowLabel'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// In-memory storage for Excalidraw elements
|
|
15
|
+
export const elements = new Map();
|
|
16
|
+
|
|
17
|
+
// Validation function for Excalidraw elements
|
|
18
|
+
export function validateElement(element) {
|
|
19
|
+
const requiredFields = ['type', 'x', 'y'];
|
|
20
|
+
const hasRequiredFields = requiredFields.every(field => field in element);
|
|
21
|
+
|
|
22
|
+
if (!hasRequiredFields) {
|
|
23
|
+
throw new Error(`Missing required fields: ${requiredFields.join(', ')}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!Object.values(EXCALIDRAW_ELEMENT_TYPES).includes(element.type)) {
|
|
27
|
+
throw new Error(`Invalid element type: ${element.type}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Helper function to generate unique IDs
|
|
34
|
+
export function generateId() {
|
|
35
|
+
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
|
36
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import winston from 'winston';
|
|
2
|
+
|
|
3
|
+
const logger = winston.createLogger({
|
|
4
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
5
|
+
|
|
6
|
+
format: winston.format.combine(
|
|
7
|
+
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
|
|
8
|
+
winston.format.uncolorize(),
|
|
9
|
+
winston.format.printf(info =>
|
|
10
|
+
`${info.timestamp} [${info.level}] ${info.message}`
|
|
11
|
+
)
|
|
12
|
+
),
|
|
13
|
+
|
|
14
|
+
transports: [
|
|
15
|
+
new winston.transports.Console({
|
|
16
|
+
level: 'warn', // only warn+error to stderr
|
|
17
|
+
stderrLevels: ['warn','error']
|
|
18
|
+
}),
|
|
19
|
+
|
|
20
|
+
new winston.transports.File({
|
|
21
|
+
filename: 'excalidraw.log', // all levels to file
|
|
22
|
+
level: 'debug'
|
|
23
|
+
})
|
|
24
|
+
]
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export default logger;
|