chrome-api.ts 6.7 KB

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