add codes

This commit is contained in:
Gabe Yuan
2023-07-20 13:45:41 +08:00
parent 10183e3013
commit 0041d6d528
44 changed files with 13020 additions and 0 deletions

37
src/libs/auth.js Normal file
View File

@@ -0,0 +1,37 @@
import storage from "./storage";
import { STOKEY_MSAUTH, URL_MICROSOFT_AUTH } from "../config";
import { fetchData } from "./fetch";
const parseMSToken = (token) => JSON.parse(atob(token.split(".")[1])).exp;
/**
* 闭包缓存token减少对storage查询
* @returns
*/
const _msAuth = () => {
let { token, exp } = {};
return async () => {
// 查询内存缓存
const now = Date.now();
if (token && exp * 1000 > now + 1000) {
return [token, exp];
}
// 查询storage缓存
const res = (await storage.getObj(STOKEY_MSAUTH)) || {};
token = res.token;
exp = res.exp;
if (token && exp * 1000 > now + 1000) {
return [token, exp];
}
// 缓存没有或失效,查询接口
token = await fetchData(URL_MICROSOFT_AUTH);
exp = parseMSToken(token);
await storage.setObj(STOKEY_MSAUTH, { token, exp });
return [token, exp];
};
};
export const msAuth = _msAuth();

15
src/libs/browser.js Normal file
View File

@@ -0,0 +1,15 @@
/**
* 浏览器兼容插件,另可用于判断是插件模式还是网页模式,方便开发
* @returns
*/
function _browser() {
try {
return require("webextension-polyfill");
} catch (err) {
console.log("[browser]", err.message);
}
}
const browser = _browser();
export default browser;

183
src/libs/fetch.js Normal file
View File

@@ -0,0 +1,183 @@
import browser from "./browser";
import { sendMsg } from "./msg";
import {
MSG_FETCH,
DEFAULT_FETCH_LIMIT,
DEFAULT_FETCH_INTERVAL,
CACHE_NAME,
OPT_TRANS_MICROSOFT,
OPT_TRANS_OPENAI,
} from "../config";
import { msAuth } from "./auth";
import { getSetting } from ".";
/**
* request 改造因缓存必须是GET方法
* @param {*} request
* @returns
*/
const newCacheReq = async (request) => {
if (request.method === "GET") {
return request;
}
const body = await request.clone().text();
const cacheUrl = new URL(request.url);
cacheUrl.pathname += body;
return new Request(cacheUrl.toString(), { method: "GET" });
};
/**
* request 改造,根据不同翻译服务
* @param {*} request
* @returns
*/
const newReq = async (request) => {
const translator = request.headers.get("X-Translator");
if (translator === OPT_TRANS_MICROSOFT) {
const [token] = await msAuth();
request.headers.set("Authorization", `Bearer ${token}`);
} else if (translator === OPT_TRANS_OPENAI) {
const { openaiKey } = await getSetting();
request.headers.set("Authorization", `Bearer ${openaiKey}`); // OpenAI
request.headers.set("api-key", openaiKey); // Azure OpenAI
}
request.headers.delete("X-Translator");
return request;
};
/**
* 请求池
* @param {*} l
* @param {*} t
* @returns
*/
const _fetchPool = (l = 1, t = 1000) => {
let limitCount = l; // 限制并发数量
const intervalTime = t; // 请求间隔时间
const pool = []; // 请求池
const maxRetry = 2; // 最大重试次数
let currentCount = 0; // 当前请求数量
setInterval(async () => {
const count = limitCount - currentCount;
if (pool.length === 0 || count <= 0) {
return;
}
for (let i = 0; i < count; i++) {
const item = pool.shift();
if (item) {
const { request, resolve, reject, retry } = item;
currentCount++;
try {
const req = await request();
const res = await fetch(req);
resolve(res);
} catch (err) {
if (retry < maxRetry) {
pool.push({ request, resolve, reject, retry: retry + 1 });
} else {
reject(err);
}
} finally {
currentCount--;
}
}
}
}, intervalTime);
return [
async (req, usePool) => {
const request = () => newReq(req.clone());
if (usePool) {
return new Promise((resolve, reject) => {
pool.push({ request, resolve, reject, retry: 0 });
});
} else {
return fetch(await request());
}
},
(limit = -1) => {
if (limit >= 1 && limit <= 10 && limitCount !== limit) {
limitCount = limit;
}
},
];
};
export const [_fetch, setFetchLimit] = _fetchPool(
DEFAULT_FETCH_LIMIT,
DEFAULT_FETCH_INTERVAL
);
/**
* 调用fetch接口
* @param {*} input
* @param {*} init
* @returns
*/
export const fetchData = async (
input,
{ useCache = false, usePool = false, ...init } = {}
) => {
const req = new Request(input, init);
const cacheReq = await newCacheReq(req);
const cache = await caches.open(CACHE_NAME);
let res;
// 查询缓存
if (useCache) {
try {
res = await cache.match(cacheReq);
} catch (err) {
console.log("[cache match]", err);
}
}
// 发送请求
if (!res) {
res = await _fetch(req, usePool);
}
if (!res?.ok) {
throw new Error(`response: ${res.statusText}`);
}
// 插入缓存
if (useCache) {
try {
await cache.put(cacheReq, res.clone());
} catch (err) {
console.log("[cache put]", err);
}
}
const contentType = res.headers.get("Content-Type");
if (contentType?.includes("json")) {
return await res.json();
}
return await res.text();
};
/**
* 兼容性封装
* @param {*} input
* @param {*} init
* @returns
*/
export const fetchPolyfill = async (input, init) => {
if (browser?.runtime) {
// 插件调用
const res = await sendMsg(MSG_FETCH, { input, init });
if (res.error) {
throw new Error(res.error);
}
return res.data;
}
// 网页直接调用
return await fetchData(input, init);
};

57
src/libs/index.js Normal file
View File

@@ -0,0 +1,57 @@
import storage from "./storage";
import {
DEFAULT_SETTING,
STOKEY_SETTING,
STOKEY_RULES,
DEFAULT_RULE,
} from "../config";
import browser from "./browser";
/**
* 获取节点列表并转为数组
* @param {*} selector
* @param {*} el
* @returns
*/
export const queryEls = (selector, el = document) =>
Array.from(el.querySelectorAll(selector));
/**
* 查询storage中的设置
* @returns
*/
export const getSetting = async () => ({
...DEFAULT_SETTING,
...((await storage.getObj(STOKEY_SETTING)) || {}),
});
/**
* 查询规则列表
* @returns
*/
export const getRules = async () => (await storage.getObj(STOKEY_RULES)) || [];
/**
* 根据href匹配规则
* TODO: 支持通配符(*)匹配
* @param {*} rules
* @param {string} href
* @returns
*/
export const matchRule = (rules, href) =>
rules.find((rule) =>
rule.pattern
.split(",")
.some((p) => p.trim() === "*" || href.includes(p.trim()))
) || DEFAULT_RULE;
/**
* 本地语言识别
* @param {*} q
* @returns
*/
export const detectLang = async (q) => {
const res = await browser?.i18n.detectLanguage(q);
console.log("detecLang", q, res);
return res?.languages?.[0]?.language;
};

21
src/libs/msg.js Normal file
View File

@@ -0,0 +1,21 @@
import browser from "./browser";
/**
* 发送消息给background
* @param {*} action
* @param {*} args
* @returns
*/
export const sendMsg = (action, args) =>
browser?.runtime?.sendMessage({ action, args });
/**
* 发送消息给当前页面
* @param {*} action
* @param {*} args
* @returns
*/
export const sendTabMsg = async (action, args) => {
const tabs = await browser?.tabs.query({ active: true, currentWindow: true });
return await browser?.tabs.sendMessage(tabs[0].id, { action, args });
};

91
src/libs/storage.js Normal file
View File

@@ -0,0 +1,91 @@
import browser from "./browser";
async function set(key, val) {
if (browser?.storage) {
await browser.storage.local.set({ [key]: val });
} else {
const oldValue = window.localStorage.getItem(key);
window.localStorage.setItem(key, val);
// 手动唤起事件
window.dispatchEvent(
new StorageEvent("storage", {
key,
oldValue,
newValue: val,
})
);
}
}
async function get(key) {
if (browser?.storage) {
const res = await browser.storage.local.get([key]);
return res[key];
}
return window.localStorage.getItem(key);
}
async function del(key) {
if (browser?.storage) {
await browser.storage.local.remove([key]);
} else {
const oldValue = window.localStorage.getItem(key);
window.localStorage.removeItem(key);
// 手动唤起事件
window.dispatchEvent(
new StorageEvent("storage", {
key,
oldValue,
newValue: null,
})
);
}
}
async function setObj(key, obj) {
await set(key, JSON.stringify(obj));
}
async function trySetObj(key, obj) {
if (!(await get(key))) {
await setObj(key, obj);
}
}
async function getObj(key) {
const val = await get(key);
return val && JSON.parse(val);
}
async function putObj(key, obj) {
const cur = (await getObj(key)) ?? {};
await setObj(key, { ...cur, ...obj });
}
/**
* 监听storage事件
* @param {*} handleChanged
*/
function onChanged(handleChanged) {
if (browser?.storage) {
browser.storage.onChanged.addListener(handleChanged);
} else {
window.addEventListener("storage", handleChanged);
}
}
/**
* 对storage的封装
*/
const storage = {
get,
set,
del,
setObj,
trySetObj,
getObj,
putObj,
onChanged,
};
export default storage;

29
src/libs/utils.js Normal file
View File

@@ -0,0 +1,29 @@
/**
* 限制数字大小
* @param {*} num
* @param {*} min
* @param {*} max
* @returns
*/
export const limitNumber = (num, min = 0, max = 100) => {
const number = parseInt(num);
if (Number.isNaN(number) || number < min) {
return min;
} else if (number > max) {
return max;
}
return number;
};
/**
* 匹配是否为数组中的值
* @param {*} arr
* @param {*} val
* @returns
*/
export const matchValue = (arr, val) => {
if (arr.length === 0 || arr.includes(val)) {
return val;
}
return arr[0];
};