isolation.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. /**
  5. * IMPORTANT: See ipc.js for the main frame implementation.
  6. * main frame -> isolation frame = isolation payload
  7. * isolation frame -> main frame = isolation message
  8. */
  9. ;(async function () {
  10. /**
  11. * Sends the message to the isolation frame.
  12. * @param {any} message
  13. */
  14. function sendMessage(message) {
  15. window.parent.postMessage(message, '*')
  16. }
  17. /**
  18. * @type {Uint8Array} - Injected by Tauri during runtime
  19. */
  20. const aesGcmKeyRaw = new Uint8Array(__TEMPLATE_runtime_aes_gcm_key__)
  21. /**
  22. * @type {CryptoKey}
  23. */
  24. const aesGcmKey = await window.crypto.subtle.importKey(
  25. 'raw',
  26. aesGcmKeyRaw,
  27. 'AES-GCM',
  28. false,
  29. ['encrypt']
  30. )
  31. /**
  32. * @param {object} data
  33. * @return {Promise<{nonce: number[], payload: number[]}>}
  34. */
  35. async function encrypt(data) {
  36. const algorithm = Object.create(null)
  37. algorithm.name = 'AES-GCM'
  38. algorithm.iv = window.crypto.getRandomValues(new Uint8Array(12))
  39. const encoder = new TextEncoder()
  40. const encoded = encoder.encode(__RAW_process_ipc_message_fn__(data).data)
  41. return window.crypto.subtle
  42. .encrypt(algorithm, aesGcmKey, encoded)
  43. .then((payload) => {
  44. const result = Object.create(null)
  45. result.nonce = Array.from(new Uint8Array(algorithm.iv))
  46. result.payload = Array.from(new Uint8Array(payload))
  47. return result
  48. })
  49. }
  50. /**
  51. * Detects if a message event is a valid isolation message.
  52. *
  53. * @param {MessageEvent<object>} event - a message event that is expected to be an isolation message
  54. * @return {boolean} - if the event was a valid isolation message
  55. */
  56. function isIsolationMessage(data) {
  57. if (typeof data === 'object' && typeof data.payload === 'object') {
  58. const keys = data.payload ? Object.keys(data.payload) : []
  59. return (
  60. keys.length > 0 &&
  61. keys.every((key) => key === 'nonce' || key === 'payload')
  62. )
  63. }
  64. return false
  65. }
  66. /**
  67. * Detect if a message event is a valid isolation payload.
  68. *
  69. * @param {MessageEvent<object>} event - a message event that is expected to be an isolation payload
  70. * @return boolean
  71. */
  72. function isIsolationPayload(data) {
  73. return (
  74. typeof data === 'object' &&
  75. 'callback' in data &&
  76. 'error' in data &&
  77. !isIsolationMessage(data)
  78. )
  79. }
  80. /**
  81. * Handle incoming payload events.
  82. * @param {MessageEvent<any>} event
  83. */
  84. async function payloadHandler(event) {
  85. if (!isIsolationPayload(event.data)) {
  86. return
  87. }
  88. let data = event.data
  89. if (typeof window.__TAURI_ISOLATION_HOOK__ === 'function') {
  90. // await even if it's not async so that we can support async ones
  91. data = await window.__TAURI_ISOLATION_HOOK__(data)
  92. }
  93. const message = Object.create(null)
  94. message.cmd = data.cmd
  95. message.callback = data.callback
  96. message.error = data.error
  97. message.options = data.options
  98. message.payload = await encrypt(data.payload)
  99. sendMessage(message)
  100. }
  101. window.addEventListener('message', payloadHandler, false)
  102. /**
  103. * @type {number} - How many milliseconds to wait between ready checks
  104. */
  105. const readyIntervalMs = 50
  106. /**
  107. * Wait until this Isolation context is ready to receive messages, and let the main frame know.
  108. */
  109. function waitUntilReady() {
  110. // consider either a function or an explicitly set null value as the ready signal
  111. if (
  112. typeof window.__TAURI_ISOLATION_HOOK__ === 'function' ||
  113. window.__TAURI_ISOLATION_HOOK__ === null
  114. ) {
  115. sendMessage('__TAURI_ISOLATION_READY__')
  116. } else {
  117. setTimeout(waitUntilReady, readyIntervalMs)
  118. }
  119. }
  120. setTimeout(waitUntilReady, readyIntervalMs)
  121. document.currentScript.remove()
  122. })()