Repository: liyuec/webSite_Update_Detector
Branch: main
Commit: 99215ef26971
Files: 13
Total size: 36.3 KB
Directory structure:
gitextract_gtr080x6/
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── build/
│ ├── rollup.config.js
│ └── rollup.link.config.js
├── core/
│ ├── config.js
│ ├── detector.js
│ ├── worker.js
│ ├── workerOfFunc copy.js
│ └── workerOfFunc.js
├── index.js
└── package.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: .npmrc
================================================
registry=https://registry.npmjs.org
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 老实的切图Man
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# webSite_Update_Detector
if web site (spa) updated,we can discover
================================================
FILE: build/rollup.config.js
================================================
import {terser} from "rollup-plugin-minification";
import resolve from '@rollup/plugin-node-resolve';
import common from 'rollup-plugin-commonjs';
import babel from '@rollup/plugin-babel';
import clearDirectory from 'rollup-plugin-clear-directory';
console.log('****************rollup.config.js******************')
export default{
input:'index.js',
output:[
{
file:'./dist/index.esm.js',
format:'esm',
//sourcemap:true
exports:'named'
},
{
file:'./dist/index.umd.js',
format:'umd',
name:'detectorumd'
}
],
plugins:[
clearDirectory({
targets: ['dist']
}
),
common(),
resolve(),
terser()
]
}
================================================
FILE: build/rollup.link.config.js
================================================
import resolve from '@rollup/plugin-node-resolve';
import common from 'rollup-plugin-commonjs';
import clearDirectory from 'rollup-plugin-clear-directory';
console.log('****************rollup.link.config.js******************')
export default{
input:'index.js',
output:[
{
file:'./dist/index.esm.js',
format:'esm',
//sourcemap:true
exports:'named'
},
{
file:'./dist/index.umd.js',
format:'umd',
name:'detectorumd'
}
],
plugins:[
clearDirectory({
targets: ['dist']
}
),
common(),
resolve()
]
}
================================================
FILE: core/config.js
================================================
const baseConfig = {
//1分钟
minInterval:(1000 * 60),
//间隔轮询
interval:10000,
//是否检查当前轮询时间,自动间隔
isCheckWaitTime:true,
//获取的站点 http || https + host
checkSiteHost:'',
//每次检查完毕,用户无论干嘛,都增加这个时长
intervalAddTime:30000,
//最大间隔检查上线
maxInterval:1000 * 60,
//检查什么
checkWho:['script','css']
}
class baseClass{
constructor(ops){
this.interval = ops.interval === void 0 ? baseConfig.interval : ops.interval;
this.isCheckWaitTime = ops.isCheckWaitTime === void 0 ? baseConfig.isCheckWaitTime : ops.isCheckWaitTimes;
this.checkSiteHost = ops.checkSiteHost;
this.intervalAddTime = ops.intervalAddTime === void 0 ? baseConfig.intervalAddTime : ops.intervalAddTime;
this.maxInterval = ops.maxInterval === void 0 ? baseConfig.maxInterval : ops.maxInterval;
this.checkWho = ops.checkWho === void 0 ? baseConfig.checkWho : ops.checkWho;
}
}
const channelName = 'webUpdateDetector'
const channelMsg = {
//开始
begin:channelName + ':begin',
upgradation:channelName + ":upgradation"
}
const localEnum = {
detectorName:'localDetector'
}
const interval = 2000;
const expireInterval = 4500;
export {
baseConfig,
baseClass,
channelName,
channelMsg,
localEnum,
interval,
expireInterval
}
================================================
FILE: core/detector.js
================================================
import {baseConfig,baseClass,channelName,channelMsg,localEnum,interval,expireInterval} from './config';
import {getWorker} from './worker';
function extractLinksAndScripts(){
const result = {
_css: [],
_script: []
};
const links = document.getElementsByTagName('link');
for (let i = 0; i < links.length; i++) {
const link = links[i];
const href = link.getAttribute('href');
if (href && (href.endsWith('.css') || href.endsWith('.CSS'))) {
result._css.push(href);
}
if (href && (href.endsWith('.js') || href.endsWith('.JS'))) {
result._script.push(href);
}
}
const scripts = document.getElementsByTagName('script');
for (let i = 0; i < scripts.length; i++) {
const script = scripts[i];
const src = script.getAttribute('src');
if (src && (src.endsWith('.js') || src.endsWith('.JS'))) {
result._script.push(src);
}
}
return result;
}
/**
* 暂不考虑不支持webWorker的情况
*/
class detector extends baseClass{
#isStart = false;
#worker = void 0;
#scripts = null;
constructor(opts){
super(opts)
this.#isStart = false;
this.callBack = opts.callBack;
}
start(){
if(document.readyState !== 'complete'){
setTimeout(()=>{
this.start()
},interval);
return;
}
if(!this.#isStart){
this.#isStart = true;
this.#worker = getWorker({
callBack:this.callBack
});
if(this.#scripts === null){
this.#scripts = extractLinksAndScripts();
}
this.#worker.postMessage({
checkSiteHost:this.checkSiteHost,
domSccripts:this.#scripts._script,
domCsss:this.#scripts._css,
checkWho:this.checkWho,
msg:'start',
interval:this.interval,
intervalAddTime:this.intervalAddTime,
maxInterval:this.maxInterval
})
window.addEventListener("beforeunload", (e) => {
this.#worker.postMessage({
msg:'beforeunload'
})
})
}
}
stop(){
this.#isStart = false;
this.#worker.postMessage({msg:'stop'})
}
}
export default detector
================================================
FILE: core/worker.js
================================================
import {channelMsg,localEnum} from './config';
function getWorkerBlobStr(){
let js = `
self.onmessage = function(e){
let {checkSiteHost,domSccripts,domCsss,checkWho,msg,interval,intervalAddTime,maxInterval} = e.data,
htmlText = '',
errText = '',
timeId = null,
suminterval = interval,
sendChannelIntervalId = null;
let expireInterval = 3000,
myStartTimeInterval = 4000,
/**
* channelIntervals
* expireTime
*/
otherChannelIntervals = [];
function getWebSite(){
return new Promise((r,rj)=>{
let _checkSiteHost = checkSiteHost + '?timekey=' + new Date().getTime()
fetch(_checkSiteHost).then(res=>{
return res.text();
}).then(_htmlText =>{
htmlText = _htmlText;
r('ok')
}).catch(err=>{
errText = err;
console.warn('get web site error:',errText)
rj('wrong')
})
})
}
function extractLinksAndScripts(htmlString) {
const css = [];
const js = [];
// 提取 CSS 链接
let startIndex = 0;
while (startIndex < htmlString.length) {
const linkStartIndex = htmlString.indexOf('= expireInterval){
_myStartTimeInterval = min + 500;
}else{
_myStartTimeInterval = max + 500;
} */
_myStartTimeInterval = max + 500;
}
isMyChange = true;
//console.error('替换后的 _myStartTimeInterval:',_myStartTimeInterval,result.toString())
myStartTimeInterval = _myStartTimeInterval;
sendChannelInterval();
}
function setChannelIntervals(channelIntervals,expireTime,type){
if(type === channelMsg.begin){
otherChannelIntervals = [];
}
//console.log('otherChannelIntervals length:',otherChannelIntervals.length)
if(Object.prototype.toString.call(channelIntervals).slice(8,-1) === 'Array'){
channelIntervals.forEach(i=>{
otherChannelIntervals.push({
channelIntervals:i.channelIntervals,
expireTime:i.expireTime
});
})
}else{
otherChannelIntervals.push({
channelIntervals:channelIntervals,
expireTime:expireTime
});
}
}
/**
* 超过3秒没更新,算过期,因为每秒都要广播;
*/
function getChannelIntervals(){
let _otherChannelIntervals = otherChannelIntervals,
res = [],
min = void 0,
max = void 0,
result = [],
now = Date.now();
_otherChannelIntervals.forEach(i=>{
//if(now - i.expireTime <= 3000){
res.push(i)
result.push(i.channelIntervals)
//}
})
otherChannelIntervals = JSON.parse(JSON.stringify(res));
min = Math.min(...result);
max = Math.max(...result);
return {
result,
min,
max
}
}
const channelName = 'webUpdateDetector'
const channelMsg = {
//开始
begin:channelName + ':begin',
interval:channelName + ':interval',
script_diff:channelName + ':script_diff',
css_diff:channelName + ':css_diff',
no_file_update:channelName + ':no_file_update',
error:channelName + ':error'
}
var _channel = getChannel();
let SlaveTimerId = null;
let setIntervalId = null;
function getChannel(opts){
const _channnel = new BroadcastChannel(channelName);
_channnel.onmessage = function(_messageEvent){
let {msg,channelIntervals,expireTime} = _messageEvent.data;
switch(msg){
//其他tab已经有请求了
case channelMsg.begin:
clearTimeout(SlaveTimerId);
clearInterval(setIntervalId);
setChannelIntervals(channelIntervals,expireTime,channelMsg.begin);
myStartTimeIntervalChange();
SlaveTimerId = setTimeout(() => {
start();
}, myStartTimeInterval);
//console.log('其他tab已经有请求了:',myStartTimeInterval,msg,channelIntervals,expireTime,otherChannelIntervals)
break;
//记录其他channel的存活情况;
case channelMsg.interval:
setChannelIntervals(channelIntervals,expireTime,channelMsg.interval);
myStartTimeIntervalChange();
//console.log('记录其他channel的存活情况:',myStartTimeInterval,msg,channelIntervals,expireTime,otherChannelIntervals)
break;
case channelMsg.script_diff:
self.postMessage({
update:1,
msg:'script_diff'
})
break;
case channelMsg.css_diff:
self.postMessage({
update:1,
msg:'css_diff'
})
break;
case channelMsg.no_file_update:
self.postMessage({
update:0,
msg:'no_file_update'
})
break;
case channelMsg.error:
self.postMessage({
update:-1,
msg:'error:' + err
})
break;
}
}
return _channnel;
}
function sendChannelInterval(isInterval = false){
function send(){
_channel.postMessage({
msg:channelMsg.interval,
//channelIntervals:otherChannelIntervals.length > 0 ? otherChannelIntervals : myStartTimeInterval,
channelIntervals:myStartTimeInterval,
expireTime:Date.now()
})
}
if(isInterval){
sendChannelIntervalId = setInterval(()=>{
send();
},2000);
}else{
send();
}
}
let isSendChannel = false;
function sendChannel(){
function send(){
_channel.postMessage({
msg:channelMsg.begin,
channelIntervals:otherChannelIntervals.length > 0 ? otherChannelIntervals : myStartTimeInterval,
expireTime:Date.now()
})
setTimeout(send,2000)
}
if(!isSendChannel){
send();
}
/* setIntervalId = setInterval(()=>{
send();
},2000); */
}
function start(){
if(!!timeId){
suminterval = suminterval <= maxInterval ? suminterval += intervalAddTime : suminterval;
}
sendChannel();
getWebSite().then(res=>{
let extractObj = extractLinksAndScripts(htmlText),
diffResult = null,
siteObj = {
_script:[],
_css:[]
};
siteObj._script = extractObj.js;
siteObj._css = extractObj.css;
if(checkWho.includes('script')){
diffResult = differentValue(domSccripts,siteObj._script);
if(diffResult !== null){
_channel.postMessage({
msg:channelMsg.script_diff
})
self.postMessage({
update:1,
msg:'script_diff'
})
return;
}
}
if(checkWho.includes('css')){
diffResult = differentValue(domCsss,siteObj._css);
if(diffResult !== null){
_channel.postMessage({
msg:channelMsg.css_diff
})
self.postMessage({
update:1,
msg:'css_diff'
})
return;
}
}
timeId = setTimeout(()=>{
_channel.postMessage({
msg:channelMsg.no_file_update
})
self.postMessage({
update:0,
msg:'no_file_update'
})
start();
},suminterval)
}).catch(err=>{
_channel.postMessage({
msg:channelMsg.error
})
self.postMessage({
update:-1,
msg:'error:' + err
})
})
}
switch(msg){
case 'start':
//这里直接频道判断,因为浏览器不会保存之前的消息;
/* setIntervalId = setInterval(()=>{
_channel.postMessage({
msg:channelMsg.begin
})
},1000); */
SlaveTimerId = setTimeout(()=>{
start();
},expireInterval)
//start();
break;
case 'stop':
clearTimeout(timeId);
break;
case 'beforeunload':
clearInterval(setIntervalId);
_channel.close();
break;
}
}
`,
_blob = new Blob([js],{ type: 'application/javascript' });
return window.URL.createObjectURL(_blob);
}
function createWorker(opts){
let _worker = Object.create(null),
callBack = opts.callBack,
channel = opts.channel;
if(window.Worker){
_worker = new Worker(getWorkerBlobStr(),{
type:'classic',
credentials:'omit',
name:'webDetectorWorker'
})
/*
let workerUri = new URL('./workerOfFunc.js',import.meta.url)
_worker = new Worker(workerUri.toString(),{
type:'classic',
credentials:'omit',
name:'webDetectorWorker'
}) */
}else{
console.error('你的浏览器不支持web worker;web worker支持IE10+')
}
_worker.addEventListener("message", function (e) {
let {update,msg} = e.data;
switch(update){
case -1:
channel.postMessage(channelMsg.upgradation)
callBack({
update:update,
msg:msg
})
break;
case 0:
callBack({
update:update,
msg:msg
})
break;
case 1:
//有不一致的情况;
callBack({
update:update,
msg:msg
})
break;
}
})
_worker.addEventListener("error", function (e,event) {
console.error(`__worker error:`,e,event)
})
_worker.addEventListener("messageerror", function (e,event) {
console.error(`__worker error:`,e,event)
})
return _worker;
}
const getWorker = (function(){
let worker = null;
/**
* type :预留
*/
return function(opts,type = 'default'){
if(worker === null){
worker = new createWorker(opts)
}
return worker;
}
})()
export {
getWorker
}
================================================
FILE: core/workerOfFunc copy.js
================================================
/*
const _regexLinkStr = ']+href=[\'"]([^\'"]+\\.(css))[\'"][^>]*>',
_regexLinkJsStr = ']+href=[\'"]([^\'"]+\\.(js))[\'"][^>]*>',
_regexScriptStr = '