chrome-api.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
  2. import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'
  3. import * as url from 'url'
  4. import { Event } from '@electron/internal/renderer/extensions/event'
  5. class Tab {
  6. public id: number
  7. constructor (tabId: number) {
  8. this.id = tabId
  9. }
  10. }
  11. class MessageSender {
  12. public tab: Tab | null
  13. public id: string
  14. public url: string
  15. constructor (tabId: number, extensionId: string) {
  16. this.tab = tabId ? new Tab(tabId) : null
  17. this.id = extensionId
  18. this.url = `chrome-extension://${extensionId}`
  19. }
  20. }
  21. class Port {
  22. public disconnected: boolean = false
  23. public onDisconnect = new Event()
  24. public onMessage = new Event()
  25. public sender: MessageSender
  26. constructor (public tabId: number, public portId: number, extensionId: string, public name: string) {
  27. this.onDisconnect = new Event()
  28. this.onMessage = new Event()
  29. this.sender = new MessageSender(tabId, extensionId)
  30. ipcRendererInternal.once(`CHROME_PORT_DISCONNECT_${portId}`, () => {
  31. this._onDisconnect()
  32. })
  33. ipcRendererInternal.on(`CHROME_PORT_POSTMESSAGE_${portId}`, (
  34. _event: Electron.Event, message: any
  35. ) => {
  36. const sendResponse = function () { console.error('sendResponse is not implemented') }
  37. this.onMessage.emit(JSON.parse(message), this.sender, sendResponse)
  38. })
  39. }
  40. disconnect () {
  41. if (this.disconnected) return
  42. ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`)
  43. this._onDisconnect()
  44. }
  45. postMessage (message: any) {
  46. ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, JSON.stringify(message))
  47. }
  48. _onDisconnect () {
  49. this.disconnected = true
  50. ipcRendererInternal.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`)
  51. this.onDisconnect.emit()
  52. }
  53. }
  54. // Inject chrome API to the |context|
  55. export function injectTo (extensionId: string, context: any) {
  56. const chrome = context.chrome = context.chrome || {}
  57. ipcRendererInternal.on(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, (
  58. _event: Electron.Event, tabId: number, portId: number, connectInfo: { name: string }
  59. ) => {
  60. chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name))
  61. })
  62. ipcRendererUtils.handle(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, (
  63. _event: Electron.Event, tabId: number, message: string
  64. ) => {
  65. return new Promise(resolve => {
  66. chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId), resolve)
  67. })
  68. })
  69. ipcRendererInternal.on('CHROME_TABS_ONCREATED', (_event: Electron.Event, tabId: number) => {
  70. chrome.tabs.onCreated.emit(new Tab(tabId))
  71. })
  72. ipcRendererInternal.on('CHROME_TABS_ONREMOVED', (_event: Electron.Event, tabId: number) => {
  73. chrome.tabs.onRemoved.emit(tabId)
  74. })
  75. chrome.runtime = {
  76. id: extensionId,
  77. // https://developer.chrome.com/extensions/runtime#method-getURL
  78. getURL: function (path: string) {
  79. return url.format({
  80. protocol: 'chrome-extension',
  81. slashes: true,
  82. hostname: extensionId,
  83. pathname: path
  84. })
  85. },
  86. // https://developer.chrome.com/extensions/runtime#method-getManifest
  87. getManifest: function () {
  88. const manifest = ipcRendererUtils.invokeSync('CHROME_EXTENSION_MANIFEST', extensionId)
  89. return manifest
  90. },
  91. // https://developer.chrome.com/extensions/runtime#method-connect
  92. connect (...args: Array<any>) {
  93. // Parse the optional args.
  94. let targetExtensionId = extensionId
  95. let connectInfo = { name: '' }
  96. if (args.length === 1) {
  97. if (typeof args[0] === 'string') {
  98. targetExtensionId = args[0]
  99. } else {
  100. connectInfo = args[0]
  101. }
  102. } else if (args.length === 2) {
  103. [targetExtensionId, connectInfo] = args
  104. }
  105. const { tabId, portId } = ipcRendererUtils.invokeSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo)
  106. return new Port(tabId, portId, extensionId, connectInfo.name)
  107. },
  108. // https://developer.chrome.com/extensions/runtime#method-sendMessage
  109. sendMessage (...args: Array<any>) {
  110. // Parse the optional args.
  111. const targetExtensionId = extensionId
  112. let message: string
  113. let options: Object | undefined
  114. let responseCallback: Chrome.Tabs.SendMessageCallback = () => {}
  115. if (typeof args[args.length - 1] === 'function') {
  116. responseCallback = args.pop()
  117. }
  118. if (args.length === 1) {
  119. [message] = args
  120. } else if (args.length === 2) {
  121. if (typeof args[0] === 'string') {
  122. [extensionId, message] = args
  123. } else {
  124. [message, options] = args
  125. }
  126. } else {
  127. [extensionId, message, options] = args
  128. }
  129. if (options) {
  130. console.error('options are not supported')
  131. }
  132. ipcRendererUtils.invoke('CHROME_RUNTIME_SEND_MESSAGE', targetExtensionId, message).then(responseCallback)
  133. },
  134. onConnect: new Event(),
  135. onMessage: new Event(),
  136. onInstalled: new Event()
  137. }
  138. chrome.tabs = {
  139. // https://developer.chrome.com/extensions/tabs#method-executeScript
  140. executeScript (
  141. tabId: number,
  142. details: Chrome.Tabs.ExecuteScriptDetails,
  143. resultCallback: Chrome.Tabs.ExecuteScriptCallback = () => {}
  144. ) {
  145. ipcRendererUtils.invoke('CHROME_TABS_EXECUTE_SCRIPT', tabId, extensionId, details)
  146. .then((result: any) => resultCallback([result]))
  147. },
  148. // https://developer.chrome.com/extensions/tabs#method-sendMessage
  149. sendMessage (
  150. tabId: number,
  151. message: any,
  152. _options: Chrome.Tabs.SendMessageDetails,
  153. responseCallback: Chrome.Tabs.SendMessageCallback = () => {}
  154. ) {
  155. ipcRendererUtils.invoke('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, message).then(responseCallback)
  156. },
  157. onUpdated: new Event(),
  158. onCreated: new Event(),
  159. onRemoved: new Event()
  160. }
  161. chrome.extension = {
  162. getURL: chrome.runtime.getURL,
  163. connect: chrome.runtime.connect,
  164. onConnect: chrome.runtime.onConnect,
  165. sendMessage: chrome.runtime.sendMessage,
  166. onMessage: chrome.runtime.onMessage
  167. }
  168. chrome.storage = require('@electron/internal/renderer/extensions/storage').setup(extensionId)
  169. chrome.pageAction = {
  170. show () {},
  171. hide () {},
  172. setTitle () {},
  173. getTitle () {},
  174. setIcon () {},
  175. setPopup () {},
  176. getPopup () {}
  177. }
  178. chrome.i18n = require('@electron/internal/renderer/extensions/i18n').setup(extensionId)
  179. chrome.webNavigation = require('@electron/internal/renderer/extensions/web-navigation').setup()
  180. // Electron has no concept of a browserAction but we should stub these APIs for compatibility
  181. chrome.browserAction = {
  182. setIcon () {},
  183. setPopup () {}
  184. }
  185. }