I am new to Ionic framework & going to develop an app which utilises rest api from my server. In each API I have to pass a token in http header. If this token is valid then I return response & inflate the listview. If token is not valid then I have to hit another API to generate token & again hit first API to fetch data.
My problem is second case. When token is invalid, it is generate successfully & then calling first API is also successful but this time listview is not inflated.
Please help.
home.ts
loadPeople(){
this.dataService.load()
.then(data => {
this.mylist = data;
});
}
data-provider.ts
load() {
if (this.data) {
return Promise.resolve(this.data);
}
return new Promise(resolve => {
let headers = new Headers({ 'token': this.token });
let options = new RequestOptions({ headers: headers });
this.http.get('myurl1', options)
.map(res => res.json())
.subscribe(data => {
if(data.message === 'TOKEN_NOTVALID'){
this.generateToken();
}else{
this.data = data.result;
resolve(this.data);
}
});
});
}
generateAccessToken(){
var creds = "param1=xxx¶m2=zzz";
var headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
this.http.post('myUrlHere', creds, {
headers: headers
})
.map(res => res.json())
.subscribe(
data => {
this.token = data.token;
this.load();
});
}
The problem is that when token is invalid , the Promise is never resolved. Only the second call to load function Promise is resolved. So you need to resolve the Promise when token is not valid. Just because the name of the function is resolve in the Promise, doesnt mean that the second call to load function will resolve the first call to load function.
You can return the data promise in generateAccessToken then resolve the promise with data returned.
load() {
if (this.data) {
return Promise.resolve(this.data);
}
return new Promise(resolve => {
let headers = new Headers({ 'token': this.token });
let options = new RequestOptions({ headers: headers });
this.http.get('myurl1', options)
.map(res => res.json())
.subscribe(data => {
if(data.message === 'TOKEN_NOTVALID'){
this.generateToken().then(data => { resole(data) } );
}else{
this.data = data.result;
resolve(this.data);
}
});
});
}
generateAccessToken(){
var creds = "param1=xxx¶m2=zzz";
var headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
return this.http.post('myUrlHere', creds, {
headers: headers
})
.map(res => res.json())
.toPromise()
.then(
data => {
this.token = data.token;
return this.load();
});
}
You will have to import toPromise
import 'rxjs/add/operator/toPromise';
Related
So I have this react native code that sends a token in string format, yes I've checked that var token = getAccessToken() is a string and I've console.log it to ensure it is a JWT token as well. But on the Django side when I check request.headers.get('Authorization', None) it outputs: 'Bearer [object Object]' what's going on?
React Native Code
import {Auth} from 'aws-amplify';
export async function getAccessToken() {
try {
const currentUser = await Auth.currentAuthenticatedUser();
console.log(currentUser);
Auth
.currentSession()
.then(res => {
let accessToken = res.getAccessToken();
// let jwt = accessToken.getJwtToken();
// You can print them to see the full objects
// console.log(`myAccessToken: ${JSON.stringify(accessToken)}`);
// console.log(`myJwt: ${JSON.stringify(jwt)}`);
console.log(accessToken.jwtToken)
return accessToken.jwtToken
});
} catch (error) {
console.log('error signing up:', error);
}
}
const getPosts = () => {
var token = getAccessToken();
const config = {
headers: { Authorization: `Bearer ` + token }
};
axios
.get(`${url}/posts`, config)
.then(response => {
console.log(response)
setData(response.data);
})
.catch(error => {
console.log(JSON.stringify(error));
});
}
I also tried
const config = {
headers: { Authorization: `Bearer ${token}` }
};
I also tried
function getPosts() {
var token = getAccessToken().then(token => {
const config = {
headers: {
Authorization: `Bearer ${token}`
}
};
console.log(token)
axios
.get(`${url}/posts`, config)
.then(response => {
console.log(response)
setData(response.data);
})
.catch(error => {
console.log(JSON.stringify(error));
});
}).catch(error => {
console.log(JSON.stringify(error));
});;
};
and console.log(token) is outputting "undefined"
Update getAccessToken to return result of
Auth .currentSession()
And
Make getPosts function async and await getAccessToken().
OR
Use the then block to result of promise
getAccessToken().then(token=>{ // Call the api },err=>{ // Handle the error }
Otherwise what you are getting is a promise that's not resolved yet.
Apollo link offers an error handler onError
Issue:
Currently, we wish to refresh oauth tokens when they expires during an apollo call and we are unable to execute an async fetch request inside the onError properly.
Code:
initApolloClient.js
import { ApolloClient } from 'apollo-client';
import { onError } from 'apollo-link-error';
import { ApolloLink, fromPromise } from 'apollo-link';
//Define Http link
const httpLink = new createHttpLink({
uri: '/my-graphql-endpoint',
credentials: 'include'
});
//Add on error handler for apollo link
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
//User access token has expired
if(graphQLErrors[0].message==="Unauthorized") {
//We assume we have both tokens needed to run the async request
if(refreshToken && clientToken) {
//let's refresh token through async request
return fromPromise(
authAPI.requestRefreshToken(refreshToken,clientToken)
.then((refreshResponse) => {
let headers = {
//readd old headers
...operation.getContext().headers,
//switch out old access token for new one
authorization: `Bearer ${refreshResponse.access_token}`,
};
operation.setContext({
headers
});
//Retry last failed request
return forward(operation);
})
.catch(function (error) {
//No refresh or client token available, we force user to login
return error;
})
)
}
}
}
}
}
}),
What happens is:
Initial graphQL query runs and fails due to unauthorization
The onError function of ApolloLink is executed.
The promise to refresh the token is executed.
The onError function of ApolloLink is executed again??
The promise to refresh the token is completed.
The initial graphQL query result is returned and its data is undefined
Between step 5 and 6, apollo doesn't re-run the initial failed graphQL query and hence the result is undefined.
Errors from console:
Uncaught (in promise) Error: Network error: Error writing result to store for query:
query UserProfile($id: ID!) {
UserProfile(id: $id) {
id
email
first_name
last_name
}
__typename
}
}
The solution should allow us to:
Run an async request when an operation fails
Wait for the result of the request
Retry failed operation with data from the request's result
Operation should succeed to return its intended result
I'm refreshing the token this way (updated OP's):
import { ApolloClient } from 'apollo-client';
import { onError } from 'apollo-link-error';
import { ApolloLink, Observable } from 'apollo-link'; // add Observable
// Define Http link
const httpLink = new createHttpLink({
uri: '/my-graphql-endpoint',
credentials: 'include'
});
// Add on error handler for apollo link
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError, operation, forward }) => {
// User access token has expired
if (graphQLErrors && graphQLErrors[0].message === 'Unauthorized') {
// We assume we have both tokens needed to run the async request
if (refreshToken && clientToken) {
// Let's refresh token through async request
return new Observable(observer => {
authAPI.requestRefreshToken(refreshToken, clientToken)
.then(refreshResponse => {
operation.setContext(({ headers = {} }) => ({
headers: {
// Re-add old headers
...headers,
// Switch out old access token for new one
authorization: `Bearer ${refreshResponse.access_token}` || null,
}
}));
})
.then(() => {
const subscriber = {
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer)
};
// Retry last failed request
forward(operation).subscribe(subscriber);
})
.catch(error => {
// No refresh or client token available, we force user to login
observer.error(error);
});
});
}
}
})
])
});
Accepted answer is quite good but it wouldn't work with 2 or more concurrent requests. I've crafted the one below after testing different cases with my token renew workflow that fits my needs.
It's necessary to set errorLink before authLink in link pipeline.
client.ts
import { ApolloClient, from, HttpLink } from '#apollo/client'
import errorLink from './errorLink'
import authLink from './authLink'
import cache from './cache'
const httpLink = new HttpLink({
uri: process.env.REACT_APP_API_URL,
})
const apiClient = new ApolloClient({
link: from([errorLink, authLink, httpLink]),
cache,
credentials: 'include',
})
export default apiClient
Cache shared between 2 apollo client instances for setting user query when my renewal token is expired
cache.ts
import { InMemoryCache } from '#apollo/client'
const cache = new InMemoryCache()
export default cache
authLink.ts
import { ApolloLink } from '#apollo/client'
type Headers = {
authorization?: string
}
const authLink = new ApolloLink((operation, forward) => {
const accessToken = localStorage.getItem('accessToken')
operation.setContext(({ headers }: { headers: Headers }) => ({
headers: {
...headers,
authorization: accessToken,
},
}))
return forward(operation)
})
export default authLink
errorLink.ts
import { ApolloClient, createHttpLink, fromPromise } from '#apollo/client'
import { onError } from '#apollo/client/link/error'
import { GET_CURRENT_USER } from 'queries'
import { RENEW_TOKEN } from 'mutations'
import cache from './cache'
let isRefreshing = false
let pendingRequests: Function[] = []
const setIsRefreshing = (value: boolean) => {
isRefreshing = value
}
const addPendingRequest = (pendingRequest: Function) => {
pendingRequests.push(pendingRequest)
}
const renewTokenApiClient = new ApolloClient({
link: createHttpLink({ uri: process.env.REACT_APP_API_URL }),
cache,
credentials: 'include',
})
const resolvePendingRequests = () => {
pendingRequests.map((callback) => callback())
pendingRequests = []
}
const getNewToken = async () => {
const oldRenewalToken = localStorage.getItem('renewalToken')
const {
data: {
renewToken: {
session: { renewalToken, accessToken },
},
},
} = await renewTokenApiClient.mutate({
mutation: RENEW_TOKEN,
variables: { input: { renewalToken: oldRenewalToken } },
})!
localStorage.setItem('renewalToken', renewalToken)
localStorage.setItem('accessToken', accessToken)
}
const errorLink = onError(({ graphQLErrors, operation, forward }) => {
if (graphQLErrors) {
for (const err of graphQLErrors) {
switch (err?.message) {
case 'expired':
if (!isRefreshing) {
setIsRefreshing(true)
return fromPromise(
getNewToken().catch(() => {
resolvePendingRequests()
setIsRefreshing(false)
localStorage.clear()
// Cache shared with main client instance
renewTokenApiClient!.writeQuery({
query: GET_CURRENT_USER,
data: { currentUser: null },
})
return forward(operation)
}),
).flatMap(() => {
resolvePendingRequests()
setIsRefreshing(false)
return forward(operation)
})
} else {
return fromPromise(
new Promise((resolve) => {
addPendingRequest(() => resolve())
}),
).flatMap(() => {
return forward(operation)
})
}
}
}
}
})
export default errorLink
We just had the same issues and after a very complicated solution with lots of Observeables we got a simple solution using promises which will be wrapped as an Observable in the end.
let tokenRefreshPromise: Promise = Promise.resolve()
let isRefreshing: boolean
function createErrorLink (store): ApolloLink {
return onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
// this is a helper method where we are checking the error message
if (isExpiredLogin(graphQLErrors) && !isRefreshing) {
isRefreshing = true
tokenRefreshPromise = store.dispatch('authentication/refreshToken')
tokenRefreshPromise.then(() => isRefreshing = false)
}
return fromPromise(tokenRefreshPromise).flatMap(() => forward(operation))
}
if (networkError) {
handleNetworkError(displayErrorMessage)
}
})
}
All pending requests are waiting for the tokenRefreshPromise and will then be forwarded.
Is it possible to use lowercase HTTP header key "content-type" with django rest framework parsers?
HTTP headers are case insensitive, but it doesn't seem to work with DRF 3.3.2. My frontend (emberjs) sends request with lowercase header name by ember-network(Fetch API).
Yes, it is. But ember-network or Fetch API doesn't return success and error callback.
Instead:
fetch(url, {/options/}).then(
(success) => {
//
},
(error) => {
//
});
use:
fetch(url, {/options/}).then(
(response) => {
if (response.status >= 400) {
//error
} else {
//success
}
}
);
Note response.json() returns a promise, my code is:
function request(url, type, data){
return new Ember.RSVP.Promise((resolve, reject) => {
let headers = {...};
let credentials = 'include';
fetch(url, {
method: type,
body: JSON.stringify(data),
headers: headers,
credentials: credentials
}).then((response) => {
response.json().then(
(json) => {
if (response.status >= 400) {
reject(json);
} else {
resolve(json);
}
})
});
}
I'm trying to mock an authentication service. I post my login credentials and get a token back, i then save the token.
Basically the test runs if i comment the line:
this.authenticationService.saveToken(res.json().token);
I injected the service in the test, and this line doesn't affect the output.
My error is "Cannot read property 'saveToken' of undefined thrown"
Here is my service:
private authSuccess(res: Response){
this.isAuthenticated = true;
this.authenticationService.saveToken(res.json().token);
return res.json();
}
public postLogin(loginData: LoginModel): Observable<any>{
let body = JSON.stringify(loginData);
var headers = new Headers();
headers.append('Content-Type', 'application/json');
let options = new RequestOptions({ headers: headers });
return this.http.post(this.loginUrl, body, options)
//success
.map(this.authSuccess)
//error
.catch(this.handleError);
}
Here is my test:
describe('login service tests', () => {
let loginService: LoginService;
let backend: MockBackend;
let injector: Injector;
let authenticationService: AuthenticationService;
beforeEach(() => {
injector = ReflectiveInjector.resolveAndCreate(<any> [
LoginService,
AuthenticationService,
BaseRequestOptions,
MockBackend,
provide(Http, {
useFactory: (mockBackend, defaultOptions) => new Http(mockBackend, defaultOptions),
deps: [MockBackend, BaseRequestOptions]
})
]);
loginService = <LoginService> injector.get(LoginService);
backend = <MockBackend> injector.get(MockBackend);
authenticationService = <AuthenticationService> injector.get(AuthenticationService)
});
afterEach(() => backend.verifyNoPendingRequests());
it('should authenticate with the web api', () => {
let loginUrl = Constants.loginUrl;
let loginData:LoginModel = new LoginModel('username', 'password');
backend.connections.subscribe((c: MockConnection) => {
expect(c.request.url).toEqual(loginUrl);
c.mockRespond(new Response(new ResponseOptions({ body: '{"token": "mockAuth"}' })));
});
//Correct login data
loginService.postLogin(loginData).subscribe((data) => {
expect(data.token).toBe('mockAuth');
});
});
Also, how do you guys debug when running tests? console.log doesn't seem to work and neither does debugger;
Alright, so it seems the issue was calling the method in the .map section instead of executing everything directly there.
Solution:
delete authsuccess
.map((response) => {
this.isAuthenticated = true;
this.authenticationService.saveToken(response.json().token);
return response.json();
})
How can I upload video files to facebook using FB api? I looked around stackoverflow but I can only find answers when the video/picture is already online somewhere, but I want to let the user directly upload a file to facebook via my application.
One solution that can upload to facebook is https://stackoverflow.com/a/8195849/364938, but it assumes I have the picture online somewhere.
Is this even possible? If if is, then how?
This is my code for uploading video using javascript from client side.I used axios for http requests, but you can use your own.
function uploadVideo(adaccount, access_token, file) {
return axios({
method: 'POST',
url: `${adaccount}/advideos`,
params: {
access_token,
upload_phase: 'start',
file_size: file.size
}
})
.then(res => {
return new Promise(resolve => {
resolve(transferVideo(
adaccount,
access_token,
res.data.upload_session_id,
res.data.start_offset,
res.data.end_offset,
file,
1
))
})
})
.catch(err => {
// Note: you can handle your error here
console.log(err)
})
}
function transferVideo(adaccount, token, sessionId, start, end, file, number) {
if (start === end) {
return new Promise(resolve => {
resolve(postVideo(adaccount, token, sessionId, file.name))
})
}
const blob = new Blob([file], {
type: file.type
})
const chunk = blob.slice(start, end, file.type)
const formData = new FormData()
formData.append(
'video_file_chunk',
chunk,
`#chunk${number}.${file.type.split('/')[1]}`
)
return new Promise(resolve => {
resolve(uploadVideoChunk(
adaccount,
token,
start,
sessionId,
formData
))
})
.then(res => {
return new Promise(resolve => {
resolve(transferVideo(
adaccount,
token,
sessionId,
res.start_offset,
res.end_offset,
file,
number++
))
})
})
.catch(err => {
throw new Error(JSON.stringify(err))
})
}
function uploadVideoChunk(adaccount, access_token, start_offset, upload_session_id, formData) {
return axios.post(`${adaccount}/advideos`, formData, {
headers: {
'content-type': 'multipart/form-data'
},
params: {
access_token,
upload_phase: 'transfer',
start_offset,
upload_session_id
}
})
.then(res => res.data)
.catch(err => {
throw new Error(JSON.stringify(err))
})
}
function postVideo(adaccount, access_token, upload_session_id, title) {
return axios({
method: 'POST',
url: `${adaccount}/advideos`,
params: {
access_token,
upload_phase: 'finish',
upload_session_id,
title
}
})
.then(res => res.data)
.catch(err => {
throw new Error(JSON.stringify(err))
})
}