OSDN Git Service

update enable function for bytom
authorZhiting Lin <zlin035@uottawa.ca>
Tue, 25 Jun 2019 01:53:58 +0000 (09:53 +0800)
committerZhiting Lin <zlin035@uottawa.ca>
Tue, 25 Jun 2019 01:53:58 +0000 (09:53 +0800)
17 files changed:
src/assets/language/cn.js
src/assets/language/en.js
src/background.js
src/content.js
src/dapp.js
src/messages/types.js
src/prompts/Prompt.js [new file with mode: 0644]
src/prompts/PromptTypes.js [new file with mode: 0644]
src/router.js
src/services/NotificationService.js [new file with mode: 0644]
src/services/StorageService.js [new file with mode: 0644]
src/utils/BrowserApis.js [new file with mode: 0644]
src/utils/Bytom.js [new file with mode: 0644]
src/utils/GenericTools.js [new file with mode: 0644]
src/utils/Settings.js [new file with mode: 0644]
src/utils/errors/Error.js
src/views/prompts/authentication.vue [new file with mode: 0644]

index 9108044..c01a02c 100644 (file)
@@ -51,6 +51,13 @@ const cn = {
     message:'签名消息',
     confirmSignature:'确认签名'
   },
+  enable:{
+    title:'请求授权',
+    domain: '域名',
+    message: '请求获取你的钱包地址,是否同意?',
+    cancel:'取消',
+    confirm:'确认授权'
+  },
   receive:{
     address: '地址',
     tips:'提示:点击地址进行拷贝。'
index a7fe5cf..953c8a1 100644 (file)
@@ -51,6 +51,13 @@ const en = {
     message:'Sign Message',
     confirmSignature:'Sign'
   },
+  enable:{
+    title:'Connect Request',
+    domain: 'Domain',
+    message: 'would like to connect your account',
+    cancel:'Cancel',
+    confirm:'Connect'
+  },
   receive:{
     address: 'Address',
     tips:'Tips: Click address to copy directly.'
index a101823..fd85f89 100644 (file)
@@ -1,11 +1,17 @@
 import { LocalStream } from 'extension-streams'
 import InternalMessage from '@/messages/internal'
 import * as MsgTypes from './messages/types'
+import NotificationService from './services/NotificationService'
+import StorageService from './services/StorageService'
+import Prompt from './prompts/Prompt';
+import * as PromptTypes from './prompts/PromptTypes'
 
 import Error from './utils/errors/Error'
 import accountAction from "@/models/account";
 import bytom from "@/models/bytom";
 
+let prompt = null;
+
 export default class Background {
   constructor() {
     this.setupInternalMessaging()
@@ -54,17 +60,32 @@ export default class Background {
         this.signMessage(sendResponse, message.payload)
         break
       case MsgTypes.REQUEST_CURRENT_ACCOUNT:
-        this.requestCurrentAccount(sendResponse)
+        this.requestCurrentAccount(sendResponse, message.payload)
         break
       case MsgTypes.REQUEST_CURRENT_NETWORK:
         this.requestCurrentNetwork(sendResponse)
         break
-      case MsgTypes.REQUEST_ACCOUNT_LIST:
-        this.requestAccountList(sendResponse)
+      case MsgTypes.ENABLE:
+        Background.authenticate(sendResponse, message.payload)
         break
+      case MsgTypes.SET_PROMPT:
+        Background.setPrompt(sendResponse, message.payload);
+        break;
+      case MsgTypes.GET_PROMPT:
+        Background.getPrompt(sendResponse);
+        break;
     }
   }
 
+  static setPrompt(sendResponse, notification){
+    prompt = notification;
+    sendResponse(true);
+  }
+
+  static getPrompt(sendResponse){
+    sendResponse(prompt);
+  }
+
   signMessage(sendResponse, payload) {
     var promptURL = chrome.extension.getURL('pages/prompt.html')
     var requestBody = payload
@@ -105,12 +126,12 @@ export default class Background {
           }
         });
 
-        chrome.windows.onRemoved.addListener(function(windowId){
-          if(windowId === window.id) {
-            sendResponse(Error.promptClosedWithoutAction());
-            return false;
-          }
-        });
+        // chrome.windows.onRemoved.addListener(function(windowId){
+        //   if(windowId === window.id) {
+        //     sendResponse(Error.promptClosedWithoutAction());
+        //     return false;
+        //   }
+        // });
       }
     )
   }
@@ -227,35 +248,30 @@ export default class Background {
     )
   }
 
-  requestCurrentAccount(sendResponse){
-    const currentAccount = JSON.parse(localStorage.currentAccount)
-    delete(currentAccount['label'])
-    delete(currentAccount['net'])
-    currentAccount['accountId'] = currentAccount['guid']
-    delete(currentAccount['guid'])
-    delete(currentAccount['balance'])
+  requestCurrentAccount(sendResponse, payload){
+    Background.load(bytom => {
+      const domain = payload.domain;
+      if(bytom.settings.domains.find(_domain => _domain === domain)) {
+        const currentAccount = JSON.parse(localStorage.currentAccount)
+        delete(currentAccount['label'])
+        delete(currentAccount['net'])
+        currentAccount['accountId'] = currentAccount['guid']
+        delete(currentAccount['guid'])
+        delete(currentAccount['balance'])
+
+        sendResponse(currentAccount);
+      } else{
+        sendResponse(null);
+        return false;
+      }
+    })
 
-    sendResponse(currentAccount)
   }
 
   requestCurrentNetwork(sendResponse){
     sendResponse(localStorage.bytomNet)
   }
 
-  requestAccountList(sendResponse){
-    accountAction.list().then(resp=>{
-      const accountList = resp
-      accountList.forEach(function(account) {
-        delete(account['label'])
-        delete(account['net'])
-        account['accountId'] = account['guid']
-        delete(account['guid'])
-        delete(account['balance'])
-      })
-      sendResponse(accountList)
-    })
-  }
-
   send(sendResponse, payload) {
     const action = payload.action
     if(action){
@@ -273,6 +289,59 @@ export default class Background {
       }
     }
   }
+
+  /***
+   * Returns the saved instance of Bytom from the storage
+   * @param sendResponse - Delegating response handler
+   * @returns {Bytom}
+   */
+  static load(sendResponse){
+    StorageService.get().then(bytom => {
+      sendResponse(bytom)
+    })
+  }
+
+  /***
+   * Updates the Scatter instance inside persistent storage
+   * @param sendResponse - Delegating response handler
+   * @param bytom - The updated cleartext Scatter instance
+   * @returns {boolean}
+   */
+  static update(sendResponse, bytom){
+    StorageService.save(bytom).then(saved => {
+      sendResponse(bytom)
+    })
+  }
+
+  static authenticate(sendResponse, payload){
+    Background.load(bytom => {
+      const domain = payload.domain;
+      const currentAccount = JSON.parse(localStorage.currentAccount)
+      delete(currentAccount['label'])
+      delete(currentAccount['net'])
+      currentAccount['accountId'] = currentAccount['guid']
+      delete(currentAccount['guid'])
+      delete(currentAccount['balance'])
+
+      if(bytom.settings.domains.find(_domain => _domain === domain)) {
+        sendResponse(currentAccount);
+      } else{
+        NotificationService.open(new Prompt(PromptTypes.REQUEST_AUTH, payload.domain, approved => {
+          if(approved === false || approved.hasOwnProperty('isError')) sendResponse(approved);
+          else {
+            bytom.settings.domains.unshift(domain);
+            if(approved === true){
+              this.update(() => sendResponse(currentAccount), bytom);
+            }else{
+              this.update(() => sendResponse(approved), bytom);
+            }
+          }
+        }));
+      }
+    })
+  }
+
+
 }
 
 new Background()
index 37c3fe4..cb918ae 100644 (file)
@@ -4,6 +4,8 @@ import NetworkMessage from '@/messages/network'
 import InternalMessage from '@/messages/internal'
 import * as MsgTypes from './messages/types'
 import * as EventNames from '@/messages/event'
+import {strippedHost} from '@/utils/GenericTools'
+
 
 let stream = new WeakMap()
 let INJECTION_SCRIPT_FILENAME = 'js/inject.js'
@@ -31,11 +33,10 @@ class Content {
     stream.onSync(async () => {
       const defaultAccount = await this.getDefaultAccount();
       const net = await this.getDefaultNetwork();
-      const accountList = await this.getAccountList();
 
       // Pushing an instance of Bytomdapp to the web application
       stream.send(
-        NetworkMessage.payload(MsgTypes.PUSH_BYTOM, {defaultAccount, net, accountList}),
+        NetworkMessage.payload(MsgTypes.PUSH_BYTOM, {defaultAccount, net}),
         EventNames.INJECT
       )
 
@@ -95,6 +96,9 @@ class Content {
       case MsgTypes.SEND:
         this.transfer(msg.type, networkMessage)
         break
+      case MsgTypes.ENABLE:
+        this.enable(msg.type, networkMessage)
+        break
       default:
         stream.send(networkMessage.error('errtest'), EventNames.INJECT)
         break
@@ -104,7 +108,7 @@ class Content {
   getVersion() {}
 
   getDefaultAccount(){
-    return InternalMessage.signal(MsgTypes.REQUEST_CURRENT_ACCOUNT)
+    return InternalMessage.payload(MsgTypes.REQUEST_CURRENT_ACCOUNT,{domain:strippedHost()})
       .send()
   }
 
@@ -113,11 +117,6 @@ class Content {
       .send()
   }
 
-  getAccountList(){
-    return InternalMessage.signal(MsgTypes.REQUEST_ACCOUNT_LIST)
-      .send()
-  }
-
   respond(message, payload) {
     if (!isReady) return
 
@@ -142,6 +141,15 @@ class Content {
       .send()
       .then(res => this.respond(message, res))
   }
+
+  enable(type, networkMessage) {
+    networkMessage.payload ={
+      domain: strippedHost()
+    }
+
+    this.transfer(type, networkMessage)
+  }
+
 }
 
 const content = new Content()
index e248159..58d44d7 100644 (file)
@@ -66,6 +66,14 @@ export default class Bytomdapp {
     _subscribe()
   }
 
+  enable(){
+    return _send(MsgTypes.ENABLE)
+      .then(async default_account =>{
+        this.default_account = default_account;
+        return default_account;
+      })
+  }
+
   send_transaction(params) {
     return _send(MsgTypes.TRANSFER, params)
   }
index d6b9a4e..de70fae 100644 (file)
@@ -4,6 +4,7 @@ export const PUSH_BYTOM = 'pushBytom'
 export const UPDATE_BYTOM = 'updateBytom'
 export const AUTHENTICATE = 'authenticate'
 export const TRANSFER = 'transfer'
+export const ENABLE = 'enable'
 export const ADVTRANSFER = 'advTransfer'
 export const SIGNMESSAGE = 'signMessage'
 export const SEND = 'send'
@@ -12,3 +13,8 @@ export const SEND = 'send'
 export const REQUEST_CURRENT_ACCOUNT = 'defaultAccount';
 export const REQUEST_CURRENT_NETWORK = 'currentNetwork';
 export const REQUEST_ACCOUNT_LIST = 'accountList';
+
+
+//Internal Message
+export const SET_PROMPT = 'setPrompt';
+export const GET_PROMPT = 'getPrompt';
diff --git a/src/prompts/Prompt.js b/src/prompts/Prompt.js
new file mode 100644 (file)
index 0000000..b87e806
--- /dev/null
@@ -0,0 +1,18 @@
+export default class Prompt {
+
+    constructor(_type = '', _domain = '', _responder = null){
+        this.type = _type;
+        this.domain = _domain;
+        // this.network = _network;
+        // this.data = _data;
+        this.responder = _responder;
+    }
+
+    static placeholder(){ return new Prompt(); }
+    static fromJson(json){ return Object.assign(this.placeholder(), json); }
+
+    routeName(){
+        return `${this.type}`;
+    }
+
+}
diff --git a/src/prompts/PromptTypes.js b/src/prompts/PromptTypes.js
new file mode 100644 (file)
index 0000000..3438561
--- /dev/null
@@ -0,0 +1,6 @@
+export const REQUEST_AUTH = 'enable';
+export const REQUEST_SIGNATURE = 'requestSignature';
+export const REQUEST_ARBITRARY_SIGNATURE = 'signatureArbitrary';
+export const REQUEST_ADD_NETWORK = 'requestAddNetwork';
+export const REQUEST_UNLOCK = 'requestUnlock';
+export const UPDATE_VERSION = 'updateVersion';
index dac05cc..16758bb 100644 (file)
@@ -42,6 +42,14 @@ const routers = [
         }
       },
       {
+        path: '/enable',
+        name: 'enable',
+        meta: { title: '授权' },
+        component: resolve => {
+          require(['@/views/prompts/authentication.vue'], resolve)
+        }
+      },
+      {
         path: '/transfer/info',
         name: 'transfer-info',
         meta: { title: '交易详情' },
diff --git a/src/services/NotificationService.js b/src/services/NotificationService.js
new file mode 100644 (file)
index 0000000..98169b2
--- /dev/null
@@ -0,0 +1,103 @@
+import Error from '../utils/errors/Error'
+import {apis} from '../utils/BrowserApis';
+import InternalMessage from '../messages/internal'
+import * as InternalMessageTypes from '../messages/types'
+
+let openWindow = null;
+
+export default class NotificationService {
+
+    /***
+     * Opens a prompt window outside of the extension
+     * @param notification
+     */
+    static async open(notification){
+        if(openWindow){
+            // For now we're just going to close the window to get rid of the error
+            // that is caused by already open windows swallowing all further requests
+            openWindow.close();
+            openWindow = null;
+
+            // Alternatively we could focus the old window, but this would cause
+            // urgent 1-time messages to be lost, such as after dying in a game and
+            // uploading a high-score. That message will be lost.
+            // openWindow.focus();
+            // return false;
+
+            // A third option would be to add a queue, but this could cause
+            // virus-like behavior as apps overflow the queue causing the user
+            // to have to quit the browser to regain control.
+        }
+
+
+        const height = 623;
+        const width = 360;
+        let middleX = window.screen.availWidth/2 - (width/2);
+        let middleY = window.screen.availHeight/2 - (height/2);
+
+        const getPopup = async () => {
+            try {
+                const url = `${apis.runtime.getURL('pages/prompt.html')}#${notification.routeName()}`;
+
+                // Notifications get bound differently depending on browser
+                // as Firefox does not support opening windows from background.
+                if(typeof chrome !== 'undefined') {
+                    window.notification = notification;
+                    apis.windows.create({
+                        url,
+                        height,
+                        width,
+                        type:'popup'
+                    },(_window) => {
+                        apis.windows.onRemoved.addListener(function(windowId){
+                          if(windowId === _window.id) {
+                            notification.responder(Error.promptClosedWithoutAction());
+                            return false;
+                          }
+                        });
+                      return _window;
+                      });
+                }
+                else {
+                    const win = window.open(url, 'BytomPrompt', `width=${width},height=${height},resizable=0,top=${middleY},left=${middleX},titlebar=0`);
+                    win.data = notification;
+                    openWindow = win;
+                    return win;
+                }
+            } catch (e) {
+                console.log('notification error', e);
+                return null;
+            }
+        }
+
+        await InternalMessage.payload(InternalMessageTypes.SET_PROMPT, JSON.stringify(notification)).send();
+
+        let popup = await getPopup();
+
+        if(popup){
+          popup.onbeforeunload = () => {
+              notification.responder(Error.promptClosedWithoutAction());
+
+              // https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload
+              // Must return undefined to bypass form protection
+              openWindow = null;
+              return undefined;
+          };
+        }
+    }
+
+    /***
+     * Always use this method for closing notification popups.
+     * Otherwise you will double send responses and one will always be null.
+     */
+    static async close(){
+        if(typeof browser !== 'undefined') {
+            const {id: windowId,} = (await apis.windows.getCurrent());
+            apis.windows.remove(windowId);
+        } else {
+            window.onbeforeunload = () => {};
+            window.close();
+        }
+    }
+
+}
diff --git a/src/services/StorageService.js b/src/services/StorageService.js
new file mode 100644 (file)
index 0000000..2f2e08a
--- /dev/null
@@ -0,0 +1,97 @@
+import BytomObj from '../utils/Bytom'
+import {apis} from '../utils/BrowserApis';
+
+export default class StorageService {
+
+    constructor(){}
+
+    /***
+     * Saves an instance of Bytom in the extension's local storage
+     * The keychain will always be encrypted when in storage
+     * @param bytom
+     * @returns {Promise}
+     */
+    static save(_bytom){
+        return new Promise(resolve => {
+            apis.storage.local.set({bytom:_bytom}, () => {
+                resolve(_bytom);
+            });
+        })
+    };
+
+    /***
+     * Gets an instance of Bytom from the extension's local storage
+     * Will return a new Bytom instance if not found.
+     * @returns {Promise}
+     */
+    static get() {
+        return new Promise(resolve => {
+            apis.storage.local.get('bytom', (possible) => {
+                (possible && Object.keys(possible).length && possible.hasOwnProperty('bytom'))
+                    ? resolve(BytomObj.fromJson(possible.bytom))
+                    : resolve(BytomObj.placeholder());
+            });
+        })
+    }
+
+    /***
+     * Removes the instance of Bytom.
+     * This will delete all user data.
+     * @returns {Promise}
+     */
+    static remove(){
+        return new Promise(resolve => {
+            apis.storage.local.remove('bytom', () => {
+                resolve();
+            });
+        })
+    }
+
+    /***
+     * Caches an ABI
+     * @param contractName
+     * @param chainId
+     * @param abi
+     * @returns {Promise}
+     */
+    static cacheABI(contractName, chainId, abi){
+        return new Promise(resolve => {
+            apis.storage.local.set({[`abi:${contractName}:${chainId}`]:abi}, () => {
+                resolve(abi);
+            });
+        });
+    }
+
+    /***
+     * Fetches an ABI from cache
+     * @param contractName
+     * @param chainId
+     * @returns {Promise}
+     */
+    static getABI(contractName, chainId){
+        return new Promise(resolve => {
+            const prop = `abi:${contractName}:${chainId}`;
+            apis.storage.local.get(prop, (possible) => {
+                if(JSON.stringify(possible) !== '{}') resolve(possible[prop]);
+                else resolve('no cache');
+            });
+        })
+    }
+
+    static getSalt(){
+        return new Promise(resolve => {
+            apis.storage.local.get('salt', (possible) => {
+                if(JSON.stringify(possible) !== '{}') resolve(possible.salt);
+                else resolve('SALT_ME');
+            });
+        })
+    }
+
+    static setSalt(salt){
+        return new Promise(resolve => {
+            apis.storage.local.set({salt}, () => {
+                resolve(salt);
+            });
+        })
+    }
+}
diff --git a/src/utils/BrowserApis.js b/src/utils/BrowserApis.js
new file mode 100644 (file)
index 0000000..af7e482
--- /dev/null
@@ -0,0 +1,22 @@
+
+const swallow = fn => {try { fn() } catch(e){}};
+
+class ApiGenerator {
+    constructor(){
+        [
+        'app',
+        'storage',
+        'extension',
+        'runtime',
+        'windows'
+        ]
+        .map(api => {
+            if(typeof chrome !== 'undefined') swallow(() => {if(chrome[api]) this[api] = chrome[api]});
+            if(typeof browser !== 'undefined') swallow(() => {if(browser[api]) this[api] = browser[api]});
+        });
+
+        if(typeof browser !== 'undefined') swallow(() => {if (browser && browser.runtime) this.runtime = browser.runtime});
+    }
+}
+
+export const apis = new ApiGenerator();
\ No newline at end of file
diff --git a/src/utils/Bytom.js b/src/utils/Bytom.js
new file mode 100644 (file)
index 0000000..f464e93
--- /dev/null
@@ -0,0 +1,17 @@
+import Settings from './Settings';
+
+export default class BytomObj {
+
+    constructor(){
+        this.settings = Settings.placeholder();
+    }
+
+    static placeholder(){ return new BytomObj(); }
+    static fromJson(json){
+        let p = Object.assign(this.placeholder(), json);
+        if(json.hasOwnProperty('settings')) p.settings = Settings.fromJson(json.settings);
+        return p;
+    }
+
+    clone(){ return BytomObj.fromJson(JSON.parse(JSON.stringify(this))) }
+}
diff --git a/src/utils/GenericTools.js b/src/utils/GenericTools.js
new file mode 100644 (file)
index 0000000..a56b67e
--- /dev/null
@@ -0,0 +1,9 @@
+
+export const strippedHost = () => {
+    let host = location.hostname;
+
+    // Replacing www. only if the domain starts with it.
+    if(host.indexOf('www.') === 0) host = host.replace('www.', '');
+
+    return host;
+};
\ No newline at end of file
diff --git a/src/utils/Settings.js b/src/utils/Settings.js
new file mode 100644 (file)
index 0000000..224a99c
--- /dev/null
@@ -0,0 +1,14 @@
+export default class Settings {
+
+    constructor(){
+        this.domains = [];
+        this.language = 'ENGLISH';
+    }
+
+    static placeholder(){ return new Settings(); }
+    static fromJson(json){
+        let p = Object.assign(this.placeholder(), json);
+        if(json.hasOwnProperty('domains')) p.domains = json.domains;
+        return p;
+    }
+}
index 1c69768..3fc980b 100644 (file)
@@ -19,7 +19,7 @@ export default class Error {
   }
 
   static locked(){
-    return new Error(ErrorTypes.LOCKED, "The user's Scatter is locked. They have been notified and should unlock before continuing.")
+    return new Error(ErrorTypes.LOCKED, "The user's Bytom is locked. They have been notified and should unlock before continuing.")
   }
 
   static promptClosedWithoutAction(){
@@ -49,7 +49,7 @@ export default class Error {
   static usedKeyProvider(){
     return new Error(
       ErrorTypes.MALICIOUS,
-      "Do not use a `keyProvider` with a Scatter. Use a `signProvider` and return only signatures to this object. A malicious person could retrieve your keys otherwise.",
+      "Do not use a `keyProvider` with a Bytom. Use a `signProvider` and return only signatures to this object. A malicious person could retrieve your keys otherwise.",
       ErrorCodes.NO_SIGNATURE
     )
   }
diff --git a/src/views/prompts/authentication.vue b/src/views/prompts/authentication.vue
new file mode 100644 (file)
index 0000000..0f827f3
--- /dev/null
@@ -0,0 +1,124 @@
+<style lang="" scoped>
+  .warp {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 600px;
+    z-index: 2;
+    overflow: scroll;
+  }
+  .header {
+    display: flex;
+  }
+  .header p{
+    text-align: center;
+    width: 280px;
+    padding-top: 17px;
+  }
+
+  .content {
+    margin: 20px;
+    padding: 20px;
+    overflow: hidden;
+    border-radius:4px;
+    width: 280px;
+  }
+
+  .btn-inline .btn {
+    margin: 10px 15px;
+  }
+  .row{
+    word-break: break-all;
+  }
+  .col{
+    font-size: 14px;
+    width: 35%;
+  }
+  .label{
+    color: #7B7B7B;
+  }
+  .message{
+    font-size: 14px;
+    color: #7B7B7B;
+  }
+  .value{
+    color: #282828;
+    width: 60%;
+  }
+  table{
+    width: 100%;
+  }
+  .form-item{
+    padding:0;
+    margin:0;
+    margin-bottom: 10px;
+  }
+  .btn-container .btn{
+    margin-top: 20px;
+    height: 48px;
+    width: 320px;
+  }
+</style>
+
+<template>
+  <div class="warp bg-gray">
+    <section class="header bg-header">
+      <i class="iconfont icon-back" @click="denied"></i>
+      <p>{{$t('enable.title')}}</p>
+    </section>
+
+    <section class="content bg-white">
+      <table>
+        <tbody>
+          <tr class="row">
+            <td class="col label">{{$t('enable.domain')}}</td>
+            <td  class="col value">{{prompt.domain}}</td>
+          </tr>
+        </tbody>
+      </table>
+    </section>
+
+    <section class="content bg-white">
+      <div class="message">{{$t('enable.message')}}</div>
+    </section>
+
+    <div class="row btn-container" style="bottom: 20px; left: 20px; position: absolute;">
+      <div class="btn" @click="denied">{{$t('enable.cancel')}}</div>
+      <div class="btn bg-green" @click="accepted">{{$t('enable.confirm')}}</div>
+    </div>
+
+  </div>
+</template>
+
+<script>
+import { BTM } from "@/utils/constants";
+import {apis} from '@/utils/BrowserApis';
+import NotificationService from '../../services/NotificationService'
+
+
+export default {
+    data() {
+        return {
+            prompt:''
+        };
+    },
+    computed: {
+    },
+    watch: {
+    },
+    methods: {
+      accepted(){
+        this.prompt.responder(true);
+        NotificationService.close();
+      },
+      denied(){
+        this.prompt.responder(false);
+        NotificationService.close();
+      }
+    }, mounted() {
+        this.prompt = window.data || apis.extension.getBackgroundPage().notification || null;
+        console.log(this.prompt)
+      }
+};
+</script>