MediaWiki:Gadget-Notification.js:修订间差异

H萌娘,万物皆可H的百科全书!
跳到导航 跳到搜索
imported>=海豚=
(导入1个版本)
imported>=海豚=
无编辑摘要
第1行: 第1行:
$(function(){
$(() => {
    if(/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)){
  const notificationIcon = 'https://img.moegirl.org.cn/common/thumb/f/f6/%E8%93%9D%E8%90%8C%E5%AD%97.png/233px-%E8%93%9D%E8%90%8C%E5%AD%97.png'
     return false
  const workerUrl = window.mw ?
'/index.php?title=用户:東東君/js/notification.js/worker.js&action=raw&ctype=text/javascript' :
'worker.js'
 
  checkNotificationPermission()
  main()
 
  async function main() {
    const worker = await registerService()
   const configListener = new WorkerDataListener(worker, 'config')
   const uiController = initUI({ onSaveConfig })
 
   configListener.pullData()
 
   configListener.addListener(data => {
    uiController.updateConfig(data)
   })
 
   function onSaveConfig(config) {
    worker.postMessage({ type: 'saveConfig', data: { config } })
    alert('配置已保存')
    }
    }
   var lock=setInterval(function(){
  }
     if(mw.Api){
       clearInterval(lock);
       function nct(nct, url, life) {
         var life = life || 8000
         if (url) { nct.onclick = function () { open(url, '_blank') } }
         nct.addEventListener('click', function () {
           setTimeout(function () {
             nct.close()
           })
         }, 500)
         setTimeout(function () {
           nct.close()
         }, life)
       }


       function setCookie(cname, cvalue, GMTStr) {
  navigator.serviceWorker.addEventListener('message', e => {
          var expires = "expires=" + GMTStr;
   const { type, data } = e.data
          document.cookie = cname + "=" + cvalue + "; " + expires;
   if (type === 'sendNotification') {
        }
    const [title, options] = data
    const link = options.data.link
    new Notification(title, options).onclick = () => window.open(link, '_blank')
   }
  })
 
  function initUI(
   onSaveConfig
  }) {
   const template = `
    <div class="widget-notification-config" style="display:none;">
     <div class="body"> 
      <fieldset>
       <legend>设置</legend>
       <form class="settings" action="javascript:void(0)">
        <label>
         <span>开启</span>
          <input type="checkbox" name="enable">
        </label>
 
        <label>
         <div>排除条目:</div>
          <textarea placeholder="当其中的条目发生变化时不会通知,使用换行分割" name="excludeList"></textarea>
        </label>
 
        <div class="buttonGroup">
         <button class="saveConfig">保存</button>
         <button class="closeModal">关闭</button>
        </div>
       </form>
      </fieldset>
     </div>
    </div>
   `
 
   const cssStyle = `
    <style>
     .widget-notification-config {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-color: rgba(0, 0, 0, 0.2);
      font-size: 14px;
      z-index: 100;
     }
 
     .widget-notification-config > .body {
      background-color: white;
      width: 300px;
      box-sizing: border-box;
      padding: 10px;
      border-radius: 5px;
      border: 2px #ccc ridge;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
     }
 
     .widget-notification-config > .body fieldset {
      margin: 0;
      padding-left: 15px;
      padding-right: 15px;
     }
 
     .widget-notification-config > .body .settings > label {
      display: block;
     }
 
     .widget-notification-config > .body .settings > label > * {
      vertical-align: middle;
     }
 
     .widget-notification-config > .body .settings textarea[name="excludeList"] {
      width: -webkit-fill-available;
      resize: none;
      margin-top: 5px;
      height: 100px;
      border: 1px #666 solid;
     }
    
     .widget-notification-config > .body .settings .buttonGroup {
      margin: 0 auto;
      margin-top: 10px;
      display: table;
     }
 
     .widget-notification-config > .body .settings .buttonGroup button {
      margin: 0 5px;
     }
    </style>
   `
 
   $('body').append(template).append(cssStyle)
 
   const rootEl = $('.widget-notification-config')
   const settingsEl = rootEl.find('.settings')
 
   // 注入按钮
   $('#p-cactions ul').append('<li id="widget-notification-showConfig"><a title="实时通知">实时通知</a></li>')
 
   // modal显示事件
   $('#widget-notification-showConfig').on('click', () => rootEl.show())
 
   // modal关闭事件
   rootEl.find('.closeModal').on('click', () => rootEl.hide())
 
   // 保存配置
   rootEl.find('.saveConfig').on('click', e => {
    const formValues = {
     enable: settingsEl.find('[name="enable"]').prop('checked'),
     listenNotification: settingsEl.find('[name="listenNotification"]').prop('checked'),
     excludeList: settingsEl.find('[name="excludeList"]').val().trim().split('\n')
    }
 
    onSaveConfig(formValues)
   })
 
   function updateConfig({ enable, listenNotification, excludeList }) {
    settingsEl.find('[name="enable"]').prop('checked', enable)
    settingsEl.find('[name="listenNotification"]').prop('checked', listenNotification)
    settingsEl.find('[name="excludeList"]').val(excludeList.join('\n'))
   }
 
   return {
    updateConfig
   }
  }
 
  function registerService() {
   return new Promise(async (resolve, reject) => {
    if (Notification === undefined || navigator.serviceWorker === undefined) {
     alert('当前浏览器不支持通知插件,请从您的用户页中移除!')
     return reject()
    }
   
    if(Notification.permission === 'default') {
     alert('点击确定关闭这条消息后,将弹出授予通知权限的窗口,届时请点击“允许”,以保证插件的正常运行。')
    }
  
    await new Promise(Notification.requestPermission)
    const status = Notification.permission
  
    if (status === 'granted') {
     if (localStorage.getItem('moegirl-widget-notification-permission-granted') !== 'true') {
      localStorage.setItem('moegirl-widget-notification-permission-granted', 'true')
      new Notification('欢迎', {
       body: '您已经成功开启实时通知功能,欢迎使用!',
        icon: notificationIcon
      })
     }
    }
  
    if (status === 'denied') {
     alert('您未能正确授予通知权限,若要继续使用,请在url栏左侧点击小锁标志开启权限,若无法设置,请从你的用户页删去此插件。')
     return reject()
    }
 
    const serviceWorkerRegistration = await navigator.serviceWorker.register(workerUrl)
    await navigator.serviceWorker.ready
    resolve(serviceWorkerRegistration.active)
   })
  }


       function getCookie(cname) {
  function checkNotificationPermission() {
         var name = cname + "=";
   if (localStorage.getItem('moegirl-widget-notification-permission-granted') === 'true' && Notification.permission !== 'granted') {
         var ca = document.cookie.split(';');
    localStorage.removeItem('moegirl-widget-notification-permission-granted')
         for (var i = 0; i < ca.length; i++) {
   }
           var c = ca[i].trim();
  }
           if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
 
         }
  class WorkerDataListener {
         return "";
   broadcastName = ''
       }
   worker = null
   data = null
   #incrementPullId = 0


       function serviceToken(flag) {
   get pullEventName() { return 'get-' + this.broadcastName }
         var flag = flag || false
   get broadcastChannelName() { return 'channel-' + this.broadcastName }
         var d = new Date()
  
         if (flag) {
   constructor(worker, broadcastName) {
           d.setTime(d.getTime() + (2 * 60 * 1000))
    this.broadcastName = broadcastName
           setCookie('widget-notification-serviceOn', 'true', d.toGMTString())
    this.worker = worker
         } else {
    this.broadcastChannel = new BroadcastChannel(this.broadcastChannelName)
           d.setTime(d.getTime() - 1)
 
           setCookie('widget-notification-serviceOn', '', d.toGMTString())
    this.addListener(data => this.data = data)
         }
   }
       }
 
   addListener(handler) {
    const usingHandler = e => {
     const { type, data, id } = e.data
     type === 'broadcast' && handler(data, id)
    }


       function isObjectValueEqual(a, b) {
    this.broadcastChannel.addEventListener('message', usingHandler)
         var aProps = Object.getOwnPropertyNames(a);
    return () => this.broadcastChannel.removeEventListener('message', usingHandler)
         var bProps = Object.getOwnPropertyNames(b);
   }
 
 
         if (aProps.length != bProps.length) {
   pullData() {
           return false;
    const pullId = this.#incrementPullId++
         }
    this.worker.postMessage({ type: this.pullEventName, data: { id: pullId } })
 
    return pullId
         for (var i = 0; i < aProps.length; i++) {
   }
           var propName = aProps[i];
 
           if (a[propName] !== b[propName]) {
   getData() {
             return false;
    return new Promise(resolve => {
           }
     const pullId = this.pullData()
         }
     const removeListener = this.addListener((data, id) => {
         return true;
      if (id !== pullId) { return }
       }
      resolve(data)
 
      removeListener()
       if (typeof Notification == 'undefined') {
     })
         mw.notify('你的浏览器版本过低或不支持某些功能,无法使用Web Notification插件!', { type: 'warn' })
    })
         setTimeout(function () {
   }
           mw.notify('请从你的用户页移除该插件,以消除这些警告!', { type: 'warn' });
  }
         }, 2000)
         return false
       }
 
       if (Notification.permission == 'default') {
         alert('点击确定关闭这条消息后,将弹出授予通知权限的窗口,届时请点击“允许”,以保证插件的正常运行。')
       }
 
       Notification.requestPermission(function () {
         var status = Notification.permission
         if (status == 'granted') {
           if (!localStorage.getItem('widget-notification-messageMark')) {
             localStorage.setItem('widget-notification-messageMark', 'true')
             nct(new Notification('欢迎', {
               body: '您已经成功开启通知功能,欢迎使用!',
               icon: 'https://fgo.wiki/resources/assets/wiki.png'
             }))
           }
         }
         if (status == 'denied') {
           alert('您未能正确授予通知权限,若要继续使用,请在url栏左侧点击小锁标志开启权限,若无法设置,请从你的用户页删去此插件。')
         }
       })
 
       var api = new mw.Api()
 
       var userName = ''
       api.get({
         "action": "query",
         "format": "json",
         "meta": "userinfo",
         "utf8": 1
       }).done(function (data) {
         userName = data.query.userinfo.name
       })
 
       var pages = []
       var pagesStr = ''
       var apiContinue = null
       function getWatch() {
         var request = {
           "action": "query",
           "format": "json",
           "list": "watchlistraw",
           "utf8": 1,
           "wrlimit": "max"
         }
         if (apiContinue) {
           request.wrcontinue = apiContinue.wrcontinue
           request['continue'] = apiContinue['continue']
         } else {
           delete request.wrcontinue
           delete request['continue']
         }
         api.get(request).done(function (data) {
           apiContinue = data['continue']
           var watch = data.watchlistraw
           $.each(watch, function () {
             if (this.title != 'User talk:' + userName) {
               pages.push(this.title)
             }
           })
           pagesStr = pages.join('|')
           localStorage.setItem('widget-notification-watchList', pagesStr)
           apiContinue && getWatch()
         })
       }
 
       if (location.href == encodeURIComponent('https://fgo.wiki/w/Special:编辑监视列表')) {
         if (/已从您的监视列表移除%d{1,2}个标题/.test($('#mw-content-text').text())) {
           pages = []
           getWatch()
         }
       }
 
       function setWatch(act) {
         var act = act || 'push'
         var watch = localStorage.getItem('widget-notification-watchList').split('|')
         var thisTitle = location.href.replace(/https\:\/\/fgo\.wiki\/w\/(.+)$/, '$1')
         if (/(index\.php|action=edit)/.test(thisTitle)) {
           thisTitle = thisTitle.replace(/.*title=(.+?)(&.+|$)/, '$1')
         }
         thisTitle = decodeURIComponent(thisTitle)
         var talkTitle = 'Talk:' + thisTitle
         var npRE = /(Template|Mooncell|模块|User|Help):(.+)/
         if (npRE.test(thisTitle)) {
           var prefix = thisTitle.replace(npRE, '$1')
           var name = thisTitle.replace(npRE, '$2')
         }
         if (/(Template|Mooncell|User|Help)/.test(prefix)) {
           talkTitle = prefix + ' talk:' + name
         }
         if (prefix == '模块') {
           talkTitle = '模块讨论:' + name
         }
         if (act == 'push') {
           var flag = true
           $.each(watch, function () {
             if (this == thisTitle || this == talkTitle) { flag = false }
           })
           flag && watch.push(thisTitle, talkTitle)
         }
         (act == 'remove') && $.each(watch, function (i) {
           if (this == thisTitle || this == talkTitle) {
             watch[i] = ''
           }
         })
 
         var watchStr = watch.join('|')
         localStorage.setItem('widget-notification-watchList', watchStr)
       }
 
       $('#ca-watch, #ca-unwatch').mousedown(function () {
         (this.id.toLowerCase() == 'ca-watch') && setWatch()
           ; (this.id.toLowerCase() == 'ca-unwatch') && setWatch('remove')
       })
       $('#mw-editpage-watch').mouseup(function () {
         $('#wpWatchthis').is(':checked') ? setWatch('remove') : setWatch()  // 此处获得了false为选中,true为未选中
       })
 
       getCookie('widget-notification-serviceOn') != 'true' && main()
       var watchdogKey = setInterval(function () {
         if (getCookie('widget-notification-serviceOn') != 'true') {
           main()
           clearInterval(watchdogKey)
         }
       }, 5000 + Math.random())
 
 
       function main() {
         console.log('notification.js已开启服务,开始进行消息与监视列表的通知推送。')
         serviceToken(true)
           ; (function serviceStart() {
             setTimeout(function () {
               serviceToken(true)
               serviceStart()
             }, 60 * 1000)
           })()
 
         $(window).on('unload', function () {
           serviceToken()
         })
 
         if (!getCookie('widget-notification-removeTime')) {
           var d = new Date()
           d.setTime(d.getTime() + (24 * 60 * 60 * 1000))
           setCookie('widget-notification-removeTime', d.toGMTString(), d.toGMTString())
         }
 
         ; (function () {
           function getTimeISO8061() {
             var fun = function (num) {
               return num < 10 ? '0' + num : num
             }
             var time = new Date()
             var y = time.getUTCFullYear(),
               m = fun(time.getUTCMonth() + 1),
               d = fun(time.getUTCDate()),
               h = fun(time.getUTCHours()),
               i = fun(time.getUTCMinutes()),
               s = fun(time.getUTCSeconds())
             var timeStr = y + '-' + m + '-' + d + 'T' + h + ':' + i + ':' + s + 'Z'
             return timeStr
           }
 
           setTimeout(function () {
             getWatch()
             console.log('notification.js:已获取监视列表')
           }, 30000)
 
           var lastSearchTime = getTimeISO8061()
           setInterval(function () {
             var usedStr = getCookie('widget-notification-usedIds')
             if (usedStr) {
               var used = usedStr.split('|')
             } else {
               var used = []
             }
 
             var rcidsStr = getCookie('widget-notification-rcids')
             if (rcidsStr) {
               var rcids = rcidsStr.split('|')
             } else {
               var rcids = []
             }
 
             var memoryTime = getCookie('widget-notification-lastSearchTime')
             if (memoryTime) {
               lastSearchTime = memoryTime
             }
             api.get({
               action: 'query',
               format: 'json',
               prop: '',
               list: 'recentchanges',
               rcend: lastSearchTime,
               rclimit: 'max',
               utf8: 1,
               formatversion: 1,
               rctoponly: 1,
               rcexcludeuser: userName,
               rcprop: 'title|user|comment|ids'
             }).done(function (data) {
               var d = new Date()
               d.setTime(d.getTime() + (30 * 60 * 1000))
               setCookie('widget-notification-lastSearchTime', getTimeISO8061(), d.toGMTString())
               var query = data.query.recentchanges
               var filter = []
               var pages = localStorage.getItem('widget-notification-watchList').split('|')
               $.each(query, function () {
                 for (var i = 0; i < pages.length; i++) {
                   if (this.title == pages[i]) {
                     var flag = true
                     for (var j = 0; j < filter.length; j++) {
                       if (isObjectValueEqual(this, filter[j])) {
                         flag = false
                       }
                     }
                     flag && filter.push(this)
                   }
                 }
               })
 
               $.each(filter, function () {
                 var mark = true
                 for (var i = 0; i < rcids.length; i++) {
                   if (this.rcids == rcids[i]) {
                     mark = false
                   }
                 }
 
                 if (mark) {
                   var commentStr = '在【' + this.title + '】作出了编辑。'
                   if (this.comment) {
                     this.comment = this.comment.replace('// Edit via Wikiplus', '')
                     var chapterRE = /\/\* (.+?) \*\/([^\/]*)/
                     if (chapterRE.test(this.comment)) {
                       var chapter = this.comment.replace(chapterRE, '$1')
                       var comment = this.comment.replace(chapterRE, '$2').replace(/[\s]*(.+)[\s]*/, '$1')
                       if (chapter && (chapter != this.comment)) {
                         commentStr += '\n章节 → ' + chapter
                       }
                       if (comment && (comment != this.comment)) {
                         commentStr += '\n摘要:' + comment
                       }
                     } else {
                       commentStr += '\n摘要:' + this.comment.replace(/[\s]*(.+)[\s]*/, '$1')
                     }
 
                   }
                   nct(new Notification(this.user, {
                     body: commentStr,
                     icon: '//fgo.wiki/extensions/Avatar/avatar.php?user=' + this.user
                   }), 'https://fgo.wiki/index.php?title=' + this.title + '&curid=' + this.pageid + '&diff=' + this.revid + '&oldid=' + this.old_revid)
                   console.log('编辑者:' + this.user + '\n' + commentStr)
                   rcids.push(this.rcid)
                 }
                 var rcidsStr = rcids.join('|')
                 setCookie('widget-notification-rcids', rcidsStr, getCookie('widget-notification-removeTime'))
               })
             })
 
             api.get({
               action: "query",
               format: "json",
               prop: "",
               meta: "notifications",
               utf8: 1,
               notfilter: "!read",
               notlimit: "max"
             }).done(function (data) {
               var query = data.query.notifications.list
               $.each(query, function () {
                 var msgNct = function (text, $this) {
                   nct(new Notification($this.agent.name + ':', {
                     body: text,
                     icon: '//fgo.wiki/extensions/Avatar/avatar.php?user=' + $this.agent.name,
                     sticky: true
                   }), 'https://fgo.wiki/w/' + $this.title.full, 9999999999)
                   console.log($this.agent.name + ':' + text)
                 }
 
                 var mark = true
                 for (var i = 0; i < used.length; i++) {
                   if (this.id == used[i]) {
                     mark = false
                   }
                 }
 
                 if (mark) {
                   switch (this.type) {
                     case 'edit-thank': {
                       msgNct('对您在【' + this.title.full + '】的编辑表示了感谢!', this)
                       break
                     }
                     case 'edit-user-talk': {
                       msgNct('在您的讨论页留言了。', this)
                       break
                     }
                     case 'flowthread_reply': {
                       msgNct('回复了您在【' + this.title.full + '】的评论。', this)
                       break
                     }
                     case 'flowthread_userpage': {
                       msgNct('在您的用户页留下了评论。', this)
                       break
                     }
                     case 'flowthread_delete': {
                       nct(new Notification('通知', {
                         body: '您在【' + this.title.full + '】下的评论被删除了。',
                         icon: 'https://fgo.wiki/resources/assets/wiki.png',
                         sticky: true
                       }), 'https://fgo.wiki/w/' + this.title.full, 9999999999)
                       console.log('评论被删除:在页面【' + this.title.full + '】上')
                       break
                     }
                     case 'mention': {
                       msgNct('在【' + this.title.full + '】中提到了您。', this)
                       break
                     }
                   }
                   used.push(this.id)
                 }
               })
               var usedStr = used.join('|')
               setCookie('widget-notification-usedIds', usedStr, getCookie('widget-notification-removeTime'))
             })
 
           }, 60000)
 
         })()
 
       }
     }
   },1)
})
})

2021年8月7日 (六) 11:52的版本

$(() => {
  const notificationIcon = 'https://img.moegirl.org.cn/common/thumb/f/f6/%E8%93%9D%E8%90%8C%E5%AD%97.png/233px-%E8%93%9D%E8%90%8C%E5%AD%97.png'
  const workerUrl = window.mw ?
	'/index.php?title=用户:東東君/js/notification.js/worker.js&action=raw&ctype=text/javascript' :
	'worker.js'
  
  checkNotificationPermission()
  main()
  
  async function main() {
    const worker = await registerService()
    const configListener = new WorkerDataListener(worker, 'config')
    const uiController = initUI({ onSaveConfig })
  
    configListener.pullData()

    configListener.addListener(data => {
      uiController.updateConfig(data)
    })
  
    function onSaveConfig(config) {
      worker.postMessage({ type: 'saveConfig', data: { config } })
      alert('配置已保存')
    }
  }

  navigator.serviceWorker.addEventListener('message', e => {
    const { type, data } = e.data
    if (type === 'sendNotification') {
      const [title, options] = data
      const link = options.data.link
      new Notification(title, options).onclick = () => window.open(link, '_blank')
    }
  })
  
  function initUI({ 
    onSaveConfig
  }) {
    const template = `
      <div class="widget-notification-config" style="display:none;">
        <div class="body">  
          <fieldset>
            <legend>设置</legend>
            <form class="settings" action="javascript:void(0)">
              <label>
                <span>开启</span>
                <input type="checkbox" name="enable">
              </label>
  
              <label>
                <div>排除条目:</div>
                <textarea placeholder="当其中的条目发生变化时不会通知,使用换行分割" name="excludeList"></textarea>
              </label>
  
              <div class="buttonGroup">
                <button class="saveConfig">保存</button>
                <button class="closeModal">关闭</button>
              </div>
            </form>
          </fieldset>
        </div>
      </div>
    `
  
    const cssStyle = `
      <style>
        .widget-notification-config {
          position: fixed;
          top: 0; 
          left: 0;
          right: 0;
          bottom: 0;
          background-color: rgba(0, 0, 0, 0.2);
          font-size: 14px;
          z-index: 100;
        }
  
        .widget-notification-config > .body {
          background-color: white;
          width: 300px;
          box-sizing: border-box;
          padding: 10px;
          border-radius: 5px;
          border: 2px #ccc ridge;
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
        }
  
        .widget-notification-config > .body fieldset {
          margin: 0;
          padding-left: 15px;
          padding-right: 15px;
        }
  
        .widget-notification-config > .body .settings > label {
          display: block;
        }
  
        .widget-notification-config > .body .settings > label > * {
          vertical-align: middle;
        }
  
        .widget-notification-config > .body .settings textarea[name="excludeList"] {
          width: -webkit-fill-available;
          resize: none;
          margin-top: 5px;
          height: 100px;
          border: 1px #666 solid;
        }
        
        .widget-notification-config > .body .settings .buttonGroup {
          margin: 0 auto;
          margin-top: 10px;
          display: table;
        }
  
        .widget-notification-config > .body .settings .buttonGroup button {
          margin: 0 5px;
        }
      </style>
    `
  
    $('body').append(template).append(cssStyle)
  
    const rootEl = $('.widget-notification-config')
    const settingsEl = rootEl.find('.settings')
  
    // 注入按钮
    $('#p-cactions ul').append('<li id="widget-notification-showConfig"><a title="实时通知">实时通知</a></li>')
  
    // modal显示事件
    $('#widget-notification-showConfig').on('click', () => rootEl.show())
  
    // modal关闭事件
    rootEl.find('.closeModal').on('click', () => rootEl.hide())
  
    // 保存配置
    rootEl.find('.saveConfig').on('click', e => {
      const formValues = {
        enable: settingsEl.find('[name="enable"]').prop('checked'),
        listenNotification: settingsEl.find('[name="listenNotification"]').prop('checked'),
        excludeList: settingsEl.find('[name="excludeList"]').val().trim().split('\n')
      }
  
      onSaveConfig(formValues)
    })
  
    function updateConfig({ enable, listenNotification, excludeList }) {
      settingsEl.find('[name="enable"]').prop('checked', enable)
      settingsEl.find('[name="listenNotification"]').prop('checked', listenNotification)
      settingsEl.find('[name="excludeList"]').val(excludeList.join('\n'))
    }
  
    return {
      updateConfig
    }
  }
  
  function registerService() {
    return new Promise(async (resolve, reject) => {
      if (Notification === undefined || navigator.serviceWorker === undefined) {
        alert('当前浏览器不支持通知插件,请从您的用户页中移除!')
        return reject()
      }
      
      if(Notification.permission === 'default') {
        alert('点击确定关闭这条消息后,将弹出授予通知权限的窗口,届时请点击“允许”,以保证插件的正常运行。')
      }
    
      await new Promise(Notification.requestPermission)
      const status = Notification.permission
    
      if (status === 'granted') {
        if (localStorage.getItem('moegirl-widget-notification-permission-granted') !== 'true') {
          localStorage.setItem('moegirl-widget-notification-permission-granted', 'true')
          new Notification('欢迎', {
            body: '您已经成功开启实时通知功能,欢迎使用!',
            icon: notificationIcon
          })
        }
      }
    
      if (status === 'denied') {
        alert('您未能正确授予通知权限,若要继续使用,请在url栏左侧点击小锁标志开启权限,若无法设置,请从你的用户页删去此插件。')
        return reject()
      }
  
      const serviceWorkerRegistration = await navigator.serviceWorker.register(workerUrl)
      await navigator.serviceWorker.ready
      resolve(serviceWorkerRegistration.active)
    })
  }

  function checkNotificationPermission() {
    if (localStorage.getItem('moegirl-widget-notification-permission-granted') === 'true' && Notification.permission !== 'granted') {
      localStorage.removeItem('moegirl-widget-notification-permission-granted')
    }
  }
  
  class WorkerDataListener {
    broadcastName = ''
    worker = null
    data = null
    #incrementPullId = 0

    get pullEventName() { return 'get-' + this.broadcastName }
    get broadcastChannelName() { return 'channel-' + this.broadcastName }
    
    constructor(worker, broadcastName) {
      this.broadcastName = broadcastName
      this.worker = worker
      this.broadcastChannel = new BroadcastChannel(this.broadcastChannelName)
  
      this.addListener(data => this.data = data)
    }
  
    addListener(handler) {
      const usingHandler = e => {
        const { type, data, id } = e.data
        type === 'broadcast' && handler(data, id)
      }

      this.broadcastChannel.addEventListener('message', usingHandler)
      return () => this.broadcastChannel.removeEventListener('message', usingHandler)
    }
  
    pullData() {
      const pullId = this.#incrementPullId++
      this.worker.postMessage({ type: this.pullEventName, data: { id: pullId } })
      return pullId
    }
  
    getData() {
      return new Promise(resolve => {
        const pullId = this.pullData()
        const removeListener = this.addListener((data, id) => {
          if (id !== pullId) { return }
          resolve(data)
          removeListener()
        })
      })
    }
  }
})