mrmd-server 0.1.5 → 0.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mrmd-server",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "HTTP server for mrmd - run mrmd in any browser, access from anywhere",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/server.js CHANGED
@@ -7,7 +7,7 @@
7
7
  import express from 'express';
8
8
  import cors from 'cors';
9
9
  import { createServer as createHttpServer } from 'http';
10
- import { WebSocketServer } from 'ws';
10
+ import { WebSocketServer, WebSocket as WsClient } from 'ws';
11
11
  import path from 'path';
12
12
  import fs from 'fs/promises';
13
13
  import { existsSync } from 'fs';
@@ -213,8 +213,19 @@ export async function createServer(config) {
213
213
  }
214
214
  }
215
215
 
216
- // Serve node_modules from mrmd-electron (for xterm, etc.)
217
- app.use('/node_modules', express.static(path.join(electronPath, 'node_modules')));
216
+ // Serve node_modules for xterm, etc. - check multiple locations (npm hoisting)
217
+ const nodeModulesCandidates = [
218
+ path.join(electronPath, 'node_modules'), // Direct dependency
219
+ path.join(electronPath, '..'), // Hoisted to parent (npm installed)
220
+ path.join(__dirname, '../node_modules'), // mrmd-server's node_modules
221
+ path.join(__dirname, '../../node_modules'), // Hoisted further up
222
+ ];
223
+ for (const nmPath of nodeModulesCandidates) {
224
+ if (existsSync(path.join(nmPath, 'xterm'))) {
225
+ app.use('/node_modules', express.static(nmPath));
226
+ break;
227
+ }
228
+ }
218
229
 
219
230
  // Serve transformed index.html at root
220
231
  app.get('/', async (req, res) => {
@@ -249,6 +260,51 @@ export async function createServer(config) {
249
260
  const wss = new WebSocketServer({ server, path: '/events' });
250
261
  setupWebSocket(wss, eventBus, token, noAuth);
251
262
 
263
+ // WebSocket proxy for sync connections (remote browsers can't reach localhost)
264
+ const syncWss = new WebSocketServer({ noServer: true });
265
+ server.on('upgrade', (request, socket, head) => {
266
+ const url = new URL(request.url, 'http://localhost');
267
+
268
+ // Handle /events normally
269
+ if (url.pathname === '/events') {
270
+ wss.handleUpgrade(request, socket, head, (ws) => {
271
+ wss.emit('connection', ws, request);
272
+ });
273
+ return;
274
+ }
275
+
276
+ // Handle /sync/:port/:doc - proxy to local sync server
277
+ const syncMatch = url.pathname.match(/^\/sync\/(\d+)\/(.+)$/);
278
+ if (syncMatch) {
279
+ const [, syncPort, docName] = syncMatch;
280
+ const targetUrl = `ws://127.0.0.1:${syncPort}/${docName}`;
281
+
282
+ // Create connection to local sync server
283
+ const upstream = new WsClient(targetUrl);
284
+
285
+ upstream.on('open', () => {
286
+ syncWss.handleUpgrade(request, socket, head, (clientWs) => {
287
+ // Bidirectional proxy
288
+ clientWs.on('message', (data) => upstream.send(data));
289
+ upstream.on('message', (data) => clientWs.send(data));
290
+ clientWs.on('close', () => upstream.close());
291
+ upstream.on('close', () => clientWs.close());
292
+ clientWs.on('error', () => upstream.close());
293
+ upstream.on('error', () => clientWs.close());
294
+ });
295
+ });
296
+
297
+ upstream.on('error', (err) => {
298
+ console.error(`[sync-proxy] Failed to connect to ${targetUrl}:`, err.message);
299
+ socket.destroy();
300
+ });
301
+ return;
302
+ }
303
+
304
+ // Unknown upgrade request
305
+ socket.destroy();
306
+ });
307
+
252
308
  return {
253
309
  app,
254
310
  server,
@@ -17,6 +17,33 @@
17
17
  const TOKEN = params.get('token') || '';
18
18
  const BASE_URL = window.MRMD_SERVER_URL || window.location.origin;
19
19
 
20
+ // ==========================================================================
21
+ // WebSocket Proxy Interceptor
22
+ // ==========================================================================
23
+ // Intercept WebSocket connections to localhost sync servers and route through proxy
24
+ const OriginalWebSocket = window.WebSocket;
25
+ window.WebSocket = function(url, protocols) {
26
+ let targetUrl = url;
27
+
28
+ // Check if this is a sync connection to localhost
29
+ const match = url.match(/^wss?:\/\/127\.0\.0\.1:(\d+)\/(.+)$/);
30
+ if (match) {
31
+ const [, port, docPath] = match;
32
+ // Route through server proxy
33
+ const proxyUrl = new URL(`/sync/${port}/${docPath}`, BASE_URL);
34
+ proxyUrl.protocol = proxyUrl.protocol === 'https:' ? 'wss:' : 'ws:';
35
+ targetUrl = proxyUrl.toString();
36
+ console.log(`[http-shim] Proxying sync WebSocket: ${url} -> ${targetUrl}`);
37
+ }
38
+
39
+ return new OriginalWebSocket(targetUrl, protocols);
40
+ };
41
+ window.WebSocket.prototype = OriginalWebSocket.prototype;
42
+ window.WebSocket.CONNECTING = OriginalWebSocket.CONNECTING;
43
+ window.WebSocket.OPEN = OriginalWebSocket.OPEN;
44
+ window.WebSocket.CLOSING = OriginalWebSocket.CLOSING;
45
+ window.WebSocket.CLOSED = OriginalWebSocket.CLOSED;
46
+
20
47
  // ==========================================================================
21
48
  // HTTP Client
22
49
  // ==========================================================================