rpc-server.js 15 KB

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