spck 0.3.1
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/.oxlintrc.json +49 -0
- package/LICENSE +21 -0
- package/README.md +631 -0
- package/bin/cli.js +20 -0
- package/bin/validate-cwd.js +41 -0
- package/dist/config/__tests__/config.test.d.ts +2 -0
- package/dist/config/__tests__/config.test.js +262 -0
- package/dist/config/__tests__/credentials.test.d.ts +2 -0
- package/dist/config/__tests__/credentials.test.js +360 -0
- package/dist/config/config.d.ts +33 -0
- package/dist/config/config.js +185 -0
- package/dist/config/credentials.d.ts +75 -0
- package/dist/config/credentials.js +259 -0
- package/dist/config/server-selection.d.ts +40 -0
- package/dist/config/server-selection.js +130 -0
- package/dist/connection/__tests__/firebase-auth.test.d.ts +2 -0
- package/dist/connection/__tests__/firebase-auth.test.js +96 -0
- package/dist/connection/__tests__/hmac.test.d.ts +2 -0
- package/dist/connection/__tests__/hmac.test.js +372 -0
- package/dist/connection/auth.d.ts +13 -0
- package/dist/connection/auth.js +91 -0
- package/dist/connection/firebase-auth.d.ts +40 -0
- package/dist/connection/firebase-auth.js +429 -0
- package/dist/connection/hmac.d.ts +24 -0
- package/dist/connection/hmac.js +109 -0
- package/dist/i18n/index.d.ts +25 -0
- package/dist/i18n/index.js +101 -0
- package/dist/i18n/locales/en.json +313 -0
- package/dist/i18n/locales/es.json +302 -0
- package/dist/i18n/locales/fr.json +302 -0
- package/dist/i18n/locales/id.json +302 -0
- package/dist/i18n/locales/ja.json +302 -0
- package/dist/i18n/locales/ko.json +302 -0
- package/dist/i18n/locales/locales/en.json +309 -0
- package/dist/i18n/locales/locales/es.json +302 -0
- package/dist/i18n/locales/locales/fr.json +302 -0
- package/dist/i18n/locales/locales/id.json +302 -0
- package/dist/i18n/locales/locales/ja.json +302 -0
- package/dist/i18n/locales/locales/ko.json +302 -0
- package/dist/i18n/locales/locales/pt.json +302 -0
- package/dist/i18n/locales/locales/zh-Hans.json +302 -0
- package/dist/i18n/locales/pt.json +302 -0
- package/dist/i18n/locales/zh-Hans.json +302 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +493 -0
- package/dist/proxy/ProxyClient.d.ts +125 -0
- package/dist/proxy/ProxyClient.js +781 -0
- package/dist/proxy/ProxySocketWrapper.d.ts +43 -0
- package/dist/proxy/ProxySocketWrapper.js +98 -0
- package/dist/proxy/__tests__/ProxyClient.test.d.ts +2 -0
- package/dist/proxy/__tests__/ProxyClient.test.js +445 -0
- package/dist/proxy/__tests__/ProxySocketWrapper.test.d.ts +2 -0
- package/dist/proxy/__tests__/ProxySocketWrapper.test.js +190 -0
- package/dist/proxy/__tests__/handshake-validation.test.d.ts +2 -0
- package/dist/proxy/__tests__/handshake-validation.test.js +282 -0
- package/dist/proxy/__tests__/token-refresh-race.test.d.ts +14 -0
- package/dist/proxy/__tests__/token-refresh-race.test.js +173 -0
- package/dist/proxy/chunking.d.ts +53 -0
- package/dist/proxy/chunking.js +127 -0
- package/dist/proxy/handshake-validation.d.ts +21 -0
- package/dist/proxy/handshake-validation.js +49 -0
- package/dist/rpc/__tests__/router.test.d.ts +2 -0
- package/dist/rpc/__tests__/router.test.js +262 -0
- package/dist/rpc/router.d.ts +37 -0
- package/dist/rpc/router.js +132 -0
- package/dist/services/BrowserProxyService.d.ts +13 -0
- package/dist/services/BrowserProxyService.js +139 -0
- package/dist/services/FilesystemService.d.ts +99 -0
- package/dist/services/FilesystemService.js +742 -0
- package/dist/services/GitService.d.ts +243 -0
- package/dist/services/GitService.js +1439 -0
- package/dist/services/SearchService.d.ts +93 -0
- package/dist/services/SearchService.js +670 -0
- package/dist/services/TerminalService.d.ts +62 -0
- package/dist/services/TerminalService.js +337 -0
- package/dist/services/__tests__/BrowserProxyService.test.d.ts +2 -0
- package/dist/services/__tests__/BrowserProxyService.test.js +145 -0
- package/dist/services/__tests__/FilesystemService.test.d.ts +2 -0
- package/dist/services/__tests__/FilesystemService.test.js +609 -0
- package/dist/services/__tests__/GitService.test.d.ts +2 -0
- package/dist/services/__tests__/GitService.test.js +953 -0
- package/dist/services/__tests__/SearchService.test.d.ts +2 -0
- package/dist/services/__tests__/SearchService.test.js +384 -0
- package/dist/services/__tests__/TerminalService.test.d.ts +2 -0
- package/dist/services/__tests__/TerminalService.test.js +513 -0
- package/dist/setup/wizard.d.ts +10 -0
- package/dist/setup/wizard.js +172 -0
- package/dist/types.d.ts +196 -0
- package/dist/types.js +44 -0
- package/dist/utils/__tests__/gitignore.test.d.ts +2 -0
- package/dist/utils/__tests__/gitignore.test.js +127 -0
- package/dist/utils/gitignore.d.ts +24 -0
- package/dist/utils/gitignore.js +77 -0
- package/dist/utils/logger.d.ts +96 -0
- package/dist/utils/logger.js +456 -0
- package/dist/utils/project-dir.d.ts +51 -0
- package/dist/utils/project-dir.js +191 -0
- package/dist/utils/ripgrep.d.ts +34 -0
- package/dist/utils/ripgrep.js +148 -0
- package/dist/utils/tool-detection.d.ts +17 -0
- package/dist/utils/tool-detection.js +126 -0
- package/dist/watcher/FileWatcher.d.ts +10 -0
- package/dist/watcher/FileWatcher.js +42 -0
- package/package.json +70 -0
- package/src/config/__tests__/config.test.ts +318 -0
- package/src/config/__tests__/credentials.test.ts +494 -0
- package/src/config/config.ts +206 -0
- package/src/config/credentials.ts +302 -0
- package/src/config/server-selection.ts +150 -0
- package/src/connection/__tests__/firebase-auth.test.ts +121 -0
- package/src/connection/__tests__/hmac.test.ts +509 -0
- package/src/connection/auth.ts +140 -0
- package/src/connection/firebase-auth.ts +504 -0
- package/src/connection/hmac.ts +139 -0
- package/src/i18n/index.ts +119 -0
- package/src/i18n/locales/en.json +313 -0
- package/src/i18n/locales/es.json +302 -0
- package/src/i18n/locales/fr.json +302 -0
- package/src/i18n/locales/id.json +302 -0
- package/src/i18n/locales/ja.json +302 -0
- package/src/i18n/locales/ko.json +302 -0
- package/src/i18n/locales/pt.json +302 -0
- package/src/i18n/locales/zh-Hans.json +302 -0
- package/src/index.ts +542 -0
- package/src/proxy/ProxyClient.ts +968 -0
- package/src/proxy/ProxySocketWrapper.ts +113 -0
- package/src/proxy/__tests__/ProxyClient.test.ts +575 -0
- package/src/proxy/__tests__/ProxySocketWrapper.test.ts +251 -0
- package/src/proxy/__tests__/handshake-validation.test.ts +367 -0
- package/src/proxy/chunking.ts +162 -0
- package/src/proxy/handshake-validation.ts +64 -0
- package/src/rpc/__tests__/router.test.ts +400 -0
- package/src/rpc/router.ts +183 -0
- package/src/services/BrowserProxyService.ts +179 -0
- package/src/services/FilesystemService.ts +841 -0
- package/src/services/GitService.ts +1639 -0
- package/src/services/SearchService.ts +809 -0
- package/src/services/TerminalService.ts +413 -0
- package/src/services/__tests__/BrowserProxyService.test.ts +155 -0
- package/src/services/__tests__/FilesystemService.test.ts +1002 -0
- package/src/services/__tests__/GitService.test.ts +1552 -0
- package/src/services/__tests__/SearchService.test.ts +484 -0
- package/src/services/__tests__/TerminalService.test.ts +702 -0
- package/src/setup/wizard.ts +242 -0
- package/src/types/fossil-delta.d.ts +4 -0
- package/src/types.ts +287 -0
- package/src/utils/__tests__/gitignore.test.ts +174 -0
- package/src/utils/gitignore.ts +91 -0
- package/src/utils/logger.ts +578 -0
- package/src/utils/project-dir.ts +218 -0
- package/src/utils/ripgrep.ts +180 -0
- package/src/utils/tool-detection.ts +141 -0
- package/src/watcher/FileWatcher.ts +53 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC 2.0 request router
|
|
3
|
+
*/
|
|
4
|
+
import { ErrorCode, createRPCError } from '../types.js';
|
|
5
|
+
import { FilesystemService } from '../services/FilesystemService.js';
|
|
6
|
+
import { GitService } from '../services/GitService.js';
|
|
7
|
+
import { TerminalService } from '../services/TerminalService.js';
|
|
8
|
+
import { SearchService } from '../services/SearchService.js';
|
|
9
|
+
import { BrowserProxyService } from '../services/BrowserProxyService.js';
|
|
10
|
+
export class RPCRouter {
|
|
11
|
+
/**
|
|
12
|
+
* Initialize services
|
|
13
|
+
*/
|
|
14
|
+
static initialize(rootPath, config, tools) {
|
|
15
|
+
this.rootPath = rootPath;
|
|
16
|
+
this.tools = tools;
|
|
17
|
+
this.terminalEnabled = config.terminal?.enabled ?? true;
|
|
18
|
+
this.browserProxyEnabled = config.browserProxy?.enabled ?? true;
|
|
19
|
+
this.filesystemService = new FilesystemService(rootPath, config.filesystem);
|
|
20
|
+
this.gitService = new GitService(rootPath);
|
|
21
|
+
this.browserProxyService = new BrowserProxyService();
|
|
22
|
+
// Parse maxFileSize from config
|
|
23
|
+
const maxFileSizeBytes = this.parseFileSize(config.filesystem.maxFileSize);
|
|
24
|
+
this.searchService = new SearchService(rootPath, maxFileSizeBytes, 64 * 1024, tools.ripgrep);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse file size string to bytes
|
|
28
|
+
*/
|
|
29
|
+
static parseFileSize(sizeStr) {
|
|
30
|
+
const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB)?$/i);
|
|
31
|
+
if (!match) {
|
|
32
|
+
return 10 * 1024 * 1024; // Default 10MB
|
|
33
|
+
}
|
|
34
|
+
const value = parseFloat(match[1]);
|
|
35
|
+
const unit = (match[2] || 'B').toUpperCase();
|
|
36
|
+
const multipliers = {
|
|
37
|
+
B: 1,
|
|
38
|
+
KB: 1024,
|
|
39
|
+
MB: 1024 * 1024,
|
|
40
|
+
GB: 1024 * 1024 * 1024,
|
|
41
|
+
};
|
|
42
|
+
return value * multipliers[unit];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get or create terminal service for socket
|
|
46
|
+
*/
|
|
47
|
+
static getTerminalService(socket) {
|
|
48
|
+
const deviceId = socket.data.deviceId;
|
|
49
|
+
// Update current socket for this deviceId (handles reconnections)
|
|
50
|
+
this.currentSockets.set(deviceId, socket);
|
|
51
|
+
if (!this.terminalServices.has(deviceId)) {
|
|
52
|
+
// Create new service with getter function that returns current socket
|
|
53
|
+
const getSocket = () => {
|
|
54
|
+
const currentSocket = this.currentSockets.get(deviceId);
|
|
55
|
+
if (!currentSocket) {
|
|
56
|
+
throw new Error(`No active socket for device: ${deviceId}`);
|
|
57
|
+
}
|
|
58
|
+
return currentSocket;
|
|
59
|
+
};
|
|
60
|
+
this.terminalServices.set(deviceId, new TerminalService(getSocket, 10, 10000, this.rootPath));
|
|
61
|
+
}
|
|
62
|
+
return this.terminalServices.get(deviceId);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Route JSON-RPC request to appropriate service
|
|
66
|
+
*/
|
|
67
|
+
static async route(message, socket) {
|
|
68
|
+
const { method, params } = message;
|
|
69
|
+
// Parse method prefix (split on first dot only so sub-namespaces like browser.proxy.request are preserved)
|
|
70
|
+
const dotIndex = method.indexOf('.');
|
|
71
|
+
const service = dotIndex !== -1 ? method.slice(0, dotIndex) : method;
|
|
72
|
+
const methodName = dotIndex !== -1 ? method.slice(dotIndex + 1) : '';
|
|
73
|
+
if (!service || !methodName) {
|
|
74
|
+
throw createRPCError(ErrorCode.INVALID_REQUEST, `Invalid method format: ${method}`);
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
switch (service) {
|
|
78
|
+
case 'fs':
|
|
79
|
+
return await this.filesystemService.handle(methodName, params, socket);
|
|
80
|
+
case 'git':
|
|
81
|
+
if (!this.tools.git) {
|
|
82
|
+
throw createRPCError(ErrorCode.FEATURE_DISABLED, 'Git is not available. Install Git 2.20.0+ to use version control features.');
|
|
83
|
+
}
|
|
84
|
+
return await this.gitService.handle(methodName, params, socket);
|
|
85
|
+
case 'search':
|
|
86
|
+
// Search is always available, but fast search (ripgrep) may be disabled
|
|
87
|
+
return await this.searchService.handle(methodName, params, socket);
|
|
88
|
+
case 'terminal':
|
|
89
|
+
if (!this.terminalEnabled) {
|
|
90
|
+
throw createRPCError(ErrorCode.FEATURE_DISABLED, 'Terminal is disabled in configuration.');
|
|
91
|
+
}
|
|
92
|
+
const terminalService = this.getTerminalService(socket);
|
|
93
|
+
return await terminalService.handle(methodName, params);
|
|
94
|
+
case 'browser': {
|
|
95
|
+
if (!this.browserProxyEnabled) {
|
|
96
|
+
throw createRPCError(ErrorCode.FEATURE_DISABLED, 'Browser proxy is disabled in configuration.');
|
|
97
|
+
}
|
|
98
|
+
// methodName is 'proxy.request' — strip the 'proxy.' sub-namespace
|
|
99
|
+
const dotIdx = methodName.indexOf('.');
|
|
100
|
+
const browserMethod = dotIdx !== -1 ? methodName.slice(dotIdx + 1) : methodName;
|
|
101
|
+
return await this.browserProxyService.handle(browserMethod, params, socket);
|
|
102
|
+
}
|
|
103
|
+
default:
|
|
104
|
+
throw createRPCError(ErrorCode.METHOD_NOT_FOUND, `Unknown service: ${service}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
// Re-throw if already an RPC error
|
|
109
|
+
if (error.code && error.message) {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
// Wrap other errors
|
|
113
|
+
console.error(`Error in ${method}:`, error);
|
|
114
|
+
throw createRPCError(ErrorCode.INTERNAL_ERROR, `Internal error: ${error.message || 'Unknown error'}`, { method, originalError: error.toString() });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Cleanup terminal service for socket
|
|
119
|
+
*/
|
|
120
|
+
static cleanupTerminalService(socket) {
|
|
121
|
+
const deviceId = socket.data.deviceId;
|
|
122
|
+
const service = this.terminalServices.get(deviceId);
|
|
123
|
+
if (service) {
|
|
124
|
+
service.cleanup();
|
|
125
|
+
this.terminalServices.delete(deviceId);
|
|
126
|
+
this.currentSockets.delete(deviceId);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
RPCRouter.terminalServices = new Map();
|
|
131
|
+
RPCRouter.currentSockets = new Map();
|
|
132
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrowserProxyService - Handles browser.proxy.request RPC calls
|
|
3
|
+
*
|
|
4
|
+
* Fetches from localhost:PORT/path and returns the HTTP response
|
|
5
|
+
* back to the editor client, which forwards it to the browser.spck.io SW.
|
|
6
|
+
*/
|
|
7
|
+
import { AuthenticatedSocket } from '../types.js';
|
|
8
|
+
export declare class BrowserProxyService {
|
|
9
|
+
handle(method: string, params: any, socket: AuthenticatedSocket): Promise<any>;
|
|
10
|
+
private proxyRequest;
|
|
11
|
+
private fetch;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=BrowserProxyService.d.ts.map
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrowserProxyService - Handles browser.proxy.request RPC calls
|
|
3
|
+
*
|
|
4
|
+
* Fetches from localhost:PORT/path and returns the HTTP response
|
|
5
|
+
* back to the editor client, which forwards it to the browser.spck.io SW.
|
|
6
|
+
*/
|
|
7
|
+
import http from 'http';
|
|
8
|
+
import https from 'https';
|
|
9
|
+
import { ErrorCode, createRPCError } from '../types.js';
|
|
10
|
+
import { logBrowserProxy } from '../utils/logger.js';
|
|
11
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
12
|
+
const ALLOWED_HOSTS = new Set(['localhost', '127.0.0.1']);
|
|
13
|
+
const ALLOWED_METHODS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']);
|
|
14
|
+
const MAX_RESPONSE_BODY_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
15
|
+
export class BrowserProxyService {
|
|
16
|
+
async handle(method, params, socket) {
|
|
17
|
+
switch (method) {
|
|
18
|
+
case 'request':
|
|
19
|
+
return await this.proxyRequest(params, socket.data.deviceId);
|
|
20
|
+
default:
|
|
21
|
+
throw createRPCError(ErrorCode.METHOD_NOT_FOUND, `Method not found: browser.proxy.${method}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async proxyRequest(params, uid) {
|
|
25
|
+
const { requestId, url, method, headers, body } = params;
|
|
26
|
+
// Validate method
|
|
27
|
+
const upperMethod = (method || 'GET').toUpperCase();
|
|
28
|
+
if (!ALLOWED_METHODS.has(upperMethod)) {
|
|
29
|
+
throw createRPCError(ErrorCode.INVALID_PARAMS, `Disallowed HTTP method: ${method}`);
|
|
30
|
+
}
|
|
31
|
+
// Validate URL — must be localhost only
|
|
32
|
+
let parsed;
|
|
33
|
+
try {
|
|
34
|
+
parsed = new URL(url);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw createRPCError(ErrorCode.INVALID_PARAMS, `Invalid URL: ${url}`);
|
|
38
|
+
}
|
|
39
|
+
if (!ALLOWED_HOSTS.has(parsed.hostname)) {
|
|
40
|
+
throw createRPCError(ErrorCode.PERMISSION_DENIED, `Only localhost requests are allowed`);
|
|
41
|
+
}
|
|
42
|
+
const port = parseInt(parsed.port || '80', 10);
|
|
43
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
44
|
+
throw createRPCError(ErrorCode.INVALID_PARAMS, `Invalid port: ${parsed.port}`);
|
|
45
|
+
}
|
|
46
|
+
// Convert body array back to Buffer
|
|
47
|
+
const bodyBuffer = body && body.length > 0 ? Buffer.from(body) : undefined;
|
|
48
|
+
// Strip hop-by-hop headers that should not be forwarded
|
|
49
|
+
const forwardHeaders = {};
|
|
50
|
+
const hopByHop = new Set([
|
|
51
|
+
'connection', 'keep-alive', 'transfer-encoding', 'te',
|
|
52
|
+
'trailer', 'upgrade', 'proxy-authorization', 'proxy-authenticate'
|
|
53
|
+
]);
|
|
54
|
+
for (const [key, value] of Object.entries(headers || {})) {
|
|
55
|
+
if (!hopByHop.has(key.toLowerCase())) {
|
|
56
|
+
forwardHeaders[key] = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (bodyBuffer) {
|
|
60
|
+
forwardHeaders['content-length'] = String(bodyBuffer.length);
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const response = await this.fetch(parsed, upperMethod, forwardHeaders, bodyBuffer);
|
|
64
|
+
logBrowserProxy(params, uid, true, undefined, {
|
|
65
|
+
status: response.status,
|
|
66
|
+
size: Buffer.byteLength(response.body, 'base64') * 3 / 4 | 0,
|
|
67
|
+
});
|
|
68
|
+
return { requestId, ...response };
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
logBrowserProxy(params, uid, false, error);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
fetch(url, method, headers, body) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const isHttps = url.protocol === 'https:';
|
|
78
|
+
const lib = isHttps ? https : http;
|
|
79
|
+
const options = {
|
|
80
|
+
hostname: url.hostname,
|
|
81
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
82
|
+
path: url.pathname + url.search,
|
|
83
|
+
method,
|
|
84
|
+
headers,
|
|
85
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
86
|
+
};
|
|
87
|
+
const req = lib.request(options, (res) => {
|
|
88
|
+
const chunks = [];
|
|
89
|
+
let totalLength = 0;
|
|
90
|
+
res.on('data', (chunk) => {
|
|
91
|
+
totalLength += chunk.length;
|
|
92
|
+
if (totalLength > MAX_RESPONSE_BODY_BYTES) {
|
|
93
|
+
req.destroy();
|
|
94
|
+
reject(createRPCError(ErrorCode.INTERNAL_ERROR, 'Response body too large'));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
chunks.push(chunk);
|
|
98
|
+
});
|
|
99
|
+
res.on('end', () => {
|
|
100
|
+
const bodyBuffer = Buffer.concat(chunks);
|
|
101
|
+
// Collect response headers (flatten multi-value headers)
|
|
102
|
+
const responseHeaders = {};
|
|
103
|
+
for (const [key, value] of Object.entries(res.headers)) {
|
|
104
|
+
if (value !== undefined) {
|
|
105
|
+
responseHeaders[key] = Array.isArray(value) ? value.join(', ') : value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
resolve({
|
|
109
|
+
status: res.statusCode || 200,
|
|
110
|
+
statusText: res.statusMessage || '',
|
|
111
|
+
headers: responseHeaders,
|
|
112
|
+
body: bodyBuffer.toString('base64'),
|
|
113
|
+
bodyEncoding: 'base64',
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
res.on('error', (err) => {
|
|
117
|
+
reject(createRPCError(ErrorCode.INTERNAL_ERROR, `Response error: ${err.message}`));
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
req.on('timeout', () => {
|
|
121
|
+
req.destroy();
|
|
122
|
+
reject(createRPCError(ErrorCode.OPERATION_TIMEOUT, 'Request to localhost timed out'));
|
|
123
|
+
});
|
|
124
|
+
req.on('error', (err) => {
|
|
125
|
+
if (err.code === 'ECONNREFUSED') {
|
|
126
|
+
reject(createRPCError(ErrorCode.INTERNAL_ERROR, `Connection refused on port ${err.port || ''} — is the dev server running?`));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
reject(createRPCError(ErrorCode.INTERNAL_ERROR, `Request failed: ${err.message}`));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
if (body) {
|
|
133
|
+
req.write(body);
|
|
134
|
+
}
|
|
135
|
+
req.end();
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=BrowserProxyService.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem service - handles file operations with fossil-delta compression
|
|
3
|
+
*/
|
|
4
|
+
import { AuthenticatedSocket } from '../types.js';
|
|
5
|
+
export declare class FilesystemService {
|
|
6
|
+
private rootPath;
|
|
7
|
+
private config;
|
|
8
|
+
private resolvedRootPath;
|
|
9
|
+
private realRootPath;
|
|
10
|
+
constructor(rootPath: string, config: any);
|
|
11
|
+
/**
|
|
12
|
+
* Get the real root path (following symlinks), cached after first call
|
|
13
|
+
*/
|
|
14
|
+
private getRealRootPath;
|
|
15
|
+
/**
|
|
16
|
+
* Handle filesystem RPC methods
|
|
17
|
+
*/
|
|
18
|
+
handle(method: string, params: any, socket: AuthenticatedSocket): Promise<any>;
|
|
19
|
+
/**
|
|
20
|
+
* Validate and sandbox path with symlink resolution
|
|
21
|
+
* Prevents directory traversal and symlink escape attacks
|
|
22
|
+
*/
|
|
23
|
+
private validatePath;
|
|
24
|
+
/**
|
|
25
|
+
* Check if file/directory exists
|
|
26
|
+
*/
|
|
27
|
+
private exists;
|
|
28
|
+
/**
|
|
29
|
+
* Check existence of multiple paths in parallel
|
|
30
|
+
*/
|
|
31
|
+
private bulkExists;
|
|
32
|
+
/**
|
|
33
|
+
* Read file contents
|
|
34
|
+
*/
|
|
35
|
+
private readFile;
|
|
36
|
+
/**
|
|
37
|
+
* Write file contents
|
|
38
|
+
*/
|
|
39
|
+
private write;
|
|
40
|
+
/**
|
|
41
|
+
* Apply fossil-delta patch to file
|
|
42
|
+
*/
|
|
43
|
+
private patchFile;
|
|
44
|
+
/**
|
|
45
|
+
* Get file hash
|
|
46
|
+
*/
|
|
47
|
+
private getFileHash;
|
|
48
|
+
/**
|
|
49
|
+
* Get file hash value (internal)
|
|
50
|
+
*/
|
|
51
|
+
private getFileHashValue;
|
|
52
|
+
/**
|
|
53
|
+
* Remove file or directory
|
|
54
|
+
* Returns success even if file doesn't exist (idempotent operation)
|
|
55
|
+
*/
|
|
56
|
+
private remove;
|
|
57
|
+
/**
|
|
58
|
+
* Create directory
|
|
59
|
+
*/
|
|
60
|
+
private mkdir;
|
|
61
|
+
/**
|
|
62
|
+
* Read directory contents
|
|
63
|
+
*/
|
|
64
|
+
private readdir;
|
|
65
|
+
/**
|
|
66
|
+
* Read directory recursively using breadth-first (level-order) traversal
|
|
67
|
+
* Performance optimizations:
|
|
68
|
+
* - matchPattern: Regex to filter paths (applied to full relative path)
|
|
69
|
+
* - limit: Maximum number of results (files + folders combined)
|
|
70
|
+
*
|
|
71
|
+
* Breadth-first ensures top-level items are returned first, which is important
|
|
72
|
+
* when using the limit parameter to get a representative sample of the directory.
|
|
73
|
+
*/
|
|
74
|
+
private readdirDeep;
|
|
75
|
+
/**
|
|
76
|
+
* Get file metadata
|
|
77
|
+
*/
|
|
78
|
+
private lstat;
|
|
79
|
+
/**
|
|
80
|
+
* Move/rename file or directory
|
|
81
|
+
* Returns the type of the moved item ('file' or 'folder')
|
|
82
|
+
*/
|
|
83
|
+
private mv;
|
|
84
|
+
/**
|
|
85
|
+
* Copy file or directory
|
|
86
|
+
* Returns the type of the copied item ('file' or 'folder')
|
|
87
|
+
*/
|
|
88
|
+
private copy;
|
|
89
|
+
/**
|
|
90
|
+
* Copy directory recursively (helper method)
|
|
91
|
+
*/
|
|
92
|
+
private copyDirectory;
|
|
93
|
+
/**
|
|
94
|
+
* Remove directory
|
|
95
|
+
* Returns success even if directory doesn't exist (idempotent operation)
|
|
96
|
+
*/
|
|
97
|
+
private rmdir;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=FilesystemService.d.ts.map
|