version-check-js 1.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ybchen292
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,366 @@
1
+ # VersionCheck.js
2
+
3
+ > A lightweight front-end version detection tool that supports automatic polling detection, manual detection, ETag mode and version file mode, with built-in native prompt dialogs. Supports custom prompts and callback functions. Suitable for any HTML-based front-end projects.
4
+
5
+ [中文版本](./README.md) | [Gitee](https://gitee.com/ybchen292/version-check) | [GitHub](https://github.com/ybchen292/version-check)
6
+
7
+ ---
8
+
9
+ ## 🌟 Key Features
10
+
11
+ - **Intelligent Dual-mode Detection**:
12
+ - **ETag Mode** (Default): Detect new versions by checking the `ETag` response header of the entry HTML file
13
+ - **Version File Mode**: Detect new versions by checking the `version` field in a specified JSON file
14
+ - **Automated Detection**: Automatically detects every 5 minutes by default, customizable interval
15
+ - **Flexible Manual Detection**: Support for actively calling detection methods
16
+ - **Smart Page Management**: Automatically pauses detection when page is hidden, resumes when page is visible
17
+ - **Highly Configurable**: Support for custom prompt messages, update callbacks, error handling, log processing, etc.
18
+ - **Multi-environment Compatibility**: Supports UMD module specification, can be used in CommonJS, AMD and browser global environments
19
+ - **Robust Storage Mechanism**: Automatic downgrade from localStorage → memory storage
20
+ - **Complete Error Handling**: Distinguishes between operation logs and error logs, provides detailed error information
21
+
22
+ ## 📦 Installation Methods
23
+
24
+ ### 1. Via `<script>` Tag (Browser Environment)
25
+
26
+ ```html
27
+ <!-- Production environment -->
28
+ <script src="dist/index.js"></script>
29
+
30
+ <!-- Or using CDN -->
31
+ <script src="https://cdn.jsdelivr.net/npm/version-check-js@latest/dist/index.js"></script>
32
+ ```
33
+
34
+ ### 2. Via NPM (Node.js Environment)
35
+
36
+ ```bash
37
+ # Install latest version
38
+ npm install version-check-js
39
+
40
+ # Or using yarn
41
+ yarn add version-check-js
42
+ ```
43
+
44
+ Then import in your code:
45
+
46
+ ```javascript
47
+ // CommonJS way
48
+ const VersionCheck = require('version-check-js');
49
+
50
+ // ES6 module way
51
+ import VersionCheck from 'version-check-js';
52
+ ```
53
+
54
+ ### 3. Via unpkg CDN
55
+
56
+ ```html
57
+ <script src="https://unpkg.com/version-check-js@latest/dist/index.js"></script>
58
+ ```
59
+
60
+ ---
61
+
62
+ ## 🚀 Quick Start
63
+
64
+ ### Basic Usage
65
+
66
+ ```javascript
67
+ // Instantiate VersionCheck
68
+ const versionCheck = new VersionCheck({
69
+ url: '/version.json', // Specify version file path (or default to '/' for ETag mode)
70
+ interval: 60 * 1000, // Set detection interval to 1 minute
71
+ message: 'New version found, refresh now?', // Custom prompt message
72
+ });
73
+
74
+ // Start automatic detection
75
+ versionCheck.start();
76
+
77
+ // Stop automatic detection
78
+ // versionCheck.stop();
79
+
80
+ // Destroy instance
81
+ // versionCheck.destroy();
82
+
83
+ // Manually trigger one detection
84
+ versionCheck.check().then(hasUpdate => {
85
+ console.log('Has update:', hasUpdate);
86
+ });
87
+ ```
88
+
89
+ ---
90
+
91
+ ## ⚙️ Configuration Options Detailed
92
+
93
+ | Parameter | Type | Default Value | Description |
94
+ | ---------- | ---------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
95
+ | `url` | `string` | `'/'` | Detection address:<br>- Default `'/'`: Enable ETag mode<br>- File path (e.g., `/version.json`): Enable version file mode |
96
+ | `interval` | `number` | `10 * 60 * 1000` (10 minutes) | Polling detection interval (milliseconds), recommended not less than 30 seconds |
97
+ | `message` | `string` | `'New version detected, refresh now?'` | Update prompt message, only effective when `onUpdate` is not set |
98
+ | `onUpdate` | `Function` | `null` | Custom update callback function (higher priority than default confirm dialog) |
99
+ | `onError` | `Function` | `(err) => console.error('Version check failed:', err)` | Error callback function, receives error object as parameter |
100
+ | `onLog` | `Function` | `(msg) => console.log('VersionCheck:', msg)` | Operation log callback function, used to record normal operation information |
101
+ | `storage` | `Object` | `null` | Custom storage configuration (requires `get`, `set`, `remove` methods), defaults to localStorage |
102
+
103
+ ### Configuration Best Practices
104
+
105
+ ```javascript
106
+ const versionCheck = new VersionCheck({
107
+ // Basic configuration
108
+ url: '/api/version', // Recommended to use specific API interface
109
+ interval: 5 * 60 * 1000, // 5-minute detection (recommended for production)
110
+
111
+ // User experience optimization
112
+ message: 'New version available, update now?',
113
+
114
+ // Custom callbacks
115
+ onUpdate: () => {
116
+ // Custom update logic
117
+ console.log('Executing update...');
118
+ // Can add animations, notifications, etc.
119
+ window.location.reload();
120
+ },
121
+
122
+ // Error handling
123
+ onError: error => {
124
+ // Production environment can send error logs to monitoring system
125
+ console.error('Version check exception:', error);
126
+ // Sentry.captureException(error);
127
+ },
128
+
129
+ // Operation logs
130
+ onLog: message => {
131
+ // Record operation logs for debugging
132
+ console.log('VersionCheck:', message);
133
+ },
134
+ });
135
+ ```
136
+
137
+ ## 🔧 Complete API Documentation
138
+
139
+ ### Instance Methods
140
+
141
+ #### `start()`
142
+
143
+ Start automatic polling detection.
144
+
145
+ ```javascript
146
+ versionCheck.start(); // Returns undefined
147
+ ```
148
+
149
+ #### `stop([isInternal])`
150
+
151
+ Stop automatic polling detection.
152
+
153
+ ```javascript
154
+ versionCheck.stop(); // External call, triggers onLog
155
+ versionCheck.stop(true); // Internal call, doesn't trigger onLog
156
+ ```
157
+
158
+ #### `check()`
159
+
160
+ Manually trigger one detection, returns Promise.
161
+
162
+ ```javascript
163
+ try {
164
+ const hasUpdate = await versionCheck.check();
165
+ if (hasUpdate) {
166
+ console.log('New version detected!');
167
+ }
168
+ } catch (error) {
169
+ console.error('Detection failed:', error);
170
+ }
171
+ ```
172
+
173
+ #### `reload()`
174
+
175
+ Force refresh page, automatically handles URL parameter deduplication.
176
+
177
+ ```javascript
178
+ versionCheck.reload(); // Will add timestamp parameter to avoid cache
179
+ ```
180
+
181
+ #### `destroy()`
182
+
183
+ Destroy instance, clean up all resources (timers, event listeners, storage references, etc.).
184
+
185
+ ```javascript
186
+ versionCheck.destroy(); // Instance cannot be used again after destruction
187
+ ```
188
+
189
+ ### Static Properties
190
+
191
+ #### `checkMode`
192
+
193
+ Get current detection mode.
194
+
195
+ ```javascript
196
+ console.log(versionCheck.checkMode); // 'etag' or 'file'
197
+ ```
198
+
199
+ #### `isRunning`
200
+
201
+ Get current detection status.
202
+
203
+ ```javascript
204
+ console.log(versionCheck.isRunning); // boolean
205
+ ```
206
+
207
+ ## 📝 Usage Scenarios and Examples
208
+
209
+ ### Integration with Axios Interceptors
210
+
211
+ ```javascript
212
+ // axios configuration
213
+ import axios from 'axios';
214
+ import VersionCheck from 'version-check-js';
215
+
216
+ const versionCheck = new VersionCheck({
217
+ url: '/api/version',
218
+ interval: 300000,
219
+ });
220
+
221
+ // Perform version check in request interceptor
222
+ axios.interceptors.request.use(
223
+ async config => {
224
+ if (process.env.NODE_ENV === 'production') {
225
+ try {
226
+ await versionCheck.check();
227
+ } catch (error) {
228
+ console.warn('Version check failed:', error);
229
+ }
230
+ }
231
+ return config;
232
+ },
233
+ error => {
234
+ return Promise.reject(error);
235
+ }
236
+ );
237
+
238
+ versionCheck.start();
239
+ ```
240
+
241
+ ## 📝 Usage Scenarios and Examples
242
+
243
+ ### Integration with Axios Interceptors
244
+
245
+ ```javascript
246
+ // axios configuration
247
+ import axios from 'axios';
248
+ import VersionCheck from 'version-check-js';
249
+
250
+ const versionCheck = new VersionCheck({
251
+ url: '/api/version',
252
+ interval: 300000,
253
+ });
254
+
255
+ // Perform version check in request interceptor
256
+ axios.interceptors.request.use(
257
+ async config => {
258
+ if (process.env.NODE_ENV === 'production') {
259
+ try {
260
+ await versionCheck.check().then(flag => {
261
+ console.log(flag);
262
+ });
263
+ } catch (error) {
264
+ console.warn('Version check failed:', error);
265
+ }
266
+ }
267
+ return config;
268
+ },
269
+ error => {
270
+ return Promise.reject(error);
271
+ }
272
+ );
273
+
274
+ versionCheck.start();
275
+ ```
276
+
277
+ ---
278
+
279
+ ### Storage Strategy Configuration
280
+
281
+ ```javascript
282
+ // Custom storage adapter
283
+ const customStorage = {
284
+ get: function (key) {
285
+ try {
286
+ return localStorage.getItem(key);
287
+ } catch (e) {
288
+ // Fallback to cookie
289
+ return this._getFromCookie(key);
290
+ }
291
+ },
292
+
293
+ set: function (key, value) {
294
+ try {
295
+ localStorage.setItem(key, value);
296
+ return true;
297
+ } catch (e) {
298
+ // Fallback to cookie
299
+ return this._setToCookie(key, value);
300
+ }
301
+ },
302
+ // Cookie operation methods
303
+ _getFromCookie: function (key) {
304
+ /* ... */
305
+ },
306
+ _setToCookie: function (key, value) {
307
+ /* ... */
308
+ },
309
+ };
310
+
311
+ const versionCheck = new VersionCheck({
312
+ storage: customStorage,
313
+ });
314
+ ```
315
+
316
+ ### Error Monitoring Integration
317
+
318
+ ```javascript
319
+ const versionCheck = new VersionCheck({
320
+ onError: error => {
321
+ // Send to custom monitoring API
322
+ fetch('/api/error-report', {
323
+ method: 'POST',
324
+ body: JSON.stringify({
325
+ error: error.message,
326
+ stack: error.stack,
327
+ timestamp: Date.now(),
328
+ }),
329
+ });
330
+ },
331
+
332
+ onLog: message => {
333
+ // Record operation logs
334
+ },
335
+ });
336
+ ```
337
+
338
+ ### Performance Optimization Recommendations
339
+
340
+ ```javascript
341
+ // Production environment configuration
342
+ const prodConfig = {
343
+ url: '/api/version',
344
+ interval: 5 * 60 * 1000, // 5 minutes (avoid too frequent)
345
+ onError: error => {
346
+ // Production environment silent handling, avoid affecting user experience
347
+ console.debug('Version check error:', error.message);
348
+ },
349
+ };
350
+
351
+ // Development environment configuration
352
+ const devConfig = {
353
+ url: '/api/version',
354
+ interval: 30 * 1000, // 30 seconds (for debugging)
355
+ onLog: message => {
356
+ // Development environment detailed logs
357
+ console.log('🔧 VersionCheck:', message);
358
+ },
359
+ };
360
+
361
+ const versionCheck = new VersionCheck(process.env.NODE_ENV === 'production' ? prodConfig : devConfig);
362
+ ```
363
+
364
+ ## 📄 License
365
+
366
+ MIT License
package/README.md ADDED
@@ -0,0 +1,341 @@
1
+ # VersionCheck.js
2
+
3
+ > 一个轻量级的前端版本检测工具,支持自动轮询检测、手动检测、ETag 模式和版本文件模式,内置原生提示弹窗。支持自定义提示和回调函数。适用于任何基于 HTML 的前端项目。
4
+
5
+ [English](./README.en.md) | [Gitee](https://gitee.com/ybchen292/version-check) | [GitHub](https://github.com/ybchen292/version-check)
6
+
7
+ ---
8
+
9
+ ## 🌟 功能特性
10
+
11
+ - **智能双模式检测**:
12
+ - **ETag 模式**(默认):通过检测入口 HTML 文件的 `ETag` 响应头判断是否有新版本
13
+ - **版本文件模式**:通过检测指定 JSON 文件中的 `version` 字段判断是否有新版本
14
+ - **自动化检测**:默认每 10 分钟自动检测一次,可自定义间隔时间
15
+ - **灵活的手动检测**:支持主动调用检测方法
16
+ - **智能页面管理**:页面隐藏时自动暂停检测,页面显示时恢复检测
17
+ - **高度可配置**:支持自定义提示文案、更新回调、错误处理、日志处理等
18
+ - **多环境兼容**:支持 UMD 模块规范,可在 CommonJS、AMD 和浏览器全局环境中使用
19
+ - **健壮的存储机制**:自动降级 localStorage → 内存存储
20
+ - **完善的错误处理**:区分操作日志和错误日志,提供详细的错误信息
21
+
22
+ ---
23
+
24
+ ## 📦 安装方式
25
+
26
+ ### 1. 通过 `<script>` 标签引入(浏览器环境)
27
+
28
+ ```html
29
+ <!-- 生产环境 -->
30
+ <script src="dist/index.js"></script>
31
+
32
+ <!-- 或使用 CDN -->
33
+ <script src="https://cdn.jsdelivr.net/npm/version-check-js@latest/dist/index.js"></script>
34
+ ```
35
+
36
+ ### 2. 通过 NPM 安装(Node.js 环境)
37
+
38
+ ```bash
39
+ # 安装最新版本
40
+ npm install version-check-js
41
+
42
+ # 或使用 yarn
43
+ yarn add version-check-js
44
+ ```
45
+
46
+ 然后在代码中导入:
47
+
48
+ ```javascript
49
+ // CommonJS 方式
50
+ const VersionCheck = require('version-check-js');
51
+
52
+ // ES6 模块方式
53
+ import VersionCheck from 'version-check-js';
54
+ ```
55
+
56
+ ### 3. 通过 unpkg CDN 引入
57
+
58
+ ```html
59
+ <script src="https://unpkg.com/version-check-js@latest/dist/index.js"></script>
60
+ ```
61
+
62
+ ---
63
+
64
+ ## 🚀 快速开始
65
+
66
+ ### 基础用法
67
+
68
+ ```javascript
69
+ // 实例化 VersionCheck
70
+ const versionCheck = new VersionCheck({
71
+ url: '/version.json', // 指定版本文件路径(或默认使用 '/' 进入 ETag 模式)
72
+ interval: 60 * 1000, // 设置检测间隔为 1 分钟
73
+ message: '发现新版本,是否立即刷新?', // 自定义提示文案
74
+ });
75
+
76
+ // 启动自动检测
77
+ versionCheck.start();
78
+
79
+ // 停止自动检测
80
+ // versionCheck.stop();
81
+
82
+ // 销毁实例
83
+ // versionCheck.destroy();
84
+
85
+ // 手动触发一次检测
86
+ versionCheck.check().then(hasUpdate => {
87
+ console.log('是否有更新:', hasUpdate);
88
+ });
89
+ ```
90
+
91
+ ---
92
+
93
+ ## ⚙️ 配置项详解
94
+
95
+ | 参数名 | 类型 | 默认值 | 描述 |
96
+ | ---------- | ---------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------ |
97
+ | `url` | `string` | `'/'` | 检测地址:<br>- 默认 `'/'`:启用 ETag 模式<br>- 文件路径(如 `/version.json`):启用版本文件模式 |
98
+ | `interval` | `number` | `10 * 60 * 1000`(10 分钟) | 轮询检测间隔时间(毫秒),建议不小于 30 秒 |
99
+ | `message` | `string` | `'检测到新版本,是否立即刷新?'` | 更新提示文案,仅在未设置 `onUpdate` 时生效 |
100
+ | `onUpdate` | `Function` | `null` | 自定义更新回调函数(优先级高于默认 confirm 弹窗) |
101
+ | `onError` | `Function` | `(err) => console.error('版本检测失败:', err)` | 错误回调函数,接收错误对象作为参数 |
102
+ | `onLog` | `Function` | `(msg) => console.log('版本检测:', msg)` | 操作日志回调函数,用于记录正常操作信息 |
103
+ | `storage` | `Object` | `null` | 自定义存储配置(需提供 `get`、`set`、`remove` 方法),默认使用 localStorage |
104
+
105
+ ### 配置项最佳实践
106
+
107
+ ```javascript
108
+ const versionCheck = new VersionCheck({
109
+ // 基础配置
110
+ url: '/api/version', // 推荐使用具体的 API 接口
111
+ interval: 5 * 60 * 1000, // 5分钟检测一次(生产环境推荐)
112
+
113
+ // 用户体验优化
114
+ message: '发现新版本可用,是否立即更新?',
115
+
116
+ // 自定义回调
117
+ onUpdate: () => {
118
+ // 自定义更新逻辑
119
+ console.log('执行更新...');
120
+ // 可以在这里添加动画、提示等
121
+ window.location.reload();
122
+ },
123
+
124
+ // 错误处理
125
+ onError: error => {
126
+ // 生产环境可以发送错误日志到监控系统
127
+ console.error('版本检测异常:', error);
128
+ },
129
+
130
+ // 操作日志
131
+ onLog: message => {
132
+ // 记录操作日志,便于调试
133
+ console.log('VersionCheck:', message);
134
+ },
135
+ });
136
+ ```
137
+
138
+ ---
139
+
140
+ ## 🔧 完整 API 文档
141
+
142
+ ### 实例方法
143
+
144
+ #### `start()`
145
+
146
+ 启动自动轮询检测。
147
+
148
+ ```javascript
149
+ versionCheck.start(); // 返回 undefined
150
+ ```
151
+
152
+ #### `stop([isInternal])`
153
+
154
+ 停止自动轮询检测。
155
+
156
+ ```javascript
157
+ versionCheck.stop(); // 外部调用,会触发 onLog
158
+ versionCheck.stop(true); // 内部调用,不会触发 onLog
159
+ ```
160
+
161
+ #### `check()`
162
+
163
+ 手动触发一次检测,返回 Promise。
164
+
165
+ ```javascript
166
+ try {
167
+ const hasUpdate = await versionCheck.check();
168
+ if (hasUpdate) {
169
+ console.log('检测到新版本!');
170
+ }
171
+ } catch (error) {
172
+ console.error('检测失败:', error);
173
+ }
174
+ ```
175
+
176
+ #### `reload()`
177
+
178
+ 强制刷新页面,自动处理 URL 参数去重。
179
+
180
+ ```javascript
181
+ versionCheck.reload(); // 会添加时间戳参数避免缓存
182
+ ```
183
+
184
+ #### `destroy()`
185
+
186
+ 销毁实例,清理所有资源(定时器、事件监听器、存储引用等)。
187
+
188
+ ```javascript
189
+ versionCheck.destroy(); // 实例销毁后不可再次使用
190
+ ```
191
+
192
+ ### 静态属性
193
+
194
+ #### `checkMode`
195
+
196
+ 获取当前检测模式。
197
+
198
+ ```javascript
199
+ console.log(versionCheck.checkMode); // 'etag' 或 'file'
200
+ ```
201
+
202
+ #### `isRunning`
203
+
204
+ 获取当前检测状态。
205
+
206
+ ```javascript
207
+ console.log(versionCheck.isRunning); // boolean
208
+ ```
209
+
210
+ ---
211
+
212
+ ## 📝 使用场景和示例
213
+
214
+ ### 配合 Axios 拦截器使用
215
+
216
+ ```javascript
217
+ // axios 配置
218
+ import axios from 'axios';
219
+ import VersionCheck from 'version-check-js';
220
+
221
+ const versionCheck = new VersionCheck({
222
+ url: '/api/version',
223
+ interval: 300000,
224
+ });
225
+
226
+ // 请求拦截器中进行版本检测
227
+ axios.interceptors.request.use(
228
+ async config => {
229
+ if (process.env.NODE_ENV === 'production') {
230
+ try {
231
+ await versionCheck.check().then(flag => {
232
+ console.log(flag);
233
+ });
234
+ } catch (error) {
235
+ console.warn('版本检测失败:', error);
236
+ }
237
+ }
238
+ return config;
239
+ },
240
+ error => {
241
+ return Promise.reject(error);
242
+ }
243
+ );
244
+
245
+ versionCheck.start();
246
+ ```
247
+
248
+ ---
249
+
250
+ ## 🛠️ 高级配置和最佳实践
251
+
252
+ ### 存储策略配置
253
+
254
+ ```javascript
255
+ // 自定义存储适配器
256
+ const customStorage = {
257
+ get: function (key) {
258
+ try {
259
+ return localStorage.getItem(key);
260
+ } catch (e) {
261
+ // 降级到 cookie
262
+ return this._getFromCookie(key);
263
+ }
264
+ },
265
+
266
+ set: function (key, value) {
267
+ try {
268
+ localStorage.setItem(key, value);
269
+ return true;
270
+ } catch (e) {
271
+ // 降级到 cookie
272
+ return this._setToCookie(key, value);
273
+ }
274
+ },
275
+ // Cookie 操作方法
276
+ _getFromCookie: function (key) {
277
+ /* ... */
278
+ },
279
+ _setToCookie: function (key, value) {
280
+ /* ... */
281
+ },
282
+ };
283
+
284
+ const versionCheck = new VersionCheck({
285
+ storage: customStorage,
286
+ });
287
+ ```
288
+
289
+ ### 错误监控集成
290
+
291
+ ```javascript
292
+ const versionCheck = new VersionCheck({
293
+ onError: error => {
294
+ // 发送到自定义监控接口
295
+ fetch('/api/error-report', {
296
+ method: 'POST',
297
+ body: JSON.stringify({
298
+ error: error.message,
299
+ stack: error.stack,
300
+ timestamp: Date.now(),
301
+ }),
302
+ });
303
+ },
304
+
305
+ onLog: message => {
306
+ // 记录操作日志
307
+ },
308
+ });
309
+ ```
310
+
311
+ ### 性能优化建议
312
+
313
+ ```javascript
314
+ // 生产环境配置
315
+ const prodConfig = {
316
+ url: '/api/version',
317
+ interval: 5 * 60 * 1000, // 5分钟(避免过于频繁)
318
+ onError: error => {
319
+ // 生产环境静默处理,避免影响用户体验
320
+ console.debug('Version check error:', error.message);
321
+ },
322
+ };
323
+
324
+ // 开发环境配置
325
+ const devConfig = {
326
+ url: '/api/version',
327
+ interval: 30 * 1000, // 30秒(便于调试)
328
+ onLog: message => {
329
+ // 开发环境详细日志
330
+ console.log('🔧 VersionCheck:', message);
331
+ },
332
+ };
333
+
334
+ const versionCheck = new VersionCheck(process.env.NODE_ENV === 'production' ? prodConfig : devConfig);
335
+ ```
336
+
337
+ ---
338
+
339
+ ## 📄 许可证
340
+
341
+ MIT License
@@ -0,0 +1,2 @@
1
+ class t{constructor(t={}){if("object"!=typeof t||null===t)throw new TypeError("options \u5fc5\u987b\u662f\u5bf9\u8c61\u7c7b\u578b");this.config=this.t({url:"/",interval:6e5,message:"\u68c0\u6d4b\u5230\u65b0\u7248\u672c\uff0c\u662f\u5426\u7acb\u5373\u5237\u65b0\uff1f",onUpdate:null,onError:t=>{},onLog:t=>{},storage:null},t),this.timer=null,this.memoryStorage=null,this.storageApi=this.i(),this.versionKey="version_identifier",this.checkMode=this.h(),this.isRunning=!1,this.o()}t(t,i){const s={...t,...i};if("string"!=typeof s.url||""===s.url.trim())throw new TypeError("url \u5fc5\u987b\u662f\u975e\u7a7a\u5b57\u7b26\u4e32");return("number"!=typeof s.interval||s.interval<=0)&&(this.config?.onLog("interval \u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528\u9ed8\u8ba4\u503c 5 \u5206\u949f"),s.interval=t.interval),"string"==typeof s.message&&""!==s.message.trim()||(this.config?.onLog("message \u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528\u9ed8\u8ba4\u503c"),s.message=t.message),["onUpdate","onError","onLog"].forEach(i=>{null!==s[i]&&"function"!=typeof s[i]&&(this.config?.onLog(`${i} \u5fc5\u987b\u662f\u51fd\u6570\uff0c\u4f7f\u7528\u9ed8\u8ba4\u5904\u7406`),s[i]=t[i])}),null!==s.storage&&("object"==typeof s.storage&&"function"==typeof s.storage.get&&"function"==typeof s.storage.set||(this.config?.onLog("storage \u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528\u9ed8\u8ba4\u5b58\u50a8"),s.storage=null)),s}o(){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.m():this.$())}m(){this.stop(!0),this.config.onLog("\u9875\u9762\u9690\u85cf\uff0c\u6682\u505c\u7248\u672c\u68c0\u6d4b")}$(){this.start(),this.config.onLog("\u9875\u9762\u663e\u793a\uff0c\u6062\u590d\u7248\u672c\u68c0\u6d4b")}i(){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}h(){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.v(s)}catch(t){return this.config.onError(t),!1}}async S(){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.v(s.version)}catch(t){return this.config.onError(t),!1}}v(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(!0),window.confirm(s)?(this.storageApi.set(this.versionKey,t),this.reload()):this.start()}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(){return"etag"===this.checkMode?await this.T():await this.S()}start(){if(this.isRunning&&this.timer)return void this.config.onLog("\u68c0\u6d4b\u5df2\u5728\u8fd0\u884c\u4e2d");this.isRunning=!0,this.j();const t=async()=>{try{await this.check()}catch(t){this.config.onError(new Error(`\u8f6e\u8be2\u68c0\u6d4b\u5931\u8d25: ${t.message}`))}this.isRunning&&(this.timer=setTimeout(t,this.config.interval))};this.timer=setTimeout(t,this.config.interval),this.config.onLog("\u7248\u672c\u68c0\u6d4b\u5df2\u542f\u52a8")}stop(t=!1){this.j(),t||(this.isRunning=!1,this.config.onLog("\u7248\u672c\u68c0\u6d4b\u5df2\u505c\u6b62"))}j(){this.timer&&(clearTimeout(this.timer),this.timer=null)}destroy(){this.stop(),this.l(),this.memoryStorage=null,this.config=null,this.timer=null,this.storageApi=null,this.checkMode=null,this.isRunning=!1}}export{t as default};
2
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
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 ADDED
@@ -0,0 +1,2 @@
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=this.t({url:"/",interval:6e5,message:"\u68c0\u6d4b\u5230\u65b0\u7248\u672c\uff0c\u662f\u5426\u7acb\u5373\u5237\u65b0\uff1f",onUpdate:null,onError:t=>{},onLog:t=>{},storage:null},t),this.timer=null,this.memoryStorage=null,this.storageApi=this.i(),this.versionKey="version_identifier",this.checkMode=this.o(),this.isRunning=!1,this.h()}t(t,i){const e={...t,...i};if("string"!=typeof e.url||""===e.url.trim())throw new TypeError("url \u5fc5\u987b\u662f\u975e\u7a7a\u5b57\u7b26\u4e32");return("number"!=typeof e.interval||e.interval<=0)&&(this.config?.onLog("interval \u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528\u9ed8\u8ba4\u503c 5 \u5206\u949f"),e.interval=t.interval),"string"==typeof e.message&&""!==e.message.trim()||(this.config?.onLog("message \u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528\u9ed8\u8ba4\u503c"),e.message=t.message),["onUpdate","onError","onLog"].forEach(i=>{null!==e[i]&&"function"!=typeof e[i]&&(this.config?.onLog(`${i} \u5fc5\u987b\u662f\u51fd\u6570\uff0c\u4f7f\u7528\u9ed8\u8ba4\u5904\u7406`),e[i]=t[i])}),null!==e.storage&&("object"==typeof e.storage&&"function"==typeof e.storage.get&&"function"==typeof e.storage.set||(this.config?.onLog("storage \u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528\u9ed8\u8ba4\u5b58\u50a8"),e.storage=null)),e}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.m():this.T())}m(){this.stop(!0),this.config.onLog("\u9875\u9762\u9690\u85cf\uff0c\u6682\u505c\u7248\u672c\u68c0\u6d4b")}T(){this.start(),this.config.onLog("\u9875\u9762\u663e\u793a\uff0c\u6062\u590d\u7248\u672c\u68c0\u6d4b")}i(){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}o(){const{url:t}=this.config;return"/"!==t&&/\.\w+$/.test(t)?"file":"etag"}async $(t,i){try{const e=await fetch(t,i);if(e.status>=400&&e.status<500)return this.config.onError(new Error(`\u5ba2\u6237\u7aef\u9519\u8bef HTTP ${e.status}: ${e.statusText}`)),e;if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e}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,{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 e=i.headers.get("ETag");if(!e)throw new Error("\u670d\u52a1\u5668\u672a\u8fd4\u56de ETag\uff0c\u68c0\u6d4b\u5931\u8d25");return this.v(e)}catch(t){return this.config.onError(t),!1}}async S(){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 e=await i.json();if(!e||"object"!=typeof e)throw new Error("\u7248\u672c\u6587\u4ef6\u683c\u5f0f\u9519\u8bef\uff0c\u5fc5\u987b\u8fd4\u56de\u5bf9\u8c61");if(!e.version)throw new Error("\u7248\u672c\u6587\u4ef6\u683c\u5f0f\u9519\u8bef\uff0c\u7f3a\u5c11 version \u5b57\u6bb5");return this.v(e.version)}catch(t){return this.config.onError(t),!1}}v(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:e}=this.config;if("function"==typeof i)return this.storageApi.set(this.versionKey,t),void i();this.stop(!0),window.confirm(e)?(this.storageApi.set(this.versionKey,t),this.reload()):this.start()}reload(){let t=window.location.href;t=t.replace(/[?&]t=\d+/g,"");const i=t.includes("?")?"&":"?",e=`${t}${i}t=${Date.now()}`;window.location.replace(e)}async check(){return"etag"===this.checkMode?await this._():await this.S()}start(){if(this.isRunning&&this.timer)return void this.config.onLog("\u68c0\u6d4b\u5df2\u5728\u8fd0\u884c\u4e2d");this.isRunning=!0,this.j();const t=async()=>{try{await this.check()}catch(t){this.config.onError(new Error(`\u8f6e\u8be2\u68c0\u6d4b\u5931\u8d25: ${t.message}`))}this.isRunning&&(this.timer=setTimeout(t,this.config.interval))};this.timer=setTimeout(t,this.config.interval),this.config.onLog("\u7248\u672c\u68c0\u6d4b\u5df2\u542f\u52a8")}stop(t=!1){this.j(),t||(this.isRunning=!1,this.config.onLog("\u7248\u672c\u68c0\u6d4b\u5df2\u505c\u6b62"))}j(){this.timer&&(clearTimeout(this.timer),this.timer=null)}destroy(){this.stop(),this.l(),this.memoryStorage=null,this.config=null,this.timer=null,this.storageApi=null,this.checkMode=null,this.isRunning=!1}}},"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();
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
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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"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "version-check-js",
3
+ "version": "1.1.0",
4
+ "description": "version-check-js极简配置的前端版本检测工具,自动判断ETag/版本文件模式,默认自动轮询,保留手动检测 。原生javascript实现。不局限于框架使用",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "rollup -c",
12
+ "dev": "rollup -c -w",
13
+ "prepublishOnly": "npm run build",
14
+ "test": "echo \"No tests specified\" && exit 0"
15
+ },
16
+ "keywords": [
17
+ "version-checker",
18
+ "version-check",
19
+ "version",
20
+ "checker",
21
+ "etag",
22
+ "版本检测",
23
+ "前端更新",
24
+ "自动轮询"
25
+ ],
26
+ "author": "446354153@qq.com",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/ybchen292/version-check"
31
+ },
32
+ "homepage": "https://github.com/ybchen292/version-check",
33
+ "devDependencies": {
34
+ "@rollup/plugin-commonjs": "^25.0.0",
35
+ "@rollup/plugin-node-resolve": "^15.0.0",
36
+ "@rollup/plugin-terser": "^0.4.0",
37
+ "rollup": "^3.0.0"
38
+ },
39
+ "volta": {
40
+ "node": "20.16.0"
41
+ }
42
+ }