http.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  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 HTTP client written in Rust.
  7. *
  8. * This package is also accessible with `window.__TAURI__.http` 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 allowlisted on `tauri.conf.json`:
  11. * ```json
  12. * {
  13. * "tauri": {
  14. * "allowlist": {
  15. * "http": {
  16. * "all": true, // enable all http APIs
  17. * "request": true // enable HTTP request API
  18. * }
  19. * }
  20. * }
  21. * }
  22. * ```
  23. * It is recommended to allowlist only the APIs you use for optimal bundle size and security.
  24. *
  25. * ## Security
  26. *
  27. * This API has a scope configuration that forces you to restrict the URLs and paths that can be accessed using glob patterns.
  28. *
  29. * For instance, this scope configuration only allows making HTTP requests to the GitHub API for the `tauri-apps` organization:
  30. * ```json
  31. * {
  32. * "tauri": {
  33. * "allowlist": {
  34. * "http": {
  35. * "scope": ["https://api.github.com/repos/tauri-apps/*"]
  36. * }
  37. * }
  38. * }
  39. * }
  40. * ```
  41. * Trying to execute any API with a URL not configured on the scope results in a promise rejection due to denied access.
  42. *
  43. * @module
  44. */
  45. /**
  46. * @since 1.0.0
  47. */
  48. var ResponseType;
  49. (function (ResponseType) {
  50. ResponseType[ResponseType["JSON"] = 1] = "JSON";
  51. ResponseType[ResponseType["Text"] = 2] = "Text";
  52. ResponseType[ResponseType["Binary"] = 3] = "Binary";
  53. })(ResponseType || (ResponseType = {}));
  54. async function formBody(data) {
  55. const form = {};
  56. const append = async (key, v) => {
  57. if (v !== null) {
  58. let r;
  59. if (typeof v === 'string') {
  60. r = v;
  61. }
  62. else if (v instanceof Uint8Array || Array.isArray(v)) {
  63. r = Array.from(v);
  64. }
  65. else if (v instanceof File) {
  66. r = {
  67. file: Array.from(new Uint8Array(await v.arrayBuffer())),
  68. mime: v.type,
  69. fileName: v.name
  70. };
  71. }
  72. else if (typeof v.file === 'string') {
  73. r = { file: v.file, mime: v.mime, fileName: v.fileName };
  74. }
  75. else {
  76. r = { file: Array.from(v.file), mime: v.mime, fileName: v.fileName };
  77. }
  78. form[String(key)] = r;
  79. }
  80. };
  81. if (data instanceof FormData) {
  82. for (const [key, value] of data) {
  83. await append(key, value);
  84. }
  85. }
  86. else {
  87. for (const [key, value] of Object.entries(data)) {
  88. await append(key, value);
  89. }
  90. }
  91. return form;
  92. }
  93. /**
  94. * The body object to be used on POST and PUT requests.
  95. *
  96. * @since 1.0.0
  97. */
  98. class Body {
  99. /** @ignore */
  100. constructor(type, payload) {
  101. this.type = type;
  102. this.payload = payload;
  103. }
  104. /**
  105. * Creates a new form data body. The form data is an object where each key is the entry name,
  106. * and the value is either a string or a file object.
  107. *
  108. * By default it sets the `application/x-www-form-urlencoded` Content-Type header,
  109. * but you can set it to `multipart/form-data` if the Cargo feature `http-multipart` is enabled.
  110. *
  111. * Note that a file path must be allowed in the `fs` allowlist scope.
  112. *
  113. * @example
  114. * ```typescript
  115. * import { Body } from "@tauri-apps/api/http"
  116. * const body = Body.form({
  117. * key: 'value',
  118. * image: {
  119. * file: '/path/to/file', // either a path or an array buffer of the file contents
  120. * mime: 'image/jpeg', // optional
  121. * fileName: 'image.jpg' // optional
  122. * }
  123. * });
  124. *
  125. * // alternatively, use a FormData:
  126. * const form = new FormData();
  127. * form.append('key', 'value');
  128. * form.append('image', file, 'image.png');
  129. * const formBody = Body.form(form);
  130. * ```
  131. *
  132. * @param data The body data.
  133. *
  134. * @returns The body object ready to be used on the POST and PUT requests.
  135. */
  136. static form(data) {
  137. return new Body('Form', data);
  138. }
  139. /**
  140. * Creates a new JSON body.
  141. * @example
  142. * ```typescript
  143. * import { Body } from "@tauri-apps/api/http"
  144. * Body.json({
  145. * registered: true,
  146. * name: 'tauri'
  147. * });
  148. * ```
  149. *
  150. * @param data The body JSON object.
  151. *
  152. * @returns The body object ready to be used on the POST and PUT requests.
  153. */
  154. static json(data) {
  155. return new Body('Json', data);
  156. }
  157. /**
  158. * Creates a new UTF-8 string body.
  159. * @example
  160. * ```typescript
  161. * import { Body } from "@tauri-apps/api/http"
  162. * Body.text('The body content as a string');
  163. * ```
  164. *
  165. * @param value The body string.
  166. *
  167. * @returns The body object ready to be used on the POST and PUT requests.
  168. */
  169. static text(value) {
  170. return new Body('Text', value);
  171. }
  172. /**
  173. * Creates a new byte array body.
  174. * @example
  175. * ```typescript
  176. * import { Body } from "@tauri-apps/api/http"
  177. * Body.bytes(new Uint8Array([1, 2, 3]));
  178. * ```
  179. *
  180. * @param bytes The body byte array.
  181. *
  182. * @returns The body object ready to be used on the POST and PUT requests.
  183. */
  184. static bytes(bytes) {
  185. // stringifying Uint8Array doesn't return an array of numbers, so we create one here
  186. return new Body('Bytes', Array.from(bytes instanceof ArrayBuffer ? new Uint8Array(bytes) : bytes));
  187. }
  188. }
  189. /**
  190. * Response object.
  191. *
  192. * @since 1.0.0
  193. * */
  194. class Response {
  195. /** @ignore */
  196. constructor(response) {
  197. this.url = response.url;
  198. this.status = response.status;
  199. this.ok = this.status >= 200 && this.status < 300;
  200. this.headers = response.headers;
  201. this.rawHeaders = response.rawHeaders;
  202. this.data = response.data;
  203. }
  204. }
  205. /**
  206. * @since 1.0.0
  207. */
  208. class Client {
  209. /** @ignore */
  210. constructor(id) {
  211. this.id = id;
  212. }
  213. /**
  214. * Drops the client instance.
  215. * @example
  216. * ```typescript
  217. * import { getClient } from '@tauri-apps/api/http';
  218. * const client = await getClient();
  219. * await client.drop();
  220. * ```
  221. */
  222. async drop() {
  223. return invokeTauriCommand({
  224. __tauriModule: 'Http',
  225. message: {
  226. cmd: 'dropClient',
  227. client: this.id
  228. }
  229. });
  230. }
  231. /**
  232. * Makes an HTTP request.
  233. * @example
  234. * ```typescript
  235. * import { getClient } from '@tauri-apps/api/http';
  236. * const client = await getClient();
  237. * const response = await client.request({
  238. * method: 'GET',
  239. * url: 'http://localhost:3003/users',
  240. * });
  241. * ```
  242. */
  243. async request(options) {
  244. var _a;
  245. const jsonResponse = !options.responseType || options.responseType === ResponseType.JSON;
  246. if (jsonResponse) {
  247. options.responseType = ResponseType.Text;
  248. }
  249. if (((_a = options.body) === null || _a === void 0 ? void 0 : _a.type) === 'Form') {
  250. options.body.payload = await formBody(options.body.payload);
  251. }
  252. return invokeTauriCommand({
  253. __tauriModule: 'Http',
  254. message: {
  255. cmd: 'httpRequest',
  256. client: this.id,
  257. options
  258. }
  259. }).then((res) => {
  260. const response = new Response(res);
  261. if (jsonResponse) {
  262. /* eslint-disable */
  263. try {
  264. response.data = JSON.parse(response.data);
  265. }
  266. catch (e) {
  267. if (response.ok && response.data === '') {
  268. response.data = {};
  269. }
  270. else if (response.ok) {
  271. throw Error(`Failed to parse response \`${response.data}\` as JSON: ${e};
  272. try setting the \`responseType\` option to \`ResponseType.Text\` or \`ResponseType.Binary\` if the API does not return a JSON response.`);
  273. }
  274. }
  275. /* eslint-enable */
  276. return response;
  277. }
  278. return response;
  279. });
  280. }
  281. /**
  282. * Makes a GET request.
  283. * @example
  284. * ```typescript
  285. * import { getClient, ResponseType } from '@tauri-apps/api/http';
  286. * const client = await getClient();
  287. * const response = await client.get('http://localhost:3003/users', {
  288. * timeout: 30,
  289. * // the expected response type
  290. * responseType: ResponseType.JSON
  291. * });
  292. * ```
  293. */
  294. async get(url, options) {
  295. return this.request({
  296. method: 'GET',
  297. url,
  298. ...options
  299. });
  300. }
  301. /**
  302. * Makes a POST request.
  303. * @example
  304. * ```typescript
  305. * import { getClient, Body, ResponseType } from '@tauri-apps/api/http';
  306. * const client = await getClient();
  307. * const response = await client.post('http://localhost:3003/users', {
  308. * body: Body.json({
  309. * name: 'tauri',
  310. * password: 'awesome'
  311. * }),
  312. * // in this case the server returns a simple string
  313. * responseType: ResponseType.Text,
  314. * });
  315. * ```
  316. */
  317. async post(url, body, options) {
  318. return this.request({
  319. method: 'POST',
  320. url,
  321. body,
  322. ...options
  323. });
  324. }
  325. /**
  326. * Makes a PUT request.
  327. * @example
  328. * ```typescript
  329. * import { getClient, Body } from '@tauri-apps/api/http';
  330. * const client = await getClient();
  331. * const response = await client.put('http://localhost:3003/users/1', {
  332. * body: Body.form({
  333. * file: {
  334. * file: '/home/tauri/avatar.png',
  335. * mime: 'image/png',
  336. * fileName: 'avatar.png'
  337. * }
  338. * })
  339. * });
  340. * ```
  341. */
  342. async put(url, body, options) {
  343. return this.request({
  344. method: 'PUT',
  345. url,
  346. body,
  347. ...options
  348. });
  349. }
  350. /**
  351. * Makes a PATCH request.
  352. * @example
  353. * ```typescript
  354. * import { getClient, Body } from '@tauri-apps/api/http';
  355. * const client = await getClient();
  356. * const response = await client.patch('http://localhost:3003/users/1', {
  357. * body: Body.json({ email: 'contact@tauri.app' })
  358. * });
  359. * ```
  360. */
  361. async patch(url, options) {
  362. return this.request({
  363. method: 'PATCH',
  364. url,
  365. ...options
  366. });
  367. }
  368. /**
  369. * Makes a DELETE request.
  370. * @example
  371. * ```typescript
  372. * import { getClient } from '@tauri-apps/api/http';
  373. * const client = await getClient();
  374. * const response = await client.delete('http://localhost:3003/users/1');
  375. * ```
  376. */
  377. async delete(url, options) {
  378. return this.request({
  379. method: 'DELETE',
  380. url,
  381. ...options
  382. });
  383. }
  384. }
  385. /**
  386. * Creates a new client using the specified options.
  387. * @example
  388. * ```typescript
  389. * import { getClient } from '@tauri-apps/api/http';
  390. * const client = await getClient();
  391. * ```
  392. *
  393. * @param options Client configuration.
  394. *
  395. * @returns A promise resolving to the client instance.
  396. *
  397. * @since 1.0.0
  398. */
  399. async function getClient(options) {
  400. return invokeTauriCommand({
  401. __tauriModule: 'Http',
  402. message: {
  403. cmd: 'createClient',
  404. options
  405. }
  406. }).then((id) => new Client(id));
  407. }
  408. /** @internal */
  409. let defaultClient = null;
  410. /**
  411. * Perform an HTTP request using the default client.
  412. * @example
  413. * ```typescript
  414. * import { fetch } from '@tauri-apps/api/http';
  415. * const response = await fetch('http://localhost:3003/users/2', {
  416. * method: 'GET',
  417. * timeout: 30,
  418. * });
  419. * ```
  420. */
  421. async function fetch(url, options) {
  422. var _a;
  423. if (defaultClient === null) {
  424. defaultClient = await getClient();
  425. }
  426. return defaultClient.request({
  427. url,
  428. method: (_a = options === null || options === void 0 ? void 0 : options.method) !== null && _a !== void 0 ? _a : 'GET',
  429. ...options
  430. });
  431. }
  432. export { Body, Client, Response, ResponseType, fetch, getClient };