@mooncompany/uplink-chat 0.5.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.

Potentially problematic release.


This version of @mooncompany/uplink-chat might be problematic. Click here for more details.

Files changed (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -0
  3. package/bin/uplink.js +279 -0
  4. package/middleware/error-handler.js +69 -0
  5. package/package.json +93 -0
  6. package/public/css/agents.36b98c0f.css +1469 -0
  7. package/public/css/agents.css +1469 -0
  8. package/public/css/app.a6a7f8f5.css +2731 -0
  9. package/public/css/app.css +2731 -0
  10. package/public/css/artifacts.css +444 -0
  11. package/public/css/commands.css +55 -0
  12. package/public/css/connection.css +131 -0
  13. package/public/css/dashboard.css +233 -0
  14. package/public/css/developer.css +328 -0
  15. package/public/css/files.css +123 -0
  16. package/public/css/markdown.css +156 -0
  17. package/public/css/message-actions.css +278 -0
  18. package/public/css/mobile.css +614 -0
  19. package/public/css/panels-unified.css +483 -0
  20. package/public/css/premium.css +415 -0
  21. package/public/css/realtime.css +189 -0
  22. package/public/css/satellites.css +401 -0
  23. package/public/css/shortcuts.css +185 -0
  24. package/public/css/split-view.4def0262.css +673 -0
  25. package/public/css/split-view.css +673 -0
  26. package/public/css/theme-generator.css +391 -0
  27. package/public/css/themes.css +387 -0
  28. package/public/css/timestamps.css +54 -0
  29. package/public/css/variables.css +78 -0
  30. package/public/dist/bundle.b55050c4.js +15757 -0
  31. package/public/favicon.svg +24 -0
  32. package/public/img/agents/ada.png +0 -0
  33. package/public/img/agents/clarice.png +0 -0
  34. package/public/img/agents/dennis-nedry.png +0 -0
  35. package/public/img/agents/elliot-alderson.png +0 -0
  36. package/public/img/agents/main.png +0 -0
  37. package/public/img/agents/scotty.png +0 -0
  38. package/public/img/agents/top-flight-security.png +0 -0
  39. package/public/index.html +1083 -0
  40. package/public/js/agents-data.js +234 -0
  41. package/public/js/agents-ui.js +72 -0
  42. package/public/js/agents.js +1525 -0
  43. package/public/js/app.js +79 -0
  44. package/public/js/appearance-settings.js +111 -0
  45. package/public/js/artifacts.js +432 -0
  46. package/public/js/audio-queue.js +168 -0
  47. package/public/js/bootstrap.js +54 -0
  48. package/public/js/chat.js +1211 -0
  49. package/public/js/commands.js +581 -0
  50. package/public/js/connection-api.js +121 -0
  51. package/public/js/connection.js +1231 -0
  52. package/public/js/context-tracker.js +271 -0
  53. package/public/js/core.js +172 -0
  54. package/public/js/dashboard.js +452 -0
  55. package/public/js/developer.js +432 -0
  56. package/public/js/encryption.js +124 -0
  57. package/public/js/errors.js +122 -0
  58. package/public/js/event-bus.js +77 -0
  59. package/public/js/fetch-utils.js +171 -0
  60. package/public/js/file-handler.js +229 -0
  61. package/public/js/files.js +352 -0
  62. package/public/js/gateway-chat.js +538 -0
  63. package/public/js/logger.js +112 -0
  64. package/public/js/markdown.js +190 -0
  65. package/public/js/message-actions.js +431 -0
  66. package/public/js/message-renderer.js +288 -0
  67. package/public/js/missed-messages.js +235 -0
  68. package/public/js/mobile-debug.js +95 -0
  69. package/public/js/notifications.js +367 -0
  70. package/public/js/offline-queue.js +178 -0
  71. package/public/js/onboarding.js +543 -0
  72. package/public/js/panels.js +156 -0
  73. package/public/js/premium.js +412 -0
  74. package/public/js/realtime-voice.js +844 -0
  75. package/public/js/satellite-sync.js +256 -0
  76. package/public/js/satellite-ui.js +175 -0
  77. package/public/js/satellites.js +1516 -0
  78. package/public/js/settings.js +1087 -0
  79. package/public/js/shortcuts.js +381 -0
  80. package/public/js/split-chat.js +1234 -0
  81. package/public/js/split-resize.js +211 -0
  82. package/public/js/splitview.js +340 -0
  83. package/public/js/storage.js +408 -0
  84. package/public/js/streaming-handler.js +324 -0
  85. package/public/js/stt-settings.js +316 -0
  86. package/public/js/theme-generator.js +661 -0
  87. package/public/js/themes.js +164 -0
  88. package/public/js/timestamps.js +198 -0
  89. package/public/js/tts-settings.js +575 -0
  90. package/public/js/ui.js +267 -0
  91. package/public/js/update-notifier.js +143 -0
  92. package/public/js/utils/constants.js +165 -0
  93. package/public/js/utils/sanitize.js +93 -0
  94. package/public/js/utils/sse-parser.js +195 -0
  95. package/public/js/voice.js +883 -0
  96. package/public/manifest.json +58 -0
  97. package/public/moon_texture.jpg +0 -0
  98. package/public/sw.js +221 -0
  99. package/public/three.min.js +6 -0
  100. package/server/channel.js +529 -0
  101. package/server/chat.js +270 -0
  102. package/server/config-store.js +362 -0
  103. package/server/config.js +159 -0
  104. package/server/context.js +131 -0
  105. package/server/gateway-commands.js +211 -0
  106. package/server/gateway-proxy.js +318 -0
  107. package/server/index.js +22 -0
  108. package/server/logger.js +89 -0
  109. package/server/middleware/auth.js +188 -0
  110. package/server/middleware.js +218 -0
  111. package/server/openclaw-discover.js +308 -0
  112. package/server/premium/index.js +156 -0
  113. package/server/premium/license.js +140 -0
  114. package/server/realtime/bridge.js +837 -0
  115. package/server/realtime/index.js +349 -0
  116. package/server/realtime/tts-stream.js +446 -0
  117. package/server/routes/agents.js +564 -0
  118. package/server/routes/artifacts.js +174 -0
  119. package/server/routes/chat.js +311 -0
  120. package/server/routes/config-settings.js +345 -0
  121. package/server/routes/config.js +603 -0
  122. package/server/routes/files.js +307 -0
  123. package/server/routes/index.js +18 -0
  124. package/server/routes/media.js +451 -0
  125. package/server/routes/missed-messages.js +107 -0
  126. package/server/routes/premium.js +75 -0
  127. package/server/routes/push.js +156 -0
  128. package/server/routes/satellite.js +406 -0
  129. package/server/routes/status.js +251 -0
  130. package/server/routes/stt.js +35 -0
  131. package/server/routes/voice.js +260 -0
  132. package/server/routes/webhooks.js +203 -0
  133. package/server/routes.js +206 -0
  134. package/server/runtime-config.js +336 -0
  135. package/server/share.js +305 -0
  136. package/server/stt/faster-whisper.js +72 -0
  137. package/server/stt/groq.js +51 -0
  138. package/server/stt/index.js +196 -0
  139. package/server/stt/openai.js +49 -0
  140. package/server/sync.js +244 -0
  141. package/server/tailscale-https.js +175 -0
  142. package/server/tts.js +646 -0
  143. package/server/update-checker.js +172 -0
  144. package/server/utils/filename.js +129 -0
  145. package/server/utils.js +147 -0
  146. package/server/watchdog.js +318 -0
  147. package/server/websocket/broadcast.js +359 -0
  148. package/server/websocket/connections.js +339 -0
  149. package/server/websocket/index.js +215 -0
  150. package/server/websocket/routing.js +277 -0
  151. package/server/websocket/sync.js +102 -0
  152. package/server.js +404 -0
  153. package/utils/detect-tool-usage.js +93 -0
  154. package/utils/errors.js +158 -0
  155. package/utils/html-escape.js +84 -0
  156. package/utils/id-sanitize.js +94 -0
  157. package/utils/response.js +130 -0
  158. package/utils/with-retry.js +105 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * HTML Escape Utility (Server-side)
3
+ * Single source of truth for HTML escaping on the server
4
+ */
5
+
6
+ // HTML entity map for escaping
7
+ const HTML_ENTITIES = {
8
+ '&': '&',
9
+ '<': '&lt;',
10
+ '>': '&gt;',
11
+ '"': '&quot;',
12
+ "'": '&#39;',
13
+ '/': '&#x2F;',
14
+ '`': '&#x60;',
15
+ '=': '&#x3D;'
16
+ };
17
+
18
+ /**
19
+ * Escape HTML special characters to prevent XSS
20
+ *
21
+ * @param {string} str - The string to escape
22
+ * @returns {string} - Escaped string safe for HTML insertion
23
+ */
24
+ export function escapeHtml(str) {
25
+ if (typeof str !== 'string') {
26
+ return '';
27
+ }
28
+ return str.replace(/[&<>"'`=/]/g, char => HTML_ENTITIES[char]);
29
+ }
30
+
31
+ /**
32
+ * Escape HTML and convert newlines to <br> tags
33
+ *
34
+ * @param {string} str - The string to escape
35
+ * @returns {string} - Escaped string with line breaks
36
+ */
37
+ export function escapeHtmlWithBreaks(str) {
38
+ return escapeHtml(str).replace(/\n/g, '<br>');
39
+ }
40
+
41
+ /**
42
+ * Escape a string for safe insertion into JavaScript
43
+ * Use when embedding strings in inline <script> tags
44
+ *
45
+ * @param {string} str - The string to escape
46
+ * @returns {string} - Escaped string safe for JS context
47
+ */
48
+ export function escapeForJS(str) {
49
+ if (typeof str !== 'string') {
50
+ return '';
51
+ }
52
+ return str
53
+ .replace(/\\/g, '\\\\')
54
+ .replace(/'/g, "\\'")
55
+ .replace(/"/g, '\\"')
56
+ .replace(/\n/g, '\\n')
57
+ .replace(/\r/g, '\\r')
58
+ .replace(/<\/script>/gi, '<\\/script>');
59
+ }
60
+
61
+ /**
62
+ * Escape a string for safe use in HTML attributes
63
+ *
64
+ * @param {string} str - The string to escape
65
+ * @returns {string} - Escaped string safe for attribute values
66
+ */
67
+ export function escapeAttribute(str) {
68
+ if (typeof str !== 'string') {
69
+ return '';
70
+ }
71
+ return str
72
+ .replace(/&/g, '&amp;')
73
+ .replace(/"/g, '&quot;')
74
+ .replace(/'/g, '&#39;')
75
+ .replace(/</g, '&lt;')
76
+ .replace(/>/g, '&gt;');
77
+ }
78
+
79
+ export default {
80
+ escapeHtml,
81
+ escapeHtmlWithBreaks,
82
+ escapeForJS,
83
+ escapeAttribute
84
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * ID Sanitization Utility
3
+ * Validates and sanitizes identifiers to prevent injection attacks
4
+ */
5
+
6
+ /**
7
+ * Sanitize an ID string (alphanumeric + hyphen/underscore only)
8
+ * @param {string} id - Raw ID
9
+ * @param {number} maxLength - Maximum allowed length (default 64)
10
+ * @returns {string} - Sanitized ID
11
+ */
12
+ export function sanitizeId(id, maxLength = 64) {
13
+ if (typeof id !== 'string') return '';
14
+ return id.replace(/[^a-zA-Z0-9_-]/g, '').substring(0, maxLength);
15
+ }
16
+
17
+ /**
18
+ * Sanitize a satellite ID
19
+ * @param {string} id - Raw satellite ID
20
+ * @returns {string} - Sanitized satellite ID (default 'main' if invalid)
21
+ */
22
+ export function sanitizeSatelliteId(id) {
23
+ const sanitized = sanitizeId(id, 32);
24
+ return sanitized || 'main';
25
+ }
26
+
27
+ /**
28
+ * Sanitize a share ID (alphanumeric only, max 12 chars)
29
+ * @param {string} id - Raw share ID
30
+ * @returns {{ valid: boolean, sanitized: string, error: string|null }}
31
+ */
32
+ export function sanitizeShareId(id) {
33
+ if (typeof id !== 'string' || !id) {
34
+ return { valid: false, sanitized: '', error: 'shareId must be a non-empty string' };
35
+ }
36
+ const sanitized = id.replace(/[^a-zA-Z0-9]/g, '').substring(0, 12);
37
+ if (!sanitized) {
38
+ return { valid: false, sanitized: '', error: 'shareId contains no valid characters' };
39
+ }
40
+ return { valid: true, sanitized, error: null };
41
+ }
42
+
43
+ /**
44
+ * Sanitize a session user string
45
+ * @param {string} user - Raw session user
46
+ * @returns {string} - Sanitized session user
47
+ */
48
+ export function sanitizeSessionUser(user) {
49
+ if (typeof user !== 'string') return '';
50
+ // Allow alphanumeric, hyphen, underscore, colon (for satellite suffix)
51
+ return user.replace(/[^a-zA-Z0-9_:-]/g, '').substring(0, 64);
52
+ }
53
+
54
+ /**
55
+ * Validate that an ID matches expected format
56
+ * @param {string} id - ID to validate
57
+ * @param {RegExp} pattern - Pattern to match
58
+ * @returns {boolean} - True if valid
59
+ */
60
+ export function isValidId(id, pattern = /^[a-zA-Z0-9_-]+$/) {
61
+ return typeof id === 'string' && pattern.test(id);
62
+ }
63
+
64
+ /**
65
+ * Sanitize a sync ID (alphanumeric + hyphen, max 64 chars)
66
+ * @param {string} id - Raw sync ID
67
+ * @returns {{ valid: boolean, sanitized: string, error: string|null }}
68
+ */
69
+ export function sanitizeSyncId(id) {
70
+ if (typeof id !== 'string' || !id) {
71
+ return { valid: false, sanitized: '', error: 'syncId must be a non-empty string' };
72
+ }
73
+ const sanitized = id.replace(/[^a-zA-Z0-9-]/g, '').substring(0, 64);
74
+ if (!sanitized) {
75
+ return { valid: false, sanitized: '', error: 'syncId contains no valid characters' };
76
+ }
77
+ return { valid: true, sanitized, error: null };
78
+ }
79
+
80
+ /**
81
+ * Parse a numeric parameter with bounds checking
82
+ * @param {any} value - Value to parse
83
+ * @param {number} defaultVal - Default value if invalid
84
+ * @param {number} min - Minimum allowed value
85
+ * @param {number} max - Maximum allowed value
86
+ * @returns {number} - Parsed and bounded number
87
+ */
88
+ export function parseNumericParam(value, defaultVal = 0, min = 0, max = Infinity) {
89
+ const parsed = parseInt(value, 10);
90
+ if (isNaN(parsed)) return defaultVal;
91
+ return Math.min(Math.max(parsed, min), max);
92
+ }
93
+
94
+ export default { sanitizeId, sanitizeSatelliteId, sanitizeShareId, sanitizeSessionUser, isValidId, sanitizeSyncId, parseNumericParam };
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Standardized Response Utilities
3
+ * Ensures consistent API responses
4
+ */
5
+
6
+ import { sanitizeErrorMessage, createErrorResponse, AppError } from './errors.js';
7
+
8
+ /**
9
+ * Send a success response
10
+ * @param {Response} res - Express response
11
+ * @param {Object} data - Response data
12
+ * @param {number} statusCode - HTTP status code (default 200)
13
+ */
14
+ export function sendSuccess(res, data = {}, statusCode = 200) {
15
+ res.status(statusCode).json({
16
+ ok: true,
17
+ ...data
18
+ });
19
+ }
20
+
21
+ /**
22
+ * Send an error response (sanitized)
23
+ * @param {Response} res - Express response
24
+ * @param {Error|string} error - Error or message
25
+ * @param {number} statusCode - HTTP status code (default 500)
26
+ */
27
+ export function sendError(res, error, statusCode = 500) {
28
+ // If it's an AppError, use its status code
29
+ if (error instanceof AppError) {
30
+ statusCode = error.statusCode;
31
+ }
32
+
33
+ const message = error instanceof Error
34
+ ? sanitizeErrorMessage(error)
35
+ : String(error);
36
+
37
+ res.status(statusCode).json({
38
+ error: true,
39
+ message,
40
+ ...(error instanceof AppError && { code: error.code })
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Send a validation error (400)
46
+ * @param {Response} res - Express response
47
+ * @param {string} message - Error message
48
+ */
49
+ export function sendValidationError(res, message = 'Invalid input') {
50
+ sendError(res, message, 400);
51
+ }
52
+
53
+ /**
54
+ * Send an authentication error (401)
55
+ * @param {Response} res - Express response
56
+ * @param {string} message - Error message
57
+ */
58
+ export function sendAuthError(res, message = 'Authentication required') {
59
+ sendError(res, message, 401);
60
+ }
61
+
62
+ /**
63
+ * Send a forbidden error (403)
64
+ * @param {Response} res - Express response
65
+ * @param {string} message - Error message
66
+ */
67
+ export function sendForbiddenError(res, message = 'Access denied') {
68
+ sendError(res, message, 403);
69
+ }
70
+
71
+ /**
72
+ * Send a not found error (404)
73
+ * @param {Response} res - Express response
74
+ * @param {string} message - Error message
75
+ */
76
+ export function sendNotFoundError(res, message = 'Resource not found') {
77
+ sendError(res, message, 404);
78
+ }
79
+
80
+ /**
81
+ * Send a rate limit error (429)
82
+ * @param {Response} res - Express response
83
+ * @param {string} message - Error message
84
+ */
85
+ export function sendRateLimitError(res, message = 'Too many requests') {
86
+ sendError(res, message, 429);
87
+ }
88
+
89
+ /**
90
+ * Send a server error (500) - always sanitized
91
+ * @param {Response} res - Express response
92
+ * @param {Error} error - Original error (will be sanitized)
93
+ */
94
+ export function sendServerError(res, error) {
95
+ sendError(res, error, 500);
96
+ }
97
+
98
+ /**
99
+ * Express error handler middleware
100
+ */
101
+ export function errorHandler(err, req, res, next) {
102
+ // Log the actual error for debugging
103
+ console.error('[Error]', err);
104
+
105
+ // Send sanitized response
106
+ const statusCode = err instanceof AppError ? err.statusCode : 500;
107
+ sendError(res, err, statusCode);
108
+ }
109
+
110
+ /**
111
+ * Wrap async route handlers to catch promise rejections
112
+ */
113
+ export function asyncHandler(fn) {
114
+ return (req, res, next) => {
115
+ Promise.resolve(fn(req, res, next)).catch(next);
116
+ };
117
+ }
118
+
119
+ export default {
120
+ sendSuccess,
121
+ sendError,
122
+ sendValidationError,
123
+ sendAuthError,
124
+ sendForbiddenError,
125
+ sendNotFoundError,
126
+ sendRateLimitError,
127
+ sendServerError,
128
+ errorHandler,
129
+ asyncHandler
130
+ };
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Retry Helper Utility (Server-side)
3
+ * Unified retry logic with exponential backoff
4
+ *
5
+ * Previously duplicated in:
6
+ * - server/utils.js (simple signature)
7
+ * - server/chat.js (options object signature)
8
+ *
9
+ * This version supports both calling conventions for backward compatibility.
10
+ */
11
+
12
+ /**
13
+ * Simple logger fallback
14
+ */
15
+ function logWarn(...args) {
16
+ console.warn('[withRetry]', ...args);
17
+ }
18
+
19
+ /**
20
+ * Execute a function with automatic retry on failure
21
+ * Uses exponential backoff between retries
22
+ *
23
+ * Supports two calling conventions:
24
+ * 1. withRetry(fn, maxRetries, delay) - simple
25
+ * 2. withRetry(fn, { maxRetries, baseDelayMs, maxDelayMs }) - options object
26
+ *
27
+ * @param {Function} fn - The async function to retry
28
+ * @param {number|Object} optionsOrMaxRetries - Options object or max retries number
29
+ * @param {number} delayArg - Base delay (only used if second arg is a number)
30
+ * @returns {Promise<any>} - The result of the function
31
+ * @throws {Error} - The last error if all retries fail
32
+ */
33
+ export async function withRetry(fn, optionsOrMaxRetries = 3, delayArg = 1000) {
34
+ // Normalize arguments - support both calling conventions
35
+ let maxRetries, baseDelayMs, maxDelayMs;
36
+
37
+ if (typeof optionsOrMaxRetries === 'object') {
38
+ // Options object style: withRetry(fn, { maxRetries, baseDelayMs, maxDelayMs })
39
+ maxRetries = optionsOrMaxRetries.maxRetries ?? 3;
40
+ baseDelayMs = optionsOrMaxRetries.baseDelayMs ?? 1000;
41
+ maxDelayMs = optionsOrMaxRetries.maxDelayMs ?? 10000;
42
+ } else {
43
+ // Simple style: withRetry(fn, maxRetries, delay)
44
+ maxRetries = optionsOrMaxRetries;
45
+ baseDelayMs = delayArg;
46
+ maxDelayMs = delayArg * Math.pow(2, maxRetries); // Calculate reasonable max
47
+ }
48
+
49
+ let lastError;
50
+
51
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
52
+ try {
53
+ return await fn();
54
+ } catch (err) {
55
+ lastError = err;
56
+
57
+ if (attempt < maxRetries - 1) {
58
+ // Calculate delay with exponential backoff, capped at maxDelayMs
59
+ const delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
60
+
61
+ logWarn(`Retry ${attempt + 1}/${maxRetries} after ${delay}ms: ${err.message}`);
62
+
63
+ await sleep(delay);
64
+ }
65
+ }
66
+ }
67
+
68
+ throw lastError;
69
+ }
70
+
71
+ /**
72
+ * Sleep for a specified duration
73
+ *
74
+ * @param {number} ms - Milliseconds to sleep
75
+ * @returns {Promise<void>}
76
+ */
77
+ export function sleep(ms) {
78
+ return new Promise(resolve => setTimeout(resolve, ms));
79
+ }
80
+
81
+ /**
82
+ * Create a configured retry function with preset options
83
+ * Useful when you want consistent retry behavior across multiple calls
84
+ *
85
+ * @param {Object} options - Default retry options
86
+ * @returns {Function} - Configured retry function
87
+ */
88
+ export function createRetrier(options = {}) {
89
+ const defaults = {
90
+ maxRetries: options.maxRetries ?? 3,
91
+ baseDelayMs: options.baseDelayMs ?? 1000,
92
+ maxDelayMs: options.maxDelayMs ?? 10000,
93
+ ...options
94
+ };
95
+
96
+ return (fn, overrides = {}) => {
97
+ return withRetry(fn, { ...defaults, ...overrides });
98
+ };
99
+ }
100
+
101
+ export default {
102
+ withRetry,
103
+ sleep,
104
+ createRetrier
105
+ };