-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Share configurations * Rename "load" to "import" * Remove "import" button
- Loading branch information
Showing
1 changed file
with
190 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<meta charset="UTF-8"/> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> | ||
<title>Xray Config Validator</title> | ||
|
||
<script src="wasm_exec.js"></script> | ||
|
@@ -24,6 +24,7 @@ | |
* { | ||
box-sizing: border-box; | ||
} | ||
|
||
html, body { | ||
height: 100%; | ||
padding: 0; | ||
|
@@ -94,23 +95,71 @@ | |
} | ||
|
||
.cool-color { | ||
background-image: linear-gradient(to right,#E70000, #FF8C00, #FFEF00, #00811F, #0044FF, #760089); | ||
background-image: linear-gradient(to right, #E70000, #FF8C00, #FFEF00, #00811F, #0044FF, #760089); | ||
-webkit-background-clip: text; | ||
color:transparent; | ||
color: transparent; | ||
} | ||
|
||
.button-group { | ||
display: flex; | ||
justify-content: center; | ||
flex-wrap: wrap; | ||
gap: 10px; | ||
margin-top: 20px; | ||
} | ||
|
||
button { | ||
padding: 10px 20px; | ||
border: 1px solid #ced4da; | ||
border-radius: 5px; | ||
background-color: #f8f9fa; | ||
color: #212529; | ||
font-size: 1rem; | ||
cursor: pointer; | ||
transition: background-color 0.2s ease, box-shadow 0.2s ease; | ||
width: 100%; | ||
max-width: 250px; | ||
} | ||
|
||
button:hover { | ||
background-color: #e9ecef; | ||
} | ||
|
||
button:active { | ||
background-color: #dee2e6; | ||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); | ||
} | ||
|
||
@media (prefers-color-scheme: dark) { | ||
button { | ||
background-color: #333; | ||
border: 1px solid #555; | ||
color: #fff; | ||
} | ||
|
||
button:hover { | ||
background-color: #444; | ||
} | ||
|
||
button:active { | ||
background-color: #555; | ||
} | ||
|
||
body { | ||
background-color: #121212; | ||
color: #ffffff; | ||
} | ||
|
||
#editor { | ||
border: 1px solid #555; | ||
} | ||
|
||
#output { | ||
border: 1px solid #555; | ||
} | ||
} | ||
|
||
|
||
</style> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.33.0/min/vs/loader.min.js"></script> | ||
</head> | ||
|
@@ -127,11 +176,14 @@ <h1>Xray Config Validator</h1> | |
Start typing your Xray config in the editor above.<br> | ||
Hover over fields for documentation. <span class="cool-color">Autocomplete</span> is available. | ||
</div> | ||
<div class="button-group"> | ||
<button id="share-config">Share Configuration</button> | ||
</div> | ||
</div> | ||
|
||
<script> | ||
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.0/min/vs' }}); | ||
require(['vs/editor/editor.main'], function() { | ||
require.config({paths: {'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.0/min/vs'}}); | ||
require(['vs/editor/editor.main'], async function () { | ||
const editorElement = document.getElementById('editor'); | ||
const output = document.getElementById("output"); | ||
|
||
|
@@ -143,55 +195,6 @@ <h1>Xray Config Validator</h1> | |
|
||
|
||
const editor = monaco.editor.create(editorElement, { | ||
value: `{ | ||
"inbounds": [ | ||
{ | ||
"listen": "127.0.0.1", | ||
"port": 10808, | ||
"protocol": "socks", | ||
"settings": { | ||
"udp": true | ||
}, | ||
"sniffing": { | ||
"enabled": true, | ||
"destOverride": [ | ||
"http", | ||
"tls" | ||
] | ||
} | ||
} | ||
], | ||
"outbounds": [ | ||
{ | ||
"protocol": "vless", | ||
"settings": { | ||
"vnext": [ | ||
{ | ||
"address": "", | ||
"port": 443, | ||
"users": [ | ||
{ | ||
"id": "user", | ||
"encryption": "none", | ||
"flow": "xtls-rprx-vision" | ||
} | ||
] | ||
} | ||
] | ||
}, | ||
"streamSettings": { | ||
"network": "tcp", | ||
"security": "tls", | ||
"tlsSettings": { | ||
"serverName": "", | ||
"allowInsecure": false, | ||
"fingerprint": "chrome" | ||
} | ||
}, | ||
"tag": "proxy" | ||
} | ||
] | ||
}`, | ||
language: 'json', | ||
theme: 'vs-light', | ||
automaticLayout: true | ||
|
@@ -228,7 +231,7 @@ <h1>Xray Config Validator</h1> | |
}) | ||
.catch(error => console.error('Scheme load error:', error)); | ||
|
||
editor.onDidChangeModelContent(function() { | ||
function validateConfig() { | ||
const jsonText = editor.getValue(); | ||
const validationError = validateJSON(jsonText); | ||
|
||
|
@@ -250,12 +253,141 @@ <h1>Xray Config Validator</h1> | |
} | ||
} | ||
} | ||
}); | ||
} | ||
|
||
window.addEventListener("resize", function() { | ||
editor.onDidChangeModelContent(validateConfig); | ||
|
||
window.addEventListener("resize", function () { | ||
editor.layout(); | ||
}); | ||
|
||
const brotli = await import("https://unpkg.com/[email protected]/index.web.js?module").then(m => m.default); | ||
const textEncoder = new TextEncoder(); | ||
const textDecoder = new TextDecoder('utf-8'); | ||
|
||
document.getElementById("share-config").addEventListener("click", async () => { | ||
if (!editor) { | ||
console.error('Editor is not loaded'); | ||
return; | ||
} | ||
const config = editor.getValue(); | ||
|
||
const compressed = await compressWithBrotli(config); | ||
const base64String = uint8ArrayToBase64(compressed) | ||
|
||
const baseUrl = window.location.origin + window.location.pathname; | ||
const url = `${baseUrl}?c=${base64String}`; | ||
|
||
navigator.clipboard.writeText(url).then(() => { | ||
alert("Shareable link copied to clipboard!"); | ||
}).catch((err) => { | ||
console.error("Failed to copy link: ", err); | ||
}); | ||
}); | ||
|
||
function uint8ArrayToBase64(uint8Array) { | ||
let binaryString = ''; | ||
for (let i = 0; i < uint8Array.length; i++) { | ||
binaryString += String.fromCharCode(uint8Array[i]); | ||
} | ||
|
||
return btoa(binaryString); | ||
} | ||
|
||
function compressWithBrotli(input) { | ||
const uncompressedData = textEncoder.encode(input); | ||
return brotli.compress(uncompressedData) | ||
} | ||
|
||
const urlParams = new URLSearchParams(window.location.search); | ||
let encodedConfig = urlParams.get('c'); | ||
|
||
if (encodedConfig) { | ||
decodeAndLoadConfig(encodedConfig) | ||
} else { | ||
setDefaultConfigValue() | ||
} | ||
|
||
function setDefaultConfigValue() { | ||
editor.setValue(`{ | ||
"inbounds": [ | ||
{ | ||
"listen": "127.0.0.1", | ||
"port": 10808, | ||
"protocol": "socks", | ||
"settings": { | ||
"udp": true | ||
}, | ||
"sniffing": { | ||
"enabled": true, | ||
"destOverride": [ | ||
"http", | ||
"tls" | ||
] | ||
} | ||
} | ||
], | ||
"outbounds": [ | ||
{ | ||
"protocol": "vless", | ||
"settings": { | ||
"vnext": [ | ||
{ | ||
"address": "", | ||
"port": 443, | ||
"users": [ | ||
{ | ||
"id": "user", | ||
"encryption": "none", | ||
"flow": "xtls-rprx-vision" | ||
} | ||
] | ||
} | ||
] | ||
}, | ||
"streamSettings": { | ||
"network": "tcp", | ||
"security": "tls", | ||
"tlsSettings": { | ||
"serverName": "", | ||
"allowInsecure": false, | ||
"fingerprint": "chrome" | ||
} | ||
}, | ||
"tag": "proxy" | ||
} | ||
] | ||
}`,); | ||
} | ||
|
||
function decodeAndLoadConfig(encodedConfig) { | ||
try { | ||
encodedConfig = encodedConfig.replace(/ /g, '+').trim(); | ||
|
||
const base64ToUint8Array = (base64String) => { | ||
const binaryString = atob(base64String); | ||
const len = binaryString.length; | ||
const bytes = new Uint8Array(len); | ||
for (let i = 0; i < len; i++) { | ||
bytes[i] = binaryString.charCodeAt(i); | ||
} | ||
return bytes; | ||
}; | ||
|
||
const compressedConfig = base64ToUint8Array(encodedConfig); | ||
const decompressedConfig = brotli.decompress(compressedConfig); | ||
const configText = textDecoder.decode(decompressedConfig); | ||
|
||
editor.setValue(configText); | ||
} catch (error) { | ||
console.error("Failed to decode and decompress configuration:", error); | ||
output.dataset.status = "error"; | ||
output.innerText = "Failed to decode and load shared configuration."; | ||
} | ||
} | ||
|
||
}); | ||
|
||
</script> | ||
|
||
</body> | ||
|