diff --git a/src/worker-vless.js b/src/worker-vless.js index 55677a02d..090cf5ebe 100644 --- a/src/worker-vless.js +++ b/src/worker-vless.js @@ -1,636 +1 @@ -// version base on commit 58686d5d125194d34a1137913b3a64ddcf55872f, time is 2024-11-27 09:26:01 UTC. -// @ts-ignore -import { connect } from 'cloudflare:sockets'; - -// How to generate your own UUID: -// [Windows] Press "Win + R", input cmd and run: Powershell -NoExit -Command "[guid]::NewGuid()" -let userID = 'd342d11e-d424-4583-b36e-524ab1f0afa4'; - -let proxyIP = ''; - - -if (!isValidUUID(userID)) { - throw new Error('uuid is not valid'); -} - -export default { - /** - * @param {import("@cloudflare/workers-types").Request} request - * @param {{UUID: string, PROXYIP: string}} env - * @param {import("@cloudflare/workers-types").ExecutionContext} ctx - * @returns {Promise} - */ - async fetch(request, env, ctx) { - try { - userID = env.UUID || userID; - proxyIP = env.PROXYIP || proxyIP; - const upgradeHeader = request.headers.get('Upgrade'); - if (!upgradeHeader || upgradeHeader !== 'websocket') { - const url = new URL(request.url); - switch (url.pathname) { - case '/': - return new Response(JSON.stringify(request.cf), { status: 200 }); - case `/${userID}`: { - const vlessConfig = getVLESSConfig(userID, request.headers.get('Host')); - return new Response(`${vlessConfig}`, { - status: 200, - headers: { - "Content-Type": "text/plain;charset=utf-8", - } - }); - } - default: - return new Response('Not found', { status: 404 }); - } - } else { - return await vlessOverWSHandler(request); - } - } catch (err) { - /** @type {Error} */ let e = err; - return new Response(e.toString()); - } - }, -}; - - - - -/** - * - * @param {import("@cloudflare/workers-types").Request} request - */ -async function vlessOverWSHandler(request) { - - /** @type {import("@cloudflare/workers-types").WebSocket[]} */ - // @ts-ignore - const webSocketPair = new WebSocketPair(); - const [client, webSocket] = Object.values(webSocketPair); - - webSocket.accept(); - - let address = ''; - let portWithRandomLog = ''; - const log = (/** @type {string} */ info, /** @type {string | undefined} */ event) => { - console.log(`[${address}:${portWithRandomLog}] ${info}`, event || ''); - }; - const earlyDataHeader = request.headers.get('sec-websocket-protocol') || ''; - - const readableWebSocketStream = makeReadableWebSocketStream(webSocket, earlyDataHeader, log); - - /** @type {{ value: import("@cloudflare/workers-types").Socket | null}}*/ - let remoteSocketWapper = { - value: null, - }; - let udpStreamWrite = null; - let isDns = false; - - // ws --> remote - readableWebSocketStream.pipeTo(new WritableStream({ - async write(chunk, controller) { - if (isDns && udpStreamWrite) { - return udpStreamWrite(chunk); - } - if (remoteSocketWapper.value) { - const writer = remoteSocketWapper.value.writable.getWriter() - await writer.write(chunk); - writer.releaseLock(); - return; - } - - const { - hasError, - message, - portRemote = 443, - addressRemote = '', - rawDataIndex, - vlessVersion = new Uint8Array([0, 0]), - isUDP, - } = processVlessHeader(chunk, userID); - address = addressRemote; - portWithRandomLog = `${portRemote}--${Math.random()} ${isUDP ? 'udp ' : 'tcp ' - } `; - if (hasError) { - // controller.error(message); - throw new Error(message); // cf seems has bug, controller.error will not end stream - // webSocket.close(1000, message); - return; - } - // if UDP but port not DNS port, close it - if (isUDP) { - if (portRemote === 53) { - isDns = true; - } else { - // controller.error('UDP proxy only enable for DNS which is port 53'); - throw new Error('UDP proxy only enable for DNS which is port 53'); // cf seems has bug, controller.error will not end stream - return; - } - } - // ["version", "附加信息长度 N"] - const vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]); - const rawClientData = chunk.slice(rawDataIndex); - - // TODO: support udp here when cf runtime has udp support - if (isDns) { - const { write } = await handleUDPOutBound(webSocket, vlessResponseHeader, log); - udpStreamWrite = write; - udpStreamWrite(rawClientData); - return; - } - handleTCPOutBound(remoteSocketWapper, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log); - }, - close() { - log(`readableWebSocketStream is close`); - }, - abort(reason) { - log(`readableWebSocketStream is abort`, JSON.stringify(reason)); - }, - })).catch((err) => { - log('readableWebSocketStream pipeTo error', err); - }); - - return new Response(null, { - status: 101, - // @ts-ignore - webSocket: client, - }); -} - -/** - * Handles outbound TCP connections. - * - * @param {any} remoteSocket - * @param {string} addressRemote The remote address to connect to. - * @param {number} portRemote The remote port to connect to. - * @param {Uint8Array} rawClientData The raw client data to write. - * @param {import("@cloudflare/workers-types").WebSocket} webSocket The WebSocket to pass the remote socket to. - * @param {Uint8Array} vlessResponseHeader The VLESS response header. - * @param {function} log The logging function. - * @returns {Promise} The remote socket. - */ -async function handleTCPOutBound(remoteSocket, addressRemote, portRemote, rawClientData, webSocket, vlessResponseHeader, log,) { - async function connectAndWrite(address, port) { - /** @type {import("@cloudflare/workers-types").Socket} */ - const tcpSocket = connect({ - hostname: address, - port: port, - }); - remoteSocket.value = tcpSocket; - log(`connected to ${address}:${port}`); - const writer = tcpSocket.writable.getWriter(); - await writer.write(rawClientData); // first write, nomal is tls client hello - writer.releaseLock(); - return tcpSocket; - } - - // if the cf connect tcp socket have no incoming data, we retry to redirect ip - async function retry() { - const tcpSocket = await connectAndWrite(proxyIP || addressRemote, portRemote) - // no matter retry success or not, close websocket - tcpSocket.closed.catch(error => { - console.log('retry tcpSocket closed error', error); - }).finally(() => { - safeCloseWebSocket(webSocket); - }) - remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, null, log); - } - - const tcpSocket = await connectAndWrite(addressRemote, portRemote); - - // when remoteSocket is ready, pass to websocket - // remote--> ws - remoteSocketToWS(tcpSocket, webSocket, vlessResponseHeader, retry, log); -} - -/** - * - * @param {import("@cloudflare/workers-types").WebSocket} webSocketServer - * @param {string} earlyDataHeader for ws 0rtt - * @param {(info: string)=> void} log for ws 0rtt - */ -function makeReadableWebSocketStream(webSocketServer, earlyDataHeader, log) { - let readableStreamCancel = false; - const stream = new ReadableStream({ - start(controller) { - webSocketServer.addEventListener('message', (event) => { - if (readableStreamCancel) { - return; - } - const message = event.data; - controller.enqueue(message); - }); - - // The event means that the client closed the client -> server stream. - // However, the server -> client stream is still open until you call close() on the server side. - // The WebSocket protocol says that a separate close message must be sent in each direction to fully close the socket. - webSocketServer.addEventListener('close', () => { - // client send close, need close server - // if stream is cancel, skip controller.close - safeCloseWebSocket(webSocketServer); - if (readableStreamCancel) { - return; - } - controller.close(); - } - ); - webSocketServer.addEventListener('error', (err) => { - log('webSocketServer has error'); - controller.error(err); - } - ); - // for ws 0rtt - const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader); - if (error) { - controller.error(error); - } else if (earlyData) { - controller.enqueue(earlyData); - } - }, - - pull(controller) { - // if ws can stop read if stream is full, we can implement backpressure - // https://streams.spec.whatwg.org/#example-rs-push-backpressure - }, - cancel(reason) { - // 1. pipe WritableStream has error, this cancel will called, so ws handle server close into here - // 2. if readableStream is cancel, all controller.close/enqueue need skip, - // 3. but from testing controller.error still work even if readableStream is cancel - if (readableStreamCancel) { - return; - } - log(`ReadableStream was canceled, due to ${reason}`) - readableStreamCancel = true; - safeCloseWebSocket(webSocketServer); - } - }); - - return stream; - -} - -// https://xtls.github.io/development/protocols/vless.html -// https://github.com/zizifn/excalidraw-backup/blob/main/v2ray-protocol.excalidraw - -/** - * - * @param { ArrayBuffer} vlessBuffer - * @param {string} userID - * @returns - */ -function processVlessHeader( - vlessBuffer, - userID -) { - if (vlessBuffer.byteLength < 24) { - return { - hasError: true, - message: 'invalid data', - }; - } - const version = new Uint8Array(vlessBuffer.slice(0, 1)); - let isValidUser = false; - let isUDP = false; - if (stringify(new Uint8Array(vlessBuffer.slice(1, 17))) === userID) { - isValidUser = true; - } - if (!isValidUser) { - return { - hasError: true, - message: 'invalid user', - }; - } - - const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0]; - //skip opt for now - - const command = new Uint8Array( - vlessBuffer.slice(18 + optLength, 18 + optLength + 1) - )[0]; - - // 0x01 TCP - // 0x02 UDP - // 0x03 MUX - if (command === 1) { - } else if (command === 2) { - isUDP = true; - } else { - return { - hasError: true, - message: `command ${command} is not support, command 01-tcp,02-udp,03-mux`, - }; - } - const portIndex = 18 + optLength + 1; - const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2); - // port is big-Endian in raw data etc 80 == 0x005d - const portRemote = new DataView(portBuffer).getUint16(0); - - let addressIndex = portIndex + 2; - const addressBuffer = new Uint8Array( - vlessBuffer.slice(addressIndex, addressIndex + 1) - ); - - // 1--> ipv4 addressLength =4 - // 2--> domain name addressLength=addressBuffer[1] - // 3--> ipv6 addressLength =16 - const addressType = addressBuffer[0]; - let addressLength = 0; - let addressValueIndex = addressIndex + 1; - let addressValue = ''; - switch (addressType) { - case 1: - addressLength = 4; - addressValue = new Uint8Array( - vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) - ).join('.'); - break; - case 2: - addressLength = new Uint8Array( - vlessBuffer.slice(addressValueIndex, addressValueIndex + 1) - )[0]; - addressValueIndex += 1; - addressValue = new TextDecoder().decode( - vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) - ); - break; - case 3: - addressLength = 16; - const dataView = new DataView( - vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength) - ); - // 2001:0db8:85a3:0000:0000:8a2e:0370:7334 - const ipv6 = []; - for (let i = 0; i < 8; i++) { - ipv6.push(dataView.getUint16(i * 2).toString(16)); - } - addressValue = ipv6.join(':'); - // seems no need add [] for ipv6 - break; - default: - return { - hasError: true, - message: `invild addressType is ${addressType}`, - }; - } - if (!addressValue) { - return { - hasError: true, - message: `addressValue is empty, addressType is ${addressType}`, - }; - } - - return { - hasError: false, - addressRemote: addressValue, - addressType, - portRemote, - rawDataIndex: addressValueIndex + addressLength, - vlessVersion: version, - isUDP, - }; -} - - -/** - * - * @param {import("@cloudflare/workers-types").Socket} remoteSocket - * @param {import("@cloudflare/workers-types").WebSocket} webSocket - * @param {ArrayBuffer} vlessResponseHeader - * @param {(() => Promise) | null} retry - * @param {*} log - */ -async function remoteSocketToWS(remoteSocket, webSocket, vlessResponseHeader, retry, log) { - // remote--> ws - let remoteChunkCount = 0; - let chunks = []; - /** @type {ArrayBuffer | null} */ - let vlessHeader = vlessResponseHeader; - let hasIncomingData = false; // check if remoteSocket has incoming data - await remoteSocket.readable - .pipeTo( - new WritableStream({ - start() { - }, - /** - * - * @param {Uint8Array} chunk - * @param {*} controller - */ - async write(chunk, controller) { - hasIncomingData = true; - // remoteChunkCount++; - if (webSocket.readyState !== WS_READY_STATE_OPEN) { - controller.error( - 'webSocket.readyState is not open, maybe close' - ); - } - if (vlessHeader) { - webSocket.send(await new Blob([vlessHeader, chunk]).arrayBuffer()); - vlessHeader = null; - } else { - // seems no need rate limit this, CF seems fix this??.. - // if (remoteChunkCount > 20000) { - // // cf one package is 4096 byte(4kb), 4096 * 20000 = 80M - // await delay(1); - // } - webSocket.send(chunk); - } - }, - close() { - log(`remoteConnection!.readable is close with hasIncomingData is ${hasIncomingData}`); - // safeCloseWebSocket(webSocket); // no need server close websocket frist for some case will casue HTTP ERR_CONTENT_LENGTH_MISMATCH issue, client will send close event anyway. - }, - abort(reason) { - console.error(`remoteConnection!.readable abort`, reason); - }, - }) - ) - .catch((error) => { - console.error( - `remoteSocketToWS has exception `, - error.stack || error - ); - safeCloseWebSocket(webSocket); - }); - - // seems is cf connect socket have error, - // 1. Socket.closed will have error - // 2. Socket.readable will be close without any data coming - if (hasIncomingData === false && retry) { - log(`retry`) - retry(); - } -} - -/** - * - * @param {string} base64Str - * @returns - */ -function base64ToArrayBuffer(base64Str) { - if (!base64Str) { - return { error: null }; - } - try { - // go use modified Base64 for URL rfc4648 which js atob not support - base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/'); - const decode = atob(base64Str); - const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0)); - return { earlyData: arryBuffer.buffer, error: null }; - } catch (error) { - return { error }; - } -} - -/** - * This is not real UUID validation - * @param {string} uuid - */ -function isValidUUID(uuid) { - const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; - return uuidRegex.test(uuid); -} - -const WS_READY_STATE_OPEN = 1; -const WS_READY_STATE_CLOSING = 2; -/** - * Normally, WebSocket will not has exceptions when close. - * @param {import("@cloudflare/workers-types").WebSocket} socket - */ -function safeCloseWebSocket(socket) { - try { - if (socket.readyState === WS_READY_STATE_OPEN || socket.readyState === WS_READY_STATE_CLOSING) { - socket.close(); - } - } catch (error) { - console.error('safeCloseWebSocket error', error); - } -} - -const byteToHex = []; -for (let i = 0; i < 256; ++i) { - byteToHex.push((i + 256).toString(16).slice(1)); -} -function unsafeStringify(arr, offset = 0) { - return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); -} -function stringify(arr, offset = 0) { - const uuid = unsafeStringify(arr, offset); - if (!isValidUUID(uuid)) { - throw TypeError("Stringified UUID is invalid"); - } - return uuid; -} - - -/** - * - * @param {import("@cloudflare/workers-types").WebSocket} webSocket - * @param {ArrayBuffer} vlessResponseHeader - * @param {(string)=> void} log - */ -async function handleUDPOutBound(webSocket, vlessResponseHeader, log) { - - let isVlessHeaderSent = false; - const transformStream = new TransformStream({ - start(controller) { - - }, - transform(chunk, controller) { - // udp message 2 byte is the the length of udp data - // TODO: this should have bug, beacsue maybe udp chunk can be in two websocket message - for (let index = 0; index < chunk.byteLength;) { - const lengthBuffer = chunk.slice(index, index + 2); - const udpPakcetLength = new DataView(lengthBuffer).getUint16(0); - const udpData = new Uint8Array( - chunk.slice(index + 2, index + 2 + udpPakcetLength) - ); - index = index + 2 + udpPakcetLength; - controller.enqueue(udpData); - } - }, - flush(controller) { - } - }); - - // only handle dns udp for now - transformStream.readable.pipeTo(new WritableStream({ - async write(chunk) { - const resp = await fetch('https://1.1.1.1/dns-query', - { - method: 'POST', - headers: { - 'content-type': 'application/dns-message', - }, - body: chunk, - }) - const dnsQueryResult = await resp.arrayBuffer(); - const udpSize = dnsQueryResult.byteLength; - // console.log([...new Uint8Array(dnsQueryResult)].map((x) => x.toString(16))); - const udpSizeBuffer = new Uint8Array([(udpSize >> 8) & 0xff, udpSize & 0xff]); - if (webSocket.readyState === WS_READY_STATE_OPEN) { - log(`doh success and dns message length is ${udpSize}`); - if (isVlessHeaderSent) { - webSocket.send(await new Blob([udpSizeBuffer, dnsQueryResult]).arrayBuffer()); - } else { - webSocket.send(await new Blob([vlessResponseHeader, udpSizeBuffer, dnsQueryResult]).arrayBuffer()); - isVlessHeaderSent = true; - } - } - } - })).catch((error) => { - log('dns udp has error' + error) - }); - - const writer = transformStream.writable.getWriter(); - - return { - /** - * - * @param {Uint8Array} chunk - */ - write(chunk) { - writer.write(chunk); - } - }; -} - -/** - * - * @param {string} userID - * @param {string | null} hostName - * @returns {string} - */ -function getVLESSConfig(userID, hostName) { - const protocol = "vless"; - const vlessMain = - `${protocol}` + - `://${userID}@${hostName}:443`+ - `?encryption=none&security=tls&sni=${hostName}&fp=randomized&type=ws&host=${hostName}&path=%2F%3Fed%3D2048#${hostName}`; - - return ` -################################################################ -v2ray ---------------------------------------------------------------- -${vlessMain} ---------------------------------------------------------------- -################################################################ -clash-meta ---------------------------------------------------------------- -- type: vless - name: ${hostName} - server: ${hostName} - port: 443 - uuid: ${userID} - network: ws - tls: true - udp: false - sni: ${hostName} - client-fingerprint: chrome - ws-opts: - path: "/?ed=2048" - headers: - host: ${hostName} ---------------------------------------------------------------- -################################################################ -`; -} - +const _0x163b9e=_0x5001;(function(_0xa1e33f,_0x56bd83){const _0x8c2977=_0x5001,_0x5580e9=_0xa1e33f();while(!![]){try{const _0x56fd05=parseInt(_0x8c2977(0xd4))/0x1*(parseInt(_0x8c2977(0x128))/0x2)+-parseInt(_0x8c2977(0x10b))/0x3*(parseInt(_0x8c2977(0x126))/0x4)+parseInt(_0x8c2977(0xe5))/0x5*(-parseInt(_0x8c2977(0xf0))/0x6)+-parseInt(_0x8c2977(0x12f))/0x7*(-parseInt(_0x8c2977(0x112))/0x8)+-parseInt(_0x8c2977(0x108))/0x9*(parseInt(_0x8c2977(0x10c))/0xa)+parseInt(_0x8c2977(0x12a))/0xb*(-parseInt(_0x8c2977(0xe9))/0xc)+parseInt(_0x8c2977(0x10d))/0xd*(parseInt(_0x8c2977(0xea))/0xe);if(_0x56fd05===_0x56bd83)break;else _0x5580e9['push'](_0x5580e9['shift']());}catch(_0x597737){_0x5580e9['push'](_0x5580e9['shift']());}}}(_0x2b00,0x52081));import{connect}from'cloudflare:sockets';let userID=_0x163b9e(0x12d),proxyIP='';if(!isValidUUID(userID))throw new Error(_0x163b9e(0x121));export default{async 'fetch'(_0x2f2dbd,_0x49ec4a,_0x57afa8){const _0x37a90e=_0x163b9e;try{userID=_0x49ec4a[_0x37a90e(0x115)]||userID,proxyIP=_0x49ec4a['PROXYIP']||proxyIP;const _0x3587e7=_0x2f2dbd['headers'][_0x37a90e(0xe0)](_0x37a90e(0xfb));if(!_0x3587e7||_0x3587e7!==_0x37a90e(0x12e)){const _0x26b303=new URL(_0x2f2dbd[_0x37a90e(0xf9)]);switch(_0x26b303['pathname']){case'/':return new Response(JSON[_0x37a90e(0xe6)](_0x2f2dbd['cf']),{'status':0xc8});case'/'+userID:{const _0x47000d=getVLESSConfig(userID,_0x2f2dbd[_0x37a90e(0xe7)][_0x37a90e(0xe0)](_0x37a90e(0xd7)));return new Response(''+_0x47000d,{'status':0xc8,'headers':{'Content-Type':_0x37a90e(0xfe)}});}default:return new Response(_0x37a90e(0x111),{'status':0x194});}}else return await vlessOverWSHandler(_0x2f2dbd);}catch(_0x33f3f0){let _0x34aba0=_0x33f3f0;return new Response(_0x34aba0[_0x37a90e(0xec)]());}}};async function vlessOverWSHandler(_0x25c3db){const _0x2bd9cf=_0x163b9e,_0x42104b=new WebSocketPair(),[_0x1c74c6,_0x5779f3]=Object[_0x2bd9cf(0xfd)](_0x42104b);_0x5779f3[_0x2bd9cf(0xe3)]();let _0x3f5d10='',_0x32533b='';const _0x365bf8=(_0x5e3e41,_0xe4b90b)=>{const _0x1cea9a=_0x2bd9cf;console[_0x1cea9a(0x118)]('['+_0x3f5d10+':'+_0x32533b+']\x20'+_0x5e3e41,_0xe4b90b||'');},_0x43c919=_0x25c3db[_0x2bd9cf(0xe7)][_0x2bd9cf(0xe0)](_0x2bd9cf(0x104))||'',_0x15d904=makeReadableWebSocketStream(_0x5779f3,_0x43c919,_0x365bf8);let _0x308333={'value':null},_0x3bac33=null,_0x3ae233=![];return _0x15d904[_0x2bd9cf(0x109)](new WritableStream({async 'write'(_0x2e64ef,_0x41a0aa){const _0x9f0000=_0x2bd9cf;if(_0x3ae233&&_0x3bac33)return _0x3bac33(_0x2e64ef);if(_0x308333[_0x9f0000(0x116)]){const _0x19b4dd=_0x308333[_0x9f0000(0x116)][_0x9f0000(0xe4)][_0x9f0000(0x12c)]();await _0x19b4dd['write'](_0x2e64ef),_0x19b4dd[_0x9f0000(0x100)]();return;}const {hasError:_0x9ba366,message:_0x1a1a6f,portRemote:portRemote=0x1bb,addressRemote:addressRemote='',rawDataIndex:_0x33cd76,vlessVersion:vlessVersion=new Uint8Array([0x0,0x0]),isUDP:_0x250053}=processVlessHeader(_0x2e64ef,userID);_0x3f5d10=addressRemote,_0x32533b=portRemote+'--'+Math['random']()+'\x20'+(_0x250053?_0x9f0000(0x11b):_0x9f0000(0x125))+'\x20';if(_0x9ba366){throw new Error(_0x1a1a6f);return;}if(_0x250053){if(portRemote===0x35)_0x3ae233=!![];else{throw new Error('UDP\x20proxy\x20only\x20enable\x20for\x20DNS\x20which\x20is\x20port\x2053');return;}}const _0x368710=new Uint8Array([vlessVersion[0x0],0x0]),_0x16e46b=_0x2e64ef[_0x9f0000(0xff)](_0x33cd76);if(_0x3ae233){const {write:_0x258fbf}=await handleUDPOutBound(_0x5779f3,_0x368710,_0x365bf8);_0x3bac33=_0x258fbf,_0x3bac33(_0x16e46b);return;}handleTCPOutBound(_0x308333,addressRemote,portRemote,_0x16e46b,_0x5779f3,_0x368710,_0x365bf8);},'close'(){const _0x226221=_0x2bd9cf;_0x365bf8(_0x226221(0x10a));},'abort'(_0x53f573){const _0x37eebb=_0x2bd9cf;_0x365bf8(_0x37eebb(0xe8),JSON[_0x37eebb(0xe6)](_0x53f573));}}))[_0x2bd9cf(0x10e)](_0x57c06c=>{const _0x31bb46=_0x2bd9cf;_0x365bf8(_0x31bb46(0xf7),_0x57c06c);}),new Response(null,{'status':0x65,'webSocket':_0x1c74c6});}function _0x2b00(){const _0x336b60=['https://1.1.1.1/dns-query','get','webSocketServer\x20has\x20error','write','accept','writable','55GdcUMq','stringify','headers','readableWebSocketStream\x20is\x20abort','1284288SMtuSt','4172042beWkhO','\x0a################################################################\x0av2ray\x0a---------------------------------------------------------------\x0a','toString','invild\x20\x20addressType\x20is\x20','vless','getUint16','65460YRohkI','invalid\x20user','send','finally','error','command\x20','from','readableWebSocketStream\x20pipeTo\x20error','connected\x20to\x20','url','dns\x20udp\x20has\x20error','Upgrade','replace','values','text/plain;charset=utf-8','slice','releaseLock','?encryption=none&security=tls&sni=','invalid\x20data','addressValue\x20is\x20empty,\x20addressType\x20is\x20','sec-websocket-protocol','byteLength','addEventListener','decode','45naDvgi','pipeTo','readableWebSocketStream\x20is\x20close','3hYTsqq','125640NYYHrj','52WsFRJQ','catch','readable','remoteSocketToWS\x20has\x20exception\x20','Not\x20found','256YLqAQv','doh\x20success\x20and\x20dns\x20message\x20length\x20is\x20','&path=%2F%3Fed%3D2048#','UUID','value','Stringified\x20UUID\x20is\x20invalid','log','ReadableStream\x20was\x20canceled,\x20due\x20to\x20','retry\x20tcpSocket\x20closed\x20error','udp\x20','data',':443','\x0a\x20\x20client-fingerprint:\x20chrome\x0a\x20\x20ws-opts:\x0a\x20\x20\x20\x20path:\x20\x22/?ed=2048\x22\x0a\x20\x20\x20\x20headers:\x0a\x20\x20\x20\x20\x20\x20host:\x20','join','readyState','uuid\x20is\x20not\x20valid','test','stack','\x20is\x20not\x20support,\x20command\x2001-tcp,02-udp,03-mux','tcp\x20','765068WXANgC','remoteConnection!.readable\x20abort','1452MFimdM','enqueue','55ftLvsA','&fp=randomized&type=ws&host=','getWriter','d342d11e-d424-4583-b36e-524ab1f0afa4','websocket','8939bjfDzd','remoteConnection!.readable\x20is\x20close\x20with\x20hasIncomingData\x20is\x20','17NZmCAr','buffer','\x0a\x20\x20port:\x20443\x0a\x20\x20uuid:\x20','Host','arrayBuffer','://','safeCloseWebSocket\x20error','message','push','POST','close'];_0x2b00=function(){return _0x336b60;};return _0x2b00();}async function handleTCPOutBound(_0x297008,_0x40e427,_0x147ae5,_0x4f0a84,_0x1923ed,_0x258753,_0x3dd726){async function _0x2eba82(_0x22831f,_0x238c34){const _0x5792ff=_0x5001,_0x1e2bda=connect({'hostname':_0x22831f,'port':_0x238c34});_0x297008['value']=_0x1e2bda,_0x3dd726(_0x5792ff(0xf8)+_0x22831f+':'+_0x238c34);const _0x118929=_0x1e2bda[_0x5792ff(0xe4)]['getWriter']();return await _0x118929[_0x5792ff(0xe2)](_0x4f0a84),_0x118929[_0x5792ff(0x100)](),_0x1e2bda;}async function _0x9134b7(){const _0x42d809=_0x5001,_0x28ffac=await _0x2eba82(proxyIP||_0x40e427,_0x147ae5);_0x28ffac['closed'][_0x42d809(0x10e)](_0x34bdd9=>{const _0x15becf=_0x42d809;console['log'](_0x15becf(0x11a),_0x34bdd9);})[_0x42d809(0xf3)](()=>{safeCloseWebSocket(_0x1923ed);}),remoteSocketToWS(_0x28ffac,_0x1923ed,_0x258753,null,_0x3dd726);}const _0x1e7882=await _0x2eba82(_0x40e427,_0x147ae5);remoteSocketToWS(_0x1e7882,_0x1923ed,_0x258753,_0x9134b7,_0x3dd726);}function makeReadableWebSocketStream(_0x41c9c9,_0x43d3f8,_0x20eb8e){let _0x5ab6d7=![];const _0x3cf30d=new ReadableStream({'start'(_0x4a42d0){const _0x57cfcb=_0x5001;_0x41c9c9[_0x57cfcb(0x106)](_0x57cfcb(0xdb),_0x2f8aef=>{const _0x12a76b=_0x57cfcb;if(_0x5ab6d7)return;const _0x23d015=_0x2f8aef[_0x12a76b(0x11c)];_0x4a42d0[_0x12a76b(0x129)](_0x23d015);}),_0x41c9c9[_0x57cfcb(0x106)](_0x57cfcb(0xde),()=>{const _0x4d4891=_0x57cfcb;safeCloseWebSocket(_0x41c9c9);if(_0x5ab6d7)return;_0x4a42d0[_0x4d4891(0xde)]();}),_0x41c9c9[_0x57cfcb(0x106)](_0x57cfcb(0xf4),_0x14de46=>{const _0x34eb44=_0x57cfcb;_0x20eb8e(_0x34eb44(0xe1)),_0x4a42d0[_0x34eb44(0xf4)](_0x14de46);});const {earlyData:_0x1e0b29,error:_0x2a50be}=base64ToArrayBuffer(_0x43d3f8);if(_0x2a50be)_0x4a42d0['error'](_0x2a50be);else _0x1e0b29&&_0x4a42d0[_0x57cfcb(0x129)](_0x1e0b29);},'pull'(_0x4a13a9){},'cancel'(_0x32e8fb){const _0x547842=_0x5001;if(_0x5ab6d7)return;_0x20eb8e(_0x547842(0x119)+_0x32e8fb),_0x5ab6d7=!![],safeCloseWebSocket(_0x41c9c9);}});return _0x3cf30d;}function processVlessHeader(_0x45b44c,_0x338948){const _0x283aad=_0x163b9e;if(_0x45b44c[_0x283aad(0x105)]<0x18)return{'hasError':!![],'message':_0x283aad(0x102)};const _0x2ecf0e=new Uint8Array(_0x45b44c[_0x283aad(0xff)](0x0,0x1));let _0x4287a3=![],_0x13e7ba=![];stringify(new Uint8Array(_0x45b44c['slice'](0x1,0x11)))===_0x338948&&(_0x4287a3=!![]);if(!_0x4287a3)return{'hasError':!![],'message':_0x283aad(0xf1)};const _0x46b7d6=new Uint8Array(_0x45b44c[_0x283aad(0xff)](0x11,0x12))[0x0],_0x328067=new Uint8Array(_0x45b44c[_0x283aad(0xff)](0x12+_0x46b7d6,0x12+_0x46b7d6+0x1))[0x0];if(_0x328067===0x1){}else{if(_0x328067===0x2)_0x13e7ba=!![];else return{'hasError':!![],'message':_0x283aad(0xf5)+_0x328067+_0x283aad(0x124)};}const _0x3ad3fa=0x12+_0x46b7d6+0x1,_0x451ae8=_0x45b44c[_0x283aad(0xff)](_0x3ad3fa,_0x3ad3fa+0x2),_0x2d2ccf=new DataView(_0x451ae8)['getUint16'](0x0);let _0x542830=_0x3ad3fa+0x2;const _0x3f8d27=new Uint8Array(_0x45b44c[_0x283aad(0xff)](_0x542830,_0x542830+0x1)),_0x3c7028=_0x3f8d27[0x0];let _0x144824=0x0,_0x1a9dcf=_0x542830+0x1,_0x501fe1='';switch(_0x3c7028){case 0x1:_0x144824=0x4,_0x501fe1=new Uint8Array(_0x45b44c[_0x283aad(0xff)](_0x1a9dcf,_0x1a9dcf+_0x144824))['join']('.');break;case 0x2:_0x144824=new Uint8Array(_0x45b44c[_0x283aad(0xff)](_0x1a9dcf,_0x1a9dcf+0x1))[0x0],_0x1a9dcf+=0x1,_0x501fe1=new TextDecoder()[_0x283aad(0x107)](_0x45b44c[_0x283aad(0xff)](_0x1a9dcf,_0x1a9dcf+_0x144824));break;case 0x3:_0x144824=0x10;const _0x429625=new DataView(_0x45b44c[_0x283aad(0xff)](_0x1a9dcf,_0x1a9dcf+_0x144824)),_0x15f0bd=[];for(let _0x2a26b8=0x0;_0x2a26b8<0x8;_0x2a26b8++){_0x15f0bd[_0x283aad(0xdc)](_0x429625[_0x283aad(0xef)](_0x2a26b8*0x2)['toString'](0x10));}_0x501fe1=_0x15f0bd[_0x283aad(0x11f)](':');break;default:return{'hasError':!![],'message':_0x283aad(0xed)+_0x3c7028};}if(!_0x501fe1)return{'hasError':!![],'message':_0x283aad(0x103)+_0x3c7028};return{'hasError':![],'addressRemote':_0x501fe1,'addressType':_0x3c7028,'portRemote':_0x2d2ccf,'rawDataIndex':_0x1a9dcf+_0x144824,'vlessVersion':_0x2ecf0e,'isUDP':_0x13e7ba};}async function remoteSocketToWS(_0x1079ef,_0x1181ac,_0x1ccfaf,_0x396df4,_0x34c4f0){const _0x6355fc=_0x163b9e;let _0x1ce64a=0x0,_0x4faae6=[],_0x31db02=_0x1ccfaf,_0x7f76d2=![];await _0x1079ef[_0x6355fc(0x10f)][_0x6355fc(0x109)](new WritableStream({'start'(){},async 'write'(_0x1272af,_0xbbb4ee){const _0x58a3d3=_0x6355fc;_0x7f76d2=!![],_0x1181ac['readyState']!==WS_READY_STATE_OPEN&&_0xbbb4ee[_0x58a3d3(0xf4)]('webSocket.readyState\x20is\x20not\x20open,\x20maybe\x20close'),_0x31db02?(_0x1181ac[_0x58a3d3(0xf2)](await new Blob([_0x31db02,_0x1272af])['arrayBuffer']()),_0x31db02=null):_0x1181ac[_0x58a3d3(0xf2)](_0x1272af);},'close'(){const _0x270494=_0x6355fc;_0x34c4f0(_0x270494(0x130)+_0x7f76d2);},'abort'(_0x2b2307){const _0x4314ad=_0x6355fc;console['error'](_0x4314ad(0x127),_0x2b2307);}}))[_0x6355fc(0x10e)](_0x2be8ed=>{const _0x41c27f=_0x6355fc;console[_0x41c27f(0xf4)](_0x41c27f(0x110),_0x2be8ed[_0x41c27f(0x123)]||_0x2be8ed),safeCloseWebSocket(_0x1181ac);}),_0x7f76d2===![]&&_0x396df4&&(_0x34c4f0('retry'),_0x396df4());}function base64ToArrayBuffer(_0x898caa){const _0x1dc67f=_0x163b9e;if(!_0x898caa)return{'error':null};try{_0x898caa=_0x898caa[_0x1dc67f(0xfc)](/-/g,'+')['replace'](/_/g,'/');const _0x4dfdbf=atob(_0x898caa),_0x32b46f=Uint8Array[_0x1dc67f(0xf6)](_0x4dfdbf,_0xf58761=>_0xf58761['charCodeAt'](0x0));return{'earlyData':_0x32b46f[_0x1dc67f(0xd5)],'error':null};}catch(_0x5ad591){return{'error':_0x5ad591};}}function _0x5001(_0x2dba3d,_0x10ba99){const _0x2b00b7=_0x2b00();return _0x5001=function(_0x50012d,_0x7a4d9){_0x50012d=_0x50012d-0xd4;let _0x1ffd73=_0x2b00b7[_0x50012d];return _0x1ffd73;},_0x5001(_0x2dba3d,_0x10ba99);}function isValidUUID(_0x2e3153){const _0xcd254=_0x163b9e,_0x4becac=/^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;return _0x4becac[_0xcd254(0x122)](_0x2e3153);}const WS_READY_STATE_OPEN=0x1,WS_READY_STATE_CLOSING=0x2;function safeCloseWebSocket(_0x32eaed){const _0x56d250=_0x163b9e;try{(_0x32eaed['readyState']===WS_READY_STATE_OPEN||_0x32eaed['readyState']===WS_READY_STATE_CLOSING)&&_0x32eaed[_0x56d250(0xde)]();}catch(_0x39b6fb){console[_0x56d250(0xf4)](_0x56d250(0xda),_0x39b6fb);}}const byteToHex=[];for(let i=0x0;i<0x100;++i){byteToHex[_0x163b9e(0xdc)]((i+0x100)[_0x163b9e(0xec)](0x10)[_0x163b9e(0xff)](0x1));}function unsafeStringify(_0x3d97cb,_0x1daaef=0x0){return(byteToHex[_0x3d97cb[_0x1daaef+0x0]]+byteToHex[_0x3d97cb[_0x1daaef+0x1]]+byteToHex[_0x3d97cb[_0x1daaef+0x2]]+byteToHex[_0x3d97cb[_0x1daaef+0x3]]+'-'+byteToHex[_0x3d97cb[_0x1daaef+0x4]]+byteToHex[_0x3d97cb[_0x1daaef+0x5]]+'-'+byteToHex[_0x3d97cb[_0x1daaef+0x6]]+byteToHex[_0x3d97cb[_0x1daaef+0x7]]+'-'+byteToHex[_0x3d97cb[_0x1daaef+0x8]]+byteToHex[_0x3d97cb[_0x1daaef+0x9]]+'-'+byteToHex[_0x3d97cb[_0x1daaef+0xa]]+byteToHex[_0x3d97cb[_0x1daaef+0xb]]+byteToHex[_0x3d97cb[_0x1daaef+0xc]]+byteToHex[_0x3d97cb[_0x1daaef+0xd]]+byteToHex[_0x3d97cb[_0x1daaef+0xe]]+byteToHex[_0x3d97cb[_0x1daaef+0xf]])['toLowerCase']();}function stringify(_0x4a611a,_0x502c91=0x0){const _0xcd4094=_0x163b9e,_0xda73a1=unsafeStringify(_0x4a611a,_0x502c91);if(!isValidUUID(_0xda73a1))throw TypeError(_0xcd4094(0x117));return _0xda73a1;}async function handleUDPOutBound(_0x498f60,_0x201ef8,_0x7c4de6){const _0x20f6d8=_0x163b9e;let _0x1c6723=![];const _0x1f3d6c=new TransformStream({'start'(_0x188e3e){},'transform'(_0x10ff3c,_0x4c2bca){const _0xfb9bc3=_0x5001;for(let _0x518aae=0x0;_0x518aae<_0x10ff3c[_0xfb9bc3(0x105)];){const _0x471594=_0x10ff3c['slice'](_0x518aae,_0x518aae+0x2),_0xd90cc4=new DataView(_0x471594)['getUint16'](0x0),_0x4a2840=new Uint8Array(_0x10ff3c[_0xfb9bc3(0xff)](_0x518aae+0x2,_0x518aae+0x2+_0xd90cc4));_0x518aae=_0x518aae+0x2+_0xd90cc4,_0x4c2bca[_0xfb9bc3(0x129)](_0x4a2840);}},'flush'(_0x2ca057){}});_0x1f3d6c['readable'][_0x20f6d8(0x109)](new WritableStream({async 'write'(_0x3a582b){const _0x8e7d28=_0x20f6d8,_0x3ce302=await fetch(_0x8e7d28(0xdf),{'method':_0x8e7d28(0xdd),'headers':{'content-type':'application/dns-message'},'body':_0x3a582b}),_0x20037c=await _0x3ce302['arrayBuffer'](),_0x5d5a0b=_0x20037c['byteLength'],_0x1e55b1=new Uint8Array([_0x5d5a0b>>0x8&0xff,_0x5d5a0b&0xff]);_0x498f60[_0x8e7d28(0x120)]===WS_READY_STATE_OPEN&&(_0x7c4de6(_0x8e7d28(0x113)+_0x5d5a0b),_0x1c6723?_0x498f60[_0x8e7d28(0xf2)](await new Blob([_0x1e55b1,_0x20037c])[_0x8e7d28(0xd8)]()):(_0x498f60[_0x8e7d28(0xf2)](await new Blob([_0x201ef8,_0x1e55b1,_0x20037c])[_0x8e7d28(0xd8)]()),_0x1c6723=!![]));}}))[_0x20f6d8(0x10e)](_0x8149e7=>{const _0x45c29b=_0x20f6d8;_0x7c4de6(_0x45c29b(0xfa)+_0x8149e7);});const _0x4ecdd4=_0x1f3d6c[_0x20f6d8(0xe4)][_0x20f6d8(0x12c)]();return{'write'(_0x5c7b5e){const _0x5d86ab=_0x20f6d8;_0x4ecdd4[_0x5d86ab(0xe2)](_0x5c7b5e);}};}function getVLESSConfig(_0x4205ff,_0x56dda2){const _0x5ae358=_0x163b9e,_0x2270da=_0x5ae358(0xee),_0x114696=''+_0x2270da+(_0x5ae358(0xd9)+_0x4205ff+'@'+_0x56dda2+_0x5ae358(0x11d))+(_0x5ae358(0x101)+_0x56dda2+_0x5ae358(0x12b)+_0x56dda2+_0x5ae358(0x114)+_0x56dda2);return _0x5ae358(0xeb)+_0x114696+'\x0a---------------------------------------------------------------\x0a################################################################\x0aclash-meta\x0a---------------------------------------------------------------\x0a-\x20type:\x20vless\x0a\x20\x20name:\x20'+_0x56dda2+'\x0a\x20\x20server:\x20'+_0x56dda2+_0x5ae358(0xd6)+_0x4205ff+'\x0a\x20\x20network:\x20ws\x0a\x20\x20tls:\x20true\x0a\x20\x20udp:\x20false\x0a\x20\x20sni:\x20'+_0x56dda2+_0x5ae358(0x11e)+_0x56dda2+'\x0a---------------------------------------------------------------\x0a################################################################\x0a';}