updater.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. /**
  5. * Customize the auto updater flow.
  6. *
  7. * This package is also accessible with `window.__TAURI__.updater` when [`build.withGlobalTauri`](https://tauri.app/v1/api/config/#buildconfig.withglobaltauri) in `tauri.conf.json` is set to `true`.
  8. * @module
  9. */
  10. import { once, listen, emit, UnlistenFn } from './event'
  11. type UpdateStatus = 'PENDING' | 'ERROR' | 'DONE' | 'UPTODATE'
  12. interface UpdateStatusResult {
  13. error?: string
  14. status: UpdateStatus
  15. }
  16. interface UpdateManifest {
  17. version: string
  18. date: string
  19. body: string
  20. }
  21. interface UpdateResult {
  22. manifest?: UpdateManifest
  23. shouldUpdate: boolean
  24. }
  25. /**
  26. * Listen to an updater event.
  27. * @example
  28. * ```typescript
  29. * import { onUpdaterEvent } from "@tauri-apps/api/updater";
  30. * const unlisten = await onUpdaterEvent(({ error, status }) => {
  31. * console.log('Updater event', error, status);
  32. * });
  33. *
  34. * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
  35. * unlisten();
  36. * ```
  37. *
  38. * @param handler
  39. * @returns A promise resolving to a function to unlisten to the event.
  40. * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
  41. */
  42. async function onUpdaterEvent(
  43. handler: (status: UpdateStatusResult) => void
  44. ): Promise<UnlistenFn> {
  45. return listen('tauri://update-status', (data: { payload: any }) => {
  46. handler(data?.payload as UpdateStatusResult)
  47. })
  48. }
  49. /**
  50. * Install the update if there's one available.
  51. * @example
  52. * ```typescript
  53. * import { checkUpdate, installUpdate } from '@tauri-apps/api/updater';
  54. * const update = await checkUpdate();
  55. * if (update.shouldUpdate) {
  56. * console.log(`Installing update ${update.manifest?.version}, ${update.manifest?.date}, ${update.manifest.body}`);
  57. * await installUpdate();
  58. * }
  59. * ```
  60. *
  61. * @return A promise indicating the success or failure of the operation.
  62. */
  63. async function installUpdate(): Promise<void> {
  64. let unlistenerFn: UnlistenFn | undefined
  65. function cleanListener(): void {
  66. if (unlistenerFn) {
  67. unlistenerFn()
  68. }
  69. unlistenerFn = undefined
  70. }
  71. return new Promise((resolve, reject) => {
  72. function onStatusChange(statusResult: UpdateStatusResult): void {
  73. if (statusResult.error) {
  74. cleanListener()
  75. return reject(statusResult.error)
  76. }
  77. // install complete
  78. if (statusResult.status === 'DONE') {
  79. cleanListener()
  80. return resolve()
  81. }
  82. }
  83. // listen status change
  84. onUpdaterEvent(onStatusChange)
  85. .then((fn) => {
  86. unlistenerFn = fn
  87. })
  88. .catch((e) => {
  89. cleanListener()
  90. // dispatch the error to our checkUpdate
  91. throw e
  92. })
  93. // start the process we dont require much security as it's
  94. // handled by rust
  95. emit('tauri://update-install').catch((e) => {
  96. cleanListener()
  97. // dispatch the error to our checkUpdate
  98. throw e
  99. })
  100. })
  101. }
  102. /**
  103. * Checks if an update is available.
  104. * @example
  105. * ```typescript
  106. * import { checkUpdate } from '@tauri-apps/api/updater';
  107. * const update = await checkUpdate();
  108. * // now run installUpdate() if needed
  109. * ```
  110. *
  111. * @return Promise resolving to the update status.
  112. */
  113. async function checkUpdate(): Promise<UpdateResult> {
  114. let unlistenerFn: UnlistenFn | undefined
  115. function cleanListener(): void {
  116. if (unlistenerFn) {
  117. unlistenerFn()
  118. }
  119. unlistenerFn = undefined
  120. }
  121. return new Promise((resolve, reject) => {
  122. function onUpdateAvailable(manifest: UpdateManifest): void {
  123. cleanListener()
  124. return resolve({
  125. manifest,
  126. shouldUpdate: true
  127. })
  128. }
  129. function onStatusChange(statusResult: UpdateStatusResult): void {
  130. if (statusResult.error) {
  131. cleanListener()
  132. return reject(statusResult.error)
  133. }
  134. if (statusResult.status === 'UPTODATE') {
  135. cleanListener()
  136. return resolve({
  137. shouldUpdate: false
  138. })
  139. }
  140. }
  141. // wait to receive the latest update
  142. once('tauri://update-available', (data: { payload: any }) => {
  143. onUpdateAvailable(data?.payload as UpdateManifest)
  144. }).catch((e) => {
  145. cleanListener()
  146. // dispatch the error to our checkUpdate
  147. throw e
  148. })
  149. // listen status change
  150. onUpdaterEvent(onStatusChange)
  151. .then((fn) => {
  152. unlistenerFn = fn
  153. })
  154. .catch((e) => {
  155. cleanListener()
  156. // dispatch the error to our checkUpdate
  157. throw e
  158. })
  159. // start the process
  160. emit('tauri://update').catch((e) => {
  161. cleanListener()
  162. // dispatch the error to our checkUpdate
  163. throw e
  164. })
  165. })
  166. }
  167. export type { UpdateStatus, UpdateStatusResult, UpdateManifest, UpdateResult }
  168. export { onUpdaterEvent, installUpdate, checkUpdate }