Merge pull request #776 from nofficalfs/Dev-Enhancement-Noname

fix some question.
This commit is contained in:
Spmario233 2024-01-12 22:49:17 +08:00 committed by GitHub
commit 07c3dbd6d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 28912 additions and 28633 deletions

View File

@ -1,100 +1,101 @@
"use strict"; "use strict";
new Promise(resolve => { new Promise(resolve => {
// 客户端自带core.js的请注意跟进 // 客户端自带core.js的请注意跟进
if ('__core-js_shared__' in window) resolve(null); if ('__core-js_shared__' in window) resolve(null);
else { else {
const nonameInitialized = localStorage.getItem('noname_inited'); const nonameInitialized = localStorage.getItem('noname_inited');
const assetURL = typeof nonameInitialized != 'string' || nonameInitialized == 'nodejs' ? '' : nonameInitialized; const assetURL = typeof nonameInitialized != 'string' || nonameInitialized == 'nodejs' ? '' : nonameInitialized;
const coreJSBundle = document.createElement('script'); const coreJSBundle = document.createElement('script');
coreJSBundle.onerror = coreJSBundle.onload = resolve; coreJSBundle.onerror = coreJSBundle.onload = resolve;
coreJSBundle.src = `${assetURL}game/core-js-bundle.js`; coreJSBundle.src = `${assetURL}game/core-js-bundle.js`;
document.head.appendChild(coreJSBundle); document.head.appendChild(coreJSBundle);
} }
}).then(() => { }).then(() => {
const nonameInitialized = localStorage.getItem('noname_inited'); const nonameInitialized = localStorage.getItem('noname_inited');
const assetURL = typeof nonameInitialized != 'string' || nonameInitialized == 'nodejs' ? '' : nonameInitialized; const assetURL = typeof nonameInitialized != 'string' || nonameInitialized == 'nodejs' ? '' : nonameInitialized;
const userAgent = navigator.userAgent.toLowerCase(); const userAgent = navigator.userAgent.toLowerCase();
const exit = () => { const exit = () => {
const ios = userAgent.includes('iphone') || userAgent.includes('ipad') || userAgent.includes('macintosh'); const ios = userAgent.includes('iphone') || userAgent.includes('ipad') || userAgent.includes('macintosh');
//electron //electron
if (typeof window.process == 'object' && typeof window.require == 'function') { if (typeof window.process == 'object' && typeof window.require == 'function') {
const versions = window.process.versions; const versions = window.process.versions;
// @ts-ignore // @ts-ignore
const electronVersion = parseFloat(versions.electron); const electronVersion = parseFloat(versions.electron);
let remote; let remote;
if (electronVersion >= 14) { if (electronVersion >= 14) {
// @ts-ignore // @ts-ignore
remote = require('@electron/remote'); remote = require('@electron/remote');
} else { } else {
// @ts-ignore // @ts-ignore
remote = require('electron').remote; remote = require('electron').remote;
} }
const thisWindow = remote.getCurrentWindow(); const thisWindow = remote.getCurrentWindow();
thisWindow.destroy(); thisWindow.destroy();
window.process.exit(); window.process.exit();
} }
//android-cordova环境 //android-cordova环境
//ios-cordova环境或ios浏览器环境 //ios-cordova环境或ios浏览器环境
//非ios的网页版 //非ios的网页版
else if (!ios) { else if (!ios) {
window.close(); window.close();
} }
}; };
if (!localStorage.getItem('gplv3_noname_alerted')) { if (!localStorage.getItem('gplv3_noname_alerted')) {
if (confirm('①无名杀是一款基于GPLv3协议的开源软件\n你可以在遵守GPLv3协议的基础上任意使用修改并转发《无名杀》以及所有基于《无名杀》开发的拓展。\n点击“确定”即代表您认可并接受GPLv3协议↓\nhttps://www.gnu.org/licenses/gpl-3.0.html\n②无名杀官方发布地址仅有GitHub仓库\n其他所有的所谓“无名杀”社群包括但不限于绝大多数“官方”QQ群、QQ频道等均为玩家自发组织与无名杀官方无关')) { if (confirm('①无名杀是一款基于GPLv3协议的开源软件\n你可以在遵守GPLv3协议的基础上任意使用修改并转发《无名杀》以及所有基于《无名杀》开发的拓展。\n点击“确定”即代表您认可并接受GPLv3协议↓\nhttps://www.gnu.org/licenses/gpl-3.0.html\n②无名杀官方发布地址仅有GitHub仓库\n其他所有的所谓“无名杀”社群包括但不限于绝大多数“官方”QQ群、QQ频道等均为玩家自发组织与无名杀官方无关')) {
// @ts-ignore // @ts-ignore
localStorage.setItem('gplv3_noname_alerted', true); localStorage.setItem('gplv3_noname_alerted', true);
} }
else { else {
exit(); exit();
} }
} }
window['b' + 'ann' + 'e' + 'dE' + 'x' + 'ten' + 's' + 'i' + 'o' + 'ns'] = ['\u4fa0\u4e49', '\u5168\u6559\u7a0b']; window['b' + 'ann' + 'e' + 'dE' + 'x' + 'ten' + 's' + 'i' + 'o' + 'ns'] = ['\u4fa0\u4e49', '\u5168\u6559\u7a0b'];
/** /**
* *
* @returns {["firefox" | "chrome" | "safari" | "other", number]} * @returns {["firefox" | "chrome" | "safari" | "other", number]}
*/ */
function coreInfo() { function coreInfo() {
const regex = /(firefox|chrome|safari)\/([\d.]+)/; const regex = /(firefox|chrome|safari)\/([\d.]+)/;
let result; let result;
if (!(result = userAgent.match(regex))) return ["other", NaN]; if (!(result = userAgent.match(regex))) return ["other", NaN];
if (result[1] !== "safari") return [result[1], parseInt(result[2])]; if (result[1] !== "safari") return [result[1], parseInt(result[2])];
result = userAgent.match(/version\/([\d.]+).*safari/); result = userAgent.match(/version\/([\d.]+).*safari/);
// @ts-ignore // @ts-ignore
return ["safari", parseInt(result[1])]; return ["safari", parseInt(result[1])];
} }
const [core, version] = coreInfo(); const [core, version] = coreInfo();
const supportMap = { const supportMap = {
"firefox": 60, "firefox": 60,
"chrome": 61, "chrome": 61,
// 因为coreInfo不考虑子版本故就强行只能以11运行 // 因为coreInfo不考虑子版本故就强行只能以11运行
"safari": 11 "safari": 11
} }
if (core in supportMap && supportMap[core] > version) { if (core in supportMap && supportMap[core] > version) {
const tip = '检测到您的浏览器内核版本无法支持ES Module请立即升级浏览器或手机webview内核'; const tip = '检测到您的浏览器内核版本无法支持ES Module请立即升级浏览器或手机webview内核';
console.error(tip); console.error(tip);
const redirect_tip = '您使用的浏览器或无名杀客户端内核版本过低,已经无法正常运行无名杀!\n点击“确认”以前往GitHub下载最新版无名杀客户端可能需要科学上网。\n稍后您的无名杀将自动退出可能的话'; const redirect_tip = '您使用的浏览器或无名杀客户端内核版本过低,已经无法正常运行无名杀!\n点击“确认”以前往GitHub下载最新版无名杀客户端可能需要科学上网。\n稍后您的无名杀将自动退出可能的话';
if (confirm(redirect_tip)) { if (confirm(redirect_tip)) {
window.open('https://github.com/libccy/noname/releases/tag/chromium77-client'); window.open('https://github.com/libccy/noname/releases/tag/chromium77-client');
} }
exit() exit()
} }
else { else {
const script = document.createElement('script') const script = document.createElement('script')
script.type = "module"; script.type = "module";
script.src = `${assetURL}game/entry.js` script.src = `${assetURL}game/entry.js`
script.async = true script.async = true
script.onerror = (event) => { script.onerror = (event) => {
console.error(event) console.error(event)
const message = `您使用的浏览器或无名杀客户端加载内容失败!\n报错内容: \n${event}\n若该BUG不为您个人原因造成的请及时反馈给无名杀开发组`; const message = `您使用的浏览器或无名杀客户端加载内容失败!\n请检查游戏环境以及"(游戏根目录)/game/entry.js"文件的位置\n若该BUG不为您个人原因造成的请及时反馈给无名杀开发组`;
alert(message); console.error(message);
exit() alert(message);
} exit()
document.head.appendChild(script) }
} document.head.appendChild(script)
}); }
});

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +1,41 @@
import { Game as game } from '../game/index.js'; import { Game as game } from '../game/index.js';
/** /**
* @param {string} name - 卡牌包名 * @param {string} name - 卡牌包名
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export const importCardPack = generateImportFunction('card', (name) => `../../card/${name}.js`) export const importCardPack = generateImportFunction('card', (name) => `../../card/${name}.js`)
/** /**
* @param {string} name - 武将包名 * @param {string} name - 武将包名
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export const importCharacterPack = generateImportFunction('character', (name) => `../../character/${name}.js`) export const importCharacterPack = generateImportFunction('character', (name) => `../../character/${name}.js`)
/** /**
* @param {string} name - 扩展名 * @param {string} name - 扩展名
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export const importExtension = generateImportFunction('extension', (name) => `../../extension/${name}/extension.js`) export const importExtension = generateImportFunction('extension', (name) => `../../extension/${name}/extension.js`)
/** /**
* @param {string} name - 模式名 * @param {string} name - 模式名
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export const importMode = generateImportFunction('mode', (name) => `../../mode/${name}.js`) export const importMode = generateImportFunction('mode', (name) => `../../mode/${name}.js`)
/** /**
* 生成导入 * 生成导入
* *
* @param {string} type * @param {string} type
* @param {(name: string) => string} pathParser * @param {(name: string) => string} pathParser
* @returns {(name: string) => Promise<void>} * @returns {(name: string) => Promise<void>}
*/ */
function generateImportFunction(type, pathParser) { function generateImportFunction(type, pathParser) {
return async (name) => { return async (name) => {
const modeContent = await import(pathParser(name)); const modeContent = await import(pathParser(name));
if (!modeContent.type) return; if (!modeContent.type) return;
if (modeContent.type !== type) throw new Error(`Loaded Content doesn't conform to "${type}" but "${modeContent.type}".`); if (modeContent.type !== type) throw new Error(`Loaded Content doesn't conform to "${type}" but "${modeContent.type}".`);
await game.import(type, modeContent.default); await game.import(type, modeContent.default);
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
import { Uninstantable } from "../../util/index.js"; 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
}

View File

@ -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

19
noname/util/browser.js Normal file
View File

@ -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
*/

View File

@ -1,18 +1,24 @@
/** /**
* * 一个非常普通的
*/ */
export class Mutex { export class Mutex {
/** /**
* 锁目前的状态只有unlockedlocked两种情况
*
* @type {'locked' | 'unlocked'} * @type {'locked' | 'unlocked'}
*/ */
#status; #status;
/** /**
* 上锁后用于等待的锁Promise
*
* @type {null | Promise<void>} * @type {null | Promise<void>}
*/ */
#promise; #promise;
/** /**
* 上锁后用于触发锁Promise的resolve函数
*
* @type {null | function(): void} * @type {null | function(): void}
*/ */
#resolve; #resolve;
@ -23,6 +29,11 @@ export class Mutex {
this.#resolve = null; this.#resolve = null;
} }
/**
* 上锁
*
* 请时刻记住使用`await Mutex#lock()`来使锁正常工作
*/
async lock() { async lock() {
switch (this.#status) { switch (this.#status) {
case 'locked': case 'locked':
@ -36,6 +47,11 @@ export class Mutex {
} }
} }
/**
* 解锁
*
* 请不要在未上锁的情况下解锁
*/
unlock() { unlock() {
if (this.#status === 'unlocked') throw new Error('This Mutex is not locked.'); 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 * @param {function(): void | Promise<void>} content
*/ */

View File

@ -0,0 +1,5 @@
/**
* 无名杀内部所需要的数据结构
*/
export * as PromiseErrorHandler from './promise-error-handler/index.js';

View File

@ -0,0 +1,5 @@
/**
*
*/
export { PromiseErrorHandler } from './promise-error-handler'

View File

@ -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
}

View File

@ -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
*/

View File

@ -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
*/

View File

@ -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';

View File

@ -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
*/