Skip to content

Instantly share code, notes, and snippets.

@dillera
Created May 22, 2024 02:00
Show Gist options
  • Save dillera/c2caf629f2af8fb81cbcd0309a1b3896 to your computer and use it in GitHub Desktop.
Save dillera/c2caf629f2af8fb81cbcd0309a1b3896 to your computer and use it in GitHub Desktop.
FujiNet Flasher Auto Manifest
cat <<'EOF' > index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP Web Tools with Dynamic Manifest</title>
<style>
#spinner {
display: none;
margin-left: 10px;
border: 4px solid rgba(0, 0, 0, 0.1);
width: 24px;
height: 24px;
border-radius: 50%;
border-left-color: #09f;
animation: spin 1s ease infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script>
<script type="module" src="js/install-button.js"></script>
<script type="module">
const CORS_PROXY = "https://flasher.6502.fun:8443/";
async function fetchDailyBuilds() {
updateStatus("Connecting to GitHub to fetch daily builds...");
try {
const response = await fetch(`${CORS_PROXY}https://api.github.com/repos/FujiNetWIFI/fujinet-firmware/releases/tags/nightly`);
if (!response.ok) {
const errorText = await response.text();
updateStatus(`Failed to fetch builds from GitHub: ${response.status} ${response.statusText}`);
console.error("GitHub API request failed:", response.status, response.statusText, errorText);
throw new Error("GitHub API request failed");
}
const data = await response.json();
const assets = data.assets;
updateStatus("Daily builds fetched successfully.");
console.log("GitHub API response:", assets);
return assets;
} catch (error) {
updateStatus("Failed to fetch daily builds: " + error.message);
console.error("Failed to fetch daily builds:", error);
throw error;
}
}
async function fetchAndExtractZip(url, size) {
updateStatus("Fetching ZIP file...");
showSpinner(true);
console.log("Fetching ZIP file from URL:", url);
const response = await fetch(`${CORS_PROXY}${url}`);
if (!response.ok) {
throw new Error(`Failed to fetch ZIP file: ${response.statusText}`);
}
const reader = response.body.getReader();
const contentLength = size;
let receivedLength = 0;
let chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
receivedLength += value.length;
updateStatus(`Received ${receivedLength} of ${contentLength} bytes (${Math.round((receivedLength / contentLength) * 100)}%)`);
}
const blob = new Blob(chunks);
console.log("ZIP file fetched. Extracting...");
const zip = await JSZip.loadAsync(blob);
console.log("ZIP file extracted:", zip);
showSpinner(false);
return zip;
}
async function generateManifest(build) {
updateStatus(`Generating manifest for build: ${build.name}`);
console.log("Generating manifest for build:", build);
const zip = await fetchAndExtractZip(build.browser_download_url, build.size);
// Extract release.json from the ZIP file
const releaseJsonFile = zip.file('release.json');
if (!releaseJsonFile) {
throw new Error("release.json not found in the ZIP file");
}
const releaseJsonContent = await releaseJsonFile.async('string');
const releaseData = JSON.parse(releaseJsonContent);
console.log("Extracted release.json content:", releaseData);
// Derive name from the ZIP filename
const zipFilename = build.name;
const nameParts = zipFilename.split('-');
const manifestName = `${nameParts[0]}-${nameParts[1]}-${nameParts[2]}`;
const manifest = {
name: manifestName,
version: releaseData.version,
builds: [
{
chipFamily: "ESP32",
parts: releaseData.files.map(file => ({
path: file.filename, // Use the filename from the release.json directly
offset: parseInt(file.offset, 16)
}))
}
]
};
console.log("Generated manifest:", JSON.stringify(manifest, null, 2)); // Log the complete manifest for troubleshooting
const blob = new Blob([JSON.stringify(manifest)], { type: 'application/json' });
return { url: URL.createObjectURL(blob), name: manifest.name };
}
async function createFlashButton(build) {
const { url: manifestUrl, name } = await generateManifest(build);
const flashButtonsList = document.getElementById('flash-buttons-list');
let existingButton = Array.from(flashButtonsList.children).find(
item => item.textContent.includes(name)
);
if (!existingButton) {
const flashButton = document.createElement('button');
flashButton.textContent = `FLASH: ${name}`;
flashButton.onclick = () => {
const installButton = document.querySelector('esp-web-install-button');
installButton.setAttribute('manifest', manifestUrl);
installButton.click();
};
const listItem = document.createElement('li');
listItem.appendChild(flashButton);
flashButtonsList.appendChild(listItem);
} else {
updateStatus(`FLASH button for ${name} already exists.`);
}
}
async function connectToBuild(build) {
updateStatus(`Generating FLASH button for build: ${build.name}`);
console.log("Generating FLASH button for build:", build);
try {
await createFlashButton(build);
updateStatus(`FLASH button generated for build: ${build.name}`);
console.log("FLASH button generated for build:", build);
} catch (error) {
updateStatus("Failed to generate FLASH button: " + error.message);
console.error("Failed to generate FLASH button:", error);
}
}
function displayBuilds(builds) {
updateStatus("Displaying builds...");
const buildsList = document.getElementById('builds-list');
buildsList.innerHTML = ''; // Clear any existing content
builds.forEach(build => {
const listItem = document.createElement('li');
const button = document.createElement('button');
button.textContent = `Prepare ${build.name}`;
button.onclick = () => connectToBuild(build);
listItem.appendChild(button);
buildsList.appendChild(listItem);
});
updateStatus("Builds displayed.");
}
function updateStatus(message) {
const statusElement = document.getElementById('status');
statusElement.textContent = message;
console.log(message); // Also log to console for debugging
}
function showSpinner(show) {
const spinner = document.getElementById('spinner');
if (show) {
spinner.style.display = 'inline-block';
} else {
spinner.style.display = 'none';
}
}
async function start() {
try {
updateStatus("Starting initialization...");
const builds = await fetchDailyBuilds();
displayBuilds(builds);
updateStatus("Initialization complete.");
} catch (error) {
updateStatus(`Error during initialization: ${error.message}`);
console.error("Error during initialization:", error);
}
}
window.onload = start;
</script>
</head>
<body>
<h1>ESP Web Tools with Dynamic Manifest</h1>
<p id="status">Initializing...</p>
<ul id="builds-list"></ul>
<hr>
<ul id="flash-buttons-list"></ul>
<esp-web-install-button manifest=""></esp-web-install-button>
<div id="spinner"></div>
</body>
</html>
EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment