158 lines
5.4 KiB
JavaScript
158 lines
5.4 KiB
JavaScript
const jwt = require('jsonwebtoken');
|
|
const http = require('http');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { execSync } = require('child_process');
|
|
|
|
const KEY = 'user:1022172:47';
|
|
const SECRET = 'da4e4367277668aa6e048b0a04d1a417ba8bad630f4ac37ccdcea064a9de151e';
|
|
const PROXY_HOST = 'localhost';
|
|
const PROXY_PORT = 12334;
|
|
const ADDON_ID = 'verstak-bridge@verstak.app';
|
|
const VERSION = '1.0.1';
|
|
const SOURCE_DIR = 'extension-firefox';
|
|
const ARTIFACTS_DIR = 'web-ext-artifacts';
|
|
|
|
function makeJWT() {
|
|
const payload = {
|
|
iss: KEY,
|
|
jti: Math.random().toString(36).slice(2),
|
|
iat: Math.floor(Date.now() / 1000),
|
|
exp: Math.floor(Date.now() / 1000) + 300
|
|
};
|
|
return jwt.sign(payload, SECRET);
|
|
}
|
|
|
|
function httpRequest(method, urlPath, headers, body, isFile) {
|
|
return new Promise((resolve, reject) => {
|
|
const apiUrl = new URL('https://addons.mozilla.org' + urlPath);
|
|
const options = {
|
|
hostname: PROXY_HOST,
|
|
port: PROXY_PORT,
|
|
path: 'https://addons.mozilla.org' + urlPath,
|
|
method: method,
|
|
headers: Object.assign({ 'Host': apiUrl.hostname }, headers)
|
|
};
|
|
const req = http.request(options, (res) => {
|
|
let data = '';
|
|
res.on('data', chunk => data += chunk);
|
|
res.on('end', () => resolve({ status: res.statusCode, body: data }));
|
|
});
|
|
req.on('error', reject);
|
|
if (body) req.write(body);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
const token = makeJWT();
|
|
const authHeader = 'JWT ' + token;
|
|
|
|
// Step 1: Build XPI
|
|
console.log('==> Building XPI...');
|
|
const xpiPath = path.join(ARTIFACTS_DIR, `verstak-bridge-${VERSION}.xpi`);
|
|
fs.mkdirSync(ARTIFACTS_DIR, { recursive: true });
|
|
|
|
// Create zip
|
|
const cwd = process.cwd();
|
|
process.chdir(SOURCE_DIR);
|
|
execSync(`zip -r "../${xpiPath}" manifest.json background.js popup/ icons/ -x "*/.DS_Store" "*/Thumbs.db" "*/node_modules/*" "*/.git/*"`, { stdio: 'pipe' });
|
|
process.chdir(cwd);
|
|
console.log(` XPI: ${xpiPath} (${(fs.statSync(xpiPath).size / 1024).toFixed(1)} KB)`);
|
|
|
|
// Step 2: Create new version
|
|
console.log(`==> Creating version ${VERSION}...`);
|
|
const boundary = '----FormBoundary' + Math.random().toString(36).slice(2);
|
|
const xpiData = fs.readFileSync(xpiPath);
|
|
|
|
const bodyParts = [
|
|
`--${boundary}`,
|
|
'Content-Disposition: form-data; name="version"',
|
|
'',
|
|
VERSION,
|
|
`--${boundary}`,
|
|
'Content-Disposition: form-data; name="channel"',
|
|
'',
|
|
'unlisted',
|
|
`--${boundary}`,
|
|
`Content-Disposition: form-data; name="upload"; filename="${path.basename(xpiPath)}"`,
|
|
'Content-Type: application/zip',
|
|
'',
|
|
];
|
|
|
|
const body = Buffer.concat([
|
|
Buffer.from(bodyParts.join('\r\n') + '\r\n'),
|
|
xpiData,
|
|
Buffer.from(`\r\n--${boundary}--\r\n`)
|
|
]);
|
|
|
|
const createRes = await httpRequest('POST', `/api/v5/addons/${ADDON_ID}/versions/`, {
|
|
'Authorization': authHeader,
|
|
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
'Content-Length': body.length
|
|
}, body);
|
|
|
|
console.log(` Status: ${createRes.status}`);
|
|
const createData = JSON.parse(createRes.body);
|
|
console.log(' Response:', JSON.stringify(createData).substring(0, 300));
|
|
|
|
if (createRes.status === 201 || createRes.status === 200) {
|
|
const versionId = createData.id;
|
|
console.log(` Version ID: ${versionId}`);
|
|
|
|
// Step 3: Wait for signing (poll)
|
|
console.log('==> Waiting for signing...');
|
|
for (let i = 0; i < 30; i++) {
|
|
await new Promise(r => setTimeout(r, 10000)); // 10s
|
|
const checkRes = await httpRequest('GET', `/api/v5/addons/${ADDON_ID}/versions/${versionId}/`, {
|
|
'Authorization': authHeader
|
|
});
|
|
const checkData = JSON.parse(checkRes.body);
|
|
const fileStatus = checkData.files?.[0]?.status || 'unknown';
|
|
console.log(` [${i+1}] File status: ${fileStatus}`);
|
|
|
|
if (fileStatus === 'public') {
|
|
const downloadUrl = checkData.files[0].download_url;
|
|
console.log(` Download URL: ${downloadUrl}`);
|
|
|
|
// Step 4: Download signed XPI
|
|
console.log('==> Downloading signed XPI...');
|
|
const dlUrl = new URL(downloadUrl);
|
|
const dlRes = await httpRequest('GET', downloadUrl, {
|
|
'Authorization': authHeader,
|
|
'Host': dlUrl.hostname
|
|
});
|
|
|
|
if (dlRes.status === 200) {
|
|
const outPath = `release/firefox/verstak-firefox-${VERSION}.xpi`;
|
|
fs.mkdirSync('release/firefox', { recursive: true });
|
|
// Need to handle binary download properly
|
|
console.log(` Downloaded ${dlRes.body.length} bytes`);
|
|
console.log(' NOTE: Binary download through proxy may need adjustment');
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (fileStatus === 'disabled' || fileStatus === 'rejected') {
|
|
console.log(' ERROR: File was disabled/rejected');
|
|
break;
|
|
}
|
|
}
|
|
} else if (createRes.status === 409) {
|
|
console.log(' Version already exists. Trying to upload to existing version...');
|
|
// Get existing version ID
|
|
const versionsRes = await httpRequest('GET', `/api/v5/addons/${ADDON_ID}/versions/`, {
|
|
'Authorization': authHeader
|
|
});
|
|
const versionsData = JSON.parse(versionsRes.body);
|
|
const existing = versionsData.results?.find(v => v.version === VERSION);
|
|
if (existing) {
|
|
console.log(` Found existing version ID: ${existing.id}`);
|
|
}
|
|
} else {
|
|
console.log(' ERROR: Unexpected response');
|
|
}
|
|
}
|
|
|
|
main().catch(e => { console.error('FATAL:', e.message); process.exit(1); });
|