Browse Source

improve(plugin): add beforeunload interceptor

charlie 4 years ago
parent
commit
1cf08d071d
4 changed files with 77 additions and 11 deletions
  1. 34 7
      libs/src/LSPlugin.caller.ts
  2. 4 3
      libs/src/LSPlugin.core.ts
  3. 5 0
      libs/src/LSPlugin.d.ts
  4. 34 1
      libs/src/LSPlugin.user.ts

+ 34 - 7
libs/src/LSPlugin.caller.ts

@@ -9,12 +9,15 @@ const debug = Debug('LSPlugin:caller')
 
 type DeferredActor = ReturnType<typeof deferred>
 
+export const FLAG_AWAIT = '#await#response#'
 export const LSPMSG = '#lspmsg#'
 export const LSPMSG_ERROR_TAG = '#lspmsg#error#'
 export const LSPMSG_SETTINGS = '#lspmsg#settings#'
+export const LSPMSG_BEFORE_UNLOAD = '#lspmsg#beforeunload#'
 export const LSPMSG_SYNC = '#lspmsg#reply#'
 export const LSPMSG_READY = '#lspmsg#ready#'
 export const LSPMSGFn = (id: string) => `${LSPMSG}${id}`
+export const AWAIT_LSPMSGFn = (id: string) => `${FLAG_AWAIT}${id}`
 
 /**
  * Call between core and user
@@ -33,10 +36,16 @@ class LSPluginCaller extends EventEmitter {
   private _call?: (type: string, payload: any, actor?: DeferredActor) => Promise<any>
   private _callUserModel?: (type: string, payload: any) => Promise<any>
 
+  private _debugTag = ''
+
   constructor (
     private _pluginLocal: PluginLocal | null
   ) {
     super()
+
+    if (_pluginLocal) {
+      this._debugTag = _pluginLocal.debugTag
+    }
   }
 
   async connectToChild () {
@@ -67,12 +76,18 @@ class LSPluginCaller extends EventEmitter {
         await readyDeferred.resolve()
       },
 
+      [LSPMSG_BEFORE_UNLOAD]: async (e) => {
+        const actor = deferred(10 * 1000)
+        caller.emit('beforeunload', Object.assign({ actor }, e))
+        await actor.promise
+      },
+
       [LSPMSG_SETTINGS]: async ({ type, payload }) => {
         caller.emit('settings:changed', payload)
       },
 
       [LSPMSG]: async ({ ns, type, payload }: any) => {
-        debug(`[call from host #${this._pluginLocal?.id}]`, ns, type, payload)
+        debug(`${this._debugTag} [call from host]`, ns, type, payload)
 
         if (ns && ns.startsWith('hook')) {
           caller.emit(`${ns}:${type}`, payload)
@@ -160,8 +175,6 @@ class LSPluginCaller extends EventEmitter {
   }
 
   async call (type: any, payload: any = {}) {
-    // TODO: ?
-    this.emit(type, payload)
     return this._call?.call(this, type, payload)
   }
 
@@ -207,12 +220,17 @@ class LSPluginCaller extends EventEmitter {
         })
 
         this._call = async (...args: any) => {
-          // parent all will get message
+          // parent all will get message before handshaked
           await refChild.call(LSPMSGFn(pl.id), { type: args[0], payload: args[1] || {} })
         }
 
-        this._callUserModel = async (...args: any) => {
-          await refChild.call(args[0], args[1] || {})
+        this._callUserModel = async (type, payload: any) => {
+          if (type.startsWith(FLAG_AWAIT)) {
+            // TODO: attach payload
+            return await refChild.get(type.replace(FLAG_AWAIT, ''))
+          } else {
+            refChild.call(type, payload)
+          }
         }
 
         resolve(null)
@@ -252,7 +270,12 @@ class LSPluginCaller extends EventEmitter {
       }
 
       this._callUserModel = async (...args: any) => {
-        const type = args[0]
+        let type = args[0] as string
+
+        if (type?.startsWith(FLAG_AWAIT)) {
+          type = type.replace(FLAG_AWAIT, '')
+        }
+
         const payload = args[1] || {}
         const fn = this._userModel[type]
 
@@ -280,6 +303,10 @@ class LSPluginCaller extends EventEmitter {
     return this._shadow?.frame
   }
 
+  set debugTag (value: string) {
+    this._debugTag = value
+  }
+
   async destroy () {
     if (this._parent) {
       await this._parent.destroy()

+ 4 - 3
libs/src/LSPlugin.core.ts

@@ -14,7 +14,7 @@ import {
   LSPluginCaller,
   LSPMSG_READY, LSPMSG_SYNC,
   LSPMSG, LSPMSG_SETTINGS,
-  LSPMSG_ERROR_TAG
+  LSPMSG_ERROR_TAG, LSPMSG_BEFORE_UNLOAD, AWAIT_LSPMSGFn
 } from './LSPlugin.caller'
 import {
   ILSPluginThemeManager,
@@ -590,10 +590,11 @@ class PluginLocal
     try {
       this._status = PluginLocalLoadStatus.UNLOADING
 
-      const eventBeforeUnload = {}
+      const eventBeforeUnload = { unregister }
 
       // sync call
       try {
+        await this._caller?.callUserModel(AWAIT_LSPMSGFn(LSPMSG_BEFORE_UNLOAD), eventBeforeUnload)
         this.emit('beforeunload', eventBeforeUnload)
       } catch (e) {
         console.error('[beforeunload Error]', e)
@@ -689,7 +690,7 @@ class PluginLocal
   }
 
   get debugTag () {
-    return `[${this._options?.name} #${this._id}]`
+    return `#${this._id} [${this._options?.name}]`
   }
 
   get localRoot (): string {

+ 5 - 0
libs/src/LSPlugin.d.ts

@@ -192,6 +192,11 @@ interface ILSPluginUser extends EventEmitter<LSPluginUserEvents> {
 
   ready (model?: Record<string, any>, callback?: (e: any) => void | {}): Promise<any>
 
+  /**
+   * @param callback
+   */
+  beforeunload: (callback:() => Promise<void>) => void
+
   /**
    * @param model
    */

+ 34 - 1
libs/src/LSPlugin.user.ts

@@ -109,12 +109,22 @@ const KEY_MAIN_UI = 0
  */
 export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements ILSPluginUser {
   /**
-   * Indicate connected with host
    * @private
    */
   private _connected: boolean = false
+
+  /**
+   * ui frame identities
+   * @private
+   */
   private _ui = new Map<number, uiState>()
 
+  /**
+   * handler of before unload plugin
+   * @private
+   */
+  private _beforeunloadCallback?: (e: any) => Promise<void>
+
   /**
    * @param _baseInfo
    * @param _caller
@@ -130,6 +140,20 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
       const a = Object.assign(this._baseInfo.settings, payload)
       this.emit('settings:changed', { ...a }, b)
     })
+
+    _caller.on('beforeunload', async (payload) => {
+      const { actor, ...rest } = payload
+      const cb = this._beforeunloadCallback
+      if (!cb || !actor) return
+
+      try {
+        cb && await cb(rest)
+        actor.resolve(null)
+      } catch (e) {
+        console.debug(`${_caller.debugTag} [beforeunload] `, e)
+        actor.reject(e)
+      }
+    })
   }
 
   async ready (
@@ -151,12 +175,21 @@ export class LSPluginUser extends EventEmitter<LSPluginUserEvents> implements IL
 
       this._connected = true
 
+      if (baseInfo?.id) {
+        this._caller.debugTag = `#${baseInfo.id} [${baseInfo.name}]`
+      }
+
       callback && callback.call(this, baseInfo)
     } catch (e) {
       console.error('[LSPlugin Ready Error]', e)
     }
   }
 
+  beforeunload (callback: (e: any) => Promise<void>): void {
+    if (typeof callback !== 'function') return
+    this._beforeunloadCallback = callback
+  }
+
   provideModel (model: Record<string, any>) {
     this.caller._extendUserModel(model)
     return this