'use strict'; exports = module.exports = { getAppVersion, downloadManifest, getAppUpdate }; const assert = require('node:assert'), BoxError = require('./boxerror.js'), debug = require('debug')('box:community'), manifestFormat = require('@cloudron/manifest-format'), safe = require('safetydance'), superagent = require('@cloudron/superagent'); async function getAppVersion(url, version) { assert.strictEqual(typeof url, 'string'); assert.strictEqual(typeof version, 'string'); if (!url.startsWith('https://')) throw new BoxError(BoxError.BAD_FIELD, 'URL must use HTTPS'); const [error, response] = await safe(superagent.get(url).timeout(60 * 1000).ok(() => true)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, error); if (response.status === 404) throw new BoxError(BoxError.NOT_FOUND, 'CloudronVersions.json not found'); if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `Fetch failed: ${response.status}`); const versions = response.body; if (!versions || typeof versions !== 'object') throw new BoxError(BoxError.EXTERNAL_ERROR, 'Invalid CloudronVersions.json format'); const sortedVersions = Object.keys(versions).sort(manifestFormat.packageVersionCompare); const versionData = version === 'latest' ? versions[sortedVersions.at(-1)] : versions[version]; if (!versionData) throw new BoxError(BoxError.NOT_FOUND, `Version ${version} not found`); const manifestError = manifestFormat.checkVersionsRequirements(versionData.manifest); if (manifestError) throw new BoxError(BoxError.BAD_FIELD, `Invalid manifest: ${manifestError.message}`); return { id: versionData.manifest.id, iconUrl: versionData.manifest.iconUrl, ...versionData // { manifest, publishState, creationDate, ts } }; } async function downloadManifest(versionsUrl) { assert.strictEqual(typeof versionsUrl, 'string'); const atIndex = versionsUrl.lastIndexOf('@'); if (atIndex === -1) throw new BoxError(BoxError.BAD_FIELD, 'version is required in versionsUrl (format: url@version)'); const url = versionsUrl.substring(0, atIndex); const version = versionsUrl.substring(atIndex + 1); if (!url.startsWith('https://')) throw new BoxError(BoxError.BAD_FIELD, 'versionsUrl must use https'); if (!version) throw new BoxError(BoxError.BAD_FIELD, 'version is required in versionsUrl (format: url@version)'); debug(`downloading manifest from ${url} version ${version}`); const [error, response] = await safe(superagent.get(url).timeout(60 * 1000).ok(() => true)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, 'Network error downloading manifest: ' + error.message); if (response.status === 404) throw new BoxError(BoxError.NOT_FOUND, 'CloudronVersions.json not found'); if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `Fetch failed: ${response.status}`); const versions = response.body; if (!versions || typeof versions !== 'object') throw new BoxError(BoxError.EXTERNAL_ERROR, 'Invalid CloudronVersions.json format'); const sortedVersions = Object.keys(versions).sort(manifestFormat.packageVersionCompare); const versionData = version === 'latest' ? versions[sortedVersions.at(-1)] : versions[version]; if (!versionData) throw new BoxError(BoxError.NOT_FOUND, `Version ${version} not found`); if (!versionData.manifest || typeof versionData.manifest !== 'object') throw new BoxError(BoxError.EXTERNAL_ERROR, 'Missing manifest in version data'); return { versionsUrl: url, manifest: versionData.manifest }; } async function getAppUpdate(app, options) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof options, 'object'); const [error, response] = await safe(superagent.get(app.versionsUrl).timeout(60 * 1000).ok(() => true)); if (error) throw new BoxError(BoxError.NETWORK_ERROR, 'Network error downloading manifest: ' + error.message); if (response.status === 404) throw new BoxError(BoxError.NOT_FOUND, 'CloudronVersions.json not found'); if (response.status !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, `Fetch failed: ${response.status}`); const versions = response.body; if (!versions || typeof versions !== 'object') throw new BoxError(BoxError.EXTERNAL_ERROR, 'Invalid CloudronVersions.json format'); const sortedVersions = Object.keys(versions).sort(manifestFormat.packageVersionCompare); const idx = sortedVersions.findIndex(v => v === app.manifest.version); if (idx === -1) throw new BoxError(BoxError.EXTERNAL_ERROR, 'No such version') if (idx === sortedVersions.length-1) return null; // no update const nextVersion = versions[sortedVersions[idx+1]]; return { id: app.manifest.id, creationDate: nextVersion.creationDate, manifest: nextVersion.manifest, unstable: nextVersion.publishState === 'approved' }; }