I am using Ember 3.4 for the client (for some external reasons I can not upgrade the version yet) and Django 3.2.5 for the server side.
I have built an API that is working fine however I can not seem to find a way to POST to NON-RESTful API endpoints using Ember.
What I would like to do:
I have an endpoint that requires an authenticated user and that redirects this user to an external page to which we provide a callback URL that then redirect to our application. This endpoint only respond to POST requests as it is only used to execute server-side code non related to any existing model. It is kinda like a “command” that the user may want to execute at anytime.
I need to perform a POST on this particular endpoint from the client which seems to be something quite uneasy to do with Ember or I can not find any proper documentation about how to do such things.
What I have tried to do:
I thought it could be done using a simple HTML form element that uses the method POST with inputs data in it but I end up having trouble passing to Django the CSRFToken (required for every POST requests of authenticated users)
More broadly I would like to know if there are some best practices to deal with such case:
A user performs a client side actions that triggers an endpoint that does not depends on any model.
The server then performs several actions that absolutely needs to be done server side (for some external reasons)
In my case:
A user triggers the subscription process from the client - the server is notified (via a POST on a special url) and then make a request to our online payment solution. The user then inputs its credit card information and is redirected to the client with a “success” notification. Under the hood the server performs several verifications and updates the subscription status for this user.
Don’t mind telling me if I am not clear enough.
Have a good day, thanks
// your-component.js
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class ExampleComponent extends Component {
@action
async subscribe() {
let body = new URLSearchParams();
body.set('price_id', 'price_x4782462');
let response = await fetch('/subscribe', {
method: 'POST',
body,
});
// todo: do stuff with the response
console.log(response.status);
}
}
That shows you enough to get started. To make the asynchronous fetch handling more robust to race conditions and failures, you may want to add ember-concurrency as a dependency and make use task instead of action, because that gives you automatic cleanup if your component is destroyed while the fetch is running.
Thanks alot for the hints.
Sadly I had to move onto another subject but I will come back as soon as I can to provide my feedback on wether that’s work or not.
Also I did not mention it but I need the POST to be synchronous since I need to then follow a redirection which is returned as an HTTPResponse with a code 303
Ok, so the main issue to be clear on here is whether you’re really using a URL as an API endpoint or as a user-facing navigation. My previous answer assumed you were trying to fetch data into the app, but you’re really trying to do a navigation, which means leaving the Ember app.
In that case, making a normal <form> like you showed is the right solution and should work. You just need to make your form compatible with Django’s CSRF. I think it needs a hidden field with the name csrfmiddlewaretoken containing the user’s csrf token. Their docs explain how to get access to the token from Javascript. So something like:
import Cookies from 'js-cookie';
export default class extends Component {
get origin {
return window.location.origin; // or whatever is the actual django origin
}
get csrfToken() {
// Here I'm following django's example as linked above and using the js-cookie library
return Cookies.get('csrftoken');
}
}
Well that is exactly what I ended up doing, I just got the CSRF from the cookie a bit different
export default Component.extend({
token: computed('document.cookie', function() {
let cookieName = 'csrftoken';
let cookies = document.cookie.split(';');
for (const cookie of cookies) {
let [name, value] = cookie.trim().split('=')
if (name == cookieName)
return value
}
return ''
}),
});
Thanks for sharing your knowledge with me, have a good day