I have a StencilJS app with Ionic web components using the following routing setup:
<ion-router useHash={false}>
<ion-route url="/home" component="page-home"/>
<ion-route url="/login" component="page-login"/>
<ion-route url="/main" component="page-main">
</ion-router>
When I receive a 401 error from the api, I would like to redirect to the login page, with the current url as a redirect querystring parameter (in order to redirect the user back to where he came from), like so:
const navCtrl: HTMLIonRouterElement = document.querySelector("ion-router");
fetch('https://someapi.com/users/1').then(
res => navCtrl.push('/main'),
err => navCtrl.push(`/login?redirect=${window.location.pathname}`)
);
When I do this an I receive an error from the api, and I want to navigate to the login page with the current route as querystring parameter, Stencil throws the following error: [ion-router] the path does not match any route.
How can I use the querystring with the ion-router?
IMO it's currently a bug in Ionic (see https://github.com/ionic-team/ionic/issues/19707).
To work around it for the moment, you could probably use
navCtrl.push('/login');
history.replaceState(undefined, '', `?redirect=${window.location.pathname}`);
Thanks for the nudge in the right direction! The code you provided did not change the url, because navCtr.push returns a promise. You have to wait for it to finish before you can change the state. I ended up with this working workaround:
const navCtrl: HTMLIonRouterElement = document.querySelector("ion-router");
fetch('https://someapi.com/users/1').then(
res => navCtrl.push('/main'),
err => {
navCtrl
.push(`/login`)
.then(() => history.replaceState(undefined, '', `?redirect=${encodeURIComponent(window.location.pathname)}`))
}
);
It's strange that such widely used frameworks like Ionic and Stencil don't support common querystrings. I hope they will fix this issue soon.
i build a native function to get every parameter from the querystring,
export const RouterGetUriParam = (name) => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
if (urlParams.has(name)) {
return urlParams.get(name);
} else {
return null;
}
};
you can use it in stencil like this:
#Prop() withMore: boolean = (RouterGetUriParam("withMore") == 'true')
the urlparam looks like ?withMore=true
Related
I'm adding react-query to my automations pipe and need generating wrapper around the useQuery for various API calls. I want to expose all options of useQuery to developer using this wrapper. How to correctly define options type to get all the benefits of ts for those?
:any works but not giving autocomplete/checks then:
const usePosts = ({ page, perPage, workspaceId }: payload, ___options?: any) => {
If I add :UseQueryOptions, just to see the error of mismatch, error message is quite big and has a lot inside. Would love to find way to import that type definition from react-query and reuse it.
Just found it, sometimes all needed is to write a question 😅 Maybe someone else will benefit from this:
import { useQuery, QueryOptions } from "#tanstack/react-query";
import sdk from "../sdks/demo";
type payload = {
page?: number;
perPage?: number;
workspaceId: string;
};
const usePosts = (
{ page, perPage, workspaceId }: payload,
___options?: QueryOptions
) => {
let key = `posts-list-${workspaceId}-${page}-${perPage}`;
return useQuery(
[key],
() =>
sdk.postsList({
workspaceId,
page,
perPage,
}),
___options
);
};
export default usePosts;
Context: I do not have a DB, everything I got came from another server's endpoints.
My directory structure looks like:
-components
-pages
--api
(I think every one do something like this)
inside API directory is where my Rest's requisitions are. Everytime some client enters, A token's requisition must be done at get_token.js into API directory, right? Here's the code:
export default async function Token(req, res) {
var formdata = new FormData();
formdata.append("user", "user_name");
formdata.append("password", "my_password");
var requestOptions = {
method: "POST",
body: formdata,
redirect: "follow",
};
await fetch(
"https://endpoint",
requestOptions
)
.then((res) => res.json())
.then((result) => (data = result))
.catch((error) => console.log("error", error));
}
If I access the result directly in localhost:3000/api/token, it returns exactly what I want. At my component's directory I have no idea how to access this fetch() result... If I try to made the request outside the API's directory, it doesn't work at all (the only error message it returns is something like "Fetch fails", with the same code. I've tried to use getStaticProps(). Returns nothing. Tried to use an inside function, async... It fails again. The fetch() only works at API's dir.
the code at the others dirs, are simple as any other "page" + "component" structure...
The code at component's dir:
export default function PageNameComponent(){
return (<div>... </div>) }
and at page's directory I have:
export default class PageName extends React.Component{
render(){
return (
<div>
<MyHeader />
<PageNameComponent/>
<MyFooter />
</div>
);
}
}
Could someone explain how it works? and the best practice to this context?
how can I access URL parameters from Azure DevOps Extension?
I am developing a hub-like extension based on this example. Basically it is a simple page that displays data in a simple table. Data are loaded from an external REST API server.
I want to call this page from some external link and for this, I need to read URL parameter idBuild from this extension. Is this possible?
Example on my URL: http://server/tfs/org/proj/_apps/hub/devops-plugin.default-hub?idBuild=15987
Edit: More details about this plugin:
For Pull requests, my Pull Request server posts Pull request status with the information about some additional checks (x86 integration tests here).
I want this Status to have a URL, so a user can display some additional information about this status on a separate page. This page is my extension.
In this extension, I read some data from an external API and idBuild is the key. So I want to make URL containing idBuild and pass idBuild to this extension page through this URL.
Some months ago I faced the same problem as you have now.
While using aurelia / typescript I was not able to read the URL parameters, but I found a workaround to retrieve them.
Use the following function to get all the parameters:
function getUrlParameters() {
var parameters = {};
window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, (m, key, value) => {
parameters[key] = value.split("#")[0];
return parameters[key];
});
return parameters;
}
And this piece of code to get the idBuild parameter:
var buildId = getUrlParameters()["idBuild"];
Thanks to R Pelzer answer, here is TypeScript version of his function:
private getUrlParameters(): Map<string, string> {
const parameters: Map<string, string> = new Map<string, string>();
window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, (m, key: string, value: string) => {
const val = value.split("#")[0];
parameters.set(key, val);
return val;
});
return parameters;
}
And here is a usage example:
public async componentDidMount() {
const idBuildParam = this.getUrlParameters().get("idBuild");
if (!idBuildParam)
{
throw RangeError("Missing 'idBuild' URL parameter");
}
const response = await axios.get<PoirotResultDto[]>(`http://localhost:50584/Poirots/${idBuildParam}`);
.... rest of code
}
I have looked all over and have found similar solutions, but nothing that matches exactly what I'm working on.
We have a .net core MVC website with an API Controller for handling requests from an ionic mobile app which we are also developing.
In most cases, adding [ValidateAntiForgeryToken] to the API controller actions works. I have gone through the process of generating the token, passing it to Ionic, and storing it in the request headers for validation.
Here is the code I am using to fetch and store the token:
static XSRF_TOKEN_KEY: string = "X-XSRF-TOKEN";
static XSRF_TOKEN_NAME_KEY: string = "X-XSRF-TOKEN-NAME";
constructor(){}
static getXsrfToken(http: HTTP) : {tokenName: string, token: string} {
let tokenName: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_NAME_KEY);
let token: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_KEY);
if(!tokenName || !token){
this.fetchXsrfToken(http);
tokenName= window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_NAME_KEY);
token = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_KEY);
}
return {
tokenName: tokenName,
token: token
};
}
private static setXsrfToken({ token, tokenName }: { token: string, tokenName: string }) {
window.sessionStorage.setItem(ValidationManager.XSRF_TOKEN_KEY, token);
window.sessionStorage.setItem(ValidationManager.XSRF_TOKEN_NAME_KEY, tokenName);
}
private static fetchXsrfToken(http: HTTP) {
let token: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_KEY);
let tokenName: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_NAME_KEY);
if (!token || !tokenName) {
let apiUrl: string = AppConfig.apiUrl + "/GetAntiforgeryToken";
http.get(apiUrl, {}, {})
.then(r => this.setXsrfToken(JSON.parse(r.data)))
.catch(r => console.error("Could not fetch XSRFTOKEN", r));
} else {
this.setXsrfToken({ token: token, tokenName: tokenName });
}
}
Here is the action in my controller that serves anti forgery tokens:
[HttpGet]
public override IActionResult GetAntiforgeryToken()
{
var tokens = _antiforgery.GetAndStoreTokens(HttpContext);
return new ObjectResult(new
{
token = tokens.RequestToken,
tokenName = tokens.HeaderName
});
}
I set the headers of the http plugin by calling this function from the view's associated typescript file:
initializeHttp() {
let token = ValidationManager.getXsrfToken(this.http);
this.http.setHeader(token.tokenName, token.token);
console.log("Http Initialized: ", token);
}
then any request I make with the http plugin is validated properly in the controller's action:
this.http.post(apiUrl, {}, {}).then(response => {
that.navCtrl.setRoot(HomePage);
});
Up to this point, everything works great. The problem arises when I try to use XmlHttpRequest to for a POST instead of the built-in http plugin:
let file = {
name: e.srcElement.files[0].name,
file: e.srcElement.files[0],
};
let formData: FormData = new FormData();
formData.append('file', file.file);
let xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.open('POST', apiUrl, true);
console.log("setting request header: ", tokenVal); //verify that tokenVal is correct
xhr.setRequestHeader("X-XSRF-TOKEN", tokenVal);
xhr.send(formData);
If I remove the [ValidateAntiForgeryToken] attribute from the controller's action, the file is posted properly. However, nothing I have tried has worked with the attribute being included.
I believe the issue has something to do with the validation tokens being added to a cookie automatically by Ionic, and the cookie is passed along with the request from the http plugin. However, XMLHttpRequest does not pass the cookie along (and is unable to do so?).
I have read up on the subject quite a bit over the past few days but I admit that this validation is still mostly a black box to me. Is there a way to validate the request in my action using only the token which is passed up in the header?
The reason I am running into this problem is that I need to upload a file, which I was unable to do using the http plugin. There are solutions for uploading images using Ionic's file-transfer plugin, but it has been deprecated and the release notes suggest using XmlHttpRequest instead.
Other things I have tried:
I have found solutions for .net standard which use System.Web.Helpers.AntiForgery for custom validation on the server, but this namespace is not included in .net core and I could not find an equivalent.
I tried many different ways to post the file using the http plugin (since it has no issues validating the antiForgery token). Everything I tried resulted in the action being hit but the file being posted was always null. A solution which uploads a file using the http plugin would also be acceptable.
Why is it that I was able to spend two full days on this problem, but as soon as I post a question about it, I find the answer? Sometimes I think the internet gods are just messing with me.
As it turns out, the native http plugin has an uploadFile() function that I never saw mentioned anywhere else. Here's what the solution does:
Use the fileChooser plugin to select a file from the phone's storage
Use the filePath plugin to resolve the native filesystem path of the image.
Use http.uploadFile() instead of http.post()
This works because as mentioned above, I was able to properly set the validation token in the http plugin's header to be accepted by the controller.
And here is the code:
let apiUrl: string = AppConfig.apiUrl + "/UploadImage/";
this.fileChooser.open().then(
uri => {
this.filePath.resolveNativePath(uri).then(resolvedPath => {
loader.present();
this.http.uploadFile(apiUrl,{ },{ },resolvedPath, "image")
.then(result => {
loader.dismiss();
toastOptions.message = "File uploaded successfully!";
let toast = this.toastCtrl.create(toastOptions);
toast.present();
let json = JSON.parse(result.data);
this.event.imageUrl = json.imgUrl;
})
.catch(err => {
console.log("error: ", err);
loader.dismiss();
toastOptions.message = "Error uploading file";
let toast = this.toastCtrl.create(toastOptions);
toast.present();
});
});
}
).catch(
e => console.log(e)
);
I want to redirect from a route, /new, and keep the query params for the new route:
As far as I know, the only place to access queryParams is within the model hook of a route.
But I want to redirect in the beforeModel hook:
import Ember from "ember";
export default Ember.Route.extend({
/**
* ##override
* Implicitly create a new applicant, redirecting to the generated ID at /applications/#{id}
* #param transition
*/
beforeModel: function(transition) {
var emptyApplicant = this.store.createRecord("applicant",
{isPrimary: true}
),
emptyApplication = this.store.createRecord("application");
emptyApplication.set("applicant", emptyApplicant);
emptyApplicant.save().then((savedApplicant) => {
emptyApplication.save().then((savedApplication) => {
this.transitionTo("applications", savedApplication);
});
});
}
});
While the above code works, the transition will complete without preserving the query params. For example, navigating to applicants/new?agent=35 will not preserve agent=35 in the query param, and will simply redirect to applicants/new instead.
How can I access the queryParams object from the beforeModel hook in my Ember app?
You should be able to pass query parameters to the transitionTo, something along the line of:
this.transitionTo("applications", savedApplication, {
queryParams: transition.queryParams
});
In more modern versions of Ember, the transition object has a to property which contains the query params. Transition no longer has a queryParams property
A redirect to the index route in a beforeModel for example, might look like this:
beforeModel(transition){
super.beforeModel(...arguments);
transition.abort();
this.transitionTo('index', {
queryParams: transition.to.queryParams
});
}