mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-uniapp.git
synced 2026-08-06 02:47:25 +08:00
fix: 修复 App 端 MQTT 运行时兼容问题
- 锁定 MQTT 版本为 5.15.1 - 为 AbortController、AbortSignal 和 navigator.language 添加兼容补丁 - 新增 MQTT App 运行时测试
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -12,9 +12,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tencentcloud/chat": "^3.5.5",
|
"@tencentcloud/chat": "^3.5.5",
|
||||||
"hls.js": "^1.6.16",
|
"hls.js": "^1.6.16",
|
||||||
"mqtt": "^5.15.0",
|
"mqtt": "5.15.1",
|
||||||
"sass": "^1.89.2",
|
"sass": "^1.89.2",
|
||||||
"uview-plus": "^3.4.72",
|
"uview-plus": "3.8.42",
|
||||||
"vuex": "^4.1.0"
|
"vuex": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"migrate:vue3": "node scripts/migrate-vue3.js && node scripts/migrate-filters-usage.js",
|
"migrate:vue3": "node scripts/migrate-vue3.js && node scripts/migrate-filters-usage.js",
|
||||||
"postinstall": "patch-package"
|
"postinstall": "patch-package && node scripts/patch-mqtt-abort.js",
|
||||||
|
"test:mqtt-app-runtime": "node scripts/test-mqtt-app-runtime.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -15,9 +16,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tencentcloud/chat": "^3.5.5",
|
"@tencentcloud/chat": "^3.5.5",
|
||||||
"hls.js": "^1.6.16",
|
"hls.js": "^1.6.16",
|
||||||
"mqtt": "^5.15.0",
|
"mqtt": "5.15.1",
|
||||||
"sass": "^1.89.2",
|
"sass": "^1.89.2",
|
||||||
"uview-plus": "^3.4.72",
|
"uview-plus": "3.8.42",
|
||||||
"vuex": "^4.1.0"
|
"vuex": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
173
scripts/patch-mqtt-abort.js
Normal file
173
scripts/patch-mqtt-abort.js
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const root = path.join(__dirname, '../node_modules/mqtt/dist')
|
||||||
|
|
||||||
|
const marker = '_MqttAbortControllerFallback'
|
||||||
|
const navigatorFallback = '"zh-CN.UTF-8"'
|
||||||
|
|
||||||
|
const esmOriginalPattern =
|
||||||
|
/var\{AbortController:([A-Za-z_$][\w$]*),AbortSignal:([A-Za-z_$][\w$]*)\}=typeof self<"u"\?self:typeof window<"u"\?window:void 0;/
|
||||||
|
const esmOldPatchPattern =
|
||||||
|
/var _mqttGlobal=typeof globalThis<"u"\?globalThis:typeof self<"u"\?self:typeof window<"u"\?window:typeof global<"u"\?global:\{\};var\{AbortController:([A-Za-z_$][\w$]*),AbortSignal:([A-Za-z_$][\w$]*)\}=_mqttGlobal;/
|
||||||
|
|
||||||
|
function createEsmReplacement(controllerName, signalName) {
|
||||||
|
return `var _mqttGlobal=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{};var _MqttAbortSignalFallback=class{constructor(){this.aborted=!1;this.reason=void 0;this.onabort=null;this._listeners=[]}addEventListener(e,t,r){if(e==="abort"&&t){this._listeners.push({listener:t,once:!!(r&&r.once)})}}removeEventListener(e,t){if(e==="abort"){this._listeners=this._listeners.filter(r=>r.listener!==t)}}dispatchEvent(e){if(!e||e.type!=="abort")return!0;var t=this._listeners.slice();this._listeners=this._listeners.filter(r=>!r.once);for(var r of t){try{typeof r.listener==="function"?r.listener.call(this,e):r.listener&&typeof r.listener.handleEvent==="function"&&r.listener.handleEvent(e)}catch(n){}}return!0}throwIfAborted(){if(this.aborted)throw this.reason||new Error("Aborted")}};var ${marker}=class{constructor(){this.signal=new _MqttAbortSignalFallback}abort(e){if(this.signal.aborted)return;this.signal.aborted=!0;this.signal.reason=e;var t={type:"abort",target:this.signal};if(typeof this.signal.onabort==="function"){try{this.signal.onabort.call(this.signal,t)}catch(r){}}this.signal.dispatchEvent(t)}};var ${controllerName}=_mqttGlobal.AbortController||${marker};var ${signalName}=_mqttGlobal.AbortSignal||_MqttAbortSignalFallback;typeof _mqttGlobal.AbortController>"u"&&(_mqttGlobal.AbortController=${controllerName});typeof _mqttGlobal.AbortSignal>"u"&&(_mqttGlobal.AbortSignal=${signalName});`
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsOriginalPattern =
|
||||||
|
/var \{ AbortController, AbortSignal \} = typeof self !== "undefined" \? self : typeof window !== "undefined" \? window : \(\s*\/\* otherwise \*\/\s*void 0\s*\);/
|
||||||
|
const jsOldPatchPattern =
|
||||||
|
/var \{ AbortController, AbortSignal \} = typeof globalThis !== "undefined" \? globalThis : typeof self !== "undefined" \? self : typeof window !== "undefined" \? window : typeof global !== "undefined" \? global : \(\s*\/\* otherwise \*\/\s*(?:void 0|\{\})\s*\);/
|
||||||
|
|
||||||
|
const jsReplacement = `var _mqttGlobal = typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {};
|
||||||
|
var _MqttAbortSignalFallback = class {
|
||||||
|
constructor() {
|
||||||
|
this.aborted = false;
|
||||||
|
this.reason = void 0;
|
||||||
|
this.onabort = null;
|
||||||
|
this._listeners = [];
|
||||||
|
}
|
||||||
|
addEventListener(type, listener, options) {
|
||||||
|
if (type === "abort" && listener) this._listeners.push({ listener, once: !!(options && options.once) });
|
||||||
|
}
|
||||||
|
removeEventListener(type, listener) {
|
||||||
|
if (type === "abort") this._listeners = this._listeners.filter((entry) => entry.listener !== listener);
|
||||||
|
}
|
||||||
|
dispatchEvent(event) {
|
||||||
|
if (!event || event.type !== "abort") return true;
|
||||||
|
const entries = this._listeners.slice();
|
||||||
|
this._listeners = this._listeners.filter((entry) => !entry.once);
|
||||||
|
for (const entry of entries) {
|
||||||
|
try {
|
||||||
|
if (typeof entry.listener === "function") entry.listener.call(this, event);
|
||||||
|
else if (entry.listener && typeof entry.listener.handleEvent === "function") entry.listener.handleEvent(event);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throwIfAborted() {
|
||||||
|
if (this.aborted) throw this.reason || new Error("Aborted");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var ${marker} = class {
|
||||||
|
constructor() {
|
||||||
|
this.signal = new _MqttAbortSignalFallback();
|
||||||
|
}
|
||||||
|
abort(reason) {
|
||||||
|
if (this.signal.aborted) return;
|
||||||
|
this.signal.aborted = true;
|
||||||
|
this.signal.reason = reason;
|
||||||
|
const event = { type: "abort", target: this.signal };
|
||||||
|
if (typeof this.signal.onabort === "function") {
|
||||||
|
try { this.signal.onabort.call(this.signal, event); } catch (error) {}
|
||||||
|
}
|
||||||
|
this.signal.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var AbortController = _mqttGlobal.AbortController || ${marker};
|
||||||
|
var AbortSignal = _mqttGlobal.AbortSignal || _MqttAbortSignalFallback;
|
||||||
|
if (typeof _mqttGlobal.AbortController === "undefined") _mqttGlobal.AbortController = AbortController;
|
||||||
|
if (typeof _mqttGlobal.AbortSignal === "undefined") _mqttGlobal.AbortSignal = AbortSignal;`
|
||||||
|
|
||||||
|
function isEsmPatched(content) {
|
||||||
|
return content.includes(marker)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isJsPatched(content) {
|
||||||
|
return content.includes(marker)
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchEsmFile(file) {
|
||||||
|
const filePath = path.join(root, file)
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.log(`skip ${file} (missing)`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = fs.readFileSync(filePath, 'utf8')
|
||||||
|
if (isEsmPatched(content)) {
|
||||||
|
console.log(`skip ${file} (already patched)`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = content.match(esmOriginalPattern) || content.match(esmOldPatchPattern)
|
||||||
|
if (match) {
|
||||||
|
content = content.replace(match[0], createEsmReplacement(match[1], match[2]))
|
||||||
|
fs.writeFileSync(filePath, content)
|
||||||
|
console.log(`patched ${file} (embedded AbortController fallback)`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unable to patch ${file}: unsupported mqtt bundle format`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchJsFile() {
|
||||||
|
const jsPath = path.join(root, 'mqtt.js')
|
||||||
|
if (!fs.existsSync(jsPath)) {
|
||||||
|
console.log('skip mqtt.js (missing)')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let jsContent = fs.readFileSync(jsPath, 'utf8')
|
||||||
|
if (isJsPatched(jsContent)) {
|
||||||
|
console.log('skip mqtt.js (already patched)')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = jsContent.match(jsOriginalPattern) || jsContent.match(jsOldPatchPattern)
|
||||||
|
if (match) {
|
||||||
|
jsContent = jsContent.replace(match[0], jsReplacement)
|
||||||
|
fs.writeFileSync(jsPath, jsContent)
|
||||||
|
console.log('patched mqtt.js (embedded AbortController fallback)')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Unable to patch mqtt.js: unsupported mqtt bundle format')
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchNavigatorLanguage(file) {
|
||||||
|
const filePath = path.join(root, file)
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.log(`skip ${file} navigator patch (missing)`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = fs.readFileSync(filePath, 'utf8')
|
||||||
|
const unsafeMinified = 'LANG:navigator.language+".UTF-8"'
|
||||||
|
const unsafePretty = 'LANG: navigator.language + ".UTF-8"'
|
||||||
|
const safeMinified =
|
||||||
|
`LANG:typeof navigator<"u"&&navigator.language?navigator.language+".UTF-8":${navigatorFallback}`
|
||||||
|
const safePretty =
|
||||||
|
`LANG: typeof navigator !== "undefined" && navigator.language ? navigator.language + ".UTF-8" : ${navigatorFallback}`
|
||||||
|
|
||||||
|
if (content.includes(unsafeMinified)) {
|
||||||
|
content = content.replaceAll(unsafeMinified, safeMinified)
|
||||||
|
fs.writeFileSync(filePath, content)
|
||||||
|
console.log(`patched ${file} (navigator.language fallback)`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.includes(unsafePretty)) {
|
||||||
|
content = content.replaceAll(unsafePretty, safePretty)
|
||||||
|
fs.writeFileSync(filePath, content)
|
||||||
|
console.log(`patched ${file} (navigator.language fallback)`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.includes(safeMinified) || content.includes(safePretty)) {
|
||||||
|
console.log(`skip ${file} navigator patch (already patched)`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unable to patch navigator.language in ${file}: unsupported mqtt bundle format`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(root)) {
|
||||||
|
console.log('skip mqtt patch (mqtt dist not installed yet)')
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
;['mqtt.esm.js', 'mqtt.min.js'].forEach(patchEsmFile)
|
||||||
|
patchJsFile()
|
||||||
|
;['mqtt.esm.js', 'mqtt.min.js', 'mqtt.js'].forEach(patchNavigatorLanguage)
|
||||||
49
scripts/test-mqtt-app-runtime.js
Normal file
49
scripts/test-mqtt-app-runtime.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const vm = require('vm')
|
||||||
|
|
||||||
|
const bundle = fs.readFileSync(
|
||||||
|
path.join(path.dirname(require.resolve('mqtt/package.json')), 'dist/mqtt.js'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
|
||||||
|
const context = vm.createContext({
|
||||||
|
console,
|
||||||
|
setTimeout,
|
||||||
|
clearTimeout,
|
||||||
|
setInterval,
|
||||||
|
clearInterval,
|
||||||
|
queueMicrotask,
|
||||||
|
TextEncoder,
|
||||||
|
TextDecoder,
|
||||||
|
URL,
|
||||||
|
})
|
||||||
|
|
||||||
|
vm.runInContext(bundle, context, { timeout: 10000 })
|
||||||
|
|
||||||
|
if (typeof context.AbortController !== 'function') {
|
||||||
|
throw new Error('MQTT bundle did not install AbortController fallback')
|
||||||
|
}
|
||||||
|
if (typeof context.AbortSignal !== 'function') {
|
||||||
|
throw new Error('MQTT bundle did not install AbortSignal fallback')
|
||||||
|
}
|
||||||
|
|
||||||
|
const controller = new context.AbortController()
|
||||||
|
let abortEvents = 0
|
||||||
|
controller.signal.addEventListener('abort', () => {
|
||||||
|
abortEvents += 1
|
||||||
|
}, { once: true })
|
||||||
|
controller.abort('test-reason')
|
||||||
|
controller.abort('ignored-reason')
|
||||||
|
|
||||||
|
if (!controller.signal.aborted) {
|
||||||
|
throw new Error('AbortSignal was not marked aborted')
|
||||||
|
}
|
||||||
|
if (controller.signal.reason !== 'test-reason') {
|
||||||
|
throw new Error('AbortSignal reason was not preserved')
|
||||||
|
}
|
||||||
|
if (abortEvents !== 1) {
|
||||||
|
throw new Error(`Expected one abort event, got ${abortEvents}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('MQTT App runtime fallback test passed')
|
||||||
Reference in New Issue
Block a user