100 lines
5.1 KiB
JavaScript
100 lines
5.1 KiB
JavaScript
const jwt = require('jsonwebtoken');
|
|
const http = require('http');
|
|
const fs = require('fs');
|
|
const { execSync } = require('child_process');
|
|
|
|
const KEY = 'user:1022172:47';
|
|
const SECRET = 'da4e4367277668aa6e048b0a04d1a417ba8bad630f4ac37ccdcea064a9de151e';
|
|
const ADDON_ID = 'verstak-bridge@verstak.app';
|
|
const SOURCE_DIR = 'extension-firefox';
|
|
|
|
function makeJWT() {
|
|
return jwt.sign({ iss: KEY, jti: Math.random().toString(36).slice(2), iat: Math.floor(Date.now()/1000), exp: Math.floor(Date.now()/1000)+300 }, SECRET);
|
|
}
|
|
function proxyReq(method, url, headers, body) {
|
|
return new Promise((resolve, reject) => {
|
|
const u = new URL(url);
|
|
const req = http.request({ hostname: 'localhost', port: 12334, path: url, method, headers: Object.assign({ 'Host': u.hostname }, headers) }, res => {
|
|
const chunks = []; res.on('data', c => chunks.push(c)); res.on('end', () => resolve({ status: res.statusCode, body: Buffer.concat(chunks) }));
|
|
});
|
|
req.on('error', reject); if (body) req.write(body); req.end();
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
const token = makeJWT();
|
|
const auth = 'JWT ' + token;
|
|
const version = JSON.parse(fs.readFileSync(`${SOURCE_DIR}/manifest.json`, 'utf8')).version;
|
|
console.log(`==> Version: ${version}`);
|
|
|
|
// Build XPI
|
|
console.log('==> Building XPI...');
|
|
const xpiPath = `/tmp/verstak-bridge-${version}.xpi`;
|
|
execSync(`cd ${SOURCE_DIR} && zip -r "${xpiPath}" manifest.json background.js popup/ icons/ -x "*/.DS_Store" "*/.git/*" "node_modules/*"`, { stdio: 'pipe' });
|
|
console.log(` Built: ${(fs.statSync(xpiPath).size / 1024).toFixed(1)} KB`);
|
|
|
|
// Create upload
|
|
console.log('==> Uploading...');
|
|
const boundary = '----B' + Math.random().toString(36).slice(2);
|
|
const xpiData = fs.readFileSync(xpiPath);
|
|
const body = Buffer.concat([
|
|
Buffer.from([`--${boundary}\r\n`, `Content-Disposition: form-data; name="upload"; filename="xpi.zip"\r\n`, 'Content-Type: application/zip\r\n\r\n'].join('')),
|
|
xpiData,
|
|
Buffer.from(`\r\n--${boundary}\r\nContent-Disposition: form-data; name="channel"\r\n\r\nunlisted\r\n--${boundary}--\r\n`)
|
|
]);
|
|
const upRes = await proxyReq('POST', 'https://addons.mozilla.org/api/v5/addons/upload/', { 'Authorization': auth, 'Content-Type': `multipart/form-data; boundary=${boundary}`, 'Content-Length': body.length }, body);
|
|
console.log(` Upload: ${upRes.status}`);
|
|
const upData = JSON.parse(upRes.body.toString());
|
|
if (upRes.status !== 201) { console.log(' ERROR:', JSON.stringify(upData)); process.exit(1); }
|
|
|
|
// Validate
|
|
console.log('==> Validating...');
|
|
for (let i = 0; i < 20; i++) {
|
|
await new Promise(r => setTimeout(r, 5000));
|
|
const vr = await proxyReq('GET', `https://addons.mozilla.org/api/v5/addons/upload/${upData.uuid}/`, { 'Authorization': auth });
|
|
const vd = JSON.parse(vr.body.toString());
|
|
console.log(` [${i+1}] valid=${vd.valid} processed=${vd.processed}`);
|
|
if (vd.valid && vd.processed) break;
|
|
if (vd.valid === false && vd.validation) { console.log(' ERRORS:', JSON.stringify(vd.validation)); process.exit(1); }
|
|
}
|
|
|
|
// Create version
|
|
console.log('==> Creating version...');
|
|
const vBody = Buffer.from(JSON.stringify({ version, upload: upData.uuid }));
|
|
const vRes = await proxyReq('POST', `https://addons.mozilla.org/api/v5/addons/addon/${ADDON_ID}/versions/`, { 'Authorization': auth, 'Content-Type': 'application/json', 'Content-Length': vBody.length }, vBody);
|
|
console.log(` Status: ${vRes.status}`);
|
|
const vData = JSON.parse(vRes.body.toString());
|
|
if (vRes.status !== 201) { console.log(' ERROR:', JSON.stringify(vData)); process.exit(1); }
|
|
const versionId = vData.id;
|
|
console.log(` Version ID: ${versionId}`);
|
|
|
|
// Poll signing
|
|
console.log('==> Waiting for signing...');
|
|
let downloadUrl = null;
|
|
for (let i = 0; i < 30; i++) {
|
|
await new Promise(r => setTimeout(r, 10000));
|
|
const vr = await proxyReq('GET', `https://addons.mozilla.org/api/v5/addons/addon/${ADDON_ID}/versions/${versionId}/`, { 'Authorization': auth });
|
|
const vd = JSON.parse(vr.body.toString());
|
|
const f = vd.file;
|
|
console.log(` [${i+1}] status=${f?.status} size=${f?.size}`);
|
|
if (f?.status === 'public' && f?.url) { downloadUrl = f.url; break; }
|
|
if (f?.status === 'disabled' || f?.status === 'rejected') { console.log(' REJECTED'); process.exit(1); }
|
|
}
|
|
if (!downloadUrl) { console.log('TIMEOUT'); process.exit(1); }
|
|
|
|
// Download
|
|
console.log('==> Downloading...');
|
|
fs.mkdirSync('release/firefox', { recursive: true });
|
|
const out = `release/firefox/verstak-firefox-${version}.xpi`;
|
|
execSync(`curl -s -x http://localhost:12334 -L "${downloadUrl}" -H "Authorization: ${auth}" -o "${out}"`, { timeout: 60000 });
|
|
const sz = fs.statSync(out).size;
|
|
console.log(` Saved: ${out} (${(sz/1024).toFixed(1)} KB)`);
|
|
|
|
// Update updates.json
|
|
const updatesJson = JSON.stringify({ addons: { 'verstak-bridge@verstak.app': { updates: [{ version, update_link: `https://mirv.top/verstak/firefox/verstak-firefox-${version}.xpi` }] } } }, null, 2);
|
|
fs.writeFileSync('release/firefox/updates.json', updatesJson);
|
|
console.log('==> updates.json updated');
|
|
console.log('==> DONE');
|
|
}
|
|
main().catch(e => { console.error(e.message); process.exit(1); });
|