build: Split sub-store.min.js for better performance on iOS devices

This commit is contained in:
Peng-YM
2022-09-09 23:11:13 +08:00
parent 1c29771de9
commit 6ee6a9b5e2
17 changed files with 673 additions and 513 deletions

View File

@@ -1,15 +1,8 @@
import { ProxyUtils } from '@/core/proxy-utils';
import { RuleUtils } from '@/core/rule-utils';
import download from '@/utils/download';
import Gist from '@/utils/gist';
import $ from '@/core/app';
import {
SUBS_KEY,
ARTIFACTS_KEY,
ARTIFACT_REPOSITORY_KEY,
COLLECTIONS_KEY,
RULES_KEY,
ARTIFACTS_KEY,
SETTINGS_KEY,
} from '@/constants';
import { deleteByName, findByName, updateByName } from '@/utils/database';
@@ -19,6 +12,7 @@ import {
RequestInvalidError,
ResourceNotFoundError,
} from '@/restful/errors';
import Gist from '@/utils/gist';
export default function register($app) {
// Initialization
@@ -31,10 +25,6 @@ export default function register($app) {
.get(getArtifact)
.patch(updateArtifact)
.delete(deleteArtifact);
// sync all artifacts
$app.get('/api/sync/artifacts', syncAllArtifacts);
$app.get('/api/sync/artifact/:name', syncArtifact);
}
function getAllArtifacts(req, res) {
@@ -160,111 +150,8 @@ async function deleteArtifact(req, res) {
}
}
async function syncArtifact(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
const allArtifacts = $.read(ARTIFACTS_KEY);
const artifact = findByName(allArtifacts, name);
if (!artifact) {
failed(
res,
new ResourceNotFoundError(
'RESOURCE_NOT_FOUND',
`Artifact ${name} does not exist!`,
),
404,
);
return;
}
const output = await produceArtifact({
type: artifact.type,
name: artifact.source,
platform: artifact.platform,
});
$.info(
`正在上传配置:${artifact.name}\n>>>${JSON.stringify(
artifact,
null,
2,
)}`,
);
try {
const resp = await syncToGist({
[encodeURIComponent(artifact.name)]: {
content: output,
},
});
artifact.updated = new Date().getTime();
const body = JSON.parse(resp.body);
artifact.url = body.files[
encodeURIComponent(artifact.name)
].raw_url.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
$.write(allArtifacts, ARTIFACTS_KEY);
success(res, artifact);
} catch (err) {
failed(
res,
new InternalServerError(
`FAILED_TO_SYNC_ARTIFACT`,
`Failed to sync artifact ${name}`,
`Reason: ${err}`,
),
);
}
}
async function syncAllArtifacts(_, res) {
$.info('开始同步所有远程配置...');
const allArtifacts = $.read(ARTIFACTS_KEY);
const files = {};
try {
await Promise.all(
allArtifacts.map(async (artifact) => {
if (artifact.sync) {
$.info(`正在同步云配置:${artifact.name}...`);
const output = await produceArtifact({
type: artifact.type,
name: artifact.source,
platform: artifact.platform,
});
files[artifact.name] = {
content: output,
};
}
}),
);
const resp = await syncToGist(files);
const body = JSON.parse(resp.body);
for (const artifact of allArtifacts) {
artifact.updated = new Date().getTime();
// extract real url from gist
artifact.url = body.files[artifact.name].raw_url.replace(
/\/raw\/[^/]*\/(.*)/,
'/raw/$1',
);
}
$.write(allArtifacts, ARTIFACTS_KEY);
$.info('全部订阅同步成功!');
success(res);
} catch (err) {
failed(
res,
new InternalServerError(
`FAILED_TO_SYNC_ARTIFACTS`,
`Failed to sync all artifacts`,
`Reason: ${err}`,
),
);
$.info(`同步订阅失败,原因:${err}`);
}
function validateArtifactName(name) {
return /^[a-zA-Z0-9._-]*$/.test(name);
}
async function syncToGist(files) {
@@ -279,160 +166,4 @@ async function syncToGist(files) {
return manager.upload(files);
}
async function produceArtifact({ type, name, platform }) {
platform = platform || 'JSON';
// produce Clash node format for ShadowRocket
if (platform === 'ShadowRocket') platform = 'Clash';
if (type === 'subscription') {
const allSubs = $.read(SUBS_KEY);
const sub = findByName(allSubs, name);
let raw;
if (sub.source === 'local') {
raw = sub.content;
} else {
raw = await download(sub.url, sub.ua);
}
// parse proxies
let proxies = ProxyUtils.parse(raw);
// apply processors
proxies = await ProxyUtils.process(
proxies,
sub.process || [],
platform,
);
// check duplicate
const exist = {};
for (const proxy of proxies) {
if (exist[proxy.name]) {
$.notify(
'🌍 Sub-Store',
'⚠️ 订阅包含重复节点!',
'请仔细检测配置!',
{
'media-url':
'https://cdn3.iconfinder.com/data/icons/seo-outline-1/512/25_code_program_programming_develop_bug_search_developer-512.png',
},
);
break;
}
exist[proxy.name] = true;
}
// produce
return ProxyUtils.produce(proxies, platform);
} else if (type === 'collection') {
const allSubs = $.read(SUBS_KEY);
const allCols = $.read(COLLECTIONS_KEY);
const collection = findByName(allCols, name);
const subnames = collection.subscriptions;
const results = {};
let processed = 0;
await Promise.all(
subnames.map(async (name) => {
const sub = findByName(allSubs, name);
try {
$.info(`正在处理子订阅:${sub.name}...`);
let raw;
if (sub.source === 'local') {
raw = sub.content;
} else {
raw = await download(sub.url, sub.ua);
}
// parse proxies
let currentProxies = ProxyUtils.parse(raw);
// apply processors
currentProxies = await ProxyUtils.process(
currentProxies,
sub.process || [],
platform,
);
results[name] = currentProxies;
processed++;
$.info(
`✅ 子订阅:${sub.name}加载成功,进度--${
100 * (processed / subnames.length).toFixed(1)
}% `,
);
} catch (err) {
processed++;
$.error(
`❌ 处理组合订阅中的子订阅: ${
sub.name
}时出现错误:${err},该订阅已被跳过!进度--${
100 * (processed / subnames.length).toFixed(1)
}%`,
);
}
}),
);
// merge proxies with the original order
let proxies = Array.prototype.concat.apply(
[],
subnames.map((name) => results[name]),
);
// apply own processors
proxies = await ProxyUtils.process(
proxies,
collection.process || [],
platform,
);
if (proxies.length === 0) {
throw new Error(`组合订阅中不含有效节点!`);
}
// check duplicate
const exist = {};
for (const proxy of proxies) {
if (exist[proxy.name]) {
$.notify(
'🌍 Sub-Store',
'⚠️ 订阅包含重复节点!',
'请仔细检测配置!',
{
'media-url':
'https://cdn3.iconfinder.com/data/icons/seo-outline-1/512/25_code_program_programming_develop_bug_search_developer-512.png',
},
);
break;
}
exist[proxy.name] = true;
}
return ProxyUtils.produce(proxies, platform);
} else if (type === 'rule') {
const allRules = $.read(RULES_KEY);
const rule = findByName(allRules, name);
let rules = [];
for (let i = 0; i < rule.urls.length; i++) {
const url = rule.urls[i];
$.info(
`正在处理URL${url},进度--${
100 * ((i + 1) / rule.urls.length).toFixed(1)
}% `,
);
try {
const { body } = await download(url);
const currentRules = RuleUtils.parse(body);
rules = rules.concat(currentRules);
} catch (err) {
$.error(
`处理分流订阅中的URL: ${url}时出现错误:${err}! 该订阅已被跳过。`,
);
}
}
// remove duplicates
rules = await RuleUtils.process(rules, [
{ type: 'Remove Duplicate Filter' },
]);
// produce output
return RuleUtils.produce(rules, platform);
}
}
function validateArtifactName(name) {
return /^[a-zA-Z0-9._-]*$/.test(name);
}
export { syncToGist, produceArtifact };
export { syncToGist };

View File

@@ -2,10 +2,10 @@ import { getPlatformFromHeaders } from '@/utils/platform';
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
import { findByName } from '@/utils/database';
import { getFlowHeaders } from '@/utils/flow';
import { produceArtifact } from './artifacts';
import $ from '@/core/app';
import { failed } from '@/restful/response';
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
import { produceArtifact } from '@/restful/sync';
export default function register($app) {
$app.get('/download/collection/:name', downloadCollection);

View File

@@ -1,33 +1,16 @@
import {
SETTINGS_KEY,
GIST_BACKUP_KEY,
GIST_BACKUP_FILE_NAME,
} from '@/constants';
import { version as substoreVersion } from '../../package.json';
import { ENV, HTTP } from '@/vendor/open-api';
import express from '@/vendor/express';
import Gist from '@/utils/gist';
import migrate from '@/utils/migration';
import $ from '@/core/app';
import registerSubscriptionRoutes from './subscriptions';
import registerCollectionRoutes from './collections';
import registerArtifactRoutes from './artifacts';
import registerSyncRoutes from './sync';
import registerDownloadRoutes from './download';
import registerSettingRoutes, {
updateArtifactStore,
updateGitHubAvatar,
} from './settings';
import registerSettingRoutes from './settings';
import registerPreviewRoutes from './preview';
import registerSortingRoutes from './sort';
import { failed, success } from '@/restful/response';
import {
InternalServerError,
NetworkError,
RequestInvalidError,
} from '@/restful/errors';
import resourceCache from '@/utils/resource-cache';
import producer from '@/core/proxy-utils/producers';
import registerMiscRoutes from './miscs';
import registerNodeInfoRoutes from './node-info';
export default function serve() {
const $app = express({ substore: $ });
@@ -40,183 +23,9 @@ export default function serve() {
registerSortingRoutes($app);
registerSettingRoutes($app);
registerArtifactRoutes($app);
// utils
$app.post('/api/utils/node-info', getNodeInfo);
$app.get('/api/utils/env', getEnv); // get runtime environment
$app.get('/api/utils/backup', gistBackup); // gist backup actions
$app.get('/api/utils/refresh', refresh);
// Storage management
$app.route('/api/storage')
.get((req, res) => {
res.json($.read('#sub-store'));
})
.post((req, res) => {
const data = req.body;
$.write(JSON.stringify(data), '#sub-store');
res.end();
});
// Redirect sub.store to vercel webpage
$app.get('/', async (req, res) => {
// 302 redirect
res.set('location', 'https://sub-store.vercel.app/').status(302).end();
});
// handle preflight request for QX
if (ENV().isQX) {
$app.options('/', async (req, res) => {
res.status(200).end();
});
}
$app.all('/', (_, res) => {
res.send('Hello from sub-store, made with ❤️ by Peng-YM');
});
registerSyncRoutes($app);
registerNodeInfoRoutes($app);
registerMiscRoutes($app);
$app.start();
}
function getEnv(req, res) {
const { isNode, isQX, isLoon, isSurge, isStash, isShadowRocket } = ENV();
let backend = 'Node';
if (isNode) backend = 'Node';
if (isQX) backend = 'QX';
if (isLoon) backend = 'Loon';
if (isSurge) backend = 'Surge';
if (isStash) backend = 'Stash';
if (isShadowRocket) backend = 'ShadowRocket';
success(res, {
backend,
version: substoreVersion,
});
}
async function refresh(_, res) {
// 1. get GitHub avatar and artifact store
await updateGitHubAvatar();
await updateArtifactStore();
// 2. clear resource cache
resourceCache.revokeAll();
success(res);
}
async function gistBackup(req, res) {
const { action } = req.query;
// read token
const { gistToken } = $.read(SETTINGS_KEY);
if (!gistToken) {
failed(
res,
new RequestInvalidError(
'GIST_TOKEN_NOT_FOUND',
`GitHub Token is required for backup!`,
),
);
} else {
const gist = new Gist({
token: gistToken,
key: GIST_BACKUP_KEY,
});
try {
let content;
const settings = $.read(SETTINGS_KEY);
const updated = settings.syncTime;
switch (action) {
case 'upload':
// update syncTime
settings.syncTime = new Date().getTime();
$.write(settings, SETTINGS_KEY);
content = $.read('#sub-store');
if ($.env.isNode)
content = JSON.stringify($.cache, null, ` `);
$.info(`上传备份中...`);
try {
await gist.upload({
[GIST_BACKUP_FILE_NAME]: { content },
});
} catch (err) {
// restore syncTime if upload failed
settings.syncTime = updated;
$.write(settings, SETTINGS_KEY);
throw err;
}
break;
case 'download':
$.info(`还原备份中...`);
content = await gist.download(GIST_BACKUP_FILE_NAME);
// restore settings
$.write(content, '#sub-store');
if ($.env.isNode) {
content = JSON.parse(content);
$.cache = content;
$.persistCache();
}
// perform migration after restoring from gist
migrate();
break;
}
success(res);
} catch (err) {
failed(
res,
new InternalServerError(
'BACKUP_FAILED',
`Failed to ${action} data to gist!`,
`Reason: ${JSON.stringify(err)}`,
),
);
}
}
}
async function getNodeInfo(req, res) {
const proxy = req.body;
const lang = req.query.lang || 'zh-CN';
let shareUrl;
try {
shareUrl = producer.URI.produce(proxy);
} catch (err) {
// do nothing
}
try {
const $http = HTTP();
const info = await $http
.get({
url: `http://ip-api.com/json/${encodeURIComponent(
proxy.server,
)}?lang=${lang}`,
headers: {
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15',
},
})
.then((resp) => {
const data = JSON.parse(resp.body);
if (data.status !== 'success') {
throw new Error(data.message);
}
// remove unnecessary fields
delete data.status;
return data;
});
success(res, {
shareUrl,
info,
});
} catch (err) {
failed(
res,
new NetworkError(
'FAILED_TO_GET_NODE_INFO',
`Failed to get node info`,
`Reason: ${err}`,
),
);
}
}

View File

@@ -0,0 +1,144 @@
import $ from '@/core/app';
import { ENV } from '@/vendor/open-api';
import { failed, success } from '@/restful/response';
import { version as substoreVersion } from '../../package.json';
import { updateArtifactStore, updateGitHubAvatar } from '@/restful/settings';
import resourceCache from '@/utils/resource-cache';
import {
GIST_BACKUP_FILE_NAME,
GIST_BACKUP_KEY,
SETTINGS_KEY,
} from '@/constants';
import { InternalServerError, RequestInvalidError } from '@/restful/errors';
import Gist from '@/utils/gist';
import migrate from '@/utils/migration';
export default function register($app) {
// utils
$app.get('/api/utils/env', getEnv); // get runtime environment
$app.get('/api/utils/backup', gistBackup); // gist backup actions
$app.get('/api/utils/refresh', refresh);
// Storage management
$app.route('/api/storage')
.get((req, res) => {
res.json($.read('#sub-store'));
})
.post((req, res) => {
const data = req.body;
$.write(JSON.stringify(data), '#sub-store');
res.end();
});
// Redirect sub.store to vercel webpage
$app.get('/', async (req, res) => {
// 302 redirect
res.set('location', 'https://sub-store.vercel.app/').status(302).end();
});
// handle preflight request for QX
if (ENV().isQX) {
$app.options('/', async (req, res) => {
res.status(200).end();
});
}
$app.all('/', (_, res) => {
res.send('Hello from sub-store, made with ❤️ by Peng-YM');
});
}
function getEnv(req, res) {
const { isNode, isQX, isLoon, isSurge, isStash, isShadowRocket } = ENV();
let backend = 'Node';
if (isNode) backend = 'Node';
if (isQX) backend = 'QX';
if (isLoon) backend = 'Loon';
if (isSurge) backend = 'Surge';
if (isStash) backend = 'Stash';
if (isShadowRocket) backend = 'ShadowRocket';
success(res, {
backend,
version: substoreVersion,
});
}
async function refresh(_, res) {
// 1. get GitHub avatar and artifact store
await updateGitHubAvatar();
await updateArtifactStore();
// 2. clear resource cache
resourceCache.revokeAll();
success(res);
}
async function gistBackup(req, res) {
const { action } = req.query;
// read token
const { gistToken } = $.read(SETTINGS_KEY);
if (!gistToken) {
failed(
res,
new RequestInvalidError(
'GIST_TOKEN_NOT_FOUND',
`GitHub Token is required for backup!`,
),
);
} else {
const gist = new Gist({
token: gistToken,
key: GIST_BACKUP_KEY,
});
try {
let content;
const settings = $.read(SETTINGS_KEY);
const updated = settings.syncTime;
switch (action) {
case 'upload':
// update syncTime
settings.syncTime = new Date().getTime();
$.write(settings, SETTINGS_KEY);
content = $.read('#sub-store');
if ($.env.isNode)
content = JSON.stringify($.cache, null, ` `);
$.info(`上传备份中...`);
try {
await gist.upload({
[GIST_BACKUP_FILE_NAME]: { content },
});
} catch (err) {
// restore syncTime if upload failed
settings.syncTime = updated;
$.write(settings, SETTINGS_KEY);
throw err;
}
break;
case 'download':
$.info(`还原备份中...`);
content = await gist.download(GIST_BACKUP_FILE_NAME);
// restore settings
$.write(content, '#sub-store');
if ($.env.isNode) {
content = JSON.parse(content);
$.cache = content;
$.persistCache();
}
// perform migration after restoring from gist
migrate();
break;
}
success(res);
} catch (err) {
failed(
res,
new InternalServerError(
'BACKUP_FAILED',
`Failed to ${action} data to gist!`,
`Reason: ${JSON.stringify(err)}`,
),
);
}
}
}

View File

@@ -0,0 +1,56 @@
import producer from '@/core/proxy-utils/producers';
import { HTTP } from '@/vendor/open-api';
import { failed, success } from '@/restful/response';
import { NetworkError } from '@/restful/errors';
export default function register($app) {
$app.post('/api/utils/node-info', getNodeInfo);
}
async function getNodeInfo(req, res) {
const proxy = req.body;
const lang = req.query.lang || 'zh-CN';
let shareUrl;
try {
shareUrl = producer.URI.produce(proxy);
} catch (err) {
// do nothing
}
try {
const $http = HTTP();
const info = await $http
.get({
url: `http://ip-api.com/json/${encodeURIComponent(
proxy.server,
)}?lang=${lang}`,
headers: {
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15',
},
})
.then((resp) => {
const data = JSON.parse(resp.body);
if (data.status !== 'success') {
throw new Error(data.message);
}
// remove unnecessary fields
delete data.status;
return data;
});
success(res, {
shareUrl,
info,
});
} catch (err) {
failed(
res,
new NetworkError(
'FAILED_TO_GET_NODE_INFO',
`Failed to get node info`,
`Reason: ${err}`,
),
);
}
}

View File

@@ -92,7 +92,7 @@ async function compareCollection(req, res) {
// merge proxies with the original order
const original = Array.prototype.concat.apply(
[],
subnames.map((name) => results[name]),
subnames.map((name) => results[name] || []),
);
original.forEach((proxy, i) => {

284
backend/src/restful/sync.js Normal file
View File

@@ -0,0 +1,284 @@
import $ from '@/core/app';
import {
ARTIFACTS_KEY,
COLLECTIONS_KEY,
RULES_KEY,
SUBS_KEY,
} from '@/constants';
import { failed, success } from '@/restful/response';
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
import { findByName } from '@/utils/database';
import download from '@/utils/download';
import { ProxyUtils } from '@/core/proxy-utils';
import { RuleUtils } from '@/core/rule-utils';
import { syncToGist } from '@/restful/artifacts';
export default function register($app) {
// Initialization
if (!$.read(ARTIFACTS_KEY)) $.write({}, ARTIFACTS_KEY);
// sync all artifacts
$app.get('/api/sync/artifacts', syncAllArtifacts);
$app.get('/api/sync/artifact/:name', syncArtifact);
}
async function produceArtifact({ type, name, platform }) {
platform = platform || 'JSON';
// produce Clash node format for ShadowRocket
if (platform === 'ShadowRocket') platform = 'Clash';
if (type === 'subscription') {
const allSubs = $.read(SUBS_KEY);
const sub = findByName(allSubs, name);
let raw;
if (sub.source === 'local') {
raw = sub.content;
} else {
raw = await download(sub.url, sub.ua);
}
// parse proxies
let proxies = ProxyUtils.parse(raw);
// apply processors
proxies = await ProxyUtils.process(
proxies,
sub.process || [],
platform,
);
// check duplicate
const exist = {};
for (const proxy of proxies) {
if (exist[proxy.name]) {
$.notify(
'🌍 Sub-Store',
'⚠️ 订阅包含重复节点!',
'请仔细检测配置!',
{
'media-url':
'https://cdn3.iconfinder.com/data/icons/seo-outline-1/512/25_code_program_programming_develop_bug_search_developer-512.png',
},
);
break;
}
exist[proxy.name] = true;
}
// produce
return ProxyUtils.produce(proxies, platform);
} else if (type === 'collection') {
const allSubs = $.read(SUBS_KEY);
const allCols = $.read(COLLECTIONS_KEY);
const collection = findByName(allCols, name);
const subnames = collection.subscriptions;
const results = {};
let processed = 0;
await Promise.all(
subnames.map(async (name) => {
const sub = findByName(allSubs, name);
try {
$.info(`正在处理子订阅:${sub.name}...`);
let raw;
if (sub.source === 'local') {
raw = sub.content;
} else {
raw = await download(sub.url, sub.ua);
}
// parse proxies
let currentProxies = ProxyUtils.parse(raw);
// apply processors
currentProxies = await ProxyUtils.process(
currentProxies,
sub.process || [],
platform,
);
results[name] = currentProxies;
processed++;
$.info(
`✅ 子订阅:${sub.name}加载成功,进度--${
100 * (processed / subnames.length).toFixed(1)
}% `,
);
} catch (err) {
processed++;
$.error(
`❌ 处理组合订阅中的子订阅: ${
sub.name
}时出现错误:${err},该订阅已被跳过!进度--${
100 * (processed / subnames.length).toFixed(1)
}%`,
);
}
}),
);
// merge proxies with the original order
let proxies = Array.prototype.concat.apply(
[],
subnames.map((name) => results[name]),
);
// apply own processors
proxies = await ProxyUtils.process(
proxies,
collection.process || [],
platform,
);
if (proxies.length === 0) {
throw new Error(`组合订阅中不含有效节点!`);
}
// check duplicate
const exist = {};
for (const proxy of proxies) {
if (exist[proxy.name]) {
$.notify(
'🌍 Sub-Store',
'⚠️ 订阅包含重复节点!',
'请仔细检测配置!',
{
'media-url':
'https://cdn3.iconfinder.com/data/icons/seo-outline-1/512/25_code_program_programming_develop_bug_search_developer-512.png',
},
);
break;
}
exist[proxy.name] = true;
}
return ProxyUtils.produce(proxies, platform);
} else if (type === 'rule') {
const allRules = $.read(RULES_KEY);
const rule = findByName(allRules, name);
let rules = [];
for (let i = 0; i < rule.urls.length; i++) {
const url = rule.urls[i];
$.info(
`正在处理URL${url},进度--${
100 * ((i + 1) / rule.urls.length).toFixed(1)
}% `,
);
try {
const { body } = await download(url);
const currentRules = RuleUtils.parse(body);
rules = rules.concat(currentRules);
} catch (err) {
$.error(
`处理分流订阅中的URL: ${url}时出现错误:${err}! 该订阅已被跳过。`,
);
}
}
// remove duplicates
rules = await RuleUtils.process(rules, [
{ type: 'Remove Duplicate Filter' },
]);
// produce output
return RuleUtils.produce(rules, platform);
}
}
async function syncAllArtifacts(_, res) {
$.info('开始同步所有远程配置...');
const allArtifacts = $.read(ARTIFACTS_KEY);
const files = {};
try {
await Promise.all(
allArtifacts.map(async (artifact) => {
if (artifact.sync) {
$.info(`正在同步云配置:${artifact.name}...`);
const output = await produceArtifact({
type: artifact.type,
name: artifact.source,
platform: artifact.platform,
});
files[artifact.name] = {
content: output,
};
}
}),
);
const resp = await syncToGist(files);
const body = JSON.parse(resp.body);
for (const artifact of allArtifacts) {
artifact.updated = new Date().getTime();
// extract real url from gist
artifact.url = body.files[artifact.name].raw_url.replace(
/\/raw\/[^/]*\/(.*)/,
'/raw/$1',
);
}
$.write(allArtifacts, ARTIFACTS_KEY);
$.info('全部订阅同步成功!');
success(res);
} catch (err) {
failed(
res,
new InternalServerError(
`FAILED_TO_SYNC_ARTIFACTS`,
`Failed to sync all artifacts`,
`Reason: ${err}`,
),
);
$.info(`同步订阅失败,原因:${err}`);
}
}
async function syncArtifact(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
const allArtifacts = $.read(ARTIFACTS_KEY);
const artifact = findByName(allArtifacts, name);
if (!artifact) {
failed(
res,
new ResourceNotFoundError(
'RESOURCE_NOT_FOUND',
`Artifact ${name} does not exist!`,
),
404,
);
return;
}
const output = await produceArtifact({
type: artifact.type,
name: artifact.source,
platform: artifact.platform,
});
$.info(
`正在上传配置:${artifact.name}\n>>>${JSON.stringify(
artifact,
null,
2,
)}`,
);
try {
const resp = await syncToGist({
[encodeURIComponent(artifact.name)]: {
content: output,
},
});
artifact.updated = new Date().getTime();
const body = JSON.parse(resp.body);
artifact.url = body.files[
encodeURIComponent(artifact.name)
].raw_url.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
$.write(allArtifacts, ARTIFACTS_KEY);
success(res, artifact);
} catch (err) {
failed(
res,
new InternalServerError(
`FAILED_TO_SYNC_ARTIFACT`,
`Failed to sync artifact ${name}`,
`Reason: ${err}`,
),
);
}
}
export { produceArtifact };