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('<link', startIndex);
if (linkStartIndex === -1) {
break;
}
const hrefStartIndex = htmlString.indexOf('href="', linkStartIndex);
if (hrefStartIndex === -1) {
break;
}
const hrefEndIndex = htmlString.indexOf('"', hrefStartIndex + 6);
if (hrefEndIndex === -1) {
break;
}
const href = htmlString.substring(hrefStartIndex + 6, hrefEndIndex);
if (href.endsWith('.css')) {
css.push(href);
}
if(href.endsWith('.js')){
js.push(href);
}
startIndex = hrefEndIndex + 1;
}
// 提取 JS 链接
startIndex = 0;
while (startIndex < htmlString.length) {
const scriptStartIndex = htmlString.indexOf('<script', startIndex);
if (scriptStartIndex === -1) {
break;
}
const srcStartIndex = htmlString.indexOf('src="', scriptStartIndex);
if (srcStartIndex === -1) {
break;
}
const srcEndIndex = htmlString.indexOf('"', srcStartIndex + 5);
if (srcEndIndex === -1) {
break;
}
const src = htmlString.substring(srcStartIndex + 5, srcEndIndex);
if (src.endsWith('.js')) {
js.push(src);
}
startIndex = srcEndIndex + 1;
}
return { css, js };
}
function differentValue(arr1, arr2) {
var targetArray = arr1.length <= arr2.length ? arr1 : arr2;
var otherArray = targetArray === arr1 ? arr2 : arr1;
for (var i = 0; i < targetArray.length; i++) {
if (targetArray[i] !== otherArray[i]) {
return targetArray[i];
}
}
return null;
}
/*
调整自己的myStartTimeInterval;
以便主tab挂了或则关闭,其他tab 只会启动单个请求
只change自己一次
*/
let isMyChange = false;
function myStartTimeIntervalChange(){
if(isMyChange){
return;
}
let _myStartTimeInterval = myStartTimeInterval,
{result,min,max} = getChannelIntervals();
//console.error('我的 _myStartTimeInterval:',_myStartTimeInterval,result.toString());
if(result.includes(_myStartTimeInterval)){
/* if(min - 500 >= 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 = '<link[^>]+href=[\'"]([^\'"]+\\.(css))[\'"][^>]*>',
_regexLinkJsStr = '<link[^>]+href=[\'"]([^\'"]+\\.(js))[\'"][^>]*>',
_regexScriptStr = '<script[^>]+src=[\'"]([^"\']+)["\']';
*/
self.onmessage = function(e){
let {checkSiteHost,domSccripts,domCsss,checkWho,msg,interval,intervalAddTime,maxInterval} = e.data,
htmlText = '',
errText = '',
timeId = null,
suminterval = interval;
function getWebSite(){
return new Promise((r,rj)=>{
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(str) {
str = str.replaceAll('\x3C','<');
str = str.replaceAll('async',' ');
str = str.replaceAll('defer',' ');
const _regexLinkStr = '<link[^>]+href=[\\'"]([^\\'"]+\\\\.(css))[\\'"][^>]*>',
_regexLinkJsStr = '<link[^>]+href=[\\'"]([^\\'"]+\\\\.(js))[\\'"][^>]*>',
'<script[^>]+src=[\\'"]([^\']+)["\']'
_regexScriptStr = '<script[^>]+src=[\\'"]([^\\']+)[\\']',
regexLinkStr = new RegExp(_regexLinkStr, 'g'),
regexLinkJsStr = new RegExp(_regexLinkJsStr, 'g'),
regexScriptStr = new RegExp(_regexScriptStr, 'g'),
matchStr = regexLinkStr.exec(str),
matchLinkJsStr = regexLinkJsStr.exec(str),
matchScriptStr = regexScriptStr.exec(str),
result = {
_css: [],
_script: []
};
if(matchStr !== null){
matchStr.forEach(i => {
result._css.push(i);
});
}
if(matchLinkJsStr !== null){
matchLinkJsStr.forEach(i => {
result._script.push(i);
});
}
if(matchScriptStr !== null){
matchScriptStr.forEach(i => {
if(i !== 'js'){
result._script.push(i);
}
});
}
return result;
}
function differentValue(arr1, arr2) {
var targetArray = arr1.length <= arr2.length ? arr1 : arr2;
var otherArray = targetArray === arr1 ? arr2 : arr1;
for (var i = 0; i < targetArray.length; i++) {
if (targetArray[i] !== otherArray[i]) {
return targetArray[i];
}
}
return null;
}
function start(){
if(!!timeId){
suminterval <= maxInterval ? suminterval += intervalAddTime : suminterval;
}
getWebSite().then(res=>{
let siteObj = extractLinksAndScripts(htmlText),
diffResult = null;
if(checkWho.includes('script')){
diffResult = differentValue(domSccripts,siteObj._script);
if(diffResult !== null){
self.postMessage({
update:1,
msg:'script_diff'
})
return;
}
}
if(checkWho.includes('css')){
diffResult = differentValue(domCsss,siteObj._css);
if(diffResult !== null){
self.postMessage({
update:1,
msg:'css_diff'
})
return;
}
}
timeId = setTimeout(()=>{
self.postMessage({
update:0,
msg:'no_file_update'
})
},suminterval)
}).catch(err=>{
self.postMessage({
update:-1,
msg:'error:' + err
})
})
}
switch(msg){
case 'start':
start();
break;
case 'stop':
clearTimeout(timeId);
break;
}
}
================================================
FILE: core/workerOfFunc.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('<link', startIndex);
if (linkStartIndex === -1) {
break;
}
const hrefStartIndex = htmlString.indexOf('href="', linkStartIndex);
if (hrefStartIndex === -1) {
break;
}
const hrefEndIndex = htmlString.indexOf('"', hrefStartIndex + 6);
if (hrefEndIndex === -1) {
break;
}
const href = htmlString.substring(hrefStartIndex + 6, hrefEndIndex);
if (href.endsWith('.css')) {
css.push(href);
}
if(href.endsWith('.js')){
js.push(href);
}
startIndex = hrefEndIndex + 1;
}
// 提取 JS 链接
startIndex = 0;
while (startIndex < htmlString.length) {
const scriptStartIndex = htmlString.indexOf('<script', startIndex);
if (scriptStartIndex === -1) {
break;
}
const srcStartIndex = htmlString.indexOf('src="', scriptStartIndex);
if (srcStartIndex === -1) {
break;
}
const srcEndIndex = htmlString.indexOf('"', srcStartIndex + 5);
if (srcEndIndex === -1) {
break;
}
const src = htmlString.substring(srcStartIndex + 5, srcEndIndex);
if (src.endsWith('.js')) {
js.push(src);
}
startIndex = srcEndIndex + 1;
}
return { css, js };
}
function differentValue(arr1, arr2) {
var targetArray = arr1.length <= arr2.length ? arr1 : arr2;
var otherArray = targetArray === arr1 ? arr2 : arr1;
for (var i = 0; i < targetArray.length; i++) {
if (targetArray[i] !== otherArray[i]) {
return targetArray[i];
}
}
return null;
}
/*
调整自己的myStartTimeInterval;
以便主tab挂了或则关闭,其他tab 只会启动单个请求
只change自己一次
*/
let isMyChange = false;
function myStartTimeIntervalChange(){
if(isMyChange){
return;
}
let _myStartTimeInterval = myStartTimeInterval,
{result,min,max} = getChannelIntervals();
//console.error('我的 _myStartTimeInterval:',_myStartTimeInterval,result.toString());
if(result.includes(_myStartTimeInterval)){
/* if(min - 500 >= 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;
}
}
================================================
FILE: index.js
================================================
import detector from './core/detector'
const createDetector = (function(){
let _detector = null;
return function(opts){
if(_detector === null){
_detector = new detector(opts);
}
return _detector;
}
})()
export default createDetector;
================================================
FILE: package.json
================================================
{
"name": "website_update_detector",
"version": "1.2.0",
"description": "web前端主动发现站点发布更新 客户端更新 主动发现更新 发现更新",
"private": false,
"type": "module",
"main": "./dist/index.umd.js",
"module": "./dist/index.esm.js",
"keywords": [
"web前端主动发现站点发布更新",
" 客户端更新",
"主动发现更新",
"发现更新"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"rolluplink": "npm run build:link",
"rollupbuild": "npm run build:esm",
"build:esm": "rollup -c ./build/rollup.config.js",
"build:link": "rollup -c ./build/rollup.link.config.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/liyuec/webSite_Update_Detector.git"
},
"author": "liyuec",
"license": "MIT",
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
],
"devDependencies": {
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-node-resolve": "^15.1.0",
"rollup": "^3.26.2",
"rollup-plugin-clear-directory": "^1.0.1",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-minification": "^0.2.0",
"rollup-plugin-postcss": "^4.0.2"
},
"bugs": {
"url": "https://github.com/liyuec/webSite_Update_Detector/issues"
},
"homepage": "https://github.com/liyuec/webSite_Update_Detector#readme",
"files": [
"dist"
],
"publishConfig": {
"access": "public"
}
}
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
SYMBOL INDEX (23 symbols across 5 files)
FILE: core/config.js
class baseClass (line 18) | class baseClass{
method constructor (line 19) | constructor(ops){
FILE: core/detector.js
function extractLinksAndScripts (line 4) | function extractLinksAndScripts(){
class detector (line 39) | class detector extends baseClass{
method constructor (line 44) | constructor(opts){
method start (line 50) | start(){
method stop (line 87) | stop(){
FILE: core/worker.js
function getWorkerBlobStr (line 2) | function getWorkerBlobStr(){
function createWorker (line 393) | function createWorker(opts){
FILE: core/workerOfFunc copy.js
function getWebSite (line 15) | function getWebSite(){
function extractLinksAndScripts (line 30) | function extractLinksAndScripts(str) {
function differentValue (line 74) | function differentValue(arr1, arr2) {
function start (line 88) | function start(){
FILE: core/workerOfFunc.js
function getWebSite (line 19) | function getWebSite(){
function extractLinksAndScripts (line 35) | function extractLinksAndScripts(htmlString) {
function differentValue (line 89) | function differentValue(arr1, arr2) {
function myStartTimeIntervalChange (line 108) | function myStartTimeIntervalChange(){
function setChannelIntervals (line 135) | function setChannelIntervals(channelIntervals,expireTime,type){
function getChannelIntervals (line 158) | function getChannelIntervals(){
function getChannel (line 199) | function getChannel(opts){
function sendChannelInterval (line 252) | function sendChannelInterval(isInterval = false){
function sendChannel (line 272) | function sendChannel(){
function start (line 289) | function start(){
Condensed preview — 13 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (40K chars).
[
{
"path": ".gitignore",
"chars": 230,
"preview": ".DS_Store\nnode_modules\n/dist\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn"
},
{
"path": ".npmrc",
"chars": 35,
"preview": "registry=https://registry.npmjs.org"
},
{
"path": "LICENSE",
"chars": 1065,
"preview": "MIT License\n\nCopyright (c) 2023 老实的切图Man\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "README.md",
"chars": 68,
"preview": "# webSite_Update_Detector\nif web site (spa) updated,we can discover\n"
},
{
"path": "build/rollup.config.js",
"chars": 788,
"preview": "import {terser} from \"rollup-plugin-minification\";\nimport resolve from '@rollup/plugin-node-resolve';\nimport common from"
},
{
"path": "build/rollup.link.config.js",
"chars": 682,
"preview": "import resolve from '@rollup/plugin-node-resolve';\nimport common from 'rollup-plugin-commonjs';\nimport clearDirectory fr"
},
{
"path": "core/config.js",
"chars": 1316,
"preview": "const baseConfig = {\n //1分钟\n minInterval:(1000 * 60),\n //间隔轮询\n interval:10000,\n //是否检查当前轮询时间,自动间隔\n isC"
},
{
"path": "core/detector.js",
"chars": 2424,
"preview": "import {baseConfig,baseClass,channelName,channelMsg,localEnum,interval,expireInterval} from './config';\nimport {getWorke"
},
{
"path": "core/worker.js",
"chars": 13795,
"preview": "import {channelMsg,localEnum} from './config';\nfunction getWorkerBlobStr(){\n let js = `\n self.onmessage = function"
},
{
"path": "core/workerOfFunc copy.js",
"chars": 4132,
"preview": "/*\n const _regexLinkStr = '<link[^>]+href=[\\'\"]([^\\'\"]+\\\\.(css))[\\'\"][^>]*>',\n _regexLinkJsStr = '<link[^>]+href=["
},
{
"path": "core/workerOfFunc.js",
"chars": 10991,
"preview": "self.onmessage = function(e){\n let {checkSiteHost,domSccripts,domCsss,checkWho,msg,interval,intervalAddTime,maxInterv"
},
{
"path": "index.js",
"chars": 285,
"preview": "import detector from './core/detector'\n\nconst createDetector = (function(){\n let _detector = null;\n\n return functi"
},
{
"path": "package.json",
"chars": 1393,
"preview": "{\n \"name\": \"website_update_detector\",\n \"version\": \"1.2.0\",\n \"description\": \"web前端主动发现站点发布更新 客户端更新 主动发现更新 发现更新\",\n \"pr"
}
]
About this extraction
This page contains the full source code of the liyuec/webSite_Update_Detector GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 13 files (36.3 KB), approximately 8.5k tokens, and a symbol index with 23 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.