version-check-js 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/dist/index.esm.js +1 -2
- package/dist/index.js +1 -2
- package/index.d.ts +102 -0
- package/package.json +7 -3
- package/dist/index.esm.js.map +0 -1
- package/dist/index.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -97,7 +97,7 @@ versionCheck.check().then(hasUpdate => {
|
|
|
97
97
|
| `message` | `string` | `'New version detected, refresh now?'` | Update prompt message, only effective when `onUpdate` is not set |
|
|
98
98
|
| `onUpdate` | `Function` | `null` | Custom update callback function (higher priority than default confirm dialog) |
|
|
99
99
|
| `onError` | `Function` | `(err) => console.error('Version check failed:', err)` | Error callback function, receives error object as parameter |
|
|
100
|
-
| `onLog` | `Function` | `
|
|
100
|
+
| `onLog` | `Function` | `null` | Operation log callback function, used to record normal operation information |
|
|
101
101
|
| `storage` | `Object` | `null` | Custom storage configuration (requires `get`, `set`, `remove` methods), defaults to localStorage |
|
|
102
102
|
|
|
103
103
|
### Configuration Best Practices
|
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ versionCheck.check().then(hasUpdate => {
|
|
|
99
99
|
| `message` | `string` | `'检测到新版本,是否立即刷新?'` | 更新提示文案,仅在未设置 `onUpdate` 时生效 |
|
|
100
100
|
| `onUpdate` | `Function` | `null` | 自定义更新回调函数(优先级高于默认 confirm 弹窗) |
|
|
101
101
|
| `onError` | `Function` | `(err) => console.error('版本检测失败:', err)` | 错误回调函数,接收错误对象作为参数 |
|
|
102
|
-
| `onLog` | `Function` | `
|
|
102
|
+
| `onLog` | `Function` | `null` | 操作日志回调函数,用于记录正常操作信息 |
|
|
103
103
|
| `storage` | `Object` | `null` | 自定义存储配置(需提供 `get`、`set`、`remove` 方法),默认使用 localStorage |
|
|
104
104
|
|
|
105
105
|
### 配置项最佳实践
|
package/dist/index.esm.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
class t{constructor(t={}){if("object"!=typeof t||null===t)throw new TypeError("options \u5fc5\u987b\u662f\u5bf9\u8c61\u7c7b\u578b");this.config=
|
|
2
|
-
//# sourceMappingURL=index.esm.js.map
|
|
1
|
+
class t{constructor(t={}){if("object"!=typeof t||null===t)throw new TypeError("options \u5fc5\u987b\u662f\u5bf9\u8c61\u7c7b\u578b");this.config={url:"/",interval:6e5,message:"\u68c0\u6d4b\u5230\u65b0\u7248\u672c\uff0c\u662f\u5426\u7acb\u5373\u5237\u65b0\uff1f",onUpdate:null,onError:t=>{},onLog:null,storage:null,...t},this.timer=null,this.memoryStorage=null,this.storageApi=this.t(),this.versionKey="version_check_key",this.checkMode=this.i(),this.isRunning=!1,this.h()}o(t){"function"==typeof this.config.onLog&&this.config.onLog(t)}h(){this.l(),this.u=this.$.bind(this),document.addEventListener("visibilitychange",this.u)}l(){this.u&&(document.removeEventListener("visibilitychange",this.u),this.u=null)}$(){this.isRunning&&(document.hidden?this.p():this.m())}p(){this.stop(!0),this.o("\u9875\u9762\u9690\u85cf\uff0c\u6682\u505cVersionCheck")}m(){this.start(),this.o("\u9875\u9762\u663e\u793a\uff0c\u6062\u590dVersionCheck")}t(){const t={get:t=>{if("string"!=typeof t)return this.config.onError(new Error("\u5b58\u50a8\u952e\u5fc5\u987b\u662f\u5b57\u7b26\u4e32")),null;try{if(window.localStorage)return localStorage.getItem(t)}catch(t){this.config.onError(new Error(`localStorage get \u5931\u8d25: ${t.message}`))}return this.memoryStorage},set:(t,i)=>{if("string"!=typeof t)return this.config.onError(new Error("\u5b58\u50a8\u952e\u5fc5\u987b\u662f\u5b57\u7b26\u4e32")),!1;try{if(window.localStorage)return localStorage.setItem(t,String(i)),!0}catch(t){this.config.onError(new Error(`localStorage set \u5931\u8d25: ${t.message}`))}return this.memoryStorage=String(i),!0}};return this.config.storage||t}i(){const{url:t}=this.config;return"/"!==t&&/\.\w+$/.test(t)?"file":"etag"}async _(t,i){try{const s=await fetch(t,i);if(s.status>=400&&s.status<500)return this.config.onError(new Error(`\u5ba2\u6237\u7aef\u9519\u8bef HTTP ${s.status}: ${s.statusText}`)),s;if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);return s}catch(t){throw this.config.onError(new Error(`\u7f51\u7edc\u8bf7\u6c42\u5931\u8d25: ${t.message}`)),t}}async T(){const{url:t}=this.config;try{const i=await this._(t,{method:"HEAD",cache:"no-cache",credentials:"same-origin"});if(!i.ok)throw new Error(`ETag \u8bf7\u6c42\u5931\u8d25\uff0c\u72b6\u6001\u7801\uff1a${i.status}`);const s=i.headers.get("ETag");if(!s)throw new Error("\u670d\u52a1\u5668\u672a\u8fd4\u56de ETag\uff0c\u68c0\u6d4b\u5931\u8d25");return this.k(s)}catch(t){return this.config.onError(t),!1}}async V(){const{url:t}=this.config;try{const i=await this._(t,{method:"GET",cache:"no-cache",credentials:"same-origin",headers:{Accept:"application/json"}});if(!i.ok)throw new Error(`\u7248\u672c\u6587\u4ef6\u8bf7\u6c42\u5931\u8d25\uff0c\u72b6\u6001\u7801\uff1a${i.status}`);const s=await i.json();if(!s||"object"!=typeof s)throw new Error("\u7248\u672c\u6587\u4ef6\u683c\u5f0f\u9519\u8bef\uff0c\u5fc5\u987b\u8fd4\u56de\u5bf9\u8c61");if(!s.version)throw new Error("\u7248\u672c\u6587\u4ef6\u683c\u5f0f\u9519\u8bef\uff0c\u7f3a\u5c11 version \u5b57\u6bb5");return this.k(s.version)}catch(t){return this.config.onError(t),!1}}k(t){const i=this.storageApi.get(this.versionKey);return i?t!=i&&(this.C(t),!0):(this.storageApi.set(this.versionKey,t),!1)}C(t){const{onUpdate:i,message:s}=this.config;if("function"==typeof i)return this.storageApi.set(this.versionKey,t),void i();this.stop(),setTimeout(()=>{window.confirm(s)?(this.storageApi.set(this.versionKey,t),this.reload()):this.start()},0)}reload(){let t=window.location.href;t=t.replace(/[?&]t=\d+/g,"");const i=t.includes("?")?"&":"?",s=`${t}${i}t=${Date.now()}`;window.location.replace(s)}async check(t=!1){this.o(`\u5f00\u59cb\u6267\u884c${t?"\u68c0\u6d4b":"\u624b\u52a8\u68c0\u6d4b"}\uff0c\u6a21\u5f0f: ${this.checkMode}`);try{const i="etag"===this.checkMode?await this.T():await this.V();return this.o(`${t?"\u68c0\u6d4b":"\u624b\u52a8\u68c0\u6d4b"}\u5b8c\u6210\uff0c\u68c0\u6d4b\u5230\u66f4\u65b0\u72b6\u6001: ${i}`),i}catch(i){throw this.o(`${t?"\u68c0\u6d4b":"\u624b\u52a8\u68c0\u6d4b"}\u5931\u8d25: ${i.message}`),i}}start(){if(this.isRunning&&this.timer)return void this.o("VersionCheck\u5df2\u5728\u8fd0\u884c\u4e2d");this.isRunning=!0,this.v();const t=async()=>{try{await this.check(!0)}catch(t){this.config.onError(new Error(`\u8f6e\u8be2\u68c0\u6d4b\u5931\u8d25: ${t.message}`))}this.isRunning&&this.timer&&(this.timer=setTimeout(t,this.config.interval))};this.timer=setTimeout(t,this.config.interval),this.o("VersionCheck\u5df2\u542f\u52a8")}stop(t=!1){this.v(),t||(this.isRunning=!1),this.o("VersionCheck\u5df2\u505c\u6b62")}v(){this.timer&&(clearTimeout(this.timer),this.timer=null)}destroy(){this.stop(),this.l(),this.memoryStorage=null,this.timer=null,this.storageApi=null,this.checkMode=null,this.isRunning=!1,this.o("VersionCheck\u5b9e\u4f8b\u5df2\u9500\u6bc1"),this.config=null}}export{t as default};
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
var global,factory;global=this,factory=function(){return class{constructor(t={}){if("object"!=typeof t||null===t)throw new TypeError("options \u5fc5\u987b\u662f\u5bf9\u8c61\u7c7b\u578b");this.config=
|
|
2
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
var global,factory;global=this,factory=function(){return class{constructor(t={}){if("object"!=typeof t||null===t)throw new TypeError("options \u5fc5\u987b\u662f\u5bf9\u8c61\u7c7b\u578b");this.config={url:"/",interval:6e5,message:"\u68c0\u6d4b\u5230\u65b0\u7248\u672c\uff0c\u662f\u5426\u7acb\u5373\u5237\u65b0\uff1f",onUpdate:null,onError:t=>{},onLog:null,storage:null,...t},this.timer=null,this.memoryStorage=null,this.storageApi=this.t(),this.versionKey="version_check_key",this.checkMode=this.i(),this.isRunning=!1,this.h()}o(t){"function"==typeof this.config.onLog&&this.config.onLog(t)}h(){this.l(),this.u=this.p.bind(this),document.addEventListener("visibilitychange",this.u)}l(){this.u&&(document.removeEventListener("visibilitychange",this.u),this.u=null)}p(){this.isRunning&&(document.hidden?this.$():this.m())}$(){this.stop(!0),this.o("\u9875\u9762\u9690\u85cf\uff0c\u6682\u505cVersionCheck")}m(){this.start(),this.o("\u9875\u9762\u663e\u793a\uff0c\u6062\u590dVersionCheck")}t(){const t={get:t=>{if("string"!=typeof t)return this.config.onError(new Error("\u5b58\u50a8\u952e\u5fc5\u987b\u662f\u5b57\u7b26\u4e32")),null;try{if(window.localStorage)return localStorage.getItem(t)}catch(t){this.config.onError(new Error(`localStorage get \u5931\u8d25: ${t.message}`))}return this.memoryStorage},set:(t,i)=>{if("string"!=typeof t)return this.config.onError(new Error("\u5b58\u50a8\u952e\u5fc5\u987b\u662f\u5b57\u7b26\u4e32")),!1;try{if(window.localStorage)return localStorage.setItem(t,String(i)),!0}catch(t){this.config.onError(new Error(`localStorage set \u5931\u8d25: ${t.message}`))}return this.memoryStorage=String(i),!0}};return this.config.storage||t}i(){const{url:t}=this.config;return"/"!==t&&/\.\w+$/.test(t)?"file":"etag"}async T(t,i){try{const s=await fetch(t,i);if(s.status>=400&&s.status<500)return this.config.onError(new Error(`\u5ba2\u6237\u7aef\u9519\u8bef HTTP ${s.status}: ${s.statusText}`)),s;if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);return s}catch(t){throw this.config.onError(new Error(`\u7f51\u7edc\u8bf7\u6c42\u5931\u8d25: ${t.message}`)),t}}async _(){const{url:t}=this.config;try{const i=await this.T(t,{method:"HEAD",cache:"no-cache",credentials:"same-origin"});if(!i.ok)throw new Error(`ETag \u8bf7\u6c42\u5931\u8d25\uff0c\u72b6\u6001\u7801\uff1a${i.status}`);const s=i.headers.get("ETag");if(!s)throw new Error("\u670d\u52a1\u5668\u672a\u8fd4\u56de ETag\uff0c\u68c0\u6d4b\u5931\u8d25");return this.k(s)}catch(t){return this.config.onError(t),!1}}async V(){const{url:t}=this.config;try{const i=await this.T(t,{method:"GET",cache:"no-cache",credentials:"same-origin",headers:{Accept:"application/json"}});if(!i.ok)throw new Error(`\u7248\u672c\u6587\u4ef6\u8bf7\u6c42\u5931\u8d25\uff0c\u72b6\u6001\u7801\uff1a${i.status}`);const s=await i.json();if(!s||"object"!=typeof s)throw new Error("\u7248\u672c\u6587\u4ef6\u683c\u5f0f\u9519\u8bef\uff0c\u5fc5\u987b\u8fd4\u56de\u5bf9\u8c61");if(!s.version)throw new Error("\u7248\u672c\u6587\u4ef6\u683c\u5f0f\u9519\u8bef\uff0c\u7f3a\u5c11 version \u5b57\u6bb5");return this.k(s.version)}catch(t){return this.config.onError(t),!1}}k(t){const i=this.storageApi.get(this.versionKey);return i?t!=i&&(this.v(t),!0):(this.storageApi.set(this.versionKey,t),!1)}v(t){const{onUpdate:i,message:s}=this.config;if("function"==typeof i)return this.storageApi.set(this.versionKey,t),void i();this.stop(),setTimeout(()=>{window.confirm(s)?(this.storageApi.set(this.versionKey,t),this.reload()):this.start()},0)}reload(){let t=window.location.href;t=t.replace(/[?&]t=\d+/g,"");const i=t.includes("?")?"&":"?",s=`${t}${i}t=${Date.now()}`;window.location.replace(s)}async check(t=!1){this.o(`\u5f00\u59cb\u6267\u884c${t?"\u68c0\u6d4b":"\u624b\u52a8\u68c0\u6d4b"}\uff0c\u6a21\u5f0f: ${this.checkMode}`);try{const i="etag"===this.checkMode?await this._():await this.V();return this.o(`${t?"\u68c0\u6d4b":"\u624b\u52a8\u68c0\u6d4b"}\u5b8c\u6210\uff0c\u68c0\u6d4b\u5230\u66f4\u65b0\u72b6\u6001: ${i}`),i}catch(i){throw this.o(`${t?"\u68c0\u6d4b":"\u624b\u52a8\u68c0\u6d4b"}\u5931\u8d25: ${i.message}`),i}}start(){if(this.isRunning&&this.timer)return void this.o("VersionCheck\u5df2\u5728\u8fd0\u884c\u4e2d");this.isRunning=!0,this.C();const t=async()=>{try{await this.check(!0)}catch(t){this.config.onError(new Error(`\u8f6e\u8be2\u68c0\u6d4b\u5931\u8d25: ${t.message}`))}this.isRunning&&this.timer&&(this.timer=setTimeout(t,this.config.interval))};this.timer=setTimeout(t,this.config.interval),this.o("VersionCheck\u5df2\u542f\u52a8")}stop(t=!1){this.C(),t||(this.isRunning=!1),this.o("VersionCheck\u5df2\u505c\u6b62")}C(){this.timer&&(clearTimeout(this.timer),this.timer=null)}destroy(){this.stop(),this.l(),this.memoryStorage=null,this.timer=null,this.storageApi=null,this.checkMode=null,this.isRunning=!1,this.o("VersionCheck\u5b9e\u4f8b\u5df2\u9500\u6bc1"),this.config=null}}},"object"==typeof exports&&"undefined"!=typeof module?module.exports=factory():"function"==typeof define&&define.amd?define(factory):(global="undefined"!=typeof globalThis?globalThis:global||self).VersionCheck=factory();
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// version-check.d.ts - 独立的 TypeScript 类型声明文件
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 通用前端版本检测工具类型声明
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 存储适配器接口
|
|
9
|
+
*/
|
|
10
|
+
export interface VersionCheckStorageAdapter {
|
|
11
|
+
/**
|
|
12
|
+
* 获取存储值
|
|
13
|
+
* @param key 存储键
|
|
14
|
+
*/
|
|
15
|
+
get(key: string): string | null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 设置存储值
|
|
19
|
+
* @param key 存储键
|
|
20
|
+
* @param value 存储值
|
|
21
|
+
*/
|
|
22
|
+
set(key: string, value: string): boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 版本检测配置项
|
|
27
|
+
*/
|
|
28
|
+
export interface VersionCheckOptions {
|
|
29
|
+
/** 检测地址(默认/:ETag模式;传文件路径如/version.json:版本文件模式) */
|
|
30
|
+
url?: string;
|
|
31
|
+
|
|
32
|
+
/** 轮询间隔(毫秒),默认10分钟 */
|
|
33
|
+
interval?: number;
|
|
34
|
+
|
|
35
|
+
/** 更新提示文案 */
|
|
36
|
+
message?: string;
|
|
37
|
+
|
|
38
|
+
/** 自定义更新回调(优先级高于默认confirm) */
|
|
39
|
+
onUpdate?: () => void;
|
|
40
|
+
|
|
41
|
+
/** 错误回调 */
|
|
42
|
+
onError?: (error: Error) => void;
|
|
43
|
+
|
|
44
|
+
/** 日志回调 */
|
|
45
|
+
onLog?: (message: string) => void;
|
|
46
|
+
|
|
47
|
+
/** 自定义存储配置 */
|
|
48
|
+
storage?: VersionCheckStorageAdapter;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 版本检测类
|
|
53
|
+
*/
|
|
54
|
+
export declare class VersionCheck {
|
|
55
|
+
/**
|
|
56
|
+
* 构造函数
|
|
57
|
+
* @param options 配置项
|
|
58
|
+
*/
|
|
59
|
+
constructor(options?: VersionCheckOptions);
|
|
60
|
+
|
|
61
|
+
/** 当前检测模式 ('etag' | 'file') */
|
|
62
|
+
readonly checkMode: string;
|
|
63
|
+
|
|
64
|
+
/** 当前检测状态 */
|
|
65
|
+
readonly isRunning: boolean;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 启动自动轮询检测
|
|
69
|
+
*/
|
|
70
|
+
start(): void;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 停止自动轮询检测
|
|
74
|
+
* @param isInternal 是否为内部调用
|
|
75
|
+
*/
|
|
76
|
+
stop(isInternal?: boolean): void;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 手动触发单次检测
|
|
80
|
+
*/
|
|
81
|
+
check(): Promise<boolean>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 强制刷新页面(避免缓存)
|
|
85
|
+
*/
|
|
86
|
+
reload(): void;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 销毁实例
|
|
90
|
+
*/
|
|
91
|
+
destroy(): void;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 默认导出
|
|
95
|
+
export default VersionCheck;
|
|
96
|
+
|
|
97
|
+
// 全局变量声明(用于UMD模块)
|
|
98
|
+
declare global {
|
|
99
|
+
interface Window {
|
|
100
|
+
VersionCheck?: typeof VersionCheck;
|
|
101
|
+
}
|
|
102
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "version-check-js",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "version-check-js极简配置的前端版本检测工具,自动判断ETag/版本文件模式,默认自动轮询,保留手动检测 。原生javascript实现。不局限于框架使用",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"files": [
|
|
8
|
-
"dist"
|
|
9
|
+
"dist",
|
|
10
|
+
"index.d.ts"
|
|
9
11
|
],
|
|
10
12
|
"scripts": {
|
|
11
13
|
"build": "rollup -c",
|
|
@@ -34,7 +36,9 @@
|
|
|
34
36
|
"@rollup/plugin-commonjs": "^25.0.0",
|
|
35
37
|
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
36
38
|
"@rollup/plugin-terser": "^0.4.0",
|
|
37
|
-
"rollup": "^3.0
|
|
39
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
40
|
+
"rollup": "^3.0.0",
|
|
41
|
+
"typescript": "^5.9.3"
|
|
38
42
|
},
|
|
39
43
|
"volta": {
|
|
40
44
|
"node": "20.16.0"
|
package/dist/index.esm.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.js","sources":["../index.js"],"sourcesContent":["/**\r\n * 通用前端版本检测工具\r\n * 自动判断检测模式(ETag/版本文件)| 默认自动轮询 | 保留手动检测 | 内置原生confirm提示\r\n * @module version-check-js\r\n * @version 1.1.0\r\n */\r\nclass VersionCheck {\r\n /**\r\n * 构造函数:初始化配置和存储(极简配置)\r\n * @param {Object} options 配置项\r\n * @param {string} [options.url='/'] 检测地址(默认/:ETag模式;传文件路径如/version.json:版本文件模式)\r\n * @param {number} [options.interval=10 * 60 * 1000] 轮询间隔(毫秒),默认10分钟\r\n * @param {string} [options.message='检测到新版本,是否立即刷新?'] 更新提示文案\r\n * @param {Function} [options.onUpdate=null] 自定义更新回调(优先级高于默认confirm)\r\n * @param {Function} [options.onError=(err)=>console.error(err)] 错误回调\r\n * @param {Function} [options.onLog=(msg)=>console.log(msg)] 日志回调\r\n * @param {Object} [options.storage=null] 自定义存储配置(get/set方法)\r\n */\r\n constructor(options = {}) {\r\n // 验证输入参数类型\r\n if (typeof options !== 'object' || options === null) {\r\n throw new TypeError('options 必须是对象类型');\r\n }\r\n\r\n // 定义默认配置\r\n const defaultConfig = {\r\n url: '/',\r\n interval: 10 * 60 * 1000,\r\n message: '检测到新版本,是否立即刷新?',\r\n onUpdate: null,\r\n onError: err => console.error('版本检测失败:', err),\r\n onLog: msg => console.log('版本检测:', msg),\r\n storage: null,\r\n };\r\n\r\n // 合并配置并验证\r\n this.config = this._mergeAndValidateConfig(defaultConfig, options);\r\n\r\n // 初始化核心状态\r\n this.timer = null; // 轮询定时器\r\n this.memoryStorage = null; // 内存存储降级\r\n this.storageApi = this._initStorage(); // 存储适配器\r\n this.versionKey = 'version_identifier'; // 存储版本标识的key\r\n this.checkMode = this._getCheckMode(); // 自动判断检测模式\r\n this.isRunning = false; // 检测状态\r\n\r\n this._bindVisibilityListener(); // 绑定可见性变化监听器\r\n }\r\n\r\n /**\r\n * 合并并验证配置项\r\n * @private\r\n * @param {Object} defaults 默认配置\r\n * @param {Object} options 用户配置\r\n * @returns {Object} 合并后的配置\r\n */\r\n _mergeAndValidateConfig(defaults, options) {\r\n const config = { ...defaults, ...options };\r\n\r\n // 验证 url\r\n if (typeof config.url !== 'string' || config.url.trim() === '') {\r\n throw new TypeError('url 必须是非空字符串');\r\n }\r\n\r\n // 验证 interval\r\n if (typeof config.interval !== 'number' || config.interval <= 0) {\r\n this.config?.onLog('interval 配置无效,使用默认值 5 分钟');\r\n config.interval = defaults.interval;\r\n }\r\n\r\n // 验证 message\r\n if (typeof config.message !== 'string' || config.message.trim() === '') {\r\n this.config?.onLog('message 配置无效,使用默认值');\r\n config.message = defaults.message;\r\n }\r\n\r\n // 验证回调函数\r\n ['onUpdate', 'onError', 'onLog'].forEach(method => {\r\n if (config[method] !== null && typeof config[method] !== 'function') {\r\n this.config?.onLog(`${method} 必须是函数,使用默认处理`);\r\n config[method] = defaults[method];\r\n }\r\n });\r\n\r\n // 验证 storage\r\n if (config.storage !== null) {\r\n if (\r\n typeof config.storage !== 'object' ||\r\n typeof config.storage.get !== 'function' ||\r\n typeof config.storage.set !== 'function'\r\n ) {\r\n this.config?.onLog('storage 配置无效,使用默认存储');\r\n config.storage = null;\r\n }\r\n }\r\n\r\n return config;\r\n }\r\n\r\n /**\r\n * 绑定页面可见性变化监听器\r\n * @private\r\n */\r\n _bindVisibilityListener() {\r\n this._unbindVisibilityListener();\r\n this._visibilityHandler = this._handleVisibilityChange.bind(this);\r\n document.addEventListener('visibilitychange', this._visibilityHandler);\r\n }\r\n\r\n /**\r\n * 解绑页面可见性变化监听器\r\n * @private\r\n */\r\n _unbindVisibilityListener() {\r\n if (this._visibilityHandler) {\r\n document.removeEventListener('visibilitychange', this._visibilityHandler);\r\n this._visibilityHandler = null;\r\n }\r\n }\r\n\r\n /**\r\n * 处理页面可见性变化\r\n * @private\r\n */\r\n _handleVisibilityChange() {\r\n if (!this.isRunning) return;\r\n\r\n if (document.hidden) {\r\n this._pauseDetection();\r\n } else {\r\n this._resumeDetection();\r\n }\r\n }\r\n\r\n /**\r\n * 暂停检测(页面隐藏时)\r\n * @private\r\n */\r\n _pauseDetection() {\r\n this.stop(true);\r\n this.config.onLog('页面隐藏,暂停版本检测');\r\n }\r\n\r\n /**\r\n * 恢复检测(页面显示时)\r\n * @private\r\n */\r\n _resumeDetection() {\r\n this.start();\r\n this.config.onLog('页面显示,恢复版本检测');\r\n }\r\n\r\n /**\r\n * 初始化存储适配器(优先自定义 → localStorage → 内存)\r\n * @private\r\n * @returns {Object} 存储接口(get/set)\r\n */\r\n _initStorage() {\r\n const defaultStorage = {\r\n get: key => {\r\n if (typeof key !== 'string') {\r\n this.config.onError(new Error('存储键必须是字符串'));\r\n return null;\r\n }\r\n\r\n try {\r\n if (window.localStorage) {\r\n return localStorage.getItem(key);\r\n }\r\n } catch (e) {\r\n this.config.onError(new Error(`localStorage get 失败: ${e.message}`));\r\n }\r\n\r\n return this.memoryStorage;\r\n },\r\n\r\n set: (key, value) => {\r\n if (typeof key !== 'string') {\r\n this.config.onError(new Error('存储键必须是字符串'));\r\n return false;\r\n }\r\n\r\n try {\r\n if (window.localStorage) {\r\n localStorage.setItem(key, String(value));\r\n return true;\r\n }\r\n } catch (e) {\r\n this.config.onError(new Error(`localStorage set 失败: ${e.message}`));\r\n }\r\n\r\n this.memoryStorage = String(value);\r\n return true;\r\n },\r\n };\r\n\r\n return this.config.storage || defaultStorage;\r\n }\r\n\r\n /**\r\n * 自动判断检测模式\r\n * @private\r\n * @returns {string} 'etag' | 'file'\r\n */\r\n _getCheckMode() {\r\n const { url } = this.config;\r\n const isVersionFile = url !== '/' && /\\.\\w+$/.test(url);\r\n return isVersionFile ? 'file' : 'etag';\r\n }\r\n\r\n /**\r\n * 简化的网络请求(依赖循环调用机制)\r\n * @private\r\n * @param {string} url 请求地址\r\n * @param {Object} options 请求选项\r\n * @returns {Promise<Response>} fetch 响应\r\n */\r\n async _fetchRequest(url, options) {\r\n try {\r\n const response = await fetch(url, options);\r\n\r\n // 4xx 错误记录但不重试(客户端错误)\r\n if (response.status >= 400 && response.status < 500) {\r\n this.config.onError(new Error(`客户端错误 HTTP ${response.status}: ${response.statusText}`));\r\n return response;\r\n }\r\n\r\n if (!response.ok) {\r\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\r\n }\r\n\r\n return response;\r\n } catch (error) {\r\n this.config.onError(new Error(`网络请求失败: ${error.message}`));\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * ETag模式检测逻辑\r\n * @private\r\n * @returns {Promise<boolean>} 是否检测到更新\r\n */\r\n async _checkByEtag() {\r\n const { url } = this.config;\r\n\r\n try {\r\n const response = await this._fetchRequest(url, {\r\n method: 'HEAD',\r\n cache: 'no-cache',\r\n credentials: 'same-origin',\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`ETag 请求失败,状态码:${response.status}`);\r\n }\r\n\r\n const newVersion = response.headers.get('ETag');\r\n if (!newVersion) {\r\n throw new Error('服务器未返回 ETag,检测失败');\r\n }\r\n\r\n return this._compareVersion(newVersion);\r\n } catch (error) {\r\n this.config.onError(error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * 版本文件模式检测逻辑\r\n * @private\r\n * @returns {Promise<boolean>} 是否检测到更新\r\n */\r\n async _checkByVersionFile() {\r\n const { url } = this.config;\r\n\r\n try {\r\n const response = await this._fetchRequest(url, {\r\n method: 'GET',\r\n cache: 'no-cache',\r\n credentials: 'same-origin',\r\n headers: {\r\n Accept: 'application/json',\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`版本文件请求失败,状态码:${response.status}`);\r\n }\r\n\r\n const result = await response.json();\r\n\r\n if (!result || typeof result !== 'object') {\r\n throw new Error('版本文件格式错误,必须返回对象');\r\n }\r\n\r\n if (!result.version) {\r\n throw new Error('版本文件格式错误,缺少 version 字段');\r\n }\r\n\r\n return this._compareVersion(result.version);\r\n } catch (error) {\r\n this.config.onError(error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * 对比版本标识\r\n * @private\r\n * @param {string} newVersion 最新版本标识\r\n * @returns {boolean} 是否检测到更新\r\n */\r\n _compareVersion(newVersion) {\r\n const oldVersion = this.storageApi.get(this.versionKey);\r\n\r\n if (!oldVersion) {\r\n this.storageApi.set(this.versionKey, newVersion);\r\n return false;\r\n }\r\n\r\n if (newVersion == oldVersion) return false;\r\n\r\n this._triggerUpdate(newVersion);\r\n return true;\r\n }\r\n\r\n /**\r\n * 触发更新回调\r\n * @private\r\n */\r\n _triggerUpdate(newVersion) {\r\n const { onUpdate, message } = this.config;\r\n\r\n if (typeof onUpdate === 'function') {\r\n this.storageApi.set(this.versionKey, newVersion);\r\n onUpdate();\r\n return;\r\n }\r\n\r\n this.stop(true);\r\n const isConfirm = window.confirm(message);\r\n if (isConfirm) {\r\n this.storageApi.set(this.versionKey, newVersion);\r\n this.reload();\r\n } else {\r\n this.start();\r\n }\r\n }\r\n\r\n /**\r\n * 重新加载页面(避免缓存)\r\n */\r\n reload() {\r\n let currentUrl = window.location.href;\r\n currentUrl = currentUrl.replace(/[?&]t=\\d+/g, '');\r\n const separator = currentUrl.includes('?') ? '&' : '?';\r\n const newUrl = `${currentUrl}${separator}t=${Date.now()}`;\r\n window.location.replace(newUrl);\r\n }\r\n\r\n /**\r\n * 手动触发单次检测\r\n * @returns {Promise<boolean>} 是否检测到更新\r\n */\r\n async check() {\r\n return this.checkMode === 'etag' ? await this._checkByEtag() : await this._checkByVersionFile();\r\n }\r\n\r\n /**\r\n * 启动自动轮询检测\r\n */\r\n start() {\r\n if (this.isRunning && this.timer) {\r\n this.config.onLog('检测已在运行中');\r\n return;\r\n }\r\n\r\n this.isRunning = true;\r\n this._clearTimer();\r\n\r\n // 立即执行一次检测\r\n // this.check().catch(error => this.config.onError(new Error(`首次检测失败: ${error.message}`)));\r\n\r\n // 启动轮询\r\n const poll = async () => {\r\n try {\r\n await this.check();\r\n } catch (error) {\r\n this.config.onError(new Error(`轮询检测失败: ${error.message}`));\r\n }\r\n if (this.isRunning) {\r\n this.timer = setTimeout(poll, this.config.interval);\r\n }\r\n };\r\n this.timer = setTimeout(poll, this.config.interval);\r\n\r\n this.config.onLog('版本检测已启动');\r\n }\r\n\r\n /**\r\n * 停止自动轮询检测\r\n * @param {boolean} isInternal 是否为内部调用\r\n */\r\n stop(isInternal = false) {\r\n this._clearTimer();\r\n\r\n if (!isInternal) {\r\n this.isRunning = false;\r\n this.config.onLog('版本检测已停止');\r\n }\r\n }\r\n\r\n /**\r\n * 清理定时器\r\n * @private\r\n */\r\n _clearTimer() {\r\n if (this.timer) {\r\n clearTimeout(this.timer);\r\n this.timer = null;\r\n }\r\n }\r\n\r\n /**\r\n * 销毁实例\r\n */\r\n destroy() {\r\n this.stop();\r\n this._unbindVisibilityListener();\r\n this.memoryStorage = null;\r\n this.config = null;\r\n this.timer = null;\r\n this.storageApi = null;\r\n this.checkMode = null;\r\n this.isRunning = false;\r\n\r\n console.log('VersionCheck 实例已销毁');\r\n }\r\n}\r\n\r\nexport default VersionCheck;\r\n"],"names":["VersionCheck","constructor","options","TypeError","this","config","_mergeAndValidateConfig","url","interval","message","onUpdate","onError","err","onLog","msg","storage","timer","memoryStorage","storageApi","_initStorage","versionKey","checkMode","_getCheckMode","isRunning","_bindVisibilityListener","defaults","trim","forEach","method","get","set","_unbindVisibilityListener","_visibilityHandler","_handleVisibilityChange","bind","document","addEventListener","removeEventListener","hidden","_pauseDetection","_resumeDetection","stop","start","defaultStorage","key","Error","window","localStorage","getItem","e","value","setItem","String","test","_fetchRequest","response","fetch","status","statusText","ok","error","_checkByEtag","cache","credentials","newVersion","headers","_compareVersion","_checkByVersionFile","Accept","result","json","version","oldVersion","_triggerUpdate","confirm","reload","currentUrl","location","href","replace","separator","includes","newUrl","Date","now","check","_clearTimer","poll","async","setTimeout","isInternal","clearTimeout","destroy"],"mappings":"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"}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../index.js"],"sourcesContent":["/**\r\n * 通用前端版本检测工具\r\n * 自动判断检测模式(ETag/版本文件)| 默认自动轮询 | 保留手动检测 | 内置原生confirm提示\r\n * @module version-check-js\r\n * @version 1.1.0\r\n */\r\nclass VersionCheck {\r\n /**\r\n * 构造函数:初始化配置和存储(极简配置)\r\n * @param {Object} options 配置项\r\n * @param {string} [options.url='/'] 检测地址(默认/:ETag模式;传文件路径如/version.json:版本文件模式)\r\n * @param {number} [options.interval=10 * 60 * 1000] 轮询间隔(毫秒),默认10分钟\r\n * @param {string} [options.message='检测到新版本,是否立即刷新?'] 更新提示文案\r\n * @param {Function} [options.onUpdate=null] 自定义更新回调(优先级高于默认confirm)\r\n * @param {Function} [options.onError=(err)=>console.error(err)] 错误回调\r\n * @param {Function} [options.onLog=(msg)=>console.log(msg)] 日志回调\r\n * @param {Object} [options.storage=null] 自定义存储配置(get/set方法)\r\n */\r\n constructor(options = {}) {\r\n // 验证输入参数类型\r\n if (typeof options !== 'object' || options === null) {\r\n throw new TypeError('options 必须是对象类型');\r\n }\r\n\r\n // 定义默认配置\r\n const defaultConfig = {\r\n url: '/',\r\n interval: 10 * 60 * 1000,\r\n message: '检测到新版本,是否立即刷新?',\r\n onUpdate: null,\r\n onError: err => console.error('版本检测失败:', err),\r\n onLog: msg => console.log('版本检测:', msg),\r\n storage: null,\r\n };\r\n\r\n // 合并配置并验证\r\n this.config = this._mergeAndValidateConfig(defaultConfig, options);\r\n\r\n // 初始化核心状态\r\n this.timer = null; // 轮询定时器\r\n this.memoryStorage = null; // 内存存储降级\r\n this.storageApi = this._initStorage(); // 存储适配器\r\n this.versionKey = 'version_identifier'; // 存储版本标识的key\r\n this.checkMode = this._getCheckMode(); // 自动判断检测模式\r\n this.isRunning = false; // 检测状态\r\n\r\n this._bindVisibilityListener(); // 绑定可见性变化监听器\r\n }\r\n\r\n /**\r\n * 合并并验证配置项\r\n * @private\r\n * @param {Object} defaults 默认配置\r\n * @param {Object} options 用户配置\r\n * @returns {Object} 合并后的配置\r\n */\r\n _mergeAndValidateConfig(defaults, options) {\r\n const config = { ...defaults, ...options };\r\n\r\n // 验证 url\r\n if (typeof config.url !== 'string' || config.url.trim() === '') {\r\n throw new TypeError('url 必须是非空字符串');\r\n }\r\n\r\n // 验证 interval\r\n if (typeof config.interval !== 'number' || config.interval <= 0) {\r\n this.config?.onLog('interval 配置无效,使用默认值 5 分钟');\r\n config.interval = defaults.interval;\r\n }\r\n\r\n // 验证 message\r\n if (typeof config.message !== 'string' || config.message.trim() === '') {\r\n this.config?.onLog('message 配置无效,使用默认值');\r\n config.message = defaults.message;\r\n }\r\n\r\n // 验证回调函数\r\n ['onUpdate', 'onError', 'onLog'].forEach(method => {\r\n if (config[method] !== null && typeof config[method] !== 'function') {\r\n this.config?.onLog(`${method} 必须是函数,使用默认处理`);\r\n config[method] = defaults[method];\r\n }\r\n });\r\n\r\n // 验证 storage\r\n if (config.storage !== null) {\r\n if (\r\n typeof config.storage !== 'object' ||\r\n typeof config.storage.get !== 'function' ||\r\n typeof config.storage.set !== 'function'\r\n ) {\r\n this.config?.onLog('storage 配置无效,使用默认存储');\r\n config.storage = null;\r\n }\r\n }\r\n\r\n return config;\r\n }\r\n\r\n /**\r\n * 绑定页面可见性变化监听器\r\n * @private\r\n */\r\n _bindVisibilityListener() {\r\n this._unbindVisibilityListener();\r\n this._visibilityHandler = this._handleVisibilityChange.bind(this);\r\n document.addEventListener('visibilitychange', this._visibilityHandler);\r\n }\r\n\r\n /**\r\n * 解绑页面可见性变化监听器\r\n * @private\r\n */\r\n _unbindVisibilityListener() {\r\n if (this._visibilityHandler) {\r\n document.removeEventListener('visibilitychange', this._visibilityHandler);\r\n this._visibilityHandler = null;\r\n }\r\n }\r\n\r\n /**\r\n * 处理页面可见性变化\r\n * @private\r\n */\r\n _handleVisibilityChange() {\r\n if (!this.isRunning) return;\r\n\r\n if (document.hidden) {\r\n this._pauseDetection();\r\n } else {\r\n this._resumeDetection();\r\n }\r\n }\r\n\r\n /**\r\n * 暂停检测(页面隐藏时)\r\n * @private\r\n */\r\n _pauseDetection() {\r\n this.stop(true);\r\n this.config.onLog('页面隐藏,暂停版本检测');\r\n }\r\n\r\n /**\r\n * 恢复检测(页面显示时)\r\n * @private\r\n */\r\n _resumeDetection() {\r\n this.start();\r\n this.config.onLog('页面显示,恢复版本检测');\r\n }\r\n\r\n /**\r\n * 初始化存储适配器(优先自定义 → localStorage → 内存)\r\n * @private\r\n * @returns {Object} 存储接口(get/set)\r\n */\r\n _initStorage() {\r\n const defaultStorage = {\r\n get: key => {\r\n if (typeof key !== 'string') {\r\n this.config.onError(new Error('存储键必须是字符串'));\r\n return null;\r\n }\r\n\r\n try {\r\n if (window.localStorage) {\r\n return localStorage.getItem(key);\r\n }\r\n } catch (e) {\r\n this.config.onError(new Error(`localStorage get 失败: ${e.message}`));\r\n }\r\n\r\n return this.memoryStorage;\r\n },\r\n\r\n set: (key, value) => {\r\n if (typeof key !== 'string') {\r\n this.config.onError(new Error('存储键必须是字符串'));\r\n return false;\r\n }\r\n\r\n try {\r\n if (window.localStorage) {\r\n localStorage.setItem(key, String(value));\r\n return true;\r\n }\r\n } catch (e) {\r\n this.config.onError(new Error(`localStorage set 失败: ${e.message}`));\r\n }\r\n\r\n this.memoryStorage = String(value);\r\n return true;\r\n },\r\n };\r\n\r\n return this.config.storage || defaultStorage;\r\n }\r\n\r\n /**\r\n * 自动判断检测模式\r\n * @private\r\n * @returns {string} 'etag' | 'file'\r\n */\r\n _getCheckMode() {\r\n const { url } = this.config;\r\n const isVersionFile = url !== '/' && /\\.\\w+$/.test(url);\r\n return isVersionFile ? 'file' : 'etag';\r\n }\r\n\r\n /**\r\n * 简化的网络请求(依赖循环调用机制)\r\n * @private\r\n * @param {string} url 请求地址\r\n * @param {Object} options 请求选项\r\n * @returns {Promise<Response>} fetch 响应\r\n */\r\n async _fetchRequest(url, options) {\r\n try {\r\n const response = await fetch(url, options);\r\n\r\n // 4xx 错误记录但不重试(客户端错误)\r\n if (response.status >= 400 && response.status < 500) {\r\n this.config.onError(new Error(`客户端错误 HTTP ${response.status}: ${response.statusText}`));\r\n return response;\r\n }\r\n\r\n if (!response.ok) {\r\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\r\n }\r\n\r\n return response;\r\n } catch (error) {\r\n this.config.onError(new Error(`网络请求失败: ${error.message}`));\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * ETag模式检测逻辑\r\n * @private\r\n * @returns {Promise<boolean>} 是否检测到更新\r\n */\r\n async _checkByEtag() {\r\n const { url } = this.config;\r\n\r\n try {\r\n const response = await this._fetchRequest(url, {\r\n method: 'HEAD',\r\n cache: 'no-cache',\r\n credentials: 'same-origin',\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`ETag 请求失败,状态码:${response.status}`);\r\n }\r\n\r\n const newVersion = response.headers.get('ETag');\r\n if (!newVersion) {\r\n throw new Error('服务器未返回 ETag,检测失败');\r\n }\r\n\r\n return this._compareVersion(newVersion);\r\n } catch (error) {\r\n this.config.onError(error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * 版本文件模式检测逻辑\r\n * @private\r\n * @returns {Promise<boolean>} 是否检测到更新\r\n */\r\n async _checkByVersionFile() {\r\n const { url } = this.config;\r\n\r\n try {\r\n const response = await this._fetchRequest(url, {\r\n method: 'GET',\r\n cache: 'no-cache',\r\n credentials: 'same-origin',\r\n headers: {\r\n Accept: 'application/json',\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`版本文件请求失败,状态码:${response.status}`);\r\n }\r\n\r\n const result = await response.json();\r\n\r\n if (!result || typeof result !== 'object') {\r\n throw new Error('版本文件格式错误,必须返回对象');\r\n }\r\n\r\n if (!result.version) {\r\n throw new Error('版本文件格式错误,缺少 version 字段');\r\n }\r\n\r\n return this._compareVersion(result.version);\r\n } catch (error) {\r\n this.config.onError(error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * 对比版本标识\r\n * @private\r\n * @param {string} newVersion 最新版本标识\r\n * @returns {boolean} 是否检测到更新\r\n */\r\n _compareVersion(newVersion) {\r\n const oldVersion = this.storageApi.get(this.versionKey);\r\n\r\n if (!oldVersion) {\r\n this.storageApi.set(this.versionKey, newVersion);\r\n return false;\r\n }\r\n\r\n if (newVersion == oldVersion) return false;\r\n\r\n this._triggerUpdate(newVersion);\r\n return true;\r\n }\r\n\r\n /**\r\n * 触发更新回调\r\n * @private\r\n */\r\n _triggerUpdate(newVersion) {\r\n const { onUpdate, message } = this.config;\r\n\r\n if (typeof onUpdate === 'function') {\r\n this.storageApi.set(this.versionKey, newVersion);\r\n onUpdate();\r\n return;\r\n }\r\n\r\n this.stop(true);\r\n const isConfirm = window.confirm(message);\r\n if (isConfirm) {\r\n this.storageApi.set(this.versionKey, newVersion);\r\n this.reload();\r\n } else {\r\n this.start();\r\n }\r\n }\r\n\r\n /**\r\n * 重新加载页面(避免缓存)\r\n */\r\n reload() {\r\n let currentUrl = window.location.href;\r\n currentUrl = currentUrl.replace(/[?&]t=\\d+/g, '');\r\n const separator = currentUrl.includes('?') ? '&' : '?';\r\n const newUrl = `${currentUrl}${separator}t=${Date.now()}`;\r\n window.location.replace(newUrl);\r\n }\r\n\r\n /**\r\n * 手动触发单次检测\r\n * @returns {Promise<boolean>} 是否检测到更新\r\n */\r\n async check() {\r\n return this.checkMode === 'etag' ? await this._checkByEtag() : await this._checkByVersionFile();\r\n }\r\n\r\n /**\r\n * 启动自动轮询检测\r\n */\r\n start() {\r\n if (this.isRunning && this.timer) {\r\n this.config.onLog('检测已在运行中');\r\n return;\r\n }\r\n\r\n this.isRunning = true;\r\n this._clearTimer();\r\n\r\n // 立即执行一次检测\r\n // this.check().catch(error => this.config.onError(new Error(`首次检测失败: ${error.message}`)));\r\n\r\n // 启动轮询\r\n const poll = async () => {\r\n try {\r\n await this.check();\r\n } catch (error) {\r\n this.config.onError(new Error(`轮询检测失败: ${error.message}`));\r\n }\r\n if (this.isRunning) {\r\n this.timer = setTimeout(poll, this.config.interval);\r\n }\r\n };\r\n this.timer = setTimeout(poll, this.config.interval);\r\n\r\n this.config.onLog('版本检测已启动');\r\n }\r\n\r\n /**\r\n * 停止自动轮询检测\r\n * @param {boolean} isInternal 是否为内部调用\r\n */\r\n stop(isInternal = false) {\r\n this._clearTimer();\r\n\r\n if (!isInternal) {\r\n this.isRunning = false;\r\n this.config.onLog('版本检测已停止');\r\n }\r\n }\r\n\r\n /**\r\n * 清理定时器\r\n * @private\r\n */\r\n _clearTimer() {\r\n if (this.timer) {\r\n clearTimeout(this.timer);\r\n this.timer = null;\r\n }\r\n }\r\n\r\n /**\r\n * 销毁实例\r\n */\r\n destroy() {\r\n this.stop();\r\n this._unbindVisibilityListener();\r\n this.memoryStorage = null;\r\n this.config = null;\r\n this.timer = null;\r\n this.storageApi = null;\r\n this.checkMode = null;\r\n this.isRunning = false;\r\n\r\n console.log('VersionCheck 实例已销毁');\r\n }\r\n}\r\n\r\nexport default VersionCheck;\r\n"],"names":["constructor","options","TypeError","this","config","_mergeAndValidateConfig","url","interval","message","onUpdate","onError","err","onLog","msg","storage","timer","memoryStorage","storageApi","_initStorage","versionKey","checkMode","_getCheckMode","isRunning","_bindVisibilityListener","defaults","trim","forEach","method","get","set","_unbindVisibilityListener","_visibilityHandler","_handleVisibilityChange","bind","document","addEventListener","removeEventListener","hidden","_pauseDetection","_resumeDetection","stop","start","defaultStorage","key","Error","window","localStorage","getItem","e","value","setItem","String","test","_fetchRequest","response","fetch","status","statusText","ok","error","_checkByEtag","cache","credentials","newVersion","headers","_compareVersion","_checkByVersionFile","Accept","result","json","version","oldVersion","_triggerUpdate","confirm","reload","currentUrl","location","href","replace","separator","includes","newUrl","Date","now","check","_clearTimer","poll","async","setTimeout","isInternal","clearTimeout","destroy"],"mappings":"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"}
|