[
  {
    "path": ".gitignore",
    "content": ".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-error.log*\npnpm-debug.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": ".npmrc",
    "content": "registry=https://registry.npmjs.org"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 老实的切图Man\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# webSite_Update_Detector\nif web site (spa) updated,we can discover\n"
  },
  {
    "path": "build/rollup.config.js",
    "content": "import {terser} from \"rollup-plugin-minification\";\nimport resolve from '@rollup/plugin-node-resolve';\nimport common from 'rollup-plugin-commonjs';\nimport babel from '@rollup/plugin-babel';\nimport clearDirectory from 'rollup-plugin-clear-directory';\n\nconsole.log('****************rollup.config.js******************')\nexport default{\n    input:'index.js',\n    output:[\n        {\n            file:'./dist/index.esm.js',\n            format:'esm',\n            //sourcemap:true\n            exports:'named'\n        },\n        {\n            file:'./dist/index.umd.js',\n            format:'umd',\n            name:'detectorumd'\n        }\n    ],\n    plugins:[\n        clearDirectory({\n            targets: ['dist']\n          }\n        ),\n        common(),\n        resolve(),\n        terser()\n    ]\n}"
  },
  {
    "path": "build/rollup.link.config.js",
    "content": "import resolve from '@rollup/plugin-node-resolve';\nimport common from 'rollup-plugin-commonjs';\nimport clearDirectory from 'rollup-plugin-clear-directory';\n\nconsole.log('****************rollup.link.config.js******************')\nexport default{\n    input:'index.js',\n    output:[\n        {\n            file:'./dist/index.esm.js',\n            format:'esm',\n            //sourcemap:true\n            exports:'named'\n        },\n        {\n            file:'./dist/index.umd.js',\n            format:'umd',\n            name:'detectorumd'\n        }\n    ],\n    plugins:[\n        clearDirectory({\n            targets: ['dist']\n          }\n        ),\n        common(),\n        resolve()\n    ]\n}"
  },
  {
    "path": "core/config.js",
    "content": "const baseConfig = {\n    //1分钟\n    minInterval:(1000 * 60),\n    //间隔轮询\n    interval:10000,\n    //是否检查当前轮询时间，自动间隔\n    isCheckWaitTime:true,\n    //获取的站点 http || https + host\n    checkSiteHost:'',\n    //每次检查完毕，用户无论干嘛，都增加这个时长\n    intervalAddTime:30000,\n    //最大间隔检查上线\n    maxInterval:1000 * 60,\n    //检查什么\n    checkWho:['script','css']\n}\n\nclass baseClass{\n    constructor(ops){\n        this.interval = ops.interval === void 0 ? baseConfig.interval : ops.interval;\n        this.isCheckWaitTime = ops.isCheckWaitTime === void 0 ? baseConfig.isCheckWaitTime : ops.isCheckWaitTimes;\n        this.checkSiteHost = ops.checkSiteHost;\n        this.intervalAddTime = ops.intervalAddTime === void 0 ? baseConfig.intervalAddTime : ops.intervalAddTime;\n        this.maxInterval = ops.maxInterval === void 0 ? baseConfig.maxInterval : ops.maxInterval;\n        this.checkWho = ops.checkWho === void 0 ? baseConfig.checkWho : ops.checkWho;\n    }\n}\n\n\nconst channelName = 'webUpdateDetector'\nconst channelMsg = {\n    //开始\n    begin:channelName + ':begin',\n    upgradation:channelName + \":upgradation\"\n}\n\nconst localEnum = {\n    detectorName:'localDetector'\n}\n\n\nconst interval = 2000;\nconst expireInterval = 4500;\n\n\nexport {\n    baseConfig,\n    baseClass,\n    channelName,\n    channelMsg,\n    localEnum,\n    interval,\n    expireInterval\n}"
  },
  {
    "path": "core/detector.js",
    "content": "import {baseConfig,baseClass,channelName,channelMsg,localEnum,interval,expireInterval} from './config';\nimport {getWorker} from './worker';\n\nfunction extractLinksAndScripts(){\n    const result = {\n      _css: [],\n      _script: []\n    };\n\n    const links = document.getElementsByTagName('link');\n    for (let i = 0; i < links.length; i++) {\n      const link = links[i];\n      const href = link.getAttribute('href');\n      if (href && (href.endsWith('.css') || href.endsWith('.CSS'))) {\n        result._css.push(href);\n      }\n      \n      if (href && (href.endsWith('.js') || href.endsWith('.JS'))) {\n        result._script.push(href);\n      }\n    }\n\n    const scripts = document.getElementsByTagName('script');\n    for (let i = 0; i < scripts.length; i++) {\n      const script = scripts[i];\n      const src = script.getAttribute('src');\n      if (src && (src.endsWith('.js') || src.endsWith('.JS'))) {\n        result._script.push(src);\n      }\n    }\n\n    return result;\n}\n\n\n/**\n * 暂不考虑不支持webWorker的情况\n */\nclass detector extends baseClass{\n    #isStart = false;\n    #worker = void 0;\n    #scripts = null;\n\n    constructor(opts){\n        super(opts)\n        this.#isStart = false;\n        this.callBack = opts.callBack;\n    }\n\n    start(){\n        if(document.readyState !== 'complete'){\n            setTimeout(()=>{\n              this.start()\n            },interval);\n            return;\n        }\n        if(!this.#isStart){\n            this.#isStart = true;\n\n              this.#worker = getWorker({\n                callBack:this.callBack\n              });\n              \n              if(this.#scripts === null){\n                  this.#scripts = extractLinksAndScripts();\n              }\n\n\n              this.#worker.postMessage({\n                checkSiteHost:this.checkSiteHost,\n                domSccripts:this.#scripts._script,\n                domCsss:this.#scripts._css,\n                checkWho:this.checkWho,\n                msg:'start',\n                interval:this.interval,\n                intervalAddTime:this.intervalAddTime,\n                maxInterval:this.maxInterval\n              })\n\n              window.addEventListener(\"beforeunload\", (e) => {\n                this.#worker.postMessage({\n                  msg:'beforeunload'\n                })\n              })\n        }\n    }\n    stop(){\n        this.#isStart = false;\n        this.#worker.postMessage({msg:'stop'})\n    }\n}\n\n\nexport default detector"
  },
  {
    "path": "core/worker.js",
    "content": "import {channelMsg,localEnum} from './config';\nfunction getWorkerBlobStr(){\n    let js = `\n    self.onmessage = function(e){\n      let {checkSiteHost,domSccripts,domCsss,checkWho,msg,interval,intervalAddTime,maxInterval} = e.data,\n      htmlText = '',\n      errText = '',\n      timeId = null,\n      suminterval = interval,\n      sendChannelIntervalId = null;\n  \n      let expireInterval = 3000,\n      myStartTimeInterval = 4000,\n      /**\n       * channelIntervals\n       * expireTime\n       */\n      otherChannelIntervals = [];\n  \n  \n  \n      function getWebSite(){\n          return new Promise((r,rj)=>{\n              let _checkSiteHost = checkSiteHost + '?timekey=' + new Date().getTime()\n              fetch(_checkSiteHost).then(res=>{\n                  return res.text();\n              }).then(_htmlText =>{\n                  htmlText = _htmlText;\n                  r('ok')\n              }).catch(err=>{\n                  errText = err;\n                  console.warn('get web site error:',errText)\n                  rj('wrong')\n              })\n          })\n      }\n  \n      function extractLinksAndScripts(htmlString) {\n          const css = [];\n          const js = [];\n        \n          // 提取 CSS 链接\n          let startIndex = 0;\n          while (startIndex < htmlString.length) {\n            const linkStartIndex = htmlString.indexOf('<link', startIndex);\n            if (linkStartIndex === -1) {\n              break;\n            }\n            const hrefStartIndex = htmlString.indexOf('href=\"', linkStartIndex);\n            if (hrefStartIndex === -1) {\n              break;\n            }\n            const hrefEndIndex = htmlString.indexOf('\"', hrefStartIndex + 6);\n            if (hrefEndIndex === -1) {\n              break;\n            }\n            const href = htmlString.substring(hrefStartIndex + 6, hrefEndIndex);\n            if (href.endsWith('.css')) {\n              css.push(href);\n            }\n            if(href.endsWith('.js')){\n              js.push(href);\n            }\n            startIndex = hrefEndIndex + 1;\n          }\n        \n          // 提取 JS 链接\n          startIndex = 0;\n          while (startIndex < htmlString.length) {\n            const scriptStartIndex = htmlString.indexOf('<script', startIndex);\n            if (scriptStartIndex === -1) {\n              break;\n            }\n            const srcStartIndex = htmlString.indexOf('src=\"', scriptStartIndex);\n            if (srcStartIndex === -1) {\n              break;\n            }\n            const srcEndIndex = htmlString.indexOf('\"', srcStartIndex + 5);\n            if (srcEndIndex === -1) {\n              break;\n            }\n            const src = htmlString.substring(srcStartIndex + 5, srcEndIndex);\n            if (src.endsWith('.js')) {\n              js.push(src);\n            }\n            startIndex = srcEndIndex + 1;\n          }\n        \n          return { css, js };\n      }\n  \n      function differentValue(arr1, arr2) {\n          var targetArray = arr1.length <= arr2.length ? arr1 : arr2;\n          var otherArray = targetArray === arr1 ? arr2 : arr1;\n        \n          for (var i = 0; i < targetArray.length; i++) {\n            if (targetArray[i] !== otherArray[i]) {\n              return targetArray[i];\n            }\n          }\n        \n          return null;\n      }\n  \n      /*\n        调整自己的myStartTimeInterval；\n        以便主tab挂了或则关闭，其他tab 只会启动单个请求\n        只change自己一次\n      */\n      let isMyChange = false;\n      function myStartTimeIntervalChange(){\n        if(isMyChange){\n          return;\n        }\n  \n       \n  \n        let _myStartTimeInterval = myStartTimeInterval,\n        {result,min,max} = getChannelIntervals();\n  \n        //console.error('我的 _myStartTimeInterval:',_myStartTimeInterval,result.toString());\n  \n        if(result.includes(_myStartTimeInterval)){\n          /* if(min - 500 >= expireInterval){\n            _myStartTimeInterval = min + 500;\n          }else{\n            _myStartTimeInterval = max + 500;\n          } */\n          _myStartTimeInterval = max + 500;\n        }\n        \n        isMyChange = true;\n        //console.error('替换后的 _myStartTimeInterval:',_myStartTimeInterval,result.toString())\n        myStartTimeInterval = _myStartTimeInterval;\n        sendChannelInterval();\n      }\n  \n      function setChannelIntervals(channelIntervals,expireTime,type){\n        if(type === channelMsg.begin){\n          otherChannelIntervals = [];\n        }\n        //console.log('otherChannelIntervals length:',otherChannelIntervals.length)\n        if(Object.prototype.toString.call(channelIntervals).slice(8,-1) === 'Array'){\n          channelIntervals.forEach(i=>{\n            otherChannelIntervals.push({\n              channelIntervals:i.channelIntervals,\n              expireTime:i.expireTime\n            });\n          })\n        }else{\n          otherChannelIntervals.push({\n            channelIntervals:channelIntervals,\n            expireTime:expireTime\n          });\n        }\n      }\n  \n      /**\n       * 超过3秒没更新，算过期，因为每秒都要广播；\n       */\n      function getChannelIntervals(){\n        let _otherChannelIntervals = otherChannelIntervals,\n        res = [],\n        min = void 0,\n        max = void 0,\n        result = [],\n        now = Date.now();\n  \n        _otherChannelIntervals.forEach(i=>{\n          //if(now - i.expireTime <= 3000){\n            res.push(i)\n            result.push(i.channelIntervals)\n          //}\n        })\n  \n        otherChannelIntervals = JSON.parse(JSON.stringify(res));\n  \n        min = Math.min(...result);\n        max = Math.max(...result);\n        return {\n          result,\n          min,\n          max\n        }\n      }\n  \n      const channelName = 'webUpdateDetector'\n      const channelMsg = {\n          //开始\n          begin:channelName + ':begin',\n          interval:channelName + ':interval',\n          script_diff:channelName + ':script_diff',\n          css_diff:channelName + ':css_diff',\n          no_file_update:channelName + ':no_file_update',\n          error:channelName + ':error'\n      }\n  \n      var _channel = getChannel();\n     \n      let SlaveTimerId = null;\n      let setIntervalId = null;\n      function getChannel(opts){\n          const _channnel = new BroadcastChannel(channelName);\n  \n          _channnel.onmessage = function(_messageEvent){\n            let {msg,channelIntervals,expireTime} = _messageEvent.data;\n            \n            switch(msg){\n              //其他tab已经有请求了\n              case channelMsg.begin:\n                clearTimeout(SlaveTimerId);\n                clearInterval(setIntervalId);\n                setChannelIntervals(channelIntervals,expireTime,channelMsg.begin);\n                myStartTimeIntervalChange();\n                SlaveTimerId = setTimeout(() => {\n                  start();\n                }, myStartTimeInterval);\n                //console.log('其他tab已经有请求了:',myStartTimeInterval,msg,channelIntervals,expireTime,otherChannelIntervals)\n              break;\n              //记录其他channel的存活情况；\n              case channelMsg.interval:\n                setChannelIntervals(channelIntervals,expireTime,channelMsg.interval);\n                myStartTimeIntervalChange();\n                //console.log('记录其他channel的存活情况:',myStartTimeInterval,msg,channelIntervals,expireTime,otherChannelIntervals)\n              break;\n              case channelMsg.script_diff:\n                self.postMessage({\n                  update:1,\n                  msg:'script_diff'\n                })\n              break;\n              case channelMsg.css_diff:\n                self.postMessage({\n                    update:1,\n                    msg:'css_diff'\n                })\n              break;\n              case channelMsg.no_file_update:\n                self.postMessage({\n                  update:0,\n                  msg:'no_file_update'\n                })\n              break;\n              case channelMsg.error:\n                self.postMessage({\n                  update:-1,\n                  msg:'error:' + err\n                })\n              break;\n            }\n          }\n          return _channnel;\n      }\n  \n      function sendChannelInterval(isInterval = false){\n        function send(){\n          _channel.postMessage({\n            msg:channelMsg.interval,\n            //channelIntervals:otherChannelIntervals.length > 0 ? otherChannelIntervals : myStartTimeInterval,\n            channelIntervals:myStartTimeInterval,\n            expireTime:Date.now()\n          })\n        }\n        if(isInterval){\n          sendChannelIntervalId = setInterval(()=>{\n            send();\n          },2000);\n        }else{\n          send();\n        }\n  \n      }\n  \n      let isSendChannel = false;\n      function sendChannel(){\n        function send(){\n          \n          _channel.postMessage({\n            msg:channelMsg.begin,\n            channelIntervals:otherChannelIntervals.length > 0 ? otherChannelIntervals : myStartTimeInterval,\n            expireTime:Date.now()\n          })\n          setTimeout(send,2000)\n        }\n        if(!isSendChannel){\n          send();\n        }\n       /*  setIntervalId = setInterval(()=>{\n          send();\n        },2000); */\n      }\n      function start(){\n          if(!!timeId){\n              suminterval = suminterval <= maxInterval ? suminterval += intervalAddTime : suminterval;\n          }\n  \n          sendChannel();\n  \n          getWebSite().then(res=>{\n              let extractObj = extractLinksAndScripts(htmlText),\n              diffResult = null,\n              siteObj = {\n                  _script:[],\n                  _css:[]\n              };\n  \n              siteObj._script = extractObj.js;\n              siteObj._css = extractObj.css;\n  \n  \n              if(checkWho.includes('script')){\n                  diffResult = differentValue(domSccripts,siteObj._script);\n                  if(diffResult !== null){\n                      _channel.postMessage({\n                        msg:channelMsg.script_diff\n                      })\n                      self.postMessage({\n                          update:1,\n                          msg:'script_diff'\n                      })\n                      return;\n                  }\n              }\n      \n              if(checkWho.includes('css')){\n                  diffResult = differentValue(domCsss,siteObj._css);\n                  if(diffResult !== null){\n                      _channel.postMessage({\n                        msg:channelMsg.css_diff\n                      })\n                      self.postMessage({\n                          update:1,\n                          msg:'css_diff'\n                      })\n                      return;\n                  }\n              }\n      \n              timeId = setTimeout(()=>{\n                  _channel.postMessage({\n                    msg:channelMsg.no_file_update\n                  })\n                  self.postMessage({\n                      update:0,\n                      msg:'no_file_update'\n                  })\n                  start();\n              },suminterval)\n              \n          }).catch(err=>{\n              _channel.postMessage({\n                msg:channelMsg.error\n              })\n              self.postMessage({\n                  update:-1,\n                  msg:'error:' + err\n              })\n          })\n      }\n      \n  \n  \n  \n      switch(msg){\n          case 'start':\n            //这里直接频道判断，因为浏览器不会保存之前的消息；\n           /*  setIntervalId = setInterval(()=>{\n              _channel.postMessage({\n                msg:channelMsg.begin\n              })\n            },1000); */\n            SlaveTimerId = setTimeout(()=>{\n                start();\n            },expireInterval)\n              //start();\n          break;\n          case 'stop':\n              clearTimeout(timeId);\n          break;\n          case 'beforeunload':\n              clearInterval(setIntervalId);\n              _channel.close();\n            break;\n      }\n     \n  }\n    `,\n    _blob = new Blob([js],{ type: 'application/javascript' });\n\n    return window.URL.createObjectURL(_blob);\n}\n\nfunction createWorker(opts){\n    let _worker = Object.create(null),\n    callBack = opts.callBack,\n    channel = opts.channel;\n\n    if(window.Worker){\n       \n        _worker = new Worker(getWorkerBlobStr(),{\n            type:'classic',\n            credentials:'omit',\n            name:'webDetectorWorker'\n        })\n        /*\n        let workerUri = new URL('./workerOfFunc.js',import.meta.url)\n         _worker = new Worker(workerUri.toString(),{\n          type:'classic',\n          credentials:'omit',\n          name:'webDetectorWorker'\n      }) */\n    }else{\n        console.error('你的浏览器不支持web worker；web worker支持IE10+')\n    }\n\n    _worker.addEventListener(\"message\", function (e) {\n        let {update,msg} = e.data;\n        switch(update){\n            case -1:\n                channel.postMessage(channelMsg.upgradation)\n                callBack({\n                    update:update,\n                    msg:msg\n                })\n            break;\n            case 0:\n                callBack({\n                    update:update,\n                    msg:msg\n                })\n            break;\n            case 1:\n            //有不一致的情况；\n                callBack({\n                    update:update,\n                    msg:msg\n                })\n            break;\n        }\n    })\n\n    _worker.addEventListener(\"error\", function (e,event) {\n        console.error(`__worker error:`,e,event)\n    })\n\n    _worker.addEventListener(\"messageerror\", function (e,event) {\n        console.error(`__worker error:`,e,event)\n    })\n\n    return _worker;\n}\n \nconst getWorker = (function(){\n    let worker = null;\n    /**\n     * type :预留\n     */\n    return function(opts,type = 'default'){\n        if(worker === null){\n            worker = new createWorker(opts)\n        }\n\n        return worker;\n    }\n})()\n\nexport {\n    getWorker\n}"
  },
  {
    "path": "core/workerOfFunc copy.js",
    "content": "/*\n    const _regexLinkStr = '<link[^>]+href=[\\'\"]([^\\'\"]+\\\\.(css))[\\'\"][^>]*>',\n    _regexLinkJsStr = '<link[^>]+href=[\\'\"]([^\\'\"]+\\\\.(js))[\\'\"][^>]*>',\n    _regexScriptStr = '<script[^>]+src=[\\'\"]([^\"\\']+)[\"\\']';\n                   \n*/\nself.onmessage = function(e){\n    let {checkSiteHost,domSccripts,domCsss,checkWho,msg,interval,intervalAddTime,maxInterval} = e.data,\n    htmlText = '',\n    errText = '',\n    timeId = null,\n    suminterval = interval;\n\n\n    function getWebSite(){\n        return new Promise((r,rj)=>{\n            fetch(checkSiteHost).then(res=>{\n                return res.text();\n            }).then(_htmlText =>{\n                htmlText = _htmlText;\n                r('ok')\n            }).catch(err=>{\n                errText = err;\n                console.warn('get web site error:',errText)\n                rj('wrong')\n            })\n        })\n    }\n\n    function extractLinksAndScripts(str) {\n        str = str.replaceAll('\\x3C','<');\n        str = str.replaceAll('async',' ');\n        str = str.replaceAll('defer',' ');\n        const _regexLinkStr = '<link[^>]+href=[\\\\'\"]([^\\\\'\"]+\\\\\\\\.(css))[\\\\'\"][^>]*>',\n        _regexLinkJsStr = '<link[^>]+href=[\\\\'\"]([^\\\\'\"]+\\\\\\\\.(js))[\\\\'\"][^>]*>',\n\n                          '<script[^>]+src=[\\\\'\"]([^\\']+)[\"\\']'\n        _regexScriptStr = '<script[^>]+src=[\\\\'\"]([^\\\\']+)[\\\\']',\n\n        regexLinkStr = new RegExp(_regexLinkStr, 'g'),\n        regexLinkJsStr = new RegExp(_regexLinkJsStr, 'g'),\n        regexScriptStr = new RegExp(_regexScriptStr, 'g'),\n        matchStr = regexLinkStr.exec(str),\n        matchLinkJsStr = regexLinkJsStr.exec(str),\n        matchScriptStr = regexScriptStr.exec(str),\n        result = {\n          _css: [],\n          _script: []\n        };\n      \n        if(matchStr !== null){\n            matchStr.forEach(i => {\n                result._css.push(i);\n            });\n        }\n\n        if(matchLinkJsStr !== null){\n            matchLinkJsStr.forEach(i => {\n                result._script.push(i);\n            });\n        }\n\n        if(matchScriptStr !== null){\n            matchScriptStr.forEach(i => {\n                if(i !== 'js'){\n                    result._script.push(i);\n                }\n            });\n        }\n       \n        return result;\n    }\n\n    function differentValue(arr1, arr2) {\n        var targetArray = arr1.length <= arr2.length ? arr1 : arr2;\n        var otherArray = targetArray === arr1 ? arr2 : arr1;\n      \n        for (var i = 0; i < targetArray.length; i++) {\n          if (targetArray[i] !== otherArray[i]) {\n            return targetArray[i];\n          }\n        }\n      \n        return null;\n    }\n\n\n    function start(){\n        if(!!timeId){\n            suminterval <= maxInterval ? suminterval += intervalAddTime : suminterval;\n        }\n        getWebSite().then(res=>{\n            let siteObj = extractLinksAndScripts(htmlText),\n            diffResult = null;\n            if(checkWho.includes('script')){\n                diffResult = differentValue(domSccripts,siteObj._script);\n                if(diffResult !== null){\n                    self.postMessage({\n                        update:1,\n                        msg:'script_diff'\n                    })\n                    return;\n                }\n            }\n    \n            if(checkWho.includes('css')){\n                diffResult = differentValue(domCsss,siteObj._css);\n                if(diffResult !== null){\n                    self.postMessage({\n                        update:1,\n                        msg:'css_diff'\n                    })\n                    return;\n                }\n            }\n    \n            timeId = setTimeout(()=>{\n                self.postMessage({\n                    update:0,\n                    msg:'no_file_update'\n                })\n            },suminterval)\n            \n        }).catch(err=>{\n            self.postMessage({\n                update:-1,\n                msg:'error:' + err\n            })\n        })\n    }\n    \n    switch(msg){\n        case 'start':\n            start();\n        break;\n        case 'stop':\n            clearTimeout(timeId);\n        break;\n    }\n   \n   \n}"
  },
  {
    "path": "core/workerOfFunc.js",
    "content": "self.onmessage = function(e){\n    let {checkSiteHost,domSccripts,domCsss,checkWho,msg,interval,intervalAddTime,maxInterval} = e.data,\n    htmlText = '',\n    errText = '',\n    timeId = null,\n    suminterval = interval,\n    sendChannelIntervalId = null;\n\n    let expireInterval = 3000,\n    myStartTimeInterval = 4000,\n    /**\n     * channelIntervals\n     * expireTime\n     */\n    otherChannelIntervals = [];\n\n\n\n    function getWebSite(){\n        return new Promise((r,rj)=>{\n            let _checkSiteHost = checkSiteHost + '?timekey=' + new Date().getTime()\n            fetch(_checkSiteHost).then(res=>{\n                return res.text();\n            }).then(_htmlText =>{\n                htmlText = _htmlText;\n                r('ok')\n            }).catch(err=>{\n                errText = err;\n                console.warn('get web site error:',errText)\n                rj('wrong')\n            })\n        })\n    }\n\n    function extractLinksAndScripts(htmlString) {\n        const css = [];\n        const js = [];\n      \n        // 提取 CSS 链接\n        let startIndex = 0;\n        while (startIndex < htmlString.length) {\n          const linkStartIndex = htmlString.indexOf('<link', startIndex);\n          if (linkStartIndex === -1) {\n            break;\n          }\n          const hrefStartIndex = htmlString.indexOf('href=\"', linkStartIndex);\n          if (hrefStartIndex === -1) {\n            break;\n          }\n          const hrefEndIndex = htmlString.indexOf('\"', hrefStartIndex + 6);\n          if (hrefEndIndex === -1) {\n            break;\n          }\n          const href = htmlString.substring(hrefStartIndex + 6, hrefEndIndex);\n          if (href.endsWith('.css')) {\n            css.push(href);\n          }\n          if(href.endsWith('.js')){\n            js.push(href);\n          }\n          startIndex = hrefEndIndex + 1;\n        }\n      \n        // 提取 JS 链接\n        startIndex = 0;\n        while (startIndex < htmlString.length) {\n          const scriptStartIndex = htmlString.indexOf('<script', startIndex);\n          if (scriptStartIndex === -1) {\n            break;\n          }\n          const srcStartIndex = htmlString.indexOf('src=\"', scriptStartIndex);\n          if (srcStartIndex === -1) {\n            break;\n          }\n          const srcEndIndex = htmlString.indexOf('\"', srcStartIndex + 5);\n          if (srcEndIndex === -1) {\n            break;\n          }\n          const src = htmlString.substring(srcStartIndex + 5, srcEndIndex);\n          if (src.endsWith('.js')) {\n            js.push(src);\n          }\n          startIndex = srcEndIndex + 1;\n        }\n      \n        return { css, js };\n    }\n\n    function differentValue(arr1, arr2) {\n        var targetArray = arr1.length <= arr2.length ? arr1 : arr2;\n        var otherArray = targetArray === arr1 ? arr2 : arr1;\n      \n        for (var i = 0; i < targetArray.length; i++) {\n          if (targetArray[i] !== otherArray[i]) {\n            return targetArray[i];\n          }\n        }\n      \n        return null;\n    }\n\n    /*\n      调整自己的myStartTimeInterval；\n      以便主tab挂了或则关闭，其他tab 只会启动单个请求\n      只change自己一次\n    */\n    let isMyChange = false;\n    function myStartTimeIntervalChange(){\n      if(isMyChange){\n        return;\n      }\n\n     \n\n      let _myStartTimeInterval = myStartTimeInterval,\n      {result,min,max} = getChannelIntervals();\n\n      //console.error('我的 _myStartTimeInterval:',_myStartTimeInterval,result.toString());\n\n      if(result.includes(_myStartTimeInterval)){\n        /* if(min - 500 >= expireInterval){\n          _myStartTimeInterval = min + 500;\n        }else{\n          _myStartTimeInterval = max + 500;\n        } */\n        _myStartTimeInterval = max + 500;\n      }\n      \n      isMyChange = true;\n      //console.error('替换后的 _myStartTimeInterval:',_myStartTimeInterval,result.toString())\n      myStartTimeInterval = _myStartTimeInterval;\n      sendChannelInterval();\n    }\n\n    function setChannelIntervals(channelIntervals,expireTime,type){\n      if(type === channelMsg.begin){\n        otherChannelIntervals = [];\n      }\n      //console.log('otherChannelIntervals length:',otherChannelIntervals.length)\n      if(Object.prototype.toString.call(channelIntervals).slice(8,-1) === 'Array'){\n        channelIntervals.forEach(i=>{\n          otherChannelIntervals.push({\n            channelIntervals:i.channelIntervals,\n            expireTime:i.expireTime\n          });\n        })\n      }else{\n        otherChannelIntervals.push({\n          channelIntervals:channelIntervals,\n          expireTime:expireTime\n        });\n      }\n    }\n\n    /**\n     * 超过3秒没更新，算过期，因为每秒都要广播；\n     */\n    function getChannelIntervals(){\n      let _otherChannelIntervals = otherChannelIntervals,\n      res = [],\n      min = void 0,\n      max = void 0,\n      result = [],\n      now = Date.now();\n\n      _otherChannelIntervals.forEach(i=>{\n        //if(now - i.expireTime <= 3000){\n          res.push(i)\n          result.push(i.channelIntervals)\n        //}\n      })\n\n      otherChannelIntervals = JSON.parse(JSON.stringify(res));\n\n      min = Math.min(...result);\n      max = Math.max(...result);\n      return {\n        result,\n        min,\n        max\n      }\n    }\n\n    const channelName = 'webUpdateDetector'\n    const channelMsg = {\n        //开始\n        begin:channelName + ':begin',\n        interval:channelName + ':interval',\n        script_diff:channelName + ':script_diff',\n        css_diff:channelName + ':css_diff',\n        no_file_update:channelName + ':no_file_update',\n        error:channelName + ':error'\n    }\n\n    var _channel = getChannel();\n   \n    let SlaveTimerId = null;\n    let setIntervalId = null;\n    function getChannel(opts){\n        const _channnel = new BroadcastChannel(channelName);\n\n        _channnel.onmessage = function(_messageEvent){\n          let {msg,channelIntervals,expireTime} = _messageEvent.data;\n          \n          switch(msg){\n            //其他tab已经有请求了\n            case channelMsg.begin:\n              clearTimeout(SlaveTimerId);\n              clearInterval(setIntervalId);\n              setChannelIntervals(channelIntervals,expireTime,channelMsg.begin);\n              myStartTimeIntervalChange();\n              SlaveTimerId = setTimeout(() => {\n                start();\n              }, myStartTimeInterval);\n              //console.log('其他tab已经有请求了:',myStartTimeInterval,msg,channelIntervals,expireTime,otherChannelIntervals)\n            break;\n            //记录其他channel的存活情况；\n            case channelMsg.interval:\n              setChannelIntervals(channelIntervals,expireTime,channelMsg.interval);\n              myStartTimeIntervalChange();\n              //console.log('记录其他channel的存活情况:',myStartTimeInterval,msg,channelIntervals,expireTime,otherChannelIntervals)\n            break;\n            case channelMsg.script_diff:\n              self.postMessage({\n                update:1,\n                msg:'script_diff'\n              })\n            break;\n            case channelMsg.css_diff:\n              self.postMessage({\n                  update:1,\n                  msg:'css_diff'\n              })\n            break;\n            case channelMsg.no_file_update:\n              self.postMessage({\n                update:0,\n                msg:'no_file_update'\n              })\n            break;\n            case channelMsg.error:\n              self.postMessage({\n                update:-1,\n                msg:'error:' + err\n              })\n            break;\n          }\n        }\n        return _channnel;\n    }\n\n    function sendChannelInterval(isInterval = false){\n      function send(){\n        _channel.postMessage({\n          msg:channelMsg.interval,\n          //channelIntervals:otherChannelIntervals.length > 0 ? otherChannelIntervals : myStartTimeInterval,\n          channelIntervals:myStartTimeInterval,\n          expireTime:Date.now()\n        })\n      }\n      if(isInterval){\n        sendChannelIntervalId = setInterval(()=>{\n          send();\n        },2000);\n      }else{\n        send();\n      }\n\n    }\n\n    let isSendChannel = false;\n    function sendChannel(){\n      function send(){\n        \n        _channel.postMessage({\n          msg:channelMsg.begin,\n          channelIntervals:otherChannelIntervals.length > 0 ? otherChannelIntervals : myStartTimeInterval,\n          expireTime:Date.now()\n        })\n        setTimeout(send,2000)\n      }\n      if(!isSendChannel){\n        send();\n      }\n     /*  setIntervalId = setInterval(()=>{\n        send();\n      },2000); */\n    }\n    function start(){\n        if(!!timeId){\n            suminterval = suminterval <= maxInterval ? suminterval += intervalAddTime : suminterval;\n        }\n\n        sendChannel();\n\n        getWebSite().then(res=>{\n            let extractObj = extractLinksAndScripts(htmlText),\n            diffResult = null,\n            siteObj = {\n                _script:[],\n                _css:[]\n            };\n\n            siteObj._script = extractObj.js;\n            siteObj._css = extractObj.css;\n\n\n            if(checkWho.includes('script')){\n                diffResult = differentValue(domSccripts,siteObj._script);\n                if(diffResult !== null){\n                    _channel.postMessage({\n                      msg:channelMsg.script_diff\n                    })\n                    self.postMessage({\n                        update:1,\n                        msg:'script_diff'\n                    })\n                    return;\n                }\n            }\n    \n            if(checkWho.includes('css')){\n                diffResult = differentValue(domCsss,siteObj._css);\n                if(diffResult !== null){\n                    _channel.postMessage({\n                      msg:channelMsg.css_diff\n                    })\n                    self.postMessage({\n                        update:1,\n                        msg:'css_diff'\n                    })\n                    return;\n                }\n            }\n    \n            timeId = setTimeout(()=>{\n                _channel.postMessage({\n                  msg:channelMsg.no_file_update\n                })\n                self.postMessage({\n                    update:0,\n                    msg:'no_file_update'\n                })\n                start();\n            },suminterval)\n            \n        }).catch(err=>{\n            _channel.postMessage({\n              msg:channelMsg.error\n            })\n            self.postMessage({\n                update:-1,\n                msg:'error:' + err\n            })\n        })\n    }\n    \n\n\n\n    switch(msg){\n        case 'start':\n          //这里直接频道判断，因为浏览器不会保存之前的消息；\n         /*  setIntervalId = setInterval(()=>{\n            _channel.postMessage({\n              msg:channelMsg.begin\n            })\n          },1000); */\n          SlaveTimerId = setTimeout(()=>{\n              start();\n          },expireInterval)\n            //start();\n        break;\n        case 'stop':\n            clearTimeout(timeId);\n        break;\n        case 'beforeunload':\n            clearInterval(setIntervalId);\n            _channel.close();\n          break;\n    }\n   \n   \n}"
  },
  {
    "path": "index.js",
    "content": "import detector from './core/detector'\n\nconst createDetector = (function(){\n    let _detector = null;\n\n    return function(opts){\n        if(_detector === null){\n           _detector = new detector(opts);\n        }\n\n        return _detector;\n    }\n})()\n\n\nexport default createDetector;"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"website_update_detector\",\n  \"version\": \"1.2.0\",\n  \"description\": \"web前端主动发现站点发布更新 客户端更新 主动发现更新 发现更新\",\n  \"private\": false,\n  \"type\": \"module\",\n  \"main\": \"./dist/index.umd.js\",\n  \"module\": \"./dist/index.esm.js\",\n  \"keywords\": [\n    \"web前端主动发现站点发布更新\",\n    \" 客户端更新\",\n    \"主动发现更新\",\n    \"发现更新\"\n  ],\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"rolluplink\": \"npm run build:link\",\n    \"rollupbuild\": \"npm run build:esm\",\n    \"build:esm\": \"rollup -c ./build/rollup.config.js\",\n    \"build:link\": \"rollup -c ./build/rollup.link.config.js\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/liyuec/webSite_Update_Detector.git\"\n  },\n  \"author\": \"liyuec\",\n  \"license\": \"MIT\",\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\",\n    \"not dead\"\n  ],\n  \"devDependencies\": {\n    \"@rollup/plugin-babel\": \"^6.0.3\",\n    \"@rollup/plugin-node-resolve\": \"^15.1.0\",\n    \"rollup\": \"^3.26.2\",\n    \"rollup-plugin-clear-directory\": \"^1.0.1\",\n    \"rollup-plugin-commonjs\": \"^10.1.0\",\n    \"rollup-plugin-copy\": \"^3.4.0\",\n    \"rollup-plugin-minification\": \"^0.2.0\",\n    \"rollup-plugin-postcss\": \"^4.0.2\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/liyuec/webSite_Update_Detector/issues\"\n  },\n  \"homepage\": \"https://github.com/liyuec/webSite_Update_Detector#readme\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  }
]