rpc-server.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. 'use strict'
  2. const {Buffer} = require('buffer')
  3. const electron = require('electron')
  4. const v8Util = process.atomBinding('v8_util')
  5. const {WebContents} = process.atomBinding('web_contents')
  6. const {ipcMain, isPromise, webContents} = electron
  7. const objectsRegistry = require('./objects-registry')
  8. const hasProp = {}.hasOwnProperty
  9. // The internal properties of Function.
  10. const FUNCTION_PROPERTIES = [
  11. 'length', 'name', 'arguments', 'caller', 'prototype'
  12. ]
  13. // The remote functions in renderer processes.
  14. // id => Function
  15. let rendererFunctions = v8Util.createDoubleIDWeakMap()
  16. // Return the description of object's members:
  17. let getObjectMembers = function (object) {
  18. let names = Object.getOwnPropertyNames(object)
  19. // For Function, we should not override following properties even though they
  20. // are "own" properties.
  21. if (typeof object === 'function') {
  22. names = names.filter((name) => {
  23. return !FUNCTION_PROPERTIES.includes(name)
  24. })
  25. }
  26. // Map properties to descriptors.
  27. return names.map((name) => {
  28. let descriptor = Object.getOwnPropertyDescriptor(object, name)
  29. let member = {name, enumerable: descriptor.enumerable, writable: false}
  30. if (descriptor.get === undefined && typeof object[name] === 'function') {
  31. member.type = 'method'
  32. } else {
  33. if (descriptor.set || descriptor.writable) member.writable = true
  34. member.type = 'get'
  35. }
  36. return member
  37. })
  38. }
  39. // Return the description of object's prototype.
  40. let getObjectPrototype = function (object) {
  41. let proto = Object.getPrototypeOf(object)
  42. if (proto === null || proto === Object.prototype) return null
  43. return {
  44. members: getObjectMembers(proto),
  45. proto: getObjectPrototype(proto)
  46. }
  47. }
  48. // Convert a real value into meta data.
  49. let valueToMeta = function (sender, contextId, value, optimizeSimpleObject = false) {
  50. // Determine the type of value.
  51. const meta = { type: typeof value }
  52. if (meta.type === 'object') {
  53. // Recognize certain types of objects.
  54. if (value === null) {
  55. meta.type = 'value'
  56. } else if (ArrayBuffer.isView(value)) {
  57. meta.type = 'buffer'
  58. } else if (Array.isArray(value)) {
  59. meta.type = 'array'
  60. } else if (value instanceof Error) {
  61. meta.type = 'error'
  62. } else if (value instanceof Date) {
  63. meta.type = 'date'
  64. } else if (isPromise(value)) {
  65. meta.type = 'promise'
  66. } else if (hasProp.call(value, 'callee') && value.length != null) {
  67. // Treat the arguments object as array.
  68. meta.type = 'array'
  69. } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
  70. // Treat simple objects as value.
  71. meta.type = 'value'
  72. }
  73. }
  74. // Fill the meta object according to value's type.
  75. if (meta.type === 'array') {
  76. meta.members = value.map((el) => valueToMeta(sender, contextId, el))
  77. } else if (meta.type === 'object' || meta.type === 'function') {
  78. meta.name = value.constructor ? value.constructor.name : ''
  79. // Reference the original value if it's an object, because when it's
  80. // passed to renderer we would assume the renderer keeps a reference of
  81. // it.
  82. meta.id = objectsRegistry.add(sender, contextId, value)
  83. meta.members = getObjectMembers(value)
  84. meta.proto = getObjectPrototype(value)
  85. } else if (meta.type === 'buffer') {
  86. meta.value = Buffer.from(value)
  87. } else if (meta.type === 'promise') {
  88. // Add default handler to prevent unhandled rejections in main process
  89. // Instead they should appear in the renderer process
  90. value.then(function () {}, function () {})
  91. meta.then = valueToMeta(sender, contextId, function (onFulfilled, onRejected) {
  92. value.then(onFulfilled, onRejected)
  93. })
  94. } else if (meta.type === 'error') {
  95. meta.members = plainObjectToMeta(value)
  96. // Error.name is not part of own properties.
  97. meta.members.push({
  98. name: 'name',
  99. value: value.name
  100. })
  101. } else if (meta.type === 'date') {
  102. meta.value = value.getTime()
  103. } else {
  104. meta.type = 'value'
  105. meta.value = value
  106. }
  107. return meta
  108. }
  109. // Convert object to meta by value.
  110. const plainObjectToMeta = function (obj) {
  111. return Object.getOwnPropertyNames(obj).map(function (name) {
  112. return {
  113. name: name,
  114. value: obj[name]
  115. }
  116. })
  117. }
  118. // Convert Error into meta data.
  119. const exceptionToMeta = function (error) {
  120. return {
  121. type: 'exception',
  122. message: error.message,
  123. stack: error.stack || error
  124. }
  125. }
  126. const throwRPCError = function (message) {
  127. const error = new Error(message)
  128. error.code = 'EBADRPC'
  129. error.errno = -72
  130. throw error
  131. }
  132. const removeRemoteListenersAndLogWarning = (meta, args, callIntoRenderer) => {
  133. let message = `Attempting to call a function in a renderer window that has been closed or released.` +
  134. `\nFunction provided here: ${meta.location}`
  135. if (args.length > 0 && (args[0].sender instanceof WebContents)) {
  136. const {sender} = args[0]
  137. const remoteEvents = sender.eventNames().filter((eventName) => {
  138. return sender.listeners(eventName).includes(callIntoRenderer)
  139. })
  140. if (remoteEvents.length > 0) {
  141. message += `\nRemote event names: ${remoteEvents.join(', ')}`
  142. remoteEvents.forEach((eventName) => {
  143. sender.removeListener(eventName, callIntoRenderer)
  144. })
  145. }
  146. }
  147. console.warn(message)
  148. }
  149. // Convert array of meta data from renderer into array of real values.
  150. const unwrapArgs = function (sender, contextId, args) {
  151. const metaToValue = function (meta) {
  152. let i, len, member, ref, returnValue
  153. switch (meta.type) {
  154. case 'value':
  155. return meta.value
  156. case 'remote-object':
  157. return objectsRegistry.get(meta.id)
  158. case 'array':
  159. return unwrapArgs(sender, contextId, meta.value)
  160. case 'buffer':
  161. return Buffer.from(meta.value)
  162. case 'date':
  163. return new Date(meta.value)
  164. case 'promise':
  165. return Promise.resolve({
  166. then: metaToValue(meta.then)
  167. })
  168. case 'object': {
  169. let ret = {}
  170. Object.defineProperty(ret.constructor, 'name', { value: meta.name })
  171. ref = meta.members
  172. for (i = 0, len = ref.length; i < len; i++) {
  173. member = ref[i]
  174. ret[member.name] = metaToValue(member.value)
  175. }
  176. return ret
  177. }
  178. case 'function-with-return-value':
  179. returnValue = metaToValue(meta.value)
  180. return function () {
  181. return returnValue
  182. }
  183. case 'function': {
  184. // Merge contextId and meta.id, since meta.id can be the same in
  185. // different webContents.
  186. const objectId = [contextId, meta.id]
  187. // Cache the callbacks in renderer.
  188. if (rendererFunctions.has(objectId)) {
  189. return rendererFunctions.get(objectId)
  190. }
  191. const webContentsId = sender.getId()
  192. let callIntoRenderer = function (...args) {
  193. if (!sender.isDestroyed() && webContentsId === sender.getId()) {
  194. sender.send('ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args))
  195. } else {
  196. removeRemoteListenersAndLogWarning(meta, args, callIntoRenderer)
  197. }
  198. }
  199. Object.defineProperty(callIntoRenderer, 'length', { value: meta.length })
  200. v8Util.setRemoteCallbackFreer(callIntoRenderer, contextId, meta.id, sender)
  201. rendererFunctions.set(objectId, callIntoRenderer)
  202. return callIntoRenderer
  203. }
  204. default:
  205. throw new TypeError(`Unknown type: ${meta.type}`)
  206. }
  207. }
  208. return args.map(metaToValue)
  209. }
  210. // Call a function and send reply asynchronously if it's a an asynchronous
  211. // style function and the caller didn't pass a callback.
  212. const callFunction = function (event, contextId, func, caller, args) {
  213. let funcMarkedAsync, funcName, funcPassedCallback, ref, ret
  214. funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous')
  215. funcPassedCallback = typeof args[args.length - 1] === 'function'
  216. try {
  217. if (funcMarkedAsync && !funcPassedCallback) {
  218. args.push(function (ret) {
  219. event.returnValue = valueToMeta(event.sender, contextId, ret, true)
  220. })
  221. func.apply(caller, args)
  222. } else {
  223. ret = func.apply(caller, args)
  224. event.returnValue = valueToMeta(event.sender, contextId, ret, true)
  225. }
  226. } catch (error) {
  227. // Catch functions thrown further down in function invocation and wrap
  228. // them with the function name so it's easier to trace things like
  229. // `Error processing argument -1.`
  230. funcName = ((ref = func.name) != null) ? ref : 'anonymous'
  231. throw new Error(`Could not call remote function '${funcName}'. Check that the function signature is correct. Underlying error: ${error.message}`)
  232. }
  233. }
  234. ipcMain.on('ELECTRON_BROWSER_REQUIRE', function (event, contextId, module) {
  235. try {
  236. event.returnValue = valueToMeta(event.sender, contextId, process.mainModule.require(module))
  237. } catch (error) {
  238. event.returnValue = exceptionToMeta(error)
  239. }
  240. })
  241. ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, module) {
  242. try {
  243. event.returnValue = valueToMeta(event.sender, contextId, electron[module])
  244. } catch (error) {
  245. event.returnValue = exceptionToMeta(error)
  246. }
  247. })
  248. ipcMain.on('ELECTRON_BROWSER_GLOBAL', function (event, contextId, name) {
  249. try {
  250. event.returnValue = valueToMeta(event.sender, contextId, global[name])
  251. } catch (error) {
  252. event.returnValue = exceptionToMeta(error)
  253. }
  254. })
  255. ipcMain.on('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) {
  256. try {
  257. event.returnValue = valueToMeta(event.sender, contextId, event.sender.getOwnerBrowserWindow())
  258. } catch (error) {
  259. event.returnValue = exceptionToMeta(error)
  260. }
  261. })
  262. ipcMain.on('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId) {
  263. event.returnValue = valueToMeta(event.sender, contextId, event.sender)
  264. })
  265. ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
  266. try {
  267. args = unwrapArgs(event.sender, contextId, args)
  268. let constructor = objectsRegistry.get(id)
  269. if (constructor == null) {
  270. throwRPCError(`Cannot call constructor on missing remote object ${id}`)
  271. }
  272. // Call new with array of arguments.
  273. // http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
  274. let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))()
  275. event.returnValue = valueToMeta(event.sender, contextId, obj)
  276. } catch (error) {
  277. event.returnValue = exceptionToMeta(error)
  278. }
  279. })
  280. ipcMain.on('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
  281. try {
  282. args = unwrapArgs(event.sender, contextId, args)
  283. let func = objectsRegistry.get(id)
  284. if (func == null) {
  285. throwRPCError(`Cannot call function on missing remote object ${id}`)
  286. }
  287. callFunction(event, contextId, func, global, args)
  288. } catch (error) {
  289. event.returnValue = exceptionToMeta(error)
  290. }
  291. })
  292. ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
  293. try {
  294. args = unwrapArgs(event.sender, contextId, args)
  295. let object = objectsRegistry.get(id)
  296. if (object == null) {
  297. throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`)
  298. }
  299. // Call new with array of arguments.
  300. let constructor = object[method]
  301. let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))()
  302. event.returnValue = valueToMeta(event.sender, contextId, obj)
  303. } catch (error) {
  304. event.returnValue = exceptionToMeta(error)
  305. }
  306. })
  307. ipcMain.on('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
  308. try {
  309. args = unwrapArgs(event.sender, contextId, args)
  310. let obj = objectsRegistry.get(id)
  311. if (obj == null) {
  312. throwRPCError(`Cannot call function '${method}' on missing remote object ${id}`)
  313. }
  314. callFunction(event, contextId, obj[method], obj, args)
  315. } catch (error) {
  316. event.returnValue = exceptionToMeta(error)
  317. }
  318. })
  319. ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
  320. try {
  321. args = unwrapArgs(event.sender, contextId, args)
  322. let obj = objectsRegistry.get(id)
  323. if (obj == null) {
  324. throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`)
  325. }
  326. obj[name] = args[0]
  327. event.returnValue = null
  328. } catch (error) {
  329. event.returnValue = exceptionToMeta(error)
  330. }
  331. })
  332. ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) {
  333. try {
  334. let obj = objectsRegistry.get(id)
  335. if (obj == null) {
  336. throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`)
  337. }
  338. event.returnValue = valueToMeta(event.sender, contextId, obj[name])
  339. } catch (error) {
  340. event.returnValue = exceptionToMeta(error)
  341. }
  342. })
  343. ipcMain.on('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id) {
  344. objectsRegistry.remove(event.sender, contextId, id)
  345. })
  346. ipcMain.on('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
  347. objectsRegistry.clear(event.sender, contextId)
  348. event.returnValue = null
  349. })
  350. ipcMain.on('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {
  351. try {
  352. let guestViewManager = require('./guest-view-manager')
  353. event.returnValue = valueToMeta(event.sender, contextId, guestViewManager.getGuest(guestInstanceId))
  354. } catch (error) {
  355. event.returnValue = exceptionToMeta(error)
  356. }
  357. })
  358. ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, requestId, guestInstanceId, method, ...args) {
  359. try {
  360. let guestViewManager = require('./guest-view-manager')
  361. let guest = guestViewManager.getGuest(guestInstanceId)
  362. if (requestId) {
  363. const responseCallback = function (result) {
  364. event.sender.send(`ELECTRON_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, result)
  365. }
  366. args.push(responseCallback)
  367. }
  368. guest[method].apply(guest, args)
  369. } catch (error) {
  370. event.returnValue = exceptionToMeta(error)
  371. }
  372. })
  373. ipcMain.on('ELECTRON_BROWSER_SEND_TO', function (event, sendToAll, webContentsId, channel, ...args) {
  374. let contents = webContents.fromId(webContentsId)
  375. if (!contents) {
  376. console.error(`Sending message to WebContents with unknown ID ${webContentsId}`)
  377. return
  378. }
  379. if (sendToAll) {
  380. contents.sendToAll(channel, ...args)
  381. } else {
  382. contents.send(channel, ...args)
  383. }
  384. })
  385. // Implements window.alert(message, title)
  386. ipcMain.on('ELECTRON_BROWSER_WINDOW_ALERT', function (event, message, title) {
  387. if (message == null) message = ''
  388. if (title == null) title = ''
  389. const dialogProperties = {
  390. message: `${message}`,
  391. title: `${title}`,
  392. buttons: ['OK']
  393. }
  394. event.returnValue = event.sender.isOffscreen()
  395. ? electron.dialog.showMessageBox(dialogProperties)
  396. : electron.dialog.showMessageBox(
  397. event.sender.getOwnerBrowserWindow(), dialogProperties)
  398. })
  399. // Implements window.confirm(message, title)
  400. ipcMain.on('ELECTRON_BROWSER_WINDOW_CONFIRM', function (event, message, title) {
  401. if (message == null) message = ''
  402. if (title == null) title = ''
  403. const dialogProperties = {
  404. message: `${message}`,
  405. title: `${title}`,
  406. buttons: ['OK', 'Cancel'],
  407. cancelId: 1
  408. }
  409. event.returnValue = !(event.sender.isOffscreen()
  410. ? electron.dialog.showMessageBox(dialogProperties)
  411. : electron.dialog.showMessageBox(
  412. event.sender.getOwnerBrowserWindow(), dialogProperties))
  413. })
  414. // Implements window.close()
  415. ipcMain.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
  416. const window = event.sender.getOwnerBrowserWindow()
  417. if (window) {
  418. window.close()
  419. }
  420. event.returnValue = null
  421. })