Fix: Resolve Realm CORS Violation for Node.js Hosted Version
Problem: The Node.js hosted version of RisuAI encountered an issue where it failed to fetch data from the Risu Realm server when accessed remotely. RisuAI's frontend directly fetches data from the Realm server (e.g., sv.risuai.xyz). While the official web version did not exhibit CORS errors (potentially due to same-origin deployment or specific server-side CORS configurations), running the Node.js version on a self-hosted server and accessing it remotely resulted in browser CORS policy violations. Solution: The fix involves detecting when the frontend runs in the Node.js host environment. When this environment is detected, instead of requesting Realm data directly from the external server (sv.risuai.xyz), the frontend now directs the request to a new proxy endpoint (`/hub-proxy/*`) on its own backend server. The backend proxy then fetches the required data (including JSON and images) from the actual Realm server, correctly handles content types and compression, and relays the response back to the frontend. This ensures that, from the browser's perspective, the frontend is communicating with its same-origin backend, effectively bypassing browser CORS restrictions and resolving the data fetching issue.
This commit is contained in:
@@ -68,6 +68,7 @@
|
||||
"ml-distance": "^4.0.1",
|
||||
"mobile-drag-drop": "3.0.0-rc.0",
|
||||
"msgpackr": "1.10.1",
|
||||
"node-fetch": "2",
|
||||
"node-html-parser": "^6.1.12",
|
||||
"ollama": "^0.5.0",
|
||||
"pdfjs-dist": "^4.0.379",
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -164,6 +164,9 @@ importers:
|
||||
msgpackr:
|
||||
specifier: 1.10.1
|
||||
version: 1.10.1
|
||||
node-fetch:
|
||||
specifier: '2'
|
||||
version: 2.7.0
|
||||
node-html-parser:
|
||||
specifier: ^6.1.12
|
||||
version: 6.1.12
|
||||
|
||||
@@ -11,6 +11,8 @@ app.use(express.raw({ type: 'application/octet-stream', limit: '50mb' }));
|
||||
const {pipeline} = require('stream/promises')
|
||||
const https = require('https');
|
||||
const sslPath = path.join(process.cwd(), 'server/node/ssl/certificate');
|
||||
const EXTERNAL_HUB_URL = 'https://sv.risuai.xyz';
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
let password = ''
|
||||
|
||||
@@ -29,7 +31,7 @@ function isHex(str) {
|
||||
}
|
||||
|
||||
app.get('/', async (req, res, next) => {
|
||||
console.log("connected")
|
||||
console.log("[Server] Connected")
|
||||
try {
|
||||
const mainIndex = await fs.readFile(path.join(process.cwd(), 'dist', 'index.html'))
|
||||
const root = htmlparser.parse(mainIndex)
|
||||
@@ -137,12 +139,117 @@ const reverseProxyFunc_get = async (req, res, next) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Risu Realm Proxy
|
||||
async function hubProxyHandler(req, res, next) {
|
||||
try {
|
||||
// Extract request path and query parameters
|
||||
const pathAndQuery = req.originalUrl.replace(/^\/hub-proxy/, '');
|
||||
const externalURL = EXTERNAL_HUB_URL + pathAndQuery;
|
||||
|
||||
console.log(`[Hub Proxy] Forwarding ${req.method} request to: ${externalURL}`);
|
||||
|
||||
// Prepare headers to send to the realm server (including Accept-Encoding modification)
|
||||
const headersToSend = { ...req.headers };
|
||||
delete headersToSend['host'];
|
||||
delete headersToSend['connection'];
|
||||
headersToSend['accept-encoding'] = 'gzip, deflate'; // Exclude zstd, etc.
|
||||
if (!headersToSend['x-forwarded-for']) {
|
||||
headersToSend['x-forwarded-for'] = req.ip;
|
||||
}
|
||||
|
||||
// Execute the fetch request to the realm server
|
||||
const response = await fetch(externalURL, {
|
||||
method: req.method,
|
||||
headers: headersToSend,
|
||||
body: (req.method !== 'GET' && req.method !== 'HEAD') ? req.body : undefined,
|
||||
});
|
||||
|
||||
console.log(`[Hub Proxy] Received status ${response.status} from external server`);
|
||||
|
||||
// Handle the realm server response
|
||||
// Clean up response headers and extract Content-Type
|
||||
const responseHeaders = {};
|
||||
// Check the Content-Type of the realm server response (use default if missing)
|
||||
let contentType = response.headers.get('content-type') || 'application/octet-stream';
|
||||
|
||||
response.headers.forEach((value, key) => {
|
||||
const lowerKey = key.toLowerCase();
|
||||
// List of headers not to be forwarded to the client
|
||||
const excludedHeaders = [
|
||||
'transfer-encoding', 'connection', 'content-encoding',
|
||||
'access-control-allow-origin', 'access-control-allow-methods',
|
||||
'access-control-allow-headers', 'content-security-policy',
|
||||
'content-security-policy-report-only', 'clear-site-data',
|
||||
'strict-transport-security', 'expect-ct',
|
||||
'cf-ray', 'cf-cache-status', 'report-to', 'nel', 'server', 'server-timing', 'alt-svc'
|
||||
];
|
||||
if (!excludedHeaders.includes(lowerKey)) {
|
||||
responseHeaders[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
// Set the status code and cleaned headers for the client
|
||||
res.status(response.status).set(responseHeaders);
|
||||
|
||||
// Determine body processing method based on Content-Type
|
||||
try {
|
||||
if (contentType.startsWith('application/json')) {
|
||||
// JSON response: read as text and send
|
||||
const bodyText = await response.text();
|
||||
console.log(`[Hub Proxy] Processing JSON response (size: ${bodyText.length})`);
|
||||
res.setHeader('Content-Type', contentType); // Set the final Content-Type
|
||||
res.send(bodyText);
|
||||
|
||||
} else if (contentType.startsWith('image/')) {
|
||||
// Image response: read as buffer and send
|
||||
const bodyBuffer = await response.buffer(); // Assuming 'fetch' response object has a .buffer() method or similar
|
||||
console.log(`[Hub Proxy] Processing Image response (type: ${contentType}, size: ${bodyBuffer.length} bytes)`);
|
||||
res.setHeader('Content-Type', contentType); // Set the final Content-Type
|
||||
res.send(bodyBuffer);
|
||||
|
||||
} else {
|
||||
// Other responses (HTML, other text, unknown binary, etc.): read as buffer and send safely
|
||||
const bodyBuffer = await response.buffer(); // Assuming 'fetch' response object has a .buffer() method or similar
|
||||
console.log(`[Hub Proxy] Processing Other response as buffer (type: ${contentType}, size: ${bodyBuffer.length} bytes)`);
|
||||
// Use original Content-Type if available, otherwise use octet-stream (already handled by default assignment)
|
||||
res.setHeader('Content-Type', contentType);
|
||||
res.send(bodyBuffer);
|
||||
}
|
||||
} catch (bodyError) {
|
||||
// If an error occurs while reading/processing the response body
|
||||
console.error("[Hub Proxy] Error reading/processing response body:", bodyError);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).send({ error: 'Failed to process response body from hub server.' });
|
||||
} else {
|
||||
console.error("[Hub Proxy] Headers already sent, cannot send body error to client.");
|
||||
res.end();
|
||||
}
|
||||
return; // End the handler
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// Fetch request itself failed or other exceptions
|
||||
console.error("[Hub Proxy] Request failed:", error);
|
||||
if (!res.headersSent) {
|
||||
res.status(502).send({ error: 'Proxy failed to connect to or get response from the hub server.' });
|
||||
} else {
|
||||
console.error("[Hub Proxy] Headers already sent, cannot send connection error to client.");
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/hub-proxy/*', hubProxyHandler);
|
||||
// app.post('/hub-proxy/*', hubProxyHandler);
|
||||
// app.put('/hub-proxy/*', hubProxyHandler);
|
||||
|
||||
app.get('/proxy', reverseProxyFunc_get);
|
||||
app.get('/proxy2', reverseProxyFunc_get);
|
||||
|
||||
app.post('/proxy', reverseProxyFunc);
|
||||
app.post('/proxy2', reverseProxyFunc);
|
||||
|
||||
|
||||
app.get('/api/password', async(req, res)=> {
|
||||
if(password === ''){
|
||||
res.send({status: 'unset'})
|
||||
@@ -317,36 +424,37 @@ async function getHttpsOptions() {
|
||||
return { key, cert };
|
||||
|
||||
} catch (error) {
|
||||
console.error('SSL setup errors:', error.message);
|
||||
console.log('Start the server with HTTP instead of HTTPS...');
|
||||
console.error('[Server] SSL setup errors:', error.message);
|
||||
console.log('[Server] Start the server with HTTP instead of HTTPS...');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function startServer() {
|
||||
try {
|
||||
|
||||
const port = process.env.PORT || 6001;
|
||||
const httpsOptions = await getHttpsOptions();
|
||||
|
||||
if (httpsOptions) {
|
||||
// HTTPS
|
||||
https.createServer(httpsOptions, app).listen(port, () => {
|
||||
console.log("HTTPS server is running.");
|
||||
console.log("https://localhost:6001/");
|
||||
console.log("[Server] HTTPS server is running.");
|
||||
console.log(`[Server] https://localhost:${port}/`);
|
||||
});
|
||||
|
||||
} else {
|
||||
// HTTP
|
||||
app.listen(port, () => {
|
||||
console.log("HTTP server is running.");
|
||||
console.log("http://localhost:6001/");
|
||||
console.log("[Server] HTTP server is running.");
|
||||
console.log(`[Server] http://localhost:${port}/`);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Server] Failed to start server :', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await startServer();
|
||||
} catch (error) {
|
||||
console.error('Fail to start server :', error);
|
||||
}
|
||||
})();
|
||||
@@ -18,7 +18,11 @@ import { exportModule, readModule, type RisuModule } from "./process/modules"
|
||||
import { readFile } from "@tauri-apps/plugin-fs"
|
||||
import { onOpenUrl } from '@tauri-apps/plugin-deep-link';
|
||||
|
||||
export const hubURL = "https://sv.risuai.xyz"
|
||||
|
||||
const EXTERNAL_HUB_URL = 'https://sv.risuai.xyz';
|
||||
export const hubURL = typeof window !== 'undefined' && (window as any).__NODE__ === true
|
||||
? '/hub-proxy'
|
||||
: EXTERNAL_HUB_URL;
|
||||
|
||||
export async function importCharacter() {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user