zan-proxy
Version:
266 lines (245 loc) • 7.31 kB
text/typescript
import { promisify } from 'es6-promisify';
import EventEmitter from 'events';
import fs from 'fs';
import jsonfile from 'jsonfile';
import { find, forEach } from 'lodash';
import fetch from 'node-fetch';
import path from 'path';
import { Service } from 'typedi';
import { AppInfoService } from './appInfo';
const ipReg = /((?:(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d?\d))/;
const jsonWriteFile = promisify(jsonfile.writeFile);
const fsUnlink = promisify(fs.unlink);
const fsExists = (p): Promise<boolean> =>
new Promise(resolve => {
fs.exists(p, exists => {
resolve(exists);
});
});
/**
* Created by tsxuehu on 8/3/17.
*/
export class HostService extends EventEmitter {
private userHostFilesMap: object;
private inUsingHostsMapCache: object;
private hostSaveDir: string;
constructor(appInfoService: AppInfoService) {
super();
// userId -> { filename -> content}
this.userHostFilesMap = {};
// 缓存
// userId, {globHostMap, hostMap}
this.inUsingHostsMapCache = {};
const proxyDataDir = appInfoService.getProxyDataDir();
this.hostSaveDir = path.join(proxyDataDir, 'host');
const contentMap = fs
.readdirSync(this.hostSaveDir)
.filter(name => name.endsWith('.json'))
.reduce((prev, curr) => {
try {
prev[curr] = jsonfile.readFileSync(path.join(this.hostSaveDir, curr));
} catch (e) {
// ignore
}
return prev;
}, {});
forEach(contentMap, (content, fileName) => {
const hostName = content.name;
const userId = fileName.substr(
0,
this._getUserIdLength(fileName, hostName),
);
this.userHostFilesMap[userId] = this.userHostFilesMap[userId] || {};
this.userHostFilesMap[userId][hostName] = content;
});
}
public async resolveHost(userId, hostname) {
if (!hostname) {
return hostname;
}
if (ipReg.test(hostname)) {
return hostname;
}
let ip;
const inUsingHosts = this.getInUsingHosts(userId);
ip = inUsingHosts.hostMap[hostname];
if (ip) {
return ip;
}
// 配置 *开头的host 计算属性globHostMap已经将*去除
ip = find(inUsingHosts.globHostMap, (_, host) => {
return hostname.endsWith(host);
});
if (ip) {
return ip;
}
return hostname;
}
/**
* 获取用户的host文件列表
* @param userId
* @returns {Array}
*/
public getHostFileList(userId) {
const fileList: Array<{
name: string;
checked: boolean;
description: string;
meta: any;
}> = [];
forEach(this.userHostFilesMap[userId], content => {
fileList.push({
checked: content.checked,
description: content.description,
meta: content.meta,
name: content.name,
});
});
return fileList;
}
/**
* 创建host文件
* @param userId
* @param name
* @param description
* @returns {boolean}
*/
public async createHostFile(userId, name, description) {
if (this.userHostFilesMap[userId] && this.userHostFilesMap[userId][name]) {
// 文件已经存在不让创建
return false;
}
const content = {
checked: false,
content: {},
description,
meta: {
local: true,
},
name,
};
this.userHostFilesMap[userId] = this.userHostFilesMap[userId] || {};
this.userHostFilesMap[userId][name] = content;
const hostfileName = this._getHostFilePath(userId, name);
await jsonWriteFile(hostfileName, content, { encoding: 'utf-8' });
this.emit('data-change', userId, this.getHostFileList(userId));
this.emit('host-saved', userId, name, content);
return true;
}
public async deleteHostFile(userId, name) {
delete this.userHostFilesMap[userId][name];
delete this.inUsingHostsMapCache[userId];
/**
* 删除文件
*/
const filePath = this._getHostFilePath(userId, name);
const exists = await fsExists(filePath);
if (exists) {
await fsUnlink(filePath);
}
this.emit('data-change', userId, this.getHostFileList(userId));
this.emit('host-deleted', userId, name);
}
public async toggleUseHost(userId, filename) {
const toSaveFileName: string[] = [];
forEach(this.userHostFilesMap[userId], (content, name) => {
if (content.name === filename) {
content.checked = !content.checked;
toSaveFileName.push(name);
}
});
// 保存文件
for (const name of toSaveFileName) {
const hostfileName = this._getHostFilePath(userId, name);
const content = this.userHostFilesMap[userId][name];
await jsonWriteFile(hostfileName, content, { encoding: 'utf-8' });
}
delete this.inUsingHostsMapCache[userId];
this.emit('data-change', userId, this.getHostFileList(userId));
}
public getHostFile(userId, name) {
if (!this.userHostFilesMap[userId]) {
return {};
}
return this.userHostFilesMap[userId][name];
}
public async saveHostFile(userId, name, content) {
if (!this.userHostFilesMap[userId]) {
this.userHostFilesMap[userId] = {};
}
this.userHostFilesMap[userId][name] = content;
// 如果正在使用,则删除
if (content.checked) {
delete this.inUsingHostsMapCache[userId];
}
const hostfileName = this._getHostFilePath(userId, name);
await jsonWriteFile(hostfileName, content, { encoding: 'utf-8' });
this.emit('host-saved', userId, name, content);
this.emit('data-change', userId, this.getHostFileList(userId));
}
public async importRemoteHostFile(userId, url): Promise<any> {
const resp = await fetch(url);
const f = await resp.json();
f.meta = {
local: false,
url,
};
if (!f.content) {
f.content = {};
}
if (!f.name) {
f.name = url.split('/').slice(-1)[0] || url;
}
if (
this.getHostFile(userId, f.name) &&
this.getHostFile(userId, f.name).checked
) {
f.checked = true;
} else {
f.checked = false;
}
return await this.saveHostFile(userId, f.name, f);
}
private _getHostFilePath(userId, hostName) {
const fileName = `${userId}_${hostName}.json`;
const filePath = path.join(this.hostSaveDir, fileName);
return filePath;
}
private _getUserIdLength(ruleFileName, hostName) {
return ruleFileName.length - hostName.length - 6;
}
/**
* 获取用户生效的host
* @param userId
* @returns {*}
*/
private getInUsingHosts(userId) {
let hosts = this.inUsingHostsMapCache[userId];
if (!hosts) {
// 读文件加载host
const hostMap = {};
const globHostMap = {};
const userHostFile = this.userHostFilesMap[userId] || {};
Object.keys(userHostFile).forEach(name => {
const file = this.userHostFilesMap[userId][name];
if (!file.checked) {
return;
}
forEach(file.content, (ip, host) => {
if (host.startsWith('*')) {
globHostMap[host.substr(1, host.length)] = ip;
} else {
hostMap[host] = ip;
}
});
});
hosts = {
globHostMap,
hostMap,
};
this.inUsingHostsMapCache[userId] = hosts;
}
return hosts;
}
}