{"id":16978948,"url":"https://github.com/tessasac/stitch-in-time","last_synced_at":"2026-04-11T08:03:39.859Z","repository":{"id":72342364,"uuid":"326661297","full_name":"tessaSAC/stitch-in-time","owner":"tessaSAC","description":"How to elide CORS with the Capacitor Community HTTP Plugin","archived":false,"fork":false,"pushed_at":"2021-01-05T05:25:50.000Z","size":1072,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-20T07:47:08.405Z","etag":null,"topics":["capacitor","cors","http","ionic"],"latest_commit_sha":null,"homepage":"","language":"CSS","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tessaSAC.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-01-04T11:25:30.000Z","updated_at":"2022-01-01T03:52:29.000Z","dependencies_parsed_at":null,"dependency_job_id":"3693253e-f420-4d55-b05a-4f9677f405ac","html_url":"https://github.com/tessaSAC/stitch-in-time","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tessaSAC%2Fstitch-in-time","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tessaSAC%2Fstitch-in-time/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tessaSAC%2Fstitch-in-time/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tessaSAC%2Fstitch-in-time/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tessaSAC","download_url":"https://codeload.github.com/tessaSAC/stitch-in-time/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244880550,"owners_count":20525511,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["capacitor","cors","http","ionic"],"created_at":"2024-10-14T01:44:34.130Z","updated_at":"2026-04-11T08:03:34.828Z","avatar_url":"https://github.com/tessaSAC.png","language":"CSS","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bypassing CORS with the Capacitor Community HTTP Plugin\n\n[CORS](https://www.codecademy.com/articles/what-is-cors), or Cross-Origin Resource Sharing, is notorious among front-end developers for being tricky to work with. A browser-enforced restriction to protect users from a type of attack known as [cross-site request forgery](https://owasp.org/www-community/attacks/csrf), CORS is more well known for the headaches it causes web developers, and that's before we even think to mention mobile applications! But what if there were a better way?\n\nThanks to the [Capacitor Community](https://github.com/capacitor-community/welcome), there is! The Capacitor Community is an open-source working group that builds and maintains useful tools and plugins for Ionic's [Capacitor runtime](https://github.com/ionic-team/capacitor), and its HTTP plugin empowers developers to sidestep CORS and smoothly make successful [HTTP requests](https://www.tutorialspoint.com/http/http_requests.htm) across desktop and mobile devices.\n\nLet's get to know the plugin by using it to build an aesthetic daily advice app!\n\n\n![Screenshot of cross-stitched advice](./public/assets/readme/app_preview.jpg?raw=true \"Being kind is more rewarding than being right.\")\n\n## What we'll build\n\n*A Vue.js app that loads and displays one cross-stitched piece of advice per day from the [Advice Slip JSON API](https://api.adviceslip.com) and which can be deployed cross platform.*\n\n\u0026nbsp;\n\n## What we'll learn\n\n- How to make a GET request with the HTTP plugin\n- How to get, set, and delete cookies\n\n\u0026nbsp;\n\n## Prior knowledge\n\nIn order to follow along, it might be helpful to have some understanding of the following technologies:\n\n- [Ionic](https://ionicframework.com/docs/vue/overview)\n- [Vue.js](https://v3.vuejs.org)\n- [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)\n- [TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html)\n- [Async/Await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)\n- [Git](https://www.udacity.com/course/version-control-with-git--ud123)\n- [Yarn](https://yarnpkg.com/getting-started) or [NPM](https://flaviocopes.com/npm)\n- [Bash](https://www.educative.io/blog/bash-shell-command-cheat-sheet)\n\n\u0026nbsp;\n\n## Getting started\n\n##### Note: If at any point you get lost, you can always refer back to the example code in the [main repo](https://github.com/tessaSAC/stitch-in-time).\n\nYou can either [clone the `startingPoint` branch](https://github.com/tessaSAC/stitch-in-time/tree/startingPoint) or start your own project from scratch with the following steps:\n\n1. Create a new Ionic Vue project as per [Creating a project with the Ionic CLI](https://ionicframework.com/docs/vue/quickstart#creating-a-project-with-the-ionic-cli)\n2. `cd` into your project and [install the HTTP plugin](https://github.com/capacitor-community/http) with the following commands:\n\n```bash\nnpm install @capacitor-community/http\nnpx cap sync\n```\n\n\u003e ### 😬 Tip\n\u003e\n\u003e If, after running both commands, you see the error `Capacitor could not find the web assets directory \"pathToYourRepo/dist\"`, try running `yarn build` or `npm build` before running `npx cap sync` a second time. If all goes well, you'll get a confirmation message similar to this one: `Sync finished in 0.011s`.\n\n\u0026nbsp;\n\n3. Open your project in your favorite code editor; I like to use [VSCode](https://code.visualstudio.com).\n\n4. _Optional:_ Download [HovdenStitch](https://www.fontspace.com/hovden-stitch-font-f43700), a free cross-stitch font, and move the `otf` file to your `public/assets` folder as `HovdenStitchRegular.otf`. Then in `src/App.vue`, copy and paste the below style tag at the bottom of the file to allow this custom font to be used across your app.\n\n```vue\n\u003cstyle\u003e\n@font-face {\n  font-family: \"HovdenStitch\";\n  src: url(\"../public/assets/HovdenStitchRegular.otf\");\n}\n\u003c/style\u003e\n```\n\nYour file structure should now look similar to this:\n######\n```bash\n├── public\n│   ├── assets\n│   │   ├── HovdenStitchRegular.otf\n│\n├── src\n│   ├── router\n│   ├── theme\n│   ├── views\n│   │   ├── Home.vue\n│   ├── App.vue\n```\n\n\u003e ### 💡 Tip:\n\u003e For the purposes of this tutorial, CSS styles will be contained within Vue's [Single File Components](https://v3.vuejs.org/guide/single-file-component.html) so that all changes are explicitly visible. However when it comes to your own projects, consider putting global styles in your [`src/theme` folder](https://ionicframework.com/docs/theming/themes) when necessary.\n\n\u0026nbsp;\n\n## Designing the order of operations\nThe basic idea for this app's functionality is that it will fetch and display a maximum of one new piece of advice every 24 hours (regardless of whether the user refreshes the page or leaves the app) and erase the current advice approximately one hour before new advice is fetched.\n\nWhile this concept may seem simple on the surface, it requires quite a bit of state tracking in order to ensure the expected behavior. To clarify my approach, I find it helpful to draw or write out a rough plan for how I expect things to go:\n\n![Screenshot of handwritten flowchart roughly diagramming above behavior](./public/assets/readme/planning_notes.jpg?raw=true \"notes\")\n\n\u0026nbsp;\n\n## Preparing HTTP helper functions\n\n\nIn `src/views/Home.vue`, replace your `script` tag with the below starter code:\n```vue\n\u003cscript\u003e\nimport { defineComponent } from 'vue'\nimport { IonPage } from '@ionic/vue'\n\nexport default defineComponent({\n  name: 'Home',\n  components: { IonPage },\n\n  data: () =\u003e ({\n    advice: '',  // Advice currently being displayed\n    animationState: '',  // Dynamic class that determines whether to fade advice in or out\n    hourToFetchNewAdvice: null,\n    lastSaveDate: null,  // The last time we fetched new Advice\n    today: new Date(),\n  }),\n\n  computed: {\n    currentDate() {\n      return this.today.getDate()\n    },\n\n    currentHour() {\n      return this.today.getHours()\n    },\n\n    hourToEraseCurrentAdvice() {\n      let oneHourPrior = this.hourToFetchNewAdvice - 1\n      if(oneHourPrior \u003c 0) oneHourPrior = 23\n      return oneHourPrior\n    }\n  },\n\n  // When we'll check if advice data needs to be changed\n  async ionViewWillEnter() {\n    // For more on`ionViewWillEnter`, see: https://ionicframework.com/docs/vue/lifecycle#guidance-for-each-lifecycle-method\n  },\n\n  methods: {},\n})\n\u003c/script\u003e\n```\n##### _**Optional:** Move the `script` tag to be at the top of the file, above the `template` tag as per the [Vue Style Guide](https://vuejs.org/v2/style-guide/#Single-file-component-top-level-element-order-recommended). Once you get used to this pattern it can speed up your development process by reducing the scrolling between `script` and `template` and between `template` and `style`._\n\n\u0026nbsp;\n\n### Integrating the plugin\n\nIn order to use the HTTP Plugin, first import it by adding the following code to the top of the `script` tag as per the [README](https://github.com/capacitor-community/http/blob/455dc0cb0add1b872dbc914077b9754df4d8c0f3/src/definitions.ts#L106):\n\n```js\nimport '@capacitor-community/http'\nimport { Plugins } from '@capacitor/core'\nconst { Http } = Plugins\n```\n\nNow that we have access to the HTTP plugin and its helper methods inside this component, add the following functions to the `Home` component's `methods`:\n\n```js\nasync fetchAdvice() {\n  return await Http.request({\n    method: 'GET',\n    url: 'https://api.adviceslip.com/advice',\n  })\n  .then(({ data }) =\u003e {\n    // Set dynamic class to fade in advice\n    this.animationState = 'fadeIn'\n    this.resetAnimationState()\n\n    // Save new advice\n    this.advice = JSON.parse(data).slip.advice.toUpperCase()\n      // In other words:\n        // const dataInJs = JSON.parse(data)\n        // const slip = dataInJs.slip\n        // this.advice = slip.advice\n        // this.advice = this.advice.toUpperCase() — // font supports upper case only\n\n    // Update lastSaveDate\n    this.lastSaveDate = this.currentDate\n  })\n},\n\nupdateAdvice() {\n\n  // If 24h have passed, fetch new advice\n  if(this.currentHour === this.hourToFetchNewAdvice \u0026\u0026 this.currentDate != this.lastSaveDate) this.fetchAdvice()\n\n  // If 23 hours have passed, start fading out current advice\n  else if (this.currentHour === this.hourToEraseCurrentAdvice \u0026\u0026 this.advice) {\n\n    // Set dynamic class to fade out text\n    this.animationState = 'fadeOut'\n    this.resetAnimationState()\n\n    // Clear advice from state after the fade out animation ends\n    setTimeout(() =\u003e {\n      this.advice = ''\n    }, 10000)\n  }\n\n  // Check every 10m if it's time to fetch/erase advice\n  setTimeout(this.updateAdvice, 600000)\n},\n\n// Clear animation from advice after one playthrough\n// A safer approach might be to listen for `transistionend`\nresetAnimationState() {\n  setTimeout(() =\u003e {\n    this.animationState = ''\n  }, 10000)\n},\n```\n##### The `fetchAdvice` method is almost identical to the `GET` example in the HTTP Plugin's README, but its syntax has been reordered to fit within [Vue's `methods` style](https://v3.vuejs.org/guide/data-methods.html#methods). The `HTTP.request` method enables us to make [HTTP calls](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) across different deploy targets without worrying about CORS issues. Here we're using it to request advice from the [Advice Slip JSON API's random advice GET endpoint](https://api.adviceslip.com/#endpoint-random).\n\n\n\u0026nbsp;\n\nNext, we'll want to add the following code to `ionViewWillEnter` to fetch advice when the page loads:\n```js\nasync ionViewWillEnter() {\n  // If we haven't stored an hourToFetchNewAdvice before, calculate and store that and hourToEraseCurrentAdvice\n  if(!this.hourToFetchNewAdvice) this.hourToFetchNewAdvice = this.currentHour\n\n  this.updateAdvice()\n},\n```\n\n\u0026nbsp;\n\n## Displaying our cross-stitched advice\n\nOpen `views/Home.vue` and replace the existing `template` with the below code:\n```vue\n\u003ctemplate\u003e\n\u003cIonPage\u003e\n  \u003cdiv class=\"Home\"\u003e\n    \u003cimg src=\"yourImageHere\" alt=\"Don't forget to add alt text!\" /\u003e\n    \u003cp class=\"embroidery\" :class=\"animationState\"\u003e{{ advice }}\u003c/p\u003e\n    \u003cimg src=\"yourImageHere\" alt=\"Don't forget to add alt text!\" /\u003e\n  \u003c/div\u003e\n\u003c/IonPage\u003e\n\u003c/template\u003e\n```\n\n\u0026nbsp;\n\nLet's review what's going on in the template:\n- This *view*, or page, is wrapped in the `IonPage` component to [enable us to leverage component lifecycle hooks](https://ionicframework.com/docs/vue/lifecycle).\n- The `Home` class will be used to visually center and style advice in the viewport.\n- **Action required:** The `img` tags are placeholders for you to add personalized decorative flourishes to surround your advice; feel free to also [copy the images from the base repo](https://github.com/tessaSAC/stitch-in-time/tree/main/public/assets) or remove them altogether.\n- The `p` tag is where we'll render advice from the Advice Slip JSON API.\n- The [v-bound](https://v3.vuejs.org/api/directives.html#v-bind) `animationState` class enables us to dynamically fade advice in and out as necessary.\n\n\u0026nbsp;\n\nNext, replace the `style` tag with the following:\n```vue\n\u003cstyle scoped\u003e\n/* Center and style the content */\n.Home {\n  background: white;\n  height: 100%;\n  padding: 1rem;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: space-between;\n\n  /* Apply the custom font and style the text */\n  font-family: 'HovdenStitch';\n  font-size: 5rem;\n  color: #002657;\n  text-align: center;\n}\n\n/* Allow the flourishes to visually curve more closely around the text */\n.embroidery {\n  max-width: 686px;\n  position: absolute;\n  top: 53%;\n  transform: translateY(-75%);\n}\n\n/* Animate new advice being added and old advice being removed */\n.fadeIn {\n  animation: fadeIn ease 10s;\n}\n.fadeOut {\n  animation: fadeOut ease 10s;\n}\n\n@keyframes fadeIn {\n  0% {\n    opacity: 0;\n  }\n  100% {\n    opacity: 1;\n  }\n}\n@keyframes fadeOut {\n  0% {\n    opacity: 1;\n  }\n  100% {\n    opacity: 0;\n  }\n}\n\u003c/style\u003e\n```\n\nNow if you run `yarn serve` or `npm serve`, you should be able to see some advice in the local preview in your browser!\n\n![Screenshot of cross-stitched advice at localhost:8080/home](./public/assets/readme/app_preview-02.jpg?raw=true \"Give up your seat for someone who needs it.\")\n\n\u0026nbsp;\n\n## Persisting state\n\nWhile it's great that our advice is rendering and all, you may have noticed a small catch: we get a new piece of advice on every page load, even though the `updateAdvice` method is supposed to wait 24 hours before fetching new advice. This is because the state is stored only within the component, which means when the component disappears, so does the data.\n\nTo get around this, we'll store some of our state in [cookies](https://en.wikipedia.org/wiki/HTTP_cookie), which can outlive the component lifecycle.\n\n### To get started, add the following helpers to the component's `methods`:\n\n```js\nasync setCookie(optionsObject) {\n  const cookie = await Http.setCookie({\n    ...optionsObject,\n    ageDays: 2,  // Sets max number of days to save cookie\n  })\n},\n```\n##### This method is very similar to the `setCookie` example in the HTTP Plugin's README, but its syntax has been reordered to fit within Vue's `methods` style. It has also been augmented to expire any cookies after two days.\n\n##### One potential point of confusion here is that you may assume (or infer from the [Http.setCookie source code](https://github.com/capacitor-community/http/blob/455dc0cb0add1b872dbc914077b9754df4d8c0f3/src/web.ts#L112)) that `setCookie` behaves identically to the [browser's HTTP Set-Cookie approach](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie); however, if you try to pass in a value for `name` or `SameSite`, you will quickly discover this is *not* what's happening. The [method's interface](https://github.com/capacitor-community/http/blob/455dc0cb0add1b872dbc914077b9754df4d8c0f3/src/definitions.ts#L99) reveals that this method will only take four potential pieces of data: `url`, `key`, `value`, and `ageDays`, where `key` becomes the cookie's `name` and `ageDays` its `Expires` value.\n\n\u0026nbsp;\n\n```js\nasync deleteCookie(optionsObject) {\n  return await Http.deleteCookie(optionsObject)\n},\n\n```\n##### This method is similar to the `deleteCookie` example in the HTTP Plugin's README, with the addition of an optional `optionsObject` argument which can be passed to the HTTP Plugin's `deleteCookie` method. If we check out [the interface for this method in the source code](https://github.com/capacitor-community/http/blob/455dc0cb0add1b872dbc914077b9754df4d8c0f3/src/definitions.ts#L112), we can see that it will accept either a `key` or a `url` to specify which cookie should be deleted.\n\n\u0026nbsp;\n\n```js\nasync getCookie(key) {\n  const allCookiesWrapper = await Http.getCookies()\n  const allCookies = allCookiesWrapper.value\n\n  for (let i = 0; i \u003c allCookies.length; ++i) {\n    const currentCookie = allCookies[i]\n    if(currentCookie.key === key) return currentCookie.value\n  }\n\n  return null\n},\n```\n##### This method is similar to the `getCookies` example in the HTTP Plugin's README. However, that method will return an Array of all cookies, so if we want to get a specific one we'll have to filter the list ourselves.\n\n##### Note that a `forEach` will not work here as that type of loop cannot be short-circuited by a `return` statement.\n\n\u0026nbsp;\n\n### Now that we have access to some cookie helper methods, let's use them.\n\nFirst, add them to `ionViewWillEnter`:\n```js\nasync ionViewWillEnter() {\n  // Check if there are a stored lastSaveDate, hourToFetchNewAdvice, and advice in cookies, i.e. outside component/session state\n  await Promise.all([\n    this.getCookie('lastSaveDate').then(lastSaveDate =\u003e this.lastSaveDate = +lastSaveDate),\n    this.getCookie('hourToFetchNewAdvice').then(hourToFetchNewAdvice =\u003e this.hourToFetchNewAdvice = +hourToFetchNewAdvice),\n    this.getCookie('advice').then(advice =\u003e this.advice = advice)\n  ])\n\n  if(!this.hourToFetchNewAdvice) this.hourToFetchNewAdvice = this.currentHour\n\n  // Store the hourToFetchNewAdvice in cookies regardless so it doesn't expire\n  this.setCookie({\n    key: 'hourToFetchNewAdvice',\n    value: this.hourToFetchNewAdvice,\n  })\n\n  this.updateAdvice()\n},\n```\n\nNext, update `fetchAdvice`:\n\n```js\nasync fetchAdvice() {\n  return await Http.request({\n    method: 'GET',\n    url: 'https://api.adviceslip.com/advice',\n  })\n  .then(({ data }) =\u003e {\n    this.animationState = 'fadeIn'\n    this.resetAnimationState()\n\n    this.advice = JSON.parse(data).slip.advice.toUpperCase()\n\n    // Save the advice to a cookie too\n    this.setCookie({\n      key: 'advice',\n      value: this.advice,\n    })\n\n    this.lastSaveDate = this.currentDate\n\n    // Update lastSaveDate in cookies also\n    this.setCookie({\n      key: 'lastSaveDate',\n      value: this.currentDate,\n    })\n  })\n},\n```\n\nAnd `updateAdvice`:\n\n```js\nupdateAdvice() {\n  if(this.currentHour === this.hourToFetchNewAdvice \u0026\u0026 this.currentDate != this.lastSaveDate) this.fetchAdvice()\n\n  else if (this.currentHour === this.hourToEraseCurrentAdvice \u0026\u0026 this.advice) {\n    this.animationState = 'fadeOut'\n    this.resetAnimationState()\n\n    setTimeout(() =\u003e {\n      this.advice = ''\n\n      // Erase the advice from cookies as well\n      this.deleteCookie({ key: 'advice' })\n    }, 10000)\n  }\n\n  setTimeout(this.updateAdvice, 600000)\n},\n```\n\n\u0026nbsp;\n\n## Celebrate!\n\nWe did it! Now the app will fetch and erase advice once every 24 hours as designed. 🎉\n\n\u0026nbsp;\n\n## Review\n\nWe covered a lot of APIs and concepts from the Capacitor Community HTTP Plugin today including:\n- How to make an HTTP `GET` request\n- How to save a cookie\n  - Which properties of a cookie can be set with the plugin\n- How to get a *specific* cookie\n- How to delete a *specific* cookie\n- How to use the source code to answer questions not covered by the README\n\n\u0026nbsp;\n\n## Next steps\n\nWhat's next in the exciting world of cross-platform Vue apps? If you're short on ideas, here are a few suggestions for next steps:\n\n- [Offline first](https://ionicframework.com/blog/best-practices-for-building-offline-apps): Try persisting state using another method, such as service-worker caching\n- PWA/mobile: Try deploying the app to your phone as a [Progressive Web App](https://ionicframework.com/docs/vue/your-first-app/6-deploying-mobile) or a native [Android/iOS one](https://ionicframework.com/docs/vue/your-first-app/6-deploying-mobile)\n  - Note: If you opted to generate your own project for this tutorial instead of cloning from the repo, you may need to follow some [additional installation steps](https://github.com/capacitor-community/http#installation) to get the HTTP Plugin working on Android\n- Styling: Cross stitch not your thing? What theme would you give this app?\n- Loading state: How might you save the state of the app such that if the user exits while advice is fading in or out, the transition will resume at the same spot the next time they reopen the app?\n- [Another API](https://dev.to/biplov/15-fun-apis-for-your-next-project-5053): There are lots of free APIs out there; how would you apply the HTTP Plugin to a new project?\n\n\u0026nbsp;\n\n### Thanks for reading and happy coding! 🎉","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftessasac%2Fstitch-in-time","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftessasac%2Fstitch-in-time","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftessasac%2Fstitch-in-time/lists"}