zan-proxy
Version:
354 lines • 14.5 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const es6_promisify_1 = require("es6-promisify");
const events_1 = __importDefault(require("events"));
const fs_1 = __importDefault(require("fs"));
const jsonfile_1 = __importDefault(require("jsonfile"));
const lodash_1 = require("lodash");
const node_fetch_1 = __importDefault(require("node-fetch"));
const path_1 = __importDefault(require("path"));
const typedi_1 = require("typedi");
const v4_1 = __importDefault(require("uuid/v4"));
const appInfo_1 = require("./appInfo");
const jsonfileWriteFile = es6_promisify_1.promisify(jsonfile_1.default.writeFile);
const fsUnlink = es6_promisify_1.promisify(fs_1.default.unlink);
exports.ErrNameExists = new Error('name exists');
let RuleService = class RuleService extends events_1.default {
constructor(appInfoService) {
super();
// userId - > (filename -> rule)
this.rules = {};
// 缓存数据: 正在使用的规则 userId -> inUsingRuleList
this.usingRuleCache = {};
const proxyDataDir = appInfoService.getProxyDataDir();
this.ruleSaveDir = path_1.default.join(proxyDataDir, 'rule');
const contentMap = fs_1.default
.readdirSync(this.ruleSaveDir)
.filter(name => name.endsWith('.json'))
.reduce((prev, curr) => {
try {
prev[curr] = jsonfile_1.default.readFileSync(path_1.default.join(this.ruleSaveDir, curr));
}
catch (e) {
// ignore
}
return prev;
}, {});
lodash_1.forEach(contentMap, (content, fileName) => {
const ruleName = content.name;
const userId = fileName.substr(0, this._getUserIdLength(fileName, ruleName));
this.rules[userId] = this.rules[userId] || {};
this.rules[userId][ruleName] = content;
});
}
// 创建规则文件
createRuleFile(userId, name, description) {
return __awaiter(this, void 0, void 0, function* () {
if (this.rules[userId] && this.rules[userId][name]) {
return exports.ErrNameExists;
}
const ruleFile = {
checked: false,
content: [],
description,
meta: {
ETag: '',
remote: false,
remoteETag: '',
url: '',
},
name,
};
this.rules[userId] = this.rules[userId] || {};
this.rules[userId][name] = ruleFile;
// 写文件
const filePath = this._getRuleFilePath(userId, name);
yield jsonfileWriteFile(filePath, ruleFile, { encoding: 'utf-8' });
// 发送消息通知
this.emit('data-change', userId, this.getRuleFileList(userId));
return true;
});
}
// 返回用户的规则文件列表
getRuleFileList(userId) {
const ruleMap = (this.rules[userId] = this.rules[userId] || {});
const rulesLocal = [];
const rulesRemote = [];
lodash_1.forEach(ruleMap, content => {
if (content.meta && content.meta.remote) {
rulesRemote.push({
checked: content.checked,
description: content.description,
disableSync: content.disableSync,
meta: content.meta,
name: content.name,
});
}
else {
rulesLocal.push({
checked: content.checked,
description: content.description,
meta: content.meta,
name: content.name,
});
}
});
return rulesLocal.concat(rulesRemote);
}
// 删除规则文件
deleteRuleFile(userId, name) {
return __awaiter(this, void 0, void 0, function* () {
const rule = this.rules[userId][name];
delete this.rules[userId][name];
const ruleFilePath = this._getRuleFilePath(userId, name);
yield fsUnlink(ruleFilePath);
// 发送消息通知
this.emit('data-change', userId, this.getRuleFileList(userId));
if (rule.checked) {
// 清空缓存
delete this.usingRuleCache[userId];
}
});
}
// 设置规则文件的使用状态
setRuleFileCheckStatus(userId, name, checked) {
return __awaiter(this, void 0, void 0, function* () {
this.rules[userId][name].checked = checked;
const ruleFilePath = this._getRuleFilePath(userId, name);
yield jsonfileWriteFile(ruleFilePath, this.rules[userId][name], {
encoding: 'utf-8',
});
// 发送消息通知
this.emit('data-change', userId, this.getRuleFileList(userId));
delete this.usingRuleCache[userId];
});
}
// 设置规则文件的禁用同步状态
setRuleFileDisableSync(userId, name, disable) {
return __awaiter(this, void 0, void 0, function* () {
this.rules[userId][name].disableSync = disable;
const ruleFilePath = this._getRuleFilePath(userId, name);
yield jsonfileWriteFile(ruleFilePath, this.rules[userId][name], {
encoding: 'utf-8',
});
// 发送消息通知
this.emit('data-change', userId, this.getRuleFileList(userId));
delete this.usingRuleCache[userId];
});
}
// 获取规则文件的内容
getRuleFile(userId, name) {
return this.rules[userId][name];
}
// 保存规则文件(可能是远程、或者本地)
saveRuleFile(userId, ruleFile) {
return __awaiter(this, void 0, void 0, function* () {
const userRuleMap = this.rules[userId] || {};
const originRuleFile = userRuleMap[ruleFile.name];
if (originRuleFile) {
ruleFile.checked = originRuleFile.checked;
ruleFile.meta = originRuleFile.meta;
}
userRuleMap[ruleFile.name] = ruleFile;
this.rules[userId] = userRuleMap;
// 写文件
const filePath = this._getRuleFilePath(userId, ruleFile.name);
yield jsonfileWriteFile(filePath, userRuleMap[ruleFile.name], {
encoding: 'utf-8',
});
// 清空缓存
delete this.usingRuleCache[userId];
this.emit('data-change', userId, this.getRuleFileList(userId));
});
}
// 修改规则文件名称
updateFileInfo(userId, originName, { name, description, }) {
return __awaiter(this, void 0, void 0, function* () {
const userRuleMap = this.rules[userId] || {};
if (userRuleMap[name] && name !== originName) {
throw exports.ErrNameExists;
}
const ruleFile = userRuleMap[originName];
// 删除旧的rule
delete this.rules[userId][originName];
const ruleFilePath = this._getRuleFilePath(userId, originName);
yield fsUnlink(ruleFilePath);
// 修改rule名称
ruleFile.name = name;
ruleFile.description = description;
yield this.saveRuleFile(userId, ruleFile);
});
}
/**
* 根据请求,获取处理请求的规则
* @param method
* @param urlObj
*/
getProcessRule(userId, method, urlObj) {
let candidateRule = null;
const inusingRules = this._getInuseRules(userId);
for (const rule of inusingRules) {
// 捕获规则
if (this._isUrlMatch(urlObj.href, rule.match) &&
this._isMethodMatch(method, rule.method)) {
candidateRule = rule;
break;
}
}
return candidateRule;
}
importRemoteRuleFile(userId, url) {
return __awaiter(this, void 0, void 0, function* () {
const ruleFile = yield this.fetchRemoteRuleFile(url);
ruleFile.content.forEach(rule => {
if (rule.action && !rule.actionList) {
rule.actionList = [rule.action];
}
});
yield this.saveRuleFile(userId, ruleFile);
return ruleFile;
});
}
fetchRemoteRuleFile(url) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield node_fetch_1.default(url);
const responseData = yield response.json();
const ETag = response.headers.etag || '';
const content = responseData.content.map(remoteRule => {
if (remoteRule.action && !remoteRule.actionList) {
remoteRule.actionList = [remoteRule.action];
}
const actionList = remoteRule.actionList.map(remoteAction => {
const actionData = {
dataId: '',
headerKey: remoteAction.data.headerKey || '',
headerValue: remoteAction.data.headerValue || '',
target: remoteAction.data.target || '',
};
const action = {
data: actionData,
type: remoteAction.type,
};
return action;
});
const rule = {
actionList,
checked: remoteRule.checked,
key: remoteRule.key || v4_1.default(),
match: remoteRule.match,
method: remoteRule.method,
name: remoteRule.name,
};
return rule;
});
const ruleFile = {
checked: false,
content,
description: responseData.description,
meta: {
ETag,
remote: true,
remoteETag: ETag,
url,
},
name: responseData.name,
};
return ruleFile;
});
}
copyRuleFile(userId, name) {
return __awaiter(this, void 0, void 0, function* () {
const ruleFile = this.getRuleFile(userId, name);
if (!ruleFile) {
return;
}
const copied = lodash_1.cloneDeep(ruleFile);
copied.checked = false;
copied.name = `${copied.name}-复制`;
copied.meta = Object.assign({}, copied.meta, {
isCopy: true,
remote: false,
});
yield this.saveRuleFile(userId, copied);
return copied;
});
}
_getInuseRules(userId) {
if (this.usingRuleCache[userId]) {
return this.usingRuleCache[userId];
}
const ruleMap = this.rules[userId] || {};
// 计算使用中的规则
const rulesLocal = [];
const rulesRemote = [];
lodash_1.forEach(ruleMap, (file, filename) => {
if (!file.checked) {
return;
}
lodash_1.forEach(file.content, rule => {
if (!rule.checked) {
return;
}
const copy = lodash_1.cloneDeep(rule);
copy.ruleFileName = filename;
if (file.meta && file.meta.remote) {
rulesRemote.push(copy);
}
else {
rulesLocal.push(copy);
}
});
});
const merged = rulesLocal.concat(rulesRemote);
this.usingRuleCache[userId] = merged;
return merged;
}
_getRuleFilePath(userId, ruleName) {
const fileName = `${userId}_${ruleName}.json`;
const filePath = path_1.default.join(this.ruleSaveDir, fileName);
return filePath;
}
_getUserIdLength(ruleFileName, ruleName) {
return ruleFileName.length - ruleName.length - 6;
}
// 请求的方法是否匹配规则
_isMethodMatch(reqMethod, ruleMethod) {
const loweredReqMethod = lodash_1.lowerCase(reqMethod);
const loweredRuleMethod = lodash_1.lowerCase(ruleMethod);
return (loweredReqMethod === loweredRuleMethod ||
!ruleMethod ||
loweredReqMethod === 'option');
}
// 请求的url是否匹配规则
_isUrlMatch(reqUrl, ruleMatchStr) {
return (ruleMatchStr &&
(reqUrl.indexOf(ruleMatchStr) >= 0 ||
new RegExp(ruleMatchStr).test(reqUrl)));
}
};
RuleService = __decorate([
typedi_1.Service(),
__metadata("design:paramtypes", [appInfo_1.AppInfoService])
], RuleService);
exports.RuleService = RuleService;
//# sourceMappingURL=rule.js.map