fs.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. import { invokeTauriCommand } from './helpers/tauri.js';
  2. // Copyright 2019-2023 Tauri Programme within The Commons Conservancy
  3. // SPDX-License-Identifier: Apache-2.0
  4. // SPDX-License-Identifier: MIT
  5. /**
  6. * Access the file system.
  7. *
  8. * This package is also accessible with `window.__TAURI__.fs` when [`build.withGlobalTauri`](https://tauri.app/v1/api/config/#buildconfig.withglobaltauri) in `tauri.conf.json` is set to `true`.
  9. *
  10. * The APIs must be added to [`tauri.allowlist.fs`](https://tauri.app/v1/api/config/#allowlistconfig.fs) in `tauri.conf.json`:
  11. * ```json
  12. * {
  13. * "tauri": {
  14. * "allowlist": {
  15. * "fs": {
  16. * "all": true, // enable all FS APIs
  17. * "readFile": true,
  18. * "writeFile": true,
  19. * "readDir": true,
  20. * "copyFile": true,
  21. * "createDir": true,
  22. * "removeDir": true,
  23. * "removeFile": true,
  24. * "renameFile": true,
  25. * "exists": true
  26. * }
  27. * }
  28. * }
  29. * }
  30. * ```
  31. * It is recommended to allowlist only the APIs you use for optimal bundle size and security.
  32. *
  33. * ## Security
  34. *
  35. * This module prevents path traversal, not allowing absolute paths or parent dir components
  36. * (i.e. "/usr/path/to/file" or "../path/to/file" paths are not allowed).
  37. * Paths accessed with this API must be relative to one of the {@link BaseDirectory | base directories}
  38. * so if you need access to arbitrary filesystem paths, you must write such logic on the core layer instead.
  39. *
  40. * The API has a scope configuration that forces you to restrict the paths that can be accessed using glob patterns.
  41. *
  42. * The scope configuration is an array of glob patterns describing folder paths that are allowed.
  43. * For instance, this scope configuration only allows accessing files on the
  44. * *databases* folder of the {@link path.appDataDir | $APPDATA directory}:
  45. * ```json
  46. * {
  47. * "tauri": {
  48. * "allowlist": {
  49. * "fs": {
  50. * "scope": ["$APPDATA/databases/*"]
  51. * }
  52. * }
  53. * }
  54. * }
  55. * ```
  56. *
  57. * Notice the use of the `$APPDATA` variable. The value is injected at runtime, resolving to the {@link path.appDataDir | app data directory}.
  58. * The available variables are:
  59. * {@link path.appConfigDir | `$APPCONFIG`}, {@link path.appDataDir | `$APPDATA`}, {@link path.appLocalDataDir | `$APPLOCALDATA`},
  60. * {@link path.appCacheDir | `$APPCACHE`}, {@link path.appLogDir | `$APPLOG`},
  61. * {@link path.audioDir | `$AUDIO`}, {@link path.cacheDir | `$CACHE`}, {@link path.configDir | `$CONFIG`}, {@link path.dataDir | `$DATA`},
  62. * {@link path.localDataDir | `$LOCALDATA`}, {@link path.desktopDir | `$DESKTOP`}, {@link path.documentDir | `$DOCUMENT`},
  63. * {@link path.downloadDir | `$DOWNLOAD`}, {@link path.executableDir | `$EXE`}, {@link path.fontDir | `$FONT`}, {@link path.homeDir | `$HOME`},
  64. * {@link path.pictureDir | `$PICTURE`}, {@link path.publicDir | `$PUBLIC`}, {@link path.runtimeDir | `$RUNTIME`},
  65. * {@link path.templateDir | `$TEMPLATE`}, {@link path.videoDir | `$VIDEO`}, {@link path.resourceDir | `$RESOURCE`}, {@link path.appDir | `$APP`},
  66. * {@link path.logDir | `$LOG`}, {@link os.tempdir | `$TEMP`}.
  67. *
  68. * Trying to execute any API with a URL not configured on the scope results in a promise rejection due to denied access.
  69. *
  70. * Note that this scope applies to **all** APIs on this module.
  71. *
  72. * @module
  73. */
  74. /**
  75. * @since 1.0.0
  76. */
  77. var BaseDirectory;
  78. (function (BaseDirectory) {
  79. BaseDirectory[BaseDirectory["Audio"] = 1] = "Audio";
  80. BaseDirectory[BaseDirectory["Cache"] = 2] = "Cache";
  81. BaseDirectory[BaseDirectory["Config"] = 3] = "Config";
  82. BaseDirectory[BaseDirectory["Data"] = 4] = "Data";
  83. BaseDirectory[BaseDirectory["LocalData"] = 5] = "LocalData";
  84. BaseDirectory[BaseDirectory["Desktop"] = 6] = "Desktop";
  85. BaseDirectory[BaseDirectory["Document"] = 7] = "Document";
  86. BaseDirectory[BaseDirectory["Download"] = 8] = "Download";
  87. BaseDirectory[BaseDirectory["Executable"] = 9] = "Executable";
  88. BaseDirectory[BaseDirectory["Font"] = 10] = "Font";
  89. BaseDirectory[BaseDirectory["Home"] = 11] = "Home";
  90. BaseDirectory[BaseDirectory["Picture"] = 12] = "Picture";
  91. BaseDirectory[BaseDirectory["Public"] = 13] = "Public";
  92. BaseDirectory[BaseDirectory["Runtime"] = 14] = "Runtime";
  93. BaseDirectory[BaseDirectory["Template"] = 15] = "Template";
  94. BaseDirectory[BaseDirectory["Video"] = 16] = "Video";
  95. BaseDirectory[BaseDirectory["Resource"] = 17] = "Resource";
  96. BaseDirectory[BaseDirectory["App"] = 18] = "App";
  97. BaseDirectory[BaseDirectory["Log"] = 19] = "Log";
  98. BaseDirectory[BaseDirectory["Temp"] = 20] = "Temp";
  99. BaseDirectory[BaseDirectory["AppConfig"] = 21] = "AppConfig";
  100. BaseDirectory[BaseDirectory["AppData"] = 22] = "AppData";
  101. BaseDirectory[BaseDirectory["AppLocalData"] = 23] = "AppLocalData";
  102. BaseDirectory[BaseDirectory["AppCache"] = 24] = "AppCache";
  103. BaseDirectory[BaseDirectory["AppLog"] = 25] = "AppLog";
  104. })(BaseDirectory || (BaseDirectory = {}));
  105. /**
  106. * Reads a file as an UTF-8 encoded string.
  107. * @example
  108. * ```typescript
  109. * import { readTextFile, BaseDirectory } from '@tauri-apps/api/fs';
  110. * // Read the text file in the `$APPCONFIG/app.conf` path
  111. * const contents = await readTextFile('app.conf', { dir: BaseDirectory.AppConfig });
  112. * ```
  113. *
  114. * @since 1.0.0
  115. */
  116. async function readTextFile(filePath, options = {}) {
  117. return invokeTauriCommand({
  118. __tauriModule: 'Fs',
  119. message: {
  120. cmd: 'readTextFile',
  121. path: filePath,
  122. options
  123. }
  124. });
  125. }
  126. /**
  127. * Reads a file as byte array.
  128. * @example
  129. * ```typescript
  130. * import { readBinaryFile, BaseDirectory } from '@tauri-apps/api/fs';
  131. * // Read the image file in the `$RESOURCEDIR/avatar.png` path
  132. * const contents = await readBinaryFile('avatar.png', { dir: BaseDirectory.Resource });
  133. * ```
  134. *
  135. * @since 1.0.0
  136. */
  137. async function readBinaryFile(filePath, options = {}) {
  138. const arr = await invokeTauriCommand({
  139. __tauriModule: 'Fs',
  140. message: {
  141. cmd: 'readFile',
  142. path: filePath,
  143. options
  144. }
  145. });
  146. return Uint8Array.from(arr);
  147. }
  148. /**
  149. * Writes a UTF-8 text file.
  150. *
  151. * @returns A promise indicating the success or failure of the operation.
  152. *
  153. * @since 1.0.0
  154. */
  155. async function writeTextFile(path, contents, options) {
  156. if (typeof options === 'object') {
  157. Object.freeze(options);
  158. }
  159. if (typeof path === 'object') {
  160. Object.freeze(path);
  161. }
  162. const file = { path: '', contents: '' };
  163. let fileOptions = options;
  164. if (typeof path === 'string') {
  165. file.path = path;
  166. }
  167. else {
  168. file.path = path.path;
  169. file.contents = path.contents;
  170. }
  171. if (typeof contents === 'string') {
  172. file.contents = contents !== null && contents !== void 0 ? contents : '';
  173. }
  174. else {
  175. fileOptions = contents;
  176. }
  177. return invokeTauriCommand({
  178. __tauriModule: 'Fs',
  179. message: {
  180. cmd: 'writeFile',
  181. path: file.path,
  182. contents: Array.from(new TextEncoder().encode(file.contents)),
  183. options: fileOptions
  184. }
  185. });
  186. }
  187. /**
  188. * Writes a byte array content to a file.
  189. *
  190. * @returns A promise indicating the success or failure of the operation.
  191. *
  192. * @since 1.0.0
  193. */
  194. async function writeBinaryFile(path, contents, options) {
  195. if (typeof options === 'object') {
  196. Object.freeze(options);
  197. }
  198. if (typeof path === 'object') {
  199. Object.freeze(path);
  200. }
  201. const file = { path: '', contents: [] };
  202. let fileOptions = options;
  203. if (typeof path === 'string') {
  204. file.path = path;
  205. }
  206. else {
  207. file.path = path.path;
  208. file.contents = path.contents;
  209. }
  210. if (contents && 'dir' in contents) {
  211. fileOptions = contents;
  212. }
  213. else if (typeof path === 'string') {
  214. // @ts-expect-error in this case `contents` is always a BinaryFileContents
  215. file.contents = contents !== null && contents !== void 0 ? contents : [];
  216. }
  217. return invokeTauriCommand({
  218. __tauriModule: 'Fs',
  219. message: {
  220. cmd: 'writeFile',
  221. path: file.path,
  222. contents: Array.from(file.contents instanceof ArrayBuffer
  223. ? new Uint8Array(file.contents)
  224. : file.contents),
  225. options: fileOptions
  226. }
  227. });
  228. }
  229. /**
  230. * List directory files.
  231. * @example
  232. * ```typescript
  233. * import { readDir, BaseDirectory } from '@tauri-apps/api/fs';
  234. * // Reads the `$APPDATA/users` directory recursively
  235. * const entries = await readDir('users', { dir: BaseDirectory.AppData, recursive: true });
  236. *
  237. * function processEntries(entries) {
  238. * for (const entry of entries) {
  239. * console.log(`Entry: ${entry.path}`);
  240. * if (entry.children) {
  241. * processEntries(entry.children)
  242. * }
  243. * }
  244. * }
  245. * ```
  246. *
  247. * @since 1.0.0
  248. */
  249. async function readDir(dir, options = {}) {
  250. return invokeTauriCommand({
  251. __tauriModule: 'Fs',
  252. message: {
  253. cmd: 'readDir',
  254. path: dir,
  255. options
  256. }
  257. });
  258. }
  259. /**
  260. * Creates a directory.
  261. * If one of the path's parent components doesn't exist
  262. * and the `recursive` option isn't set to true, the promise will be rejected.
  263. * @example
  264. * ```typescript
  265. * import { createDir, BaseDirectory } from '@tauri-apps/api/fs';
  266. * // Create the `$APPDATA/users` directory
  267. * await createDir('users', { dir: BaseDirectory.AppData, recursive: true });
  268. * ```
  269. *
  270. * @returns A promise indicating the success or failure of the operation.
  271. *
  272. * @since 1.0.0
  273. */
  274. async function createDir(dir, options = {}) {
  275. return invokeTauriCommand({
  276. __tauriModule: 'Fs',
  277. message: {
  278. cmd: 'createDir',
  279. path: dir,
  280. options
  281. }
  282. });
  283. }
  284. /**
  285. * Removes a directory.
  286. * If the directory is not empty and the `recursive` option isn't set to true, the promise will be rejected.
  287. * @example
  288. * ```typescript
  289. * import { removeDir, BaseDirectory } from '@tauri-apps/api/fs';
  290. * // Remove the directory `$APPDATA/users`
  291. * await removeDir('users', { dir: BaseDirectory.AppData });
  292. * ```
  293. *
  294. * @returns A promise indicating the success or failure of the operation.
  295. *
  296. * @since 1.0.0
  297. */
  298. async function removeDir(dir, options = {}) {
  299. return invokeTauriCommand({
  300. __tauriModule: 'Fs',
  301. message: {
  302. cmd: 'removeDir',
  303. path: dir,
  304. options
  305. }
  306. });
  307. }
  308. /**
  309. * Copies a file to a destination.
  310. * @example
  311. * ```typescript
  312. * import { copyFile, BaseDirectory } from '@tauri-apps/api/fs';
  313. * // Copy the `$APPCONFIG/app.conf` file to `$APPCONFIG/app.conf.bk`
  314. * await copyFile('app.conf', 'app.conf.bk', { dir: BaseDirectory.AppConfig });
  315. * ```
  316. *
  317. * @returns A promise indicating the success or failure of the operation.
  318. *
  319. * @since 1.0.0
  320. */
  321. async function copyFile(source, destination, options = {}) {
  322. return invokeTauriCommand({
  323. __tauriModule: 'Fs',
  324. message: {
  325. cmd: 'copyFile',
  326. source,
  327. destination,
  328. options
  329. }
  330. });
  331. }
  332. /**
  333. * Removes a file.
  334. * @example
  335. * ```typescript
  336. * import { removeFile, BaseDirectory } from '@tauri-apps/api/fs';
  337. * // Remove the `$APPConfig/app.conf` file
  338. * await removeFile('app.conf', { dir: BaseDirectory.AppConfig });
  339. * ```
  340. *
  341. * @returns A promise indicating the success or failure of the operation.
  342. *
  343. * @since 1.0.0
  344. */
  345. async function removeFile(file, options = {}) {
  346. return invokeTauriCommand({
  347. __tauriModule: 'Fs',
  348. message: {
  349. cmd: 'removeFile',
  350. path: file,
  351. options
  352. }
  353. });
  354. }
  355. /**
  356. * Renames a file.
  357. * @example
  358. * ```typescript
  359. * import { renameFile, BaseDirectory } from '@tauri-apps/api/fs';
  360. * // Rename the `$APPDATA/avatar.png` file
  361. * await renameFile('avatar.png', 'deleted.png', { dir: BaseDirectory.AppData });
  362. * ```
  363. *
  364. * @returns A promise indicating the success or failure of the operation.
  365. *
  366. * @since 1.0.0
  367. */
  368. async function renameFile(oldPath, newPath, options = {}) {
  369. return invokeTauriCommand({
  370. __tauriModule: 'Fs',
  371. message: {
  372. cmd: 'renameFile',
  373. oldPath,
  374. newPath,
  375. options
  376. }
  377. });
  378. }
  379. /**
  380. * Check if a path exists.
  381. * @example
  382. * ```typescript
  383. * import { exists, BaseDirectory } from '@tauri-apps/api/fs';
  384. * // Check if the `$APPDATA/avatar.png` file exists
  385. * await exists('avatar.png', { dir: BaseDirectory.AppData });
  386. * ```
  387. *
  388. * @since 1.1.0
  389. */
  390. async function exists(path, options = {}) {
  391. return invokeTauriCommand({
  392. __tauriModule: 'Fs',
  393. message: {
  394. cmd: 'exists',
  395. path,
  396. options
  397. }
  398. });
  399. }
  400. export { BaseDirectory, BaseDirectory as Dir, copyFile, createDir, exists, readBinaryFile, readDir, readTextFile, removeDir, removeFile, renameFile, writeBinaryFile, writeTextFile as writeFile, writeTextFile };