Merge pull request #776 from nofficalfs/Dev-Enhancement-Noname
fix some question.
This commit is contained in:
commit
07c3dbd6d4
|
@ -91,7 +91,8 @@ new Promise(resolve => {
|
|||
script.async = true
|
||||
script.onerror = (event) => {
|
||||
console.error(event)
|
||||
const message = `您使用的浏览器或无名杀客户端加载内容失败!\n报错内容: \n${event}\n若该BUG不为您个人原因造成的,请及时反馈给无名杀开发组!`;
|
||||
const message = `您使用的浏览器或无名杀客户端加载内容失败!\n请检查游戏环境以及"(游戏根目录)/game/entry.js"文件的位置\n若该BUG不为您个人原因造成的,请及时反馈给无名杀开发组!`;
|
||||
console.error(message);
|
||||
alert(message);
|
||||
exit()
|
||||
}
|
||||
|
|
|
@ -799,10 +799,11 @@ export class Get extends Uninstantable {
|
|||
*
|
||||
* @template T
|
||||
* @param {T} obj - 要复制的对象,若不是对象则直接返回原值
|
||||
* @param {boolean} [copyKeyDeep = false] - 是否深复制`Map`的`key`
|
||||
* @param {WeakMap<object, unknown>} [map] - 拷贝用的临时存储,用于处理循环引用(请勿自行赋值)
|
||||
* @returns {T} - 深拷贝后的对象,若传入值不是对象则为传入值
|
||||
*/
|
||||
static copy(obj, map = new WeakMap()) {
|
||||
static copy(obj, copyKeyDeep = false, map = new WeakMap()) {
|
||||
// 参考[这里](https://juejin.cn/post/7315612852890026021)实现深拷贝
|
||||
// 不再判断是否能structuredClone是因为structuredClone会把Symbol给毙了
|
||||
const getType = (obj) => Object.prototype.toString.call(obj);
|
||||
|
@ -815,29 +816,36 @@ export class Get extends Uninstantable {
|
|||
"[object Arguments]": true,
|
||||
};
|
||||
|
||||
if (typeof obj !== "object" || obj === null)
|
||||
if (typeof obj !== "object" || obj === null || !canTranverse[getType(obj)])
|
||||
return obj;
|
||||
|
||||
// @ts-ignore
|
||||
if (map.has(obj)) return map.get(obj);
|
||||
|
||||
const constructor = obj.constructor;
|
||||
let target;
|
||||
if (!canTranverse[getType(obj)]) {
|
||||
target = obj;
|
||||
return target;
|
||||
}
|
||||
// @ts-ignore
|
||||
else target = constructor ? new constructor(target) : Object.create(null);
|
||||
const target =
|
||||
constructor
|
||||
? (
|
||||
// 这三类数据处理单独处理
|
||||
// (实际上需要处理的只有Map和Set)
|
||||
// 除此之外的就只能祝愿有拷贝构造函数了
|
||||
(Array.isArray(obj) || obj instanceof Map || obj instanceof Set)
|
||||
// @ts-ignore
|
||||
? new constructor()
|
||||
// @ts-ignore
|
||||
: new constructor(obj)
|
||||
)
|
||||
: Object.create(null);
|
||||
map.set(obj, target);
|
||||
|
||||
if (obj instanceof Map) {
|
||||
obj.forEach((value, key) => {
|
||||
target.set(get.copy(key, map), get.copy(value, map));
|
||||
target.set(copyKeyDeep ? get.copy(key, copyKeyDeep, map) : key, get.copy(value, copyKeyDeep, map));
|
||||
});
|
||||
} else if (obj instanceof Set) {
|
||||
obj.forEach((value) => {
|
||||
target.add(get.copy(value, map));
|
||||
target.add(get.copy(value, copyKeyDeep, map));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -848,7 +856,7 @@ export class Get extends Uninstantable {
|
|||
if (obj.hasOwnProperty(key)) {
|
||||
const result = { enumerable, configurable };
|
||||
if (descriptor.hasOwnProperty('value')) {
|
||||
result.value = get.copy(descriptor.value, map);
|
||||
result.value = get.copy(descriptor.value, copyKeyDeep, map);
|
||||
result.writable = descriptor.writable;
|
||||
} else {
|
||||
const { get, set } = descriptor;
|
||||
|
@ -862,7 +870,7 @@ export class Get extends Uninstantable {
|
|||
|
||||
const symbols = Object.getOwnPropertySymbols(obj);
|
||||
symbols.forEach((symbol) => {
|
||||
target[symbol] = get.copy(obj[symbol], map);
|
||||
target[symbol] = get.copy(obj[symbol], copyKeyDeep, map);
|
||||
});
|
||||
|
||||
return target;
|
||||
|
@ -1645,12 +1653,17 @@ export class Get extends Uninstantable {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (obj instanceof lib.element.Button || (obj instanceof HTMLDivElement && obj.classList.contains('button'))) return 'button';
|
||||
if (obj instanceof lib.element.Button) return 'button';
|
||||
if (obj instanceof lib.element.Card) return 'card';
|
||||
if (obj instanceof lib.element.Player) return 'player';
|
||||
if (obj instanceof lib.element.Dialog) return 'dialog';
|
||||
if (obj instanceof lib.element.GameEvent ||
|
||||
obj instanceof lib.element.GameEventPromise) return 'event';
|
||||
|
||||
if (typeof obj !== 'object' || obj === null) return;
|
||||
|
||||
if (lib.experimental.symbol.itemType in obj)
|
||||
return obj[lib.experimental.symbol.itemType];
|
||||
}
|
||||
static equipNum(card) {
|
||||
if (get.type(card) == 'equip') {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { UI as ui } from '../ui/index.js';
|
|||
|
||||
import { userAgent } from '../util/index.js';
|
||||
import * as config from '../util/config.js';
|
||||
import { promiseErrorHandlerMap } from '../util/browser.js';
|
||||
import { gnc } from '../gnc/index.js';
|
||||
|
||||
import { importCardPack, importCharacterPack, importExtension, importMode } from './import.js';
|
||||
|
@ -33,7 +34,7 @@ export async function boot() {
|
|||
// 设定游戏加载时间,超过时间未加载就提醒
|
||||
const configLoadTime = localStorage.getItem(lib.configprefix + 'loadtime');
|
||||
// 现在不暴露到全局变量里了,直接传给onload
|
||||
const resetGameTimeout = setTimeout(lib.init.reset, configLoadTime ? parseInt(configLoadTime) : 10000)
|
||||
const resetGameTimeout = setTimeout(lib.init.reset, configLoadTime ? parseInt(configLoadTime) : 10000);
|
||||
|
||||
if (Reflect.has(window, 'cordovaLoadTimeout')) {
|
||||
clearTimeout(Reflect.get(window, 'cordovaLoadTimeout'));
|
||||
|
@ -57,6 +58,7 @@ export async function boot() {
|
|||
_status.event = lib.element.GameEvent.initialGameEvent();
|
||||
|
||||
setWindowListener();
|
||||
await setOnError();
|
||||
|
||||
// 无名杀更新日志
|
||||
if (window.noname_update) {
|
||||
|
@ -86,7 +88,7 @@ export async function boot() {
|
|||
const waitDomLoad = new Promise((resolve) => {
|
||||
if (document.readyState !== 'complete') {
|
||||
window.onload = resolve;
|
||||
} else resolve(void 0)
|
||||
} else resolve(void 0);
|
||||
}).then(onWindowReady.bind(window));
|
||||
|
||||
|
||||
|
@ -141,7 +143,7 @@ export async function boot() {
|
|||
// 读取模式
|
||||
if (config2.mode) config.set('mode', config2.mode);
|
||||
if (config.get('mode_config')[config.get('mode')] === undefined)
|
||||
config.get('mode_config')[config.get('mode')] = {};
|
||||
config.get('mode_config')[config.get('mode')] = {};
|
||||
|
||||
// 复制共有模式设置
|
||||
for (const name in config.get('mode_config').global) {
|
||||
|
@ -697,9 +699,9 @@ async function onWindowReady() {
|
|||
document.addEventListener('deviceready', async () => {
|
||||
const { cordovaReady } = await import('./cordova.js');
|
||||
await cordovaReady();
|
||||
resolve(void 0)
|
||||
resolve(void 0);
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
/*
|
||||
if (_status.packLoaded) {
|
||||
|
@ -751,33 +753,123 @@ function setServerIndex() {
|
|||
}
|
||||
}
|
||||
|
||||
function setWindowListener() {
|
||||
/**
|
||||
* @type { [Error, NodeJS.CallSite[]][] }
|
||||
*/
|
||||
const errorList = [];
|
||||
// 这他娘的能捕获浏览器控制台里的输入值,尽量别用
|
||||
// 在火狐里无效
|
||||
Error.prepareStackTrace = function (e, stackTraces) {
|
||||
errorList.push([e, stackTraces]);
|
||||
};
|
||||
// 已经有用了
|
||||
window.addEventListener("unhandledrejection", promiseRejectionEvent => {
|
||||
promiseRejectionEvent.promise.catch(e => {
|
||||
const result = errorList.find(v => v[0] === e);
|
||||
if (result) {
|
||||
// @ts-ignore
|
||||
window.onerror(
|
||||
result[0].message,
|
||||
result[1][0].getScriptNameOrSourceURL() || void 0,
|
||||
result[1][0].getLineNumber() || void 0,
|
||||
result[1][0].getColumnNumber() || void 0,
|
||||
result[0]
|
||||
);
|
||||
}
|
||||
});
|
||||
async function setOnError() {
|
||||
const [core] = get.coreInfo();
|
||||
|
||||
const promiseErrorHandler = new ((core in promiseErrorHandlerMap) ? promiseErrorHandlerMap[core] : promiseErrorHandlerMap.other);
|
||||
|
||||
if (promiseErrorHandler.onLoad) await promiseErrorHandler.onLoad();
|
||||
|
||||
window.addEventListener("unhandledrejection", async (event) => {
|
||||
if (promiseErrorHandler.onHandle) await promiseErrorHandler.onHandle(event);
|
||||
});
|
||||
|
||||
window.onerror = function (msg, src, line, column, err) {
|
||||
if (promiseErrorHandler.onErrorPrepare) promiseErrorHandler.onErrorPrepare();
|
||||
const winPath = window.__dirname ? ('file:///' + (__dirname.replace(new RegExp('\\\\', 'g'), '/') + '/')) : '';
|
||||
let str = `错误文件: ${typeof src == 'string' ? decodeURI(src).replace(lib.assetURL, '').replace(winPath, '') : '未知文件'}`;
|
||||
str += `\n错误信息: ${msg}`;
|
||||
const tip = lib.getErrorTip(msg);
|
||||
if (tip) str += `\n错误提示: ${tip}`;
|
||||
str += `\n行号: ${line}`;
|
||||
str += `\n列号: ${column}`;
|
||||
const version = Reflect.has(lib, 'version') ? Reflect.get(lib, 'version') : '';
|
||||
const reg = /[^\d.]/;
|
||||
const match = version.match(reg) != null;
|
||||
str += '\n' + `${match ? '游戏' : '无名杀'}版本: ${version || '未知版本'}`;
|
||||
if (match) str += '\n⚠️您使用的游戏代码不是源于libccy/noname无名杀官方仓库,请自行寻找您所使用的游戏版本开发者反馈!';
|
||||
if (_status && _status.event) {
|
||||
let evt = _status.event;
|
||||
str += `\nevent.name: ${evt.name}\nevent.step: ${evt.step}`;
|
||||
// @ts-ignore
|
||||
if (evt.parent) str += `\nevent.parent.name: ${evt.parent.name}\nevent.parent.step: ${evt.parent.step}`;
|
||||
// @ts-ignore
|
||||
if (evt.parent && evt.parent.parent) str += `\nevent.parent.parent.name: ${evt.parent.parent.name}\nevent.parent.parent.step: ${evt.parent.parent.step}`;
|
||||
if (evt.player || evt.target || evt.source || evt.skill || evt.card) {
|
||||
str += '\n-------------';
|
||||
}
|
||||
if (evt.player) {
|
||||
if (lib.translate[evt.player.name]) str += `\nplayer: ${lib.translate[evt.player.name]}[${evt.player.name}]`;
|
||||
else str += '\nplayer: ' + evt.player.name;
|
||||
let distance = get.distance(_status.roundStart, evt.player, 'absolute');
|
||||
if (distance != Infinity) {
|
||||
str += `\n座位号: ${distance + 1}`;
|
||||
}
|
||||
}
|
||||
if (evt.target) {
|
||||
if (lib.translate[evt.target.name]) str += `\ntarget: ${lib.translate[evt.target.name]}[${evt.target.name}]`;
|
||||
else str += '\ntarget: ' + evt.target.name;
|
||||
}
|
||||
if (evt.source) {
|
||||
if (lib.translate[evt.source.name]) str += `\nsource: ${lib.translate[evt.source.name]}[${evt.source.name}]`;
|
||||
else str += '\nsource: ' + evt.source.name;
|
||||
}
|
||||
if (evt.skill) {
|
||||
if (lib.translate[evt.skill]) str += `\nskill: ${lib.translate[evt.skill]}[${evt.skill}]`;
|
||||
else str += '\nskill: ' + evt.skill;
|
||||
}
|
||||
if (evt.card) {
|
||||
if (lib.translate[evt.card.name]) str += `\ncard: ${lib.translate[evt.card.name]}[${evt.card.name}]`;
|
||||
else str += '\ncard: ' + evt.card.name;
|
||||
}
|
||||
}
|
||||
str += '\n-------------';
|
||||
if (typeof line == 'number' && (typeof Reflect.get(game, 'readFile') == 'function' || location.origin != 'file://')) {
|
||||
const createShowCode = function (lines) {
|
||||
let showCode = '';
|
||||
if (lines.length >= 10) {
|
||||
if (line > 4) {
|
||||
for (let i = line - 5; i < line + 6 && i < lines.length; i++) {
|
||||
showCode += `${i + 1}| ${line == i + 1 ? '⚠️' : ''}${lines[i]}\n`;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < line + 6 && i < lines.length; i++) {
|
||||
showCode += `${i + 1}| ${line == i + 1 ? '⚠️' : ''}${lines[i]}\n`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showCode = lines.map((_line, i) => `${i + 1}| ${line == i + 1 ? '⚠️' : ''}${_line}\n`).toString();
|
||||
}
|
||||
return showCode;
|
||||
};
|
||||
//协议名须和html一致(网页端防跨域),且文件是js
|
||||
if (typeof src == 'string' && src.startsWith(location.protocol) && src.endsWith('.js')) {
|
||||
//获取代码
|
||||
const codes = lib.init.reqSync('local:' + decodeURI(src).replace(lib.assetURL, '').replace(winPath, ''));
|
||||
if (codes) {
|
||||
const lines = codes.split("\n");
|
||||
str += '\n' + createShowCode(lines);
|
||||
str += '\n-------------';
|
||||
}
|
||||
}
|
||||
//解析parsex里的content fun内容(通常是技能content)
|
||||
else if (err && err.stack && err.stack.split('\n')[1].trim().startsWith('at Object.eval [as content]')) {
|
||||
const codes = _status.event.content;
|
||||
if (typeof codes == 'function') {
|
||||
const lines = codes.toString().split("\n");
|
||||
str += '\n' + createShowCode(lines);
|
||||
str += '\n-------------';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err && err.stack) str += '\n' + decodeURI(err.stack).replace(new RegExp(lib.assetURL, 'g'), '').replace(new RegExp(winPath, 'g'), '');
|
||||
alert(str);
|
||||
Reflect.set(window, 'ea', Array.from(arguments));
|
||||
Reflect.set(window, 'em', msg);
|
||||
Reflect.set(window, 'el', line);
|
||||
Reflect.set(window, 'ec', column);
|
||||
Reflect.set(window, 'eo', err);
|
||||
game.print(str);
|
||||
if (promiseErrorHandler.onErrorFinish) promiseErrorHandler.onErrorFinish();
|
||||
// @ts-ignore
|
||||
if (!lib.config.errstop) {
|
||||
_status.withError = true;
|
||||
game.loop();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function setWindowListener() {
|
||||
window.onkeydown = function (e) {
|
||||
if (!Reflect.has(ui, 'menuContainer') || !Reflect.get(ui, 'menuContainer').classList.contains('hidden')) {
|
||||
if (e.keyCode == 116 || ((e.ctrlKey || e.metaKey) && e.keyCode == 82)) {
|
||||
|
@ -875,107 +967,4 @@ function setWindowListener() {
|
|||
// }
|
||||
}
|
||||
};
|
||||
|
||||
window.onerror = function (msg, src, line, column, err) {
|
||||
errorList.length = 0;
|
||||
const winPath = window.__dirname ? ('file:///' + (__dirname.replace(new RegExp('\\\\', 'g'), '/') + '/')) : '';
|
||||
let str = `错误文件: ${typeof src == 'string' ? decodeURI(src).replace(lib.assetURL, '').replace(winPath, '') : '未知文件'}`;
|
||||
str += `\n错误信息: ${msg}`;
|
||||
const tip = lib.getErrorTip(msg);
|
||||
if (tip) str += `\n错误提示: ${tip}`;
|
||||
str += `\n行号: ${line}`;
|
||||
str += `\n列号: ${column}`;
|
||||
const version = Reflect.has(lib, 'version') ? Reflect.get(lib, 'version') : '';
|
||||
const reg = /[^\d.]/;
|
||||
const match = version.match(reg) != null;
|
||||
str += '\n' + `${match ? '游戏' : '无名杀'}版本: ${version || '未知版本'}`;
|
||||
if (match) str += '\n⚠️您使用的游戏代码不是源于libccy/noname无名杀官方仓库,请自行寻找您所使用的游戏版本开发者反馈!';
|
||||
if (_status && _status.event) {
|
||||
let evt = _status.event;
|
||||
str += `\nevent.name: ${evt.name}\nevent.step: ${evt.step}`;
|
||||
// @ts-ignore
|
||||
if (evt.parent) str += `\nevent.parent.name: ${evt.parent.name}\nevent.parent.step: ${evt.parent.step}`;
|
||||
// @ts-ignore
|
||||
if (evt.parent && evt.parent.parent) str += `\nevent.parent.parent.name: ${evt.parent.parent.name}\nevent.parent.parent.step: ${evt.parent.parent.step}`;
|
||||
if (evt.player || evt.target || evt.source || evt.skill || evt.card) {
|
||||
str += '\n-------------';
|
||||
}
|
||||
if (evt.player) {
|
||||
if (lib.translate[evt.player.name]) str += `\nplayer: ${lib.translate[evt.player.name]}[${evt.player.name}]`;
|
||||
else str += '\nplayer: ' + evt.player.name;
|
||||
let distance = get.distance(_status.roundStart, evt.player, 'absolute');
|
||||
if (distance != Infinity) {
|
||||
str += `\n座位号: ${distance + 1}`;
|
||||
}
|
||||
}
|
||||
if (evt.target) {
|
||||
if (lib.translate[evt.target.name]) str += `\ntarget: ${lib.translate[evt.target.name]}[${evt.target.name}]`;
|
||||
else str += '\ntarget: ' + evt.target.name;
|
||||
}
|
||||
if (evt.source) {
|
||||
if (lib.translate[evt.source.name]) str += `\nsource: ${lib.translate[evt.source.name]}[${evt.source.name}]`;
|
||||
else str += '\nsource: ' + evt.source.name;
|
||||
}
|
||||
if (evt.skill) {
|
||||
if (lib.translate[evt.skill]) str += `\nskill: ${lib.translate[evt.skill]}[${evt.skill}]`;
|
||||
else str += '\nskill: ' + evt.skill;
|
||||
}
|
||||
if (evt.card) {
|
||||
if (lib.translate[evt.card.name]) str += `\ncard: ${lib.translate[evt.card.name]}[${evt.card.name}]`;
|
||||
else str += '\ncard: ' + evt.card.name;
|
||||
}
|
||||
}
|
||||
str += '\n-------------';
|
||||
if (typeof line == 'number' && (typeof Reflect.get(game, 'readFile') == 'function' || location.origin != 'file://')) {
|
||||
const createShowCode = function (lines) {
|
||||
let showCode = '';
|
||||
if (lines.length >= 10) {
|
||||
if (line > 4) {
|
||||
for (let i = line - 5; i < line + 6 && i < lines.length; i++) {
|
||||
showCode += `${i + 1}| ${line == i + 1 ? '⚠️' : ''}${lines[i]}\n`;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < line + 6 && i < lines.length; i++) {
|
||||
showCode += `${i + 1}| ${line == i + 1 ? '⚠️' : ''}${lines[i]}\n`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showCode = lines.map((_line, i) => `${i + 1}| ${line == i + 1 ? '⚠️' : ''}${_line}\n`).toString();
|
||||
}
|
||||
return showCode;
|
||||
};
|
||||
//协议名须和html一致(网页端防跨域),且文件是js
|
||||
if (typeof src == 'string' && src.startsWith(location.protocol) && src.endsWith('.js')) {
|
||||
//获取代码
|
||||
const codes = lib.init.reqSync('local:' + decodeURI(src).replace(lib.assetURL, '').replace(winPath, ''));
|
||||
if (codes) {
|
||||
const lines = codes.split("\n");
|
||||
str += '\n' + createShowCode(lines);
|
||||
str += '\n-------------';
|
||||
}
|
||||
}
|
||||
//解析parsex里的content fun内容(通常是技能content)
|
||||
else if (err && err.stack && err.stack.split('\n')[1].trim().startsWith('at Object.eval [as content]')) {
|
||||
const codes = _status.event.content;
|
||||
if (typeof codes == 'function') {
|
||||
const lines = codes.toString().split("\n");
|
||||
str += '\n' + createShowCode(lines);
|
||||
str += '\n-------------';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err && err.stack) str += '\n' + decodeURI(err.stack).replace(new RegExp(lib.assetURL, 'g'), '').replace(new RegExp(winPath, 'g'), '');
|
||||
alert(str);
|
||||
Reflect.set(window, 'ea', Array.from(arguments));
|
||||
Reflect.set(window, 'em', msg);
|
||||
Reflect.set(window, 'el', line);
|
||||
Reflect.set(window, 'ec', column);
|
||||
Reflect.set(window, 'eo', err);
|
||||
game.print(str);
|
||||
// @ts-ignore
|
||||
if (!lib.config.errstop) {
|
||||
_status.withError = true;
|
||||
game.loop();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@ import { GNC as gnc } from '../../gnc/index.js';
|
|||
|
||||
// 未来再改
|
||||
export const Content = {
|
||||
emptyEvent: async (event) => {
|
||||
emptyEvent: () => {
|
||||
event.trigger(event.name);
|
||||
},
|
||||
//增加明置手牌
|
||||
addShownCards: async (event, _trigger, player) => {
|
||||
addShownCards: () => {
|
||||
const hs = player.getCards('h'), showingCards = event._cards.filter(showingCard => hs.includes(showingCard)), shown = player.getShownCards();
|
||||
event.gaintag.forEach(tag => player.addGaintag(showingCards, tag));
|
||||
if (!(event.cards = showingCards.filter(showingCard => !shown.includes(showingCard))).length) return;
|
||||
|
@ -6665,8 +6665,7 @@ export const Content = {
|
|||
"step 5";
|
||||
ui.clear();
|
||||
},
|
||||
draw: async (event, _trigger, player) => {
|
||||
let { num } = event;
|
||||
draw: function() {
|
||||
// if(lib.config.background_audio){
|
||||
// game.playAudio('effect','draw');
|
||||
// }
|
||||
|
@ -6724,7 +6723,6 @@ export const Content = {
|
|||
}
|
||||
if (event.gaintag) next.gaintag.addArray(event.gaintag);
|
||||
event.result = cards;
|
||||
await next;
|
||||
},
|
||||
discard: function () {
|
||||
"step 0";
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { Uninstantable } from "../../util/index.js";
|
||||
|
||||
export class Experimental extends Uninstantable {
|
||||
import { ExperimentalSymbol } from "./symbol.js";
|
||||
|
||||
export class Experimental extends Uninstantable {
|
||||
static symbol = ExperimentalSymbol
|
||||
static symbols = ExperimentalSymbol
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import { Uninstantable } from "../../util/index.js";
|
||||
|
||||
export class ExperimentalSymbol extends Uninstantable {
|
||||
static itemType = Symbol('noname.experimental.itemType')
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
|||
import { PromiseErrorHandler } from './struct/index.js';
|
||||
|
||||
/**
|
||||
* 从浏览器名到不同浏览器下异步处理方式的映射
|
||||
*
|
||||
* `key`的值同`get.coreInfo`函数返回值的第一个元素
|
||||
*
|
||||
* @type {Record<"firefox" | "chrome" | "safari" | "other", new () => PromiseErrorHandler>}
|
||||
*/
|
||||
export const promiseErrorHandlerMap = {
|
||||
'chrome': PromiseErrorHandler.ChromePromiseErrorHandler,
|
||||
'firefox': PromiseErrorHandler.FirefoxPromiseErrorHandler,
|
||||
'safari': PromiseErrorHandler.UnknownPromiseErrorHandler,
|
||||
'other': PromiseErrorHandler.UnknownPromiseErrorHandler
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {import('./struct/interface/promise-error-handler.js').PromiseErrorHandler} PromiseErrorHandler
|
||||
*/
|
|
@ -1,18 +1,24 @@
|
|||
/**
|
||||
*
|
||||
* 一个非常普通的“锁”
|
||||
*/
|
||||
export class Mutex {
|
||||
/**
|
||||
* 锁目前的状态,只有“unlocked”和“locked”两种情况
|
||||
*
|
||||
* @type {'locked' | 'unlocked'}
|
||||
*/
|
||||
#status;
|
||||
|
||||
/**
|
||||
* 上锁后用于等待的锁Promise
|
||||
*
|
||||
* @type {null | Promise<void>}
|
||||
*/
|
||||
#promise;
|
||||
|
||||
/**
|
||||
* 上锁后用于触发锁Promise的resolve函数
|
||||
*
|
||||
* @type {null | function(): void}
|
||||
*/
|
||||
#resolve;
|
||||
|
@ -23,6 +29,11 @@ export class Mutex {
|
|||
this.#resolve = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上锁
|
||||
*
|
||||
* 请时刻记住使用`await Mutex#lock()`来使锁正常工作
|
||||
*/
|
||||
async lock() {
|
||||
switch (this.#status) {
|
||||
case 'locked':
|
||||
|
@ -36,6 +47,11 @@ export class Mutex {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解锁
|
||||
*
|
||||
* 请不要在未上锁的情况下解锁
|
||||
*/
|
||||
unlock() {
|
||||
if (this.#status === 'unlocked') throw new Error('This Mutex is not locked.');
|
||||
|
||||
|
@ -44,6 +60,7 @@ export class Mutex {
|
|||
}
|
||||
|
||||
/**
|
||||
* 启用锁的try-finally封装,用于在函数执行完后自动解放锁的控制权(就算发生错误)
|
||||
*
|
||||
* @param {function(): void | Promise<void>} content
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* 无名杀内部所需要的数据结构
|
||||
*/
|
||||
|
||||
export * as PromiseErrorHandler from './promise-error-handler/index.js';
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* 无名杀内部构建所需要用到的接口
|
||||
*/
|
||||
|
||||
export { PromiseErrorHandler } from './promise-error-handler'
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* 不同浏览器下异步报错问题处理方式的接口,用于定义不同浏览器对待异步报错的处理方式
|
||||
*/
|
||||
export interface PromiseErrorHandler {
|
||||
/**
|
||||
* 当异步处理对象初始化时执行的操作
|
||||
*
|
||||
* 因为未来或许会涉及到错误处理方式的更改,故所有初始化操作请于此处执行
|
||||
*
|
||||
* 若该函数为异步函数,则将阻塞运行
|
||||
*/
|
||||
onLoad?(): void | Promise<void>
|
||||
|
||||
/**
|
||||
* 当异步处理对象被释放时执行的操作
|
||||
*
|
||||
* 目前版本无任何用处,请等待未来的更新
|
||||
*
|
||||
* 未来或许会涉及到错误处理方式的更改,此后请使用无名杀提供的方式来进行异步错误处理的更改
|
||||
*
|
||||
* 若该函数为异步函数,则将阻塞运行
|
||||
*/
|
||||
onUnload?(): void | Promise<void>
|
||||
|
||||
/**
|
||||
* 当全局监听器捕获到`unhandledrejection`事件时会调用的函数
|
||||
*
|
||||
* 该函数用于正式处理异步报错事件
|
||||
*
|
||||
* 该函数为异步函数时不会阻塞运行
|
||||
*
|
||||
* @param event - 被捕获到的异步报错事件
|
||||
*/
|
||||
onHandle?(event: PromiseRejectionEvent): void | Promise<void>
|
||||
|
||||
/**
|
||||
* 当触发`window.onerror`时会调用的函数
|
||||
*
|
||||
* 用于在执行`window.onerror`前执行一些可能存在的善后操作
|
||||
*
|
||||
* 该函数无法为异步函数
|
||||
*/
|
||||
onErrorPrepare?(): void
|
||||
|
||||
/**
|
||||
* 当`window.onerror`运行**即将结束**时会调用的函数
|
||||
*
|
||||
* 用于在执行`window.onerror`后执行一些可能存在的善后操作
|
||||
*
|
||||
* 该函数无法为异步函数
|
||||
*/
|
||||
onErrorFinish?(): void
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* 关于`Google Chrome`的异步错误处理
|
||||
*
|
||||
* `Chrome`所用的`v8`引擎为`Error`提供了特有的报错栈堆处理函数,用于用户自定义报错栈堆的内容。
|
||||
*
|
||||
* 我们用到了`Error.prepareStackTrace(error, structuredStackTrace)`这个函数,这个函数的信息可参考[这里](https://v8.dev/docs/stack-trace-api#customizing-stack-traces)
|
||||
*
|
||||
* 该函数提供了结构化的栈堆信息,很幸运的是,这个结构化的栈堆能直接告诉我们报错的文件以及位置,故我们使用该函数,让异步报错能直接定位原始位置
|
||||
*
|
||||
* @implements {PromiseErrorHandler}
|
||||
*/
|
||||
export class ChromePromiseErrorHandler {
|
||||
|
||||
/**
|
||||
* 用于临时记录报错信息的列表,通过`Error.prepareStackTrace`更新该列表
|
||||
*
|
||||
* @type {[Error, NodeJS.CallSite[]][]}
|
||||
*/
|
||||
#errorList;
|
||||
|
||||
/**
|
||||
* @type {typeof Error.prepareStackTrace}
|
||||
*/
|
||||
#originErrorPrepareStackTrace;
|
||||
|
||||
/**
|
||||
* 初始化`Error.prepareStackTrace`,将该值赋值成我们需要的函数
|
||||
*
|
||||
* 未防止本来Error.prepareStackTrace便存在赋值的行为,我们将原始值存储,并在需要的函数中调用
|
||||
*
|
||||
* > 这或许就是本体扩展化的第一步(小声)
|
||||
*/
|
||||
onLoad() {
|
||||
this.#errorList = [];
|
||||
this.#originErrorPrepareStackTrace = Error.prepareStackTrace;
|
||||
Error.prepareStackTrace = (error, stackTraces) => {
|
||||
// 其实这步或许不需要
|
||||
// 但真赋值了Error.prepareStackTrace的话,保不齐会出现需要返回值的情况
|
||||
const result = this.#originErrorPrepareStackTrace
|
||||
? this.#originErrorPrepareStackTrace(error, stackTraces)
|
||||
: void 0;
|
||||
this.#errorList.push([error, stackTraces]);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将原来可能的`Error.prepareStackTrace`赋值回去
|
||||
*/
|
||||
onUnload() {
|
||||
Error.prepareStackTrace = this.#originErrorPrepareStackTrace;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在获取报错的时候,我们通过发生报错的`Promise`来进行捕获错误的操作
|
||||
*
|
||||
* 如果捕获出来的错误存放我们存报错栈堆的列表中,则证明该错误能获取到栈堆,由此来获取报错的地址和行列号
|
||||
*
|
||||
* @param {PromiseRejectionEvent} event
|
||||
*/
|
||||
onHandle(event) {
|
||||
event.promise.catch((error) => {
|
||||
const result = this.#errorList.find(savedError => savedError[0] === error);
|
||||
if (result) {
|
||||
// @ts-ignore
|
||||
window.onerror(
|
||||
result[0].message,
|
||||
result[1][0].getScriptNameOrSourceURL(),
|
||||
result[1][0].getLineNumber() || void 0,
|
||||
result[1][0].getColumnNumber() || void 0,
|
||||
result[0]
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 正式报错时便不再需要报错信息了,故直接清空列表,释放内存
|
||||
*/
|
||||
onErrorPrepare() {
|
||||
this.#errorList.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('../interface/promise-error-handler').PromiseErrorHandler} PromiseErrorHandler
|
||||
*/
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* 关于`Mozilla Firefox`的异步错误处理
|
||||
*
|
||||
* 很幸运,Mozilla直接为`Firefox`的报错提供了地址和行列号,故我们能直接获取到要获取的信息,不用像`v8`那样通过栈堆获取
|
||||
*
|
||||
* 虽然但是,我们还是需要判断一下捕获的报错是否是错误
|
||||
*
|
||||
* @implements {PromiseErrorHandler}
|
||||
*/
|
||||
export class FirefoxPromiseErrorHandler {
|
||||
/**
|
||||
* 在获取报错的时候,我们通过发生报错的`Promise`来进行捕获错误的操作
|
||||
*
|
||||
* 如果捕获到的错误是`Error`,则能直接通过`Firefox`的特性来获取地址和行列号
|
||||
*
|
||||
* @param {PromiseRejectionEvent} event
|
||||
*/
|
||||
onHandle(event) {
|
||||
event.promise.catch((error) => {
|
||||
if (typeof error === 'object' && error instanceof Error) {
|
||||
// Firefox在大环境下默认情况必须要那么多ts-ignore
|
||||
// @ts-ignore
|
||||
window.onerror(
|
||||
error.message,
|
||||
// @ts-ignore
|
||||
error.fileName,
|
||||
// @ts-ignore
|
||||
error.lineNumber,
|
||||
// @ts-ignore
|
||||
error.columnNumber,
|
||||
error
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('../interface/promise-error-handler').PromiseErrorHandler} PromiseErrorHandler
|
||||
*/
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* 关于不同浏览器下对异步错误的处理方式
|
||||
*
|
||||
* 目前已实现的浏览器如下:
|
||||
*
|
||||
* - `Google Chrome`(包括`electron`、`cordova`以及`crosswalk`)
|
||||
* - `Mozilla Firefox`
|
||||
*
|
||||
*/
|
||||
|
||||
export { ChromePromiseErrorHandler } from './chrome.js';
|
||||
export { FirefoxPromiseErrorHandler } from './firefox.js';
|
||||
export { UnknownPromiseErrorHandler } from './unknown.js';
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* 关于除已实现浏览器外其余浏览器的异步错误处理
|
||||
*
|
||||
* 很遗憾,对于这类浏览器,因为标准未涉及报错栈堆或地址及行列号,故我们只能直接,暴力的throw出我们捕获道德错误,
|
||||
*
|
||||
* 尽管我们还是会为了这类浏览器判断是不是捕获到了一个`Error`
|
||||
*
|
||||
* 总之,虽然这里跟Safari无关,但我们还是为新时代IE默哀一秒
|
||||
*
|
||||
* @implements {PromiseErrorHandler}
|
||||
*/
|
||||
export class UnknownPromiseErrorHandler {
|
||||
/**
|
||||
* 在获取报错的时候,我们通过发生报错的`Promise`来进行捕获错误的操作
|
||||
*
|
||||
* 如果捕获到的错误是`Error`,则...我们只能暴力的将`Error`再次`throw`出去
|
||||
*
|
||||
* @param {PromiseRejectionEvent} event
|
||||
*/
|
||||
onHandle(event) {
|
||||
event.promise.catch((error) => {
|
||||
if (typeof error === 'object' && error instanceof Error) {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('../interface/promise-error-handler').PromiseErrorHandler} PromiseErrorHandler
|
||||
*/
|
Loading…
Reference in New Issue