vite-uni-dev-tool
Version:
vite-uni-dev-tool, debug, uni-app, 一处编写,到处调试
746 lines (677 loc) • 18.7 kB
text/typescript
import { backup } from '../core';
import { DevEvent } from '../devEvent';
import type { DevTool } from '../type';
import {
escapeHTML,
getCurrentDate,
getCurrentPagePath,
parseValue,
} from '../utils/index';
import { transformValueToView } from '../utils/language';
/**
* 拦截器
*
* @export
* @class DevIntercept
*/
export class DevIntercept {
private event: DevEvent;
initPinia = false;
cache$on = new Map();
cache$once = new Map();
cache$emit = new Map();
cache$off = new Map();
constructor(options: DevTool.DevInterceptOptions) {
this.event = options.event;
this.init(options);
}
init(options: DevTool.DevInterceptOptions) {
this.interceptAppError();
this.interceptAppConsole();
this.interceptSetStorage();
this.interceptRemoveStorage();
this.interceptClearStorage();
this.interceptUploadFile();
this.interceptRequest();
this.interceptSocket();
this.interceptSwitchTab();
this.interceptNavigateTo();
this.interceptUniEvent();
options.enableInterceptPromiseReject && this.interceptPromiseReject();
}
/**
* app 中拦截 console
* app
* @memberof DevIntercept
*/
interceptAppConsole() {
if (!(uni as any).__log__) return;
const that = this;
(uni as any).__log__ = function (
type: DevTool.ConsoleType,
line: string,
...args: any
) {
const path = getCurrentPagePath();
backup['__log__'](type, line, ...args);
that.event.updateConsoleList([
{
type,
position: path,
time: getCurrentDate(),
args: args.map((item: any) => {
// 处理 item 循环引用的问题
return {
type: transformValueToView(item),
value: parseValue(item),
};
}),
stack: line,
},
]);
};
}
/**
* 拦截 vue3 信息
*
* @memberof DevIntercept
*/
interceptVue3(vue3instance: any) {
if (vue3instance) {
// 微信小程序触发了该事件
vue3instance.appContext.config.errorHandler = (
error: Error,
vm: any,
info: string,
) => {
if (this.event.getDevToolDestroy()) {
return;
}
this.interceptErrorVue3(error);
};
vue3instance.appContext.config.warnHandler = (
msg: string,
vm: any,
trace: string,
) => {
if (this.event.getDevToolDestroy()) {
return;
}
this.interceptWarnVue3(msg + '\n' + trace);
};
}
}
/**
* app 中捕获全局错误
*
* @memberof DevIntercept
*/
interceptAppError() {
uni.onError((error) => {
if (this.event.getDevToolDestroy()) {
return;
}
const info = error.toString();
const stack = (error as unknown as Error)?.stack?.split('\n')?.[1] ?? '';
this.event.updateConsoleList([
{
type: 'error',
args: [
{
type: 'string',
value: info,
},
],
position: getCurrentPagePath(),
time: getCurrentDate(),
stack,
},
]);
});
}
/**
* 拦截 app 错误
*
* @param {*} error
* @memberof DevIntercept
*/
interceptErrorVue3(error: Error) {
const stackList = error?.stack?.split('\n');
const stack = stackList?.[1];
console.error(error);
this.event.updateConsoleList([
{
type: 'error',
args: [
{
type: 'string',
value: error.toString(),
},
],
position: getCurrentPagePath(),
time: getCurrentDate(),
stack,
},
]);
}
preWarn: any;
/**
* 拦截 app 警告
*
* @param {*} warn
* @return {*}
* @memberof DevIntercept
*/
interceptWarnVue3(warn: string) {
if (this.preWarn === warn) return;
this.preWarn = warn;
const path = getCurrentPagePath();
const stackList = new Error()?.stack?.split('\n');
const stack = stackList?.slice(2)?.[0];
console.warn(warn);
const args = warn
.split('\n')
?.map((item) => escapeHTML(item))
.join('\n');
this.event.updateConsoleList([
{
type: 'warn',
args: [
{
type: 'string',
value: args,
},
],
position: path,
time: getCurrentDate(),
stack,
},
]);
}
/**
* 拦截 promise reject
*
* @memberof DevIntercept
*/
interceptPromiseReject() {
Object.defineProperty(Promise, 'reject', {
value: function (reason: any) {
this.interceptErrorApp(reason);
return backup.reject.call(Promise, reason);
},
});
}
/**
* 拦截 nav 跳转
*
* @memberof DevIntercept
*/
interceptSwitchTab() {
const that = this;
uni.addInterceptor('switchTab', {
invoke(args: UniNamespace.NavigateToOptions) {
const complete = args.complete;
args.complete = function (e: any) {
complete && complete(e);
const url = args.url?.slice(1)?.split('?')?.[0] || '/';
that.event.updateCurrentPagePath(url);
};
},
});
}
/**
* 拦截页面跳转
*
* @memberof DevIntercept
*/
interceptNavigateTo() {
const that = this;
uni.addInterceptor('navigateTo', {
invoke(args: UniNamespace.NavigateToOptions) {
const complete = args.complete;
args.complete = function (e: any) {
complete && complete(e);
const url = args.url?.slice(1)?.split('?')?.[0] || '/';
that.event.updateCurrentPagePath(url);
};
},
});
}
/**
* 拦截网络请求
*
* @memberof DevIntercept
*/
interceptRequest() {
const that = this;
uni.addInterceptor('request', {
invoke(args: UniNamespace.RequestOptions) {
const preIndex = that.event.getRequestIndex();
const index = that.event.setRequestIndex(preIndex + 1);
const url = args.url;
const noParams = url?.split('?')?.[0];
const lastG = noParams?.lastIndexOf('/');
const name = noParams?.slice(lastG + 1) ?? '';
const addInterceptorRequestData: DevTool.NetworkItem = {
index,
url: args.url,
name,
startTime: Date.now(),
endTime: 0,
time: '-',
headers: {
requestHeader: Object.entries(args.header ?? {}).map(
([key, value]) => ({
key,
value,
}),
),
responseHeader: [],
},
method: args.method || 'GET',
status: 'pending',
payload: args?.data ? JSON.stringify(args.data) : '',
response: '',
size: '',
};
that.event.updateNetworkList([addInterceptorRequestData]);
let _complete = args.complete;
args.complete = function (e: any) {
_complete && _complete(e);
addInterceptorRequestData.status = e.statusCode ?? 'error';
addInterceptorRequestData.endTime = Date.now();
const diff = Date.now() - addInterceptorRequestData.startTime;
addInterceptorRequestData.time =
diff < 1000 ? diff + 'ms' : diff / 1000 + 's';
const len =
e?.header?.['Content-Length'] || e?.header?.['content-length'] || 0;
addInterceptorRequestData.size =
len > 1024 ? (len / 1024).toFixed(2) + 'k' : len + 'b';
addInterceptorRequestData.response = e;
addInterceptorRequestData.headers.responseHeader = Object.entries(
e.header ?? {},
).map(([key, value]) => ({
key,
value,
}));
that.event.updateNetworkList([addInterceptorRequestData], index);
};
},
});
}
/**
* 拦截 websocket
*
* @memberof DevIntercept
*/
interceptSocket() {
const that = this;
const connectSocket = function (
connectOption: UniNamespace.ConnectSocketOption,
) {
const url = connectOption.url;
const headers = Object.entries(connectOption.header ?? {}).map(
([key, value]) => {
return { key, value } as { key: string; value: string };
},
);
const method = (connectOption.method ?? 'GET').toLowerCase();
const protocols = connectOption.protocols ?? [];
that.event.updateWsList({
url,
headers,
method,
protocols,
readyState: 'connection',
message: [],
});
const socketTask = backup.connectSocket({
...connectOption,
success: (res) => {
connectOption.success?.(res);
that.event.updateWsList({
url,
readyState: 'open',
headers,
protocols,
message: [
{
data: '连接成功',
type: 'success',
time: Date.now(),
},
],
});
},
fail: (error) => {
connectOption.fail?.(error);
that.event.updateWsList({
url,
readyState: 'error',
headers,
protocols,
message: [
{
data: '连接失败',
type: 'error',
time: Date.now(),
},
],
});
},
complete: (res) => {
connectOption?.complete?.(res);
},
});
const send = socketTask.send;
socketTask.send = (options: UniApp.SendSocketMessageOptions) => {
send.call(socketTask, {
data: options.data,
fail: (error) => {
options?.fail?.(error);
that.event.updateWsList({
url,
readyState: 'error',
headers,
protocols,
message: [
{
data: JSON.stringify(error),
type: 'error',
time: Date.now(),
},
],
});
},
success: (res) => {
options?.success?.(res);
that.event.updateWsList({
readyState: 'open',
url,
headers,
protocols,
message: [
{
type: 'success',
time: Date.now(),
data: options.data?.toString(),
},
],
});
},
complete: (res) => {
options?.complete?.(res);
},
});
};
const close = socketTask.close;
socketTask.close = (options: UniApp.CloseSocketOptions) => {
that.event.updateWsList({
url,
readyState: 'closing',
headers,
protocols,
message: [],
});
close.call(socketTask, {
...options,
fail: (error) => {
options?.fail?.(error);
that.event.updateWsList({
url,
headers,
readyState: 'open',
protocols,
message: [],
});
},
success: (res) => {
options?.success?.(res);
that.event.updateWsList({
url,
headers,
readyState: 'closed',
protocols,
message: [],
});
},
complete: (res) => {
options?.complete?.(res);
},
});
};
socketTask.onMessage((res) => {
that.event.updateWsList({
url,
headers,
protocols,
message: [
{
type: 'success',
time: Date.now(),
data: JSON.parse(res.data),
},
],
});
});
return socketTask;
};
uni.connectSocket = connectSocket;
}
/**
* 拦截 uni.setStorageSync 和 uni.setStorage
*
* @memberof DevIntercept
*/
interceptSetStorage() {
uni.setStorageSync = (key, value) => {
backup.setStorageSync(key.toString(), value);
this.event.updateStoreList([
{
key: key.toString(),
_oldKey: key.toString(),
value: value,
},
]);
};
uni.setStorage = (options: UniNamespace.SetStorageOptions) => {
backup
.setStorage({
...options,
key: options.key?.toString(),
})
.then(() => {
this.event.updateStoreList([
{
key: options.key?.toString(),
_oldKey: options.key?.toString(),
value: options.data,
},
]);
});
};
}
/**
* 拦截 uni.clearStorageSync 和 uni.clearStorage
*
* @memberof DevIntercept
*/
interceptClearStorage() {
uni.clearStorageSync = () => {
backup.clearStorageSync();
this.event.clearStorage();
};
uni.clearStorage = () => {
backup.clearStorage();
this.event.clearStorage();
};
}
/**
* 拦截 uni.removeStorageSync 和 uni.removeStorage
*
* @memberof DevIntercept
*/
interceptRemoveStorage() {
uni.removeStorageSync = (key) => {
backup.removeStorageSync(key);
// remove(key);
this.event.removeStorage(key);
};
uni.removeStorage = (options: UniNamespace.RemoveStorageOptions) => {
backup.removeStorage(options).then(() => {
this.event.removeStorage(options.key);
});
};
}
/** 拦截vuex */
interceptVuexStorage(store: any) {
this.event.setVuexList(store.state);
store?.subscribe?.((_: any, state: any) => {
this.event.setVuexList(state);
});
}
/** 拦截pinia */
interceptPiniaStore(context: any) {
if (!this.initPinia) {
this.initPinia = true;
this.event.setPiniaStore(context.pinia);
}
this.event.setPiniaList(context.pinia.state.value);
context?.store?.$subscribe(() => {
this.event.setPiniaList({
[context.store.$id]: context.store.$state,
});
});
return context.pinia;
}
/**
* 拦截 uni.uploadFile
*
* @memberof DevIntercept
*/
interceptUploadFile() {
const that = this;
const uploadFile = (uploadOption: UniNamespace.UploadFileOption) => {
const preIndex = that.event.getUploadIndex();
const index = that.event.setUploadIndex(preIndex + 1);
that.event.updateUploadList(
[
{
index,
name: uploadOption.name,
url: uploadOption.url,
filePath: uploadOption.filePath,
fileType: uploadOption.fileType,
headers: {
requestHeader: Object.entries(uploadOption.header || {}).map(
([key, value]) => ({ key, value }),
),
responseHeader: [],
},
formData: uploadOption.formData,
status: 'pending',
progress: 0,
totalBytesSent: 0,
totalBytesExpectedToSend: 0,
startTime: Date.now(),
},
],
index,
);
const uploadTask = backup.uploadFile({
...uploadOption,
success: (res) => {
uploadOption?.success?.(res);
that.event.updateUploadList(
[
{
index,
status: 'success',
endTime: Date.now(),
response: {
...res,
data: JSON.parse(res.data),
},
},
],
index,
);
that.event.removeUploadTask(index);
},
fail: (error) => {
uploadOption?.fail?.(error);
that.event.updateUploadList(
[
{
index,
status: 'error',
response: error,
endTime: Date.now(),
},
],
index,
);
that.event.removeUploadTask(index);
},
});
uploadTask.onProgressUpdate((res) => {
that.event.updateUploadList(
[
{
index,
progress: res.progress,
totalBytesSent: res.totalBytesSent,
totalBytesExpectedToSend: res.totalBytesExpectedToSend,
status: 'uploading',
},
],
index,
);
});
that.event.addUploadTask(index, uploadTask);
return uploadTask;
};
uni.uploadFile = uploadFile.bind(uni);
}
interceptUniEventFactory(type: DevTool.EventCountKey) {
const key = `$${type}` as '$on' | '$emit' | '$once' | '$off';
uni[`$${type}`] = (eventName: string, callback: (result: any) => void) => {
const stockList = new Error()?.stack?.split('\n');
const stack = stockList?.[2];
backup?.[key]?.(eventName, callback);
this.event.updateUniEventList([
{
eventName,
timer: getCurrentDate(),
stack,
type,
},
]);
this.event.updateUniEventCount(type);
};
}
interceptUniEvent() {
// 判断是否是相同的位置注册,相同的位置注册只算一次
this.interceptUniEventFactory('on');
this.interceptUniEventFactory('once');
this.interceptUniEventFactory('emit');
this.interceptUniEventFactory('off');
}
/**
* 监听截屏
*
* @memberof DevIntercept
*/
interceptCaptureScreen() {
uni.onUserCaptureScreen(() => {
if (!this.event.getDevToolDestroy()) {
this.event.updateCaptureScreenList([
{
position: getCurrentPagePath(),
timer: getCurrentDate(),
},
]);
}
});
}
}