add codes
This commit is contained in:
37
src/libs/auth.js
Normal file
37
src/libs/auth.js
Normal 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
15
src/libs/browser.js
Normal 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
183
src/libs/fetch.js
Normal 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
57
src/libs/index.js
Normal 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
21
src/libs/msg.js
Normal 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
91
src/libs/storage.js
Normal 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
29
src/libs/utils.js
Normal 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];
|
||||
};
|
||||
Reference in New Issue
Block a user