@wangyaoshen/remux 0.3.10-dev.19fb76c → 0.3.10-dev.574c4d2

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.
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+ // Local mock of the Remux Discovery Service for development.
3
+ // Stores codes in memory (not persistent).
4
+ // Usage: node local-server.js [port]
5
+
6
+ import { createServer } from 'http';
7
+
8
+ const PORT = parseInt(process.argv[2] || '8780');
9
+ const codes = new Map();
10
+
11
+ // Clean expired codes every 5 minutes
12
+ setInterval(() => {
13
+ const now = Date.now();
14
+ for (const [code, data] of codes) {
15
+ if (now - data.createdAt > 86400000) codes.delete(code);
16
+ }
17
+ }, 300000);
18
+
19
+ function generateCode() {
20
+ const a = 100 + Math.floor(Math.random() * 900);
21
+ const b = 100 + Math.floor(Math.random() * 900);
22
+ const c = 100 + Math.floor(Math.random() * 900);
23
+ return `${a}${b}${c}`;
24
+ }
25
+
26
+ function json(res, data, status = 200) {
27
+ res.writeHead(status, {
28
+ 'Content-Type': 'application/json',
29
+ 'Access-Control-Allow-Origin': '*',
30
+ 'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
31
+ 'Access-Control-Allow-Headers': 'Content-Type',
32
+ });
33
+ res.end(JSON.stringify(data));
34
+ }
35
+
36
+ function readBody(req) {
37
+ return new Promise((resolve) => {
38
+ let body = '';
39
+ req.on('data', (chunk) => (body += chunk));
40
+ req.on('end', () => resolve(body));
41
+ });
42
+ }
43
+
44
+ const server = createServer(async (req, res) => {
45
+ const url = new URL(req.url, `http://localhost:${PORT}`);
46
+ const path = url.pathname;
47
+
48
+ if (req.method === 'OPTIONS') {
49
+ res.writeHead(204, {
50
+ 'Access-Control-Allow-Origin': '*',
51
+ 'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
52
+ 'Access-Control-Allow-Headers': 'Content-Type',
53
+ });
54
+ return res.end();
55
+ }
56
+
57
+ if (path === '/register' && req.method === 'POST') {
58
+ const body = JSON.parse(await readBody(req));
59
+ if (!body.tunnelUrl) return json(res, { error: 'tunnelUrl required' }, 400);
60
+ let code;
61
+ for (let i = 0; i < 10; i++) {
62
+ code = generateCode();
63
+ if (!codes.has(code)) break;
64
+ }
65
+ codes.set(code, {
66
+ tunnelUrl: body.tunnelUrl,
67
+ token: body.token || null,
68
+ createdAt: Date.now(),
69
+ });
70
+ const formatted = `${code.slice(0, 3)} ${code.slice(3, 6)} ${code.slice(6, 9)}`;
71
+ console.log(`[register] ${formatted} → ${body.tunnelUrl}`);
72
+ return json(res, { code, formatted });
73
+ }
74
+
75
+ if (path.startsWith('/resolve/') && req.method === 'GET') {
76
+ const rawCode = path.slice('/resolve/'.length).replace(/[\s-]/g, '');
77
+ if (!/^\d{9}$/.test(rawCode)) return json(res, { error: 'invalid code' }, 400);
78
+ const data = codes.get(rawCode);
79
+ if (!data) return json(res, { error: 'not found' }, 404);
80
+ console.log(`[resolve] ${rawCode} → ${data.tunnelUrl}`);
81
+ return json(res, { tunnelUrl: data.tunnelUrl, token: data.token });
82
+ }
83
+
84
+ if (path.startsWith('/unregister/') && req.method === 'DELETE') {
85
+ const rawCode = path.slice('/unregister/'.length).replace(/[\s-]/g, '');
86
+ codes.delete(rawCode);
87
+ res.writeHead(204);
88
+ return res.end();
89
+ }
90
+
91
+ if (path === '/health') return json(res, { ok: true, codes: codes.size });
92
+
93
+ json(res, { error: 'not found' }, 404);
94
+ });
95
+
96
+ server.listen(PORT, () => {
97
+ console.log(`Remux Discovery (local) running at http://localhost:${PORT}`);
98
+ });
@@ -0,0 +1,125 @@
1
+ // Remux Discovery Service — Cloudflare Worker
2
+ // Maps 9-digit short codes to tunnel URLs for RustDesk-like remote access.
3
+ //
4
+ // KV Namespace binding: CODES
5
+ //
6
+ // Endpoints:
7
+ // POST /register { tunnelUrl, token? } → { code: "847293015" }
8
+ // GET /resolve/:code → { tunnelUrl, token? } or 404
9
+ // DELETE /unregister/:code → 204
10
+ // GET /health → { ok: true }
11
+
12
+ export default {
13
+ async fetch(request, env) {
14
+ const url = new URL(request.url);
15
+ const path = url.pathname;
16
+
17
+ // CORS headers for browser clients
18
+ const corsHeaders = {
19
+ 'Access-Control-Allow-Origin': '*',
20
+ 'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
21
+ 'Access-Control-Allow-Headers': 'Content-Type',
22
+ };
23
+
24
+ if (request.method === 'OPTIONS') {
25
+ return new Response(null, { status: 204, headers: corsHeaders });
26
+ }
27
+
28
+ try {
29
+ // POST /register
30
+ if (path === '/register' && request.method === 'POST') {
31
+ const body = await request.json();
32
+ const { tunnelUrl, token } = body;
33
+
34
+ if (!tunnelUrl || typeof tunnelUrl !== 'string') {
35
+ return json({ error: 'tunnelUrl required' }, 400, corsHeaders);
36
+ }
37
+
38
+ // Generate unique 9-digit code (3 groups of 3)
39
+ let code;
40
+ let attempts = 0;
41
+ do {
42
+ code = generateCode();
43
+ const existing = await env.CODES.get(code);
44
+ if (!existing) break;
45
+ attempts++;
46
+ } while (attempts < 10);
47
+
48
+ if (attempts >= 10) {
49
+ return json({ error: 'failed to generate unique code' }, 500, corsHeaders);
50
+ }
51
+
52
+ const value = JSON.stringify({
53
+ tunnelUrl,
54
+ token: token || null,
55
+ createdAt: Date.now(),
56
+ });
57
+
58
+ // TTL: 24 hours
59
+ await env.CODES.put(code, value, { expirationTtl: 86400 });
60
+
61
+ return json({ code, formatted: formatCode(code) }, 200, corsHeaders);
62
+ }
63
+
64
+ // GET /resolve/:code
65
+ if (path.startsWith('/resolve/') && request.method === 'GET') {
66
+ const rawCode = path.slice('/resolve/'.length).replace(/[\s-]/g, '');
67
+ if (!/^\d{9}$/.test(rawCode)) {
68
+ return json({ error: 'invalid code format, expected 9 digits' }, 400, corsHeaders);
69
+ }
70
+
71
+ const data = await env.CODES.get(rawCode);
72
+ if (!data) {
73
+ return json({ error: 'code not found or expired' }, 404, corsHeaders);
74
+ }
75
+
76
+ const parsed = JSON.parse(data);
77
+ return json({
78
+ tunnelUrl: parsed.tunnelUrl,
79
+ token: parsed.token,
80
+ }, 200, corsHeaders);
81
+ }
82
+
83
+ // DELETE /unregister/:code
84
+ if (path.startsWith('/unregister/') && request.method === 'DELETE') {
85
+ const rawCode = path.slice('/unregister/'.length).replace(/[\s-]/g, '');
86
+ if (!/^\d{9}$/.test(rawCode)) {
87
+ return json({ error: 'invalid code format' }, 400, corsHeaders);
88
+ }
89
+ await env.CODES.delete(rawCode);
90
+ return new Response(null, { status: 204, headers: corsHeaders });
91
+ }
92
+
93
+ // GET /health
94
+ if (path === '/health') {
95
+ return json({ ok: true, ts: Date.now() }, 200, corsHeaders);
96
+ }
97
+
98
+ return json({ error: 'not found' }, 404, corsHeaders);
99
+ } catch (err) {
100
+ return json({ error: err.message }, 500, corsHeaders);
101
+ }
102
+ },
103
+ };
104
+
105
+ function generateCode() {
106
+ // 9 random digits: each group 100-999
107
+ const a = 100 + Math.floor(Math.random() * 900);
108
+ const b = 100 + Math.floor(Math.random() * 900);
109
+ const c = 100 + Math.floor(Math.random() * 900);
110
+ return `${a}${b}${c}`;
111
+ }
112
+
113
+ function formatCode(code) {
114
+ return `${code.slice(0, 3)} ${code.slice(3, 6)} ${code.slice(6, 9)}`;
115
+ }
116
+
117
+ function json(data, status, extraHeaders = {}) {
118
+ return new Response(JSON.stringify(data), {
119
+ status,
120
+ headers: {
121
+ 'Content-Type': 'application/json',
122
+ ...extraHeaders,
123
+ },
124
+ });
125
+ }
@@ -0,0 +1,7 @@
1
+ name = "remux-discovery"
2
+ main = "worker.js"
3
+ compatibility_date = "2024-01-01"
4
+
5
+ [[kv_namespaces]]
6
+ binding = "CODES"
7
+ id = "" # Fill after `wrangler kv:namespace create CODES`