Skip to content

Commit

Permalink
Share configurations (#8)
Browse files Browse the repository at this point in the history
* Share configurations

* Rename "load" to "import"

* Remove "import" button
  • Loading branch information
jomertix authored Jan 1, 2025
1 parent 79a3ae1 commit 3fa9f6b
Showing 1 changed file with 190 additions and 58 deletions.
248 changes: 190 additions & 58 deletions index.html
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>
Expand All @@ -24,6 +24,7 @@
* {
box-sizing: border-box;
}

html, body {
height: 100%;
padding: 0;
Expand Down Expand Up @@ -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>
Expand All @@ -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");

Expand All @@ -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
Expand Down Expand Up @@ -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);

Expand All @@ -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>
Expand Down

0 comments on commit 3fa9f6b

Please sign in to comment.