Files
workinf_Blender_Wasm/tools/web/native-extension-proxy.mjs
mes123456 f3e21c6b98
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-10 11:18:01 +08:00

55 lines
4.5 KiB
JavaScript

import http from 'node:http';
import net from 'node:net';
import dns from 'node:dns/promises';
import fs from 'node:fs';
import path from 'node:path';
const privateAddresses=new net.BlockList();
for(const [address,prefix] of [['0.0.0.0',8],['10.0.0.0',8],['100.64.0.0',10],['127.0.0.0',8],['169.254.0.0',16],['172.16.0.0',12],['192.0.0.0',24],['192.168.0.0',16],['198.18.0.0',15],['224.0.0.0',4],['240.0.0.0',4]])privateAddresses.addSubnet(address,prefix,'ipv4');
for(const [address,prefix] of [['::',128],['::1',128],['::ffff:0:0',96],['fc00::',7],['fe80::',10],['ff00::',8],['2001:db8::',32]])privateAddresses.addSubnet(address,prefix,'ipv6');
export async function startNativeExtensionProxy(workspace){
const baseHosts=['extensions.blender.org','download.blender.org','projects.blender.org',...(process.env.BLENDER_WEB_EXTENSION_HOSTS??'').split(',').map(host=>host.trim().toLowerCase()).filter(Boolean)];
const hosts=new Set(baseHosts);
const sockets=new Set();let tunnels=0,closed=false;
const status={mode:'REPOSITORY_HTTPS_PROXY',hosts:[...hosts],lastError:null};
const server=http.createServer((request,response)=>{response.writeHead(405);response.end('HTTPS CONNECT required');});
server.headersTimeout=10000;server.requestTimeout=15000;
server.on('connection',socket=>{sockets.add(socket);socket.on('close',()=>sockets.delete(socket));socket.on('error',()=>{});});
server.on('clientError',(_error,socket)=>socket.destroy());
server.on('connect',async(request,client,head)=>{
let upstream,reserved=false;
try{
hosts.clear();for(const host of baseHosts)hosts.add(host);
const registration=path.join(workspace,'extension-hosts.json');
if(fs.existsSync(registration)&&fs.statSync(registration).size<=32768){
const data=JSON.parse(fs.readFileSync(registration,'utf8'));
if(Array.isArray(data.hosts))for(const host of data.hosts.slice(0,128))if(typeof host==='string'&&/^[a-z0-9.-]+$/.test(host))hosts.add(host);
status.hosts=[...hosts];
}
const target=new URL('https://'+request.url);
if(target.username||target.password||target.pathname!=='/'||target.search||target.hash||(target.port&&target.port!=='443')||!hosts.has(target.hostname))throw new Error('EXTENSION_HOST_NOT_ALLOWED: '+target.hostname);
if(tunnels>=8)throw new Error('EXTENSION_CONNECTION_BUDGET');tunnels++;reserved=true;
const addresses=await dns.lookup(target.hostname,{all:true});
const address=addresses.find(item=>!privateAddresses.check(item.address,item.family===6?'ipv6':'ipv4'));
if(!address)throw new Error('EXTENSION_PUBLIC_ADDRESS_REQUIRED');
if(closed||client.destroyed)return;
upstream=net.createConnection({host:address.address,family:address.family,port:443});sockets.add(upstream);
let bytes=0;const timeout=setTimeout(()=>{client.destroy();upstream.destroy();},240000);timeout.unref();
const account=chunk=>{bytes+=chunk.length;if(bytes>512*1024*1024){status.lastError='EXTENSION_TRANSFER_BUDGET';client.destroy();upstream.destroy();}};
upstream.setTimeout(60000,()=>upstream.destroy());client.setTimeout(60000,()=>client.destroy());
upstream.on('data',account);client.on('data',account);
upstream.once('connect',()=>{status.lastError=null;client.write('HTTP/1.1 200 Connection Established\r\n\r\n');if(head.length)upstream.write(head);client.pipe(upstream);upstream.pipe(client);});
upstream.on('error',()=>{status.lastError='EXTENSION_UPSTREAM_UNAVAILABLE';client.destroy();});
upstream.once('close',()=>{clearTimeout(timeout);sockets.delete(upstream);client.destroy();});
client.once('close',()=>{clearTimeout(timeout);upstream.destroy();});
}catch(error){status.lastError=String(error.message);if(!client.destroyed)client.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n'+status.lastError);}
finally{if(reserved){if(client.destroyed)tunnels--;else client.once('close',()=>tunnels--);}}
});
const socketPath=path.join(workspace,'extension-proxy.sock');
const directory=fs.openSync(workspace,fs.constants.O_RDONLY|fs.constants.O_DIRECTORY|fs.constants.O_NOFOLLOW);fs.rmSync(socketPath,{force:true});
try{await new Promise((resolve,reject)=>{server.once('error',reject);server.listen(`/proc/self/fd/${directory}/extension-proxy.sock`,resolve);});}finally{fs.closeSync(directory);}
fs.chmodSync(socketPath,0o600);
return {status,async close(){closed=true;for(const socket of sockets)socket.destroy();await new Promise(resolve=>server.close(resolve));fs.rmSync(socketPath,{force:true});}};
}