ipc.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. /**
  5. * @typedef {{callback: string, error: string, data: *}} IsolationPayload - a valid isolation payload
  6. */
  7. ;(function () {
  8. /**
  9. * @type {string}
  10. */
  11. const pattern = window.__TAURI_INTERNALS__.__TAURI_PATTERN__.pattern
  12. /**
  13. * @type {string}
  14. */
  15. const isolationOrigin = __TEMPLATE_isolation_origin__
  16. /**
  17. * @type {{queue: object[], ready: boolean, frame: HTMLElement | null}}
  18. */
  19. const isolation = Object.create(null)
  20. isolation.queue = []
  21. isolation.ready = false
  22. isolation.frame = null
  23. /**
  24. * Detects if a message event is a valid isolation message.
  25. *
  26. * @param {MessageEvent<object>} event - a message event that is expected to be an isolation message
  27. * @return {boolean} - if the event was a valid isolation message
  28. */
  29. function isIsolationMessage(event) {
  30. if (
  31. typeof event.data === 'object' &&
  32. typeof event.data.payload === 'object'
  33. ) {
  34. const keys = Object.keys(event.data.payload || {})
  35. return (
  36. keys.length > 0 &&
  37. keys.every(
  38. (key) => key === 'contentType' || key === 'nonce' || key === 'payload'
  39. )
  40. )
  41. }
  42. return false
  43. }
  44. /**
  45. * Detects if data is able to transform into an isolation payload.
  46. *
  47. * @param {object} data - object that is expected to contain at least a callback and error identifier
  48. * @return {boolean} - if the data is able to transform into an isolation payload
  49. */
  50. function isIsolationPayload(data) {
  51. return (
  52. typeof data === 'object' &&
  53. 'callback' in data &&
  54. 'error' in data &&
  55. !isIsolationMessage(data)
  56. )
  57. }
  58. /**
  59. * Sends a properly formatted message to the isolation frame.
  60. *
  61. * @param {IsolationPayload} data - data that has been validated to be an isolation payload
  62. */
  63. function sendIsolationMessage(data) {
  64. // set the frame dom element if it's not been set before
  65. if (!isolation.frame) {
  66. const frame = document.querySelector('iframe#__tauri_isolation__')
  67. if (frame.src.startsWith(isolationOrigin)) {
  68. isolation.frame = frame
  69. } else {
  70. console.error(
  71. 'Tauri IPC found an isolation iframe, but it had the wrong origin'
  72. )
  73. }
  74. }
  75. // ensure we have the target to send the message to
  76. if (!isolation.frame || !isolation.frame.contentWindow) {
  77. console.error(
  78. 'Tauri "Isolation" Pattern could not find the Isolation iframe window'
  79. )
  80. return
  81. }
  82. // `postMessage` uses `structuredClone` to serialize the data before sending it
  83. // unlike `JSON.stringify`, we can't extend a type with a method similar to `toJSON`
  84. // so that `structuredClone` would use, so until https://github.com/whatwg/html/issues/7428
  85. // we manually call `toIPC`
  86. function serializeIpcPayload(data) {
  87. // if this value changes, make sure to update it in:
  88. // 1. process-ipc-message-fn.js
  89. // 2. core.ts
  90. const SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__'
  91. if (
  92. typeof data === 'object' &&
  93. data !== null &&
  94. 'constructor' in data &&
  95. data.constructor === Array
  96. ) {
  97. return data.map((v) => serializeIpcPayload(v))
  98. }
  99. if (
  100. typeof data === 'object' &&
  101. data !== null &&
  102. SERIALIZE_TO_IPC_FN in data
  103. ) {
  104. return data[SERIALIZE_TO_IPC_FN]()
  105. }
  106. if (
  107. typeof data === 'object' &&
  108. data !== null &&
  109. 'constructor' in data &&
  110. data.constructor === Object
  111. ) {
  112. const acc = {}
  113. Object.entries(data).forEach(([k, v]) => {
  114. acc[k] = serializeIpcPayload(v)
  115. })
  116. return acc
  117. }
  118. return data
  119. }
  120. data.payload = serializeIpcPayload(data.payload)
  121. isolation.frame.contentWindow.postMessage(
  122. data,
  123. '*' /* todo: set this to the secure origin */
  124. )
  125. }
  126. Object.defineProperty(window.__TAURI_INTERNALS__, 'ipc', {
  127. // todo: JSDoc this function
  128. value: Object.freeze((message) => {
  129. switch (pattern) {
  130. case 'brownfield':
  131. window.__TAURI_INTERNALS__.postMessage(message)
  132. break
  133. case 'isolation':
  134. if (!isIsolationPayload(message)) {
  135. console.error(
  136. 'Tauri "Isolation" Pattern found an invalid isolation message payload',
  137. message
  138. )
  139. break
  140. }
  141. if (isolation.ready) {
  142. sendIsolationMessage(message)
  143. } else {
  144. isolation.queue.push(message)
  145. }
  146. break
  147. case 'error':
  148. console.error(
  149. 'Tauri IPC found a Tauri Pattern, but it was an error. Check for other log messages to find the cause.'
  150. )
  151. break
  152. default:
  153. console.error(
  154. 'Tauri IPC did not find a Tauri Pattern that it understood.'
  155. )
  156. break
  157. }
  158. })
  159. })
  160. /**
  161. * IMPORTANT: See isolation_secure.js for the isolation frame implementation.
  162. * main frame -> isolation frame = isolation payload
  163. * isolation frame -> main frame = isolation message
  164. */
  165. if (pattern === 'isolation') {
  166. window.addEventListener(
  167. 'message',
  168. (event) => {
  169. // watch for the isolation frame being ready and flush any queued messages
  170. if (event.data === '__TAURI_ISOLATION_READY__') {
  171. isolation.ready = true
  172. for (const message of isolation.queue) {
  173. sendIsolationMessage(message)
  174. }
  175. isolation.queue = []
  176. return
  177. }
  178. if (isIsolationMessage(event)) {
  179. window.__TAURI_INTERNALS__.postMessage(event.data)
  180. }
  181. },
  182. false
  183. )
  184. }
  185. })()