messager.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. /**
  2. * 跨域页面消息通信
  3. * <pre>
  4. * 作者:hugh zhuang
  5. * 邮箱:3378340995@qq.com
  6. * 日期:2018-07-02-下午3:29:34
  7. * 版权:广州流辰信息技术有限公司
  8. * </pre>
  9. */
  10. import Storage from './storage'
  11. export const MESSAGE_TYPE = '__ibps_WEB_MESSAGER__'
  12. /**
  13. * 跨域页面消息通信类
  14. * @class
  15. */
  16. export default class Messager {
  17. /**
  18. *
  19. * @param {object} options 实例化参数选项
  20. * @param {Window} [options.target] 通信目标,使用桥通讯时可以不传
  21. * @param {string} [options.bridge] 目标桥页面url,如需要跨浏览器窗口传输数据,必须要设置
  22. * @param {string} [options.origin] 自身的桥,目标可以通过桥联系到自己
  23. * @param {function} [options.ready] 桥页面加载完成时回调函数
  24. */
  25. constructor(options) {
  26. const o = (this.options = {
  27. target: window,
  28. bridge: null,
  29. origin: null,
  30. ready: null,
  31. ...options
  32. })
  33. /**
  34. * 通信目标对象
  35. * @property {Window} target
  36. * @type {Window | *}
  37. */
  38. this.target = o.target
  39. this.handlers = {}
  40. this.proxyBridgeHandler = this.bridgeHandler.bind(this)
  41. window.addEventListener('storage', this.proxyBridgeHandler)
  42. if (o.bridge) {
  43. this.buildBridge().then((el) => {
  44. this.target = el.contentWindow
  45. this.el = el
  46. o.ready && o.ready(this)
  47. })
  48. } else {
  49. o.ready && o.ready(this)
  50. }
  51. }
  52. /**
  53. * 侦听消息
  54. * @param {string} type 消息类型
  55. * @param {Function} handler 处理函数
  56. */
  57. on(type, handler) {
  58. const listener = function (evt) {
  59. // message: {type:'XDH_WEB_MESSAGER', data:{type:'事件类型', data:{真正发送的数据},bridge:'' }}
  60. const message = evt.data || {}
  61. if (!message) return
  62. if (message.type === MESSAGE_TYPE && message.data.type === type) {
  63. handler(message.data.data, message.data.bridge)
  64. }
  65. }
  66. if (!this.handlers[type]) {
  67. this.handlers[type] = []
  68. }
  69. this.handlers[type].push(listener)
  70. window.addEventListener('message', listener)
  71. }
  72. /**
  73. * 取消侦听
  74. * @param {string} [type] 消息类型
  75. * @param {Function} [handler] 处理函数
  76. */
  77. off(type, handler) {
  78. // 制定类型和事件句柄
  79. if (type && handler) {
  80. const handlers = this.handlers[type] || []
  81. handlers.forEach((h, index) => {
  82. if (handler === h) {
  83. handlers.splice(index, 1)
  84. window.removeEventListener('message', h)
  85. }
  86. })
  87. // 制定事件类型
  88. } else if (type) {
  89. const handlers = this.handlers[type] || []
  90. handlers.forEach((h) => {
  91. window.removeEventListener('message', h)
  92. })
  93. delete this.handlers[type]
  94. // 全不制定,全部取消侦听
  95. } else {
  96. Object.keys(this.handlers).forEach((key) => {
  97. this.off(key)
  98. })
  99. }
  100. }
  101. /**
  102. * 发送消息
  103. * @param {string} [type] 消息类型
  104. * @param {Object} [data] 发送数据
  105. */
  106. fire(type, data) {
  107. if (!this.target) return
  108. const message = {
  109. type: MESSAGE_TYPE,
  110. data: {
  111. type,
  112. data,
  113. bridge: this.options.origin
  114. }
  115. }
  116. // 桥转发时,数据包多一层
  117. if (this.options.bridge) {
  118. const bridge = {
  119. type: MESSAGE_TYPE,
  120. data: message
  121. }
  122. this.target.postMessage(bridge, '*')
  123. } else {
  124. this.target.postMessage(message, '*')
  125. }
  126. }
  127. /**
  128. * 接收消息,只接收一次
  129. * @param {string} [type] 消息类型
  130. * @param {Function} [handler] 处理函数
  131. */
  132. once(type, handler) {
  133. this.on(type, () => {
  134. handler.apply(this, arguments)
  135. this.off(type, handler)
  136. })
  137. }
  138. /**
  139. * 搭桥,创建iframe,加载桥页面
  140. * @return {Promise<any>}
  141. */
  142. buildBridge() {
  143. return new Promise((resolve, reject) => {
  144. const el = document.createElement('iframe')
  145. el.style.display = 'none'
  146. el.setAttribute('src', this.options.bridge + '?t=' + new Date().getTime())
  147. el.onload = () => {
  148. resolve(el)
  149. }
  150. el.onerror = (e) => {
  151. reject(e)
  152. }
  153. document.body.appendChild(el)
  154. })
  155. }
  156. /**
  157. * 桥页面传输写入localStorage
  158. * @param {object} message
  159. * @param {string} origin 消息来源
  160. */
  161. pass(message) {
  162. Storage.set(MESSAGE_TYPE, {
  163. message: message,
  164. __t__: new Date().getTime()
  165. })
  166. }
  167. /**
  168. * localStorage 事件处理函数
  169. * @param {object} evt
  170. */
  171. bridgeHandler(evt) {
  172. if (evt.key !== MESSAGE_TYPE) return
  173. const value = JSON.parse(evt.newValue)
  174. if (value && value.message) {
  175. const message = value.message
  176. const handlers = this.handlers[message.type] || []
  177. handlers.forEach((handler) => {
  178. handler({
  179. data: {
  180. type: MESSAGE_TYPE,
  181. data: message
  182. }
  183. })
  184. })
  185. }
  186. }
  187. /**
  188. * 销毁
  189. */
  190. destroy() {
  191. this.off()
  192. if (this.proxyBridgeHandler) {
  193. window.removeEventListener('storage', this.proxyBridgeHandler)
  194. }
  195. if (this.el) {
  196. this.el.parentNode.removeChild(this.el)
  197. }
  198. }
  199. }