Label Masalah
Pertanyaan
Skenario tepat yang saya coba capai disebutkan di sini:
https://github.com/apollographql/apollo-link/tree/master/packages/apollo-link-error#retrying -failed-requests
operation.setContext({
headers: {
...oldHeaders,
authorization: getNewToken(),
},
});
Namun, jika token kedaluwarsa, async resfreshToken
harus dipanggil dan "menunggu", sebelum getNewToken
dapat mengembalikan token autentikasi yang valid. Kupikir.
Pertanyaan saya adalah, bagaimana melakukan panggilan async resfreshToken
. Saya sudah mencoba await refreshToken()
(yang menyelesaikan janji ketika selesai), tetapi dari jejak tumpukan yang saya log tampaknya ini cukup banyak mengacaukan RxJS. Saya seorang RxJS n00b, bantuan apa pun sangat dihargai!
Jika Anda lebih akrab dengan janji, Anda dapat menggunakan fromPromise
helper
import { fromPromise } from 'apollo-link';
return fromPromise(refreshToken().then(token => {
operation.setContext({
headers: {
...oldHeaders,
authorization: token,
},
});
return forward(operation);
}))
@thymikee Mencoba solusi Anda dan gagal dengan pesan berikut:
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
activated
created_at
updated_at
last_active
roles {
id
name
__typename
}
permissions {
name
value
__typename
}
profile {
address
secondary_email
phone {
id
number
type {
id
name
__typename
}
__typename
}
__typename
}
__typename
}
}
Cannot read property 'UserProfile' of undefined
at new ApolloError (ApolloError.js:43)
at QueryManager.js:327
at QueryManager.js:759
at Array.forEach (<anonymous>)
at QueryManager.js:758
at Map.forEach (<anonymous>)
at QueryManager.webpackJsonp../node_modules/apollo-client/core/QueryManager.js.QueryManager.broadcastQueries (QueryManager.js:751)
at QueryManager.js:254
Pemeriksaan lebih lanjut menunjukkan bahwa tautan Apollo onError
dipanggil dua kali saat menggunakan kode di atas. Bahkan membatasi janji refresh token
untuk dijalankan sekali, tidak memperbaiki kesalahan.
Apa yang terjadi adalah:
1) Permintaan awal dijalankan
2) Gagal dan menjalankan tautan apollo onError
3) ?? Ini menjalankan tautan apollo onError
lagi
4) Janji untuk menyegarkan token, di onError
selesai dieksekusi dan diselesaikan.
5) (Kueri awal tidak dieksekusi untuk kedua kalinya setelah janji berhasil)
6) Permintaan awal mengembalikan hasil yang berisi data
sebagai tidak terdefinisi
Ini untuk berharap seseorang menemukan solusi untuk ini, jika tidak, kita harus kembali menggunakan token akses yang berumur panjang daripada menyegarkannya setelah kedaluwarsa.
Jika logika pengambilan token Anda benar, onError
hanya boleh dipanggil sekali. Sepertinya Anda memiliki masalah dengan kueri token Anda
@thymikee Mengganti permintaan asinkron dengan janji palsu. Masih gagal dengan pesan di atas dan kueri awal tidak dijalankan dua kali. Semua token valid pada saat pengujian.
Kode:
return fromPromise(
new Promise((resolve) => {
let headers = {
//readd old headers
...operation.getContext().headers,
//switch out old access token for new one
authorization: `Bearer mynewaccesstoken`,
};
operation.setContext({
headers
});
return resolve(forward(operation));
})
)
Sunting: Menghapus fromPromise
dan berfungsi dengan benar. Entah bagaimana, pemrosesan tumpukan tautan berakhir sebelum mengembalikan hasilnya sehingga forward(operation)
tidak dieksekusi.
Setelah menganalisis kode fromPromise
dan commit #172 , fromPromise
hanya dapat digunakan dalam dugaan dengan objek Apollo Link.
Setelah meneliti solusi, saya akhirnya menemukan proyek ini: apollo-link-token-refresh
Tumpukan tautan apollo saya sekarang sebagai berikut:
[
refreshTokenLink,
requestLink,
batchHttpLink
]
refreshTokenLink
selalu dipanggil untuk memeriksa token akses sebelum mengeksekusi respons apa pun ke titik akhir graphql dan berfungsi seperti pesona.
Sayangnya, ini mengasumsikan bahwa panggilan ke titik akhir graphql harus selalu diautentikasi (yang, dalam kasus saya).
Sepertinya onError
callback tidak menerima fungsi aync
atau Promise
kembali. Lihat kode https://github.com/apollographql/apollo-link/blob/59abe7064004b600c848ee7c7e4a97acf5d230c2/packages/apollo-link-error/src/index.ts#L60 -L74
Masalah ini telah dilaporkan sebelumnya: #190
Saya pikir itu akan bekerja lebih baik jika apollo-link-error
dapat menangani Promise
, mirip dengan apa yang dilakukan apollo-link-retry
sini: #436
memiliki masalah yang sama, menggunakan apollo dengan reaksi asli saya perlu menghapus beberapa token dari AsyncStorage onError sehingga perlu fungsi async
Solusi ini bekerja untuk saya: https://stackoverflow.com/a/51321068/60223
Saya memecahkan ini dengan membuat utilitas promiseToObservable.js
:
import { Observable } from 'apollo-link';
export default promise =>
new Observable((subscriber) => {
promise.then(
(value) => {
if (subscriber.closed) return;
subscriber.next(value);
subscriber.complete();
},
err => subscriber.error(err)
);
return subscriber; // this line can removed, as per next comment
});
lalu
import { onError } from 'apollo-link-error';
import promiseToObservable from './promiseToObservable';
export default (refreshToken: Function) =>
onError(({
forward,
graphQLErrors,
networkError = {},
operation,
// response,
}) => {
if (networkError.message === 'UNAUTHORIZED') { // or whatever you want to check
// note: await refreshToken, then call its link middleware again!
return promiseToObservable(refreshToken()).flatMap(() => forward(operation));
}
});
@crazy4groovy terima kasih atas contoh Anda, ini sangat membantu. Namun, ada masalah kecil dengannya: subscriber
adalah nilai pengembalian yang tidak valid menurut pengetikan Observable
: seharusnya ZenObservable.SubscriptionObserver
:
export declare type Subscriber<T> = ZenObservable.Subscriber<T>;
export declare const Observable: {
new <T>(subscriber: Subscriber<T>): Observable<T>;
};
export declare namespace ZenObservable {
interface SubscriptionObserver<T> {
closed: boolean;
next(value: T): void;
error(errorValue: any): void;
complete(): void;
}
type Subscriber<T> = (observer: SubscriptionObserver<T>) => void | (() => void) | Subscription;
}
yaitu aman untuk mengembalikan tidak terdefinisi sebagai gantinya. Saya kira itu harus disebutkan dalam file README proyek.
UPD: Saya menambahkan PR tentang ini: https://github.com/apollographql/apollo-link/pull/825
Masalah ini berperingkat tinggi di Google jadi saya membagikan solusi saya di sini untuk membantu beberapa orang: https://Gist.github.com/alfonmga/9602085094651c03cd2e270da9b2e3f7
Saya telah mencoba solusi Anda tetapi saya menghadapi masalah baru:
Argument of type '(this: Observable<{}>, observer: Subscriber<{}>) => Observable<{}> | Promise<{}>' is not assignable to parameter of type '(this: Observable<{}>, subscriber: Subscriber<{}>) => TeardownLogic'.
Type 'Observable<{}> | Promise<{}>' is not assignable to type 'TeardownLogic'.
Type 'Observable<{}>' is not assignable to type 'TeardownLogic'.
Property 'unsubscribe' is missing in type 'Observable<{}>' but required in type 'Unsubscribable'
Bagaimana kalian menyimpan token auth baru setelah di-refresh?
Tentu, saya dapat mengatur tajuk baru dalam permintaan coba lagi, namun token akses asli (yang saya simpan di cookie) tidak diperbarui yang berarti bahwa setiap permintaan ke server akan menggunakan token akses lama (dan selanjutnya perlu disegarkan lagi).
Untuk beberapa alasan saya mendapatkan pesan kesalahan berikut setiap kali saya mencoba memperbarui cookie selama penyegaran (saya membuat masalah baru di sini tentang itu):
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:470:11)
at setCookie (/root/SimplyTidyAdmin/node_modules/nookies/dist/index.js:98:17)
at /root/SimplyTidyAdmin/.next/server/static/CAhshxrRWHVF6Gzbce~pU/pages/_app.js:1273:63
at process._tickCallback (internal/process/next_tick.js:68:7)
@StupidSexyJake mungkin ini akan membantu Anda https://stackoverflow.com/questions/55356736/change-apollo-client-options-for-jwt-token Saya mengalami masalah serupa tentang cara memperbarui token
Hai, terima kasih @crazy4groovy. Saya sudah mencoba solusi Anda tetapi saya masih mengalami masalah, bahwa middleware tempat saya menambahkan token ke permintaan graphql dipanggil sebelum token baru disetel ke permintaan. Oleh karena itu, header masih memiliki token yang tidak valid.
Sedikit info latar belakang: Kami mendapatkan kesalahan jaringan, ketika token tidak valid, dan melalui token penyegaran, kami bisa mendapatkan yang baru dan mencoba lagi. Tetapi karena middleware dipanggil sebelum token penyegaran dikumpulkan dan disetel ke penyimpanan lokal, itu masih memiliki yang tidak valid. Segarkan logika token berfungsi dengan baik, karena kami kemudian mendapatkan set token baru pada akhirnya. Saya telah sedikit men-debug masalah dan waktunya adalah sebagai berikut:
onError
ditangani melalui logika promiseToObservable
dan dicoba lagi.onRefreshToken
, middleware sudah di jalankan kedua dengan token lama.Berikut cuplikan dari bagian-bagian ini (melewatkan onRefreshtoken. Ini adalah fungsi asinkron, mengembalikan Janji):
const promiseToObservable = (promise: Promise<any>) =>
new Observable((subscriber: any) => {
promise.then(
value => {
console.log(subscriber);
if (subscriber.closed) return;
subscriber.next(value);
subscriber.complete();
},
err => subscriber.error(err)
);
});
const authMiddleware = setContext((operation: GraphQLRequest) => {
const token = localStorage.getItem('ca_token');
return {
headers: {
...(token && !isSkipHeader(operation)
? { authorization: `Bearer ${token}` }
: {})
}
};
});
const errorLink = onError(
({
networkError,
graphQLErrors,
operation,
forward
}: ErrorResponse): any => {
if (networkError) {
switch (networkError.statusCode) {
case 401:
console.warn('Refreshing token and trying again');
// await refreshToken, then call its link middleware again
return promiseToObservable(onRefreshToken(client.mutate)).flatMap(() => forward(operation));
default:
// Handle all other errors here. Irrelevant here.
}
}
if (graphQLErrors) {
// Handle gql errors, irrelevant here.
}
}
);
Bisakah Anda memberi tahu saya apa yang saya lewatkan di sini? Terima kasih banyak sebelumnya...
Oke, maaf atas kebingungan, jika ada ...
Saya telah menemukan jawabannya dan itu adalah jawaban yang bodoh setelah mencari selama berjam-jam dan menemukan - tentu saja - setelah memposting di sini: selama inisialisasi klien apollo, saya telah menukar middleware dan tautan kesalahan. Sekarang berhasil. Link error harus yang pertama, jelas..
lama: link: from([authMiddleware, errorLink, /* others */])
baru: link: from([errorLink, authMiddleware, /* others */])
Maaf lagi..
Hallo teman-teman,
Saya memiliki masalah berikut menggunakan onError untuk token penyegaran. Untuk tujuan SSR menggunakan nextjs saya mengumpulkan data dari semua kueri graphql, tetapi apa yang terjadi ketika kami memiliki 2 kueri misalnya dan masing-masing berakhir dengan kesalahan karena token jwt kedaluwarsa. Kemudian menyalakan dua kali onError dan kami memanggil dua kali untuk token penyegaran yang mahal. Saya tidak tahu dari mana masalah itu mungkin berasal. Berikut adalah kode yang saya gunakan. Bisakah Anda membantu dengan ini.
https://Gist.github.com/shaxaaa/15817f1bcc7b479f3c541383d2e83650
Saya bergulat dengan masalah ini sebentar, tetapi akhirnya saya berhasil. Aku melemparkan sebuah paket bersama-sama.
https://github.com/baleeds/apollo-link-refresh-token
Perbedaan utama antara paket ini dan yang disebut apollo-link-token-refresh adalah bahwa paket ini akan menunggu kesalahan jaringan sebelum mencoba melakukan penyegaran.
Beri tahu saya jika Anda memiliki ide untuk perubahan.
Berikut penggunaan dasarnya:
const refreshTokenLink = getRefreshTokenLink({
authorizationHeaderKey: 'Authorization',
fetchNewAccessToken,
getAccessToken: () => localStorage.getItem('access_token'),
getRefreshToken: () => localStorage.getItem('refresh_token'),
isAccessTokenValid: accessToken => isTokenValid(accessToken),
isUnauthenticatedError: graphQLError => {
const { extensions } = graphQLError;
if (
extensions &&
extensions.code &&
extensions.code === 'UNAUTHENTICATED'
) {
return true;
}
return false;
},
});
Saya memecahkan ini dengan membuat utilitas
promiseToObservable.js
:import { Observable } from 'apollo-link'; export default promise => new Observable((subscriber) => { promise.then( (value) => { if (subscriber.closed) return; subscriber.next(value); subscriber.complete(); }, err => subscriber.error(err) ); return subscriber; // this line can removed, as per next comment });
lalu
import { onError } from 'apollo-link-error'; import promiseToObservable from './promiseToObservable'; export default (refreshToken: Function) => onError(({ forward, graphQLErrors, networkError = {}, operation, // response, }) => { if (networkError.message === 'UNAUTHORIZED') { // or whatever you want to check // note: await refreshToken, then call its link middleware again! return promiseToObservable(refreshToken()).flatMap(() => forward(operation)); } });
Saya menggunakannya dan ternyata masih menggunakan token lama setelah permintaan token penyegaran. jadi, saya mencoba sebagai berikut:
return promiseToObservable(refreshToken()).flatMap((value) => {
operation.setContext(({ headers = {} }) => ({
headers: {
// re-add old headers
// ...headers,
Authorization: `JWT ${value.token}`
}
}));
return forward(operation)
});
dan itu bekerja.
Namun, masih ada masalah jika saya menambahkan ...headers
(artinya menambahkan kembali header lama), ada yang salah sebelum permintaan penerusan dikirim:
ERROR Error: Network error: Cannot read property 'length' of null
Saya pikir Otorisasi di ...header mungkin bertentangan dengan Otorisasi baru.
masalah di atas ada di apollo-angular "apollo-angular-link-http": "^1.6.0",
dan bukan di apollo-client "apollo-link-http": "^1.5.16",
sedangkan link-error sama "apollo-link-error": "^1.1.12",
sintaks lain :mata:
import Vue from 'vue'
import { Observable } from 'apollo-link'
import { onError } from 'apollo-link-error'
const onGraphqlError = async ({ graphQLErrors = [], observer, operation, forward }) => {
// here you could call the refresh query in case you receive an expired error
for (let error of graphQLErrors)
observer.next(forward(operation)) // this line would retry the operation
}
const onNetworkError = async ({ observer, networkError, operation, forward }) => { }
export const errorHandler = opt => new Observable(async observer => {
try {
const payload = { ...opt, observer }
await Promise.all([onGraphqlError(payload), onNetworkError(payload)])
if (observer.closed) return
observer.complete()
} catch (error) {
observer.error(error)
}
})
Hai! Saya menggunakan transportasi soket web lengkap, perlu meminta kueri token. Tidak tahu bagaimana melakukan itu.
Saya ingin melakukan permintaan terima ketika server menjawab bahwa accessToken
telah kedaluwarsa.
import { onError } from "apollo-link-error";
import gql from 'graphql-tag'
// Client: VUE APOLLO
const q = {
query: gql`query token { token { accessToken } }`,
manual: true,
result({ data, loading }) {
if (!loading) {
console.log(data)
}
},
}
const link = onError(({ graphQLErrors, networkError, operation, response, forward }) => {
if (networkError) {
switch (networkError.message) {
case 'accessTokenExpired':
console.log('accessTokenExpired')
return forward(q) // NOT WORKS, NEED HELP
case 'unauthorized':
return console.log('unauthorized')
default:
return forward(operation)
}
}
return forward(operation)
})
export default link
@nikitamarcius kami memposting solusi di atas, lihat yang dapat diamati
Saya tidak dapat memperbarui token, adakah yang bisa memberikan contoh kerja?
@Ramyapriya24 di sini adalah kode yang saya gunakan.
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/link-context';
import AuthService from 'services/auth-service' // this is my implementation
const asyncAuthLink = setContext(async () => {
// this is an async call, it will be done before each request
const { token } = await AuthService.getCredentials();
return {
headers: {
authorization: token
},
};
},
);
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql',
});
export const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: asyncAuthLink.concat(httpLink),
});
@adrianolsk dapatkah Anda memberikan kode layanannya?
import AuthService dari 'services/auth-service' // ini implementasi saya
const { token } = menunggu AuthService.getCredentials();
ketika saya mencoba mengimpor layanan, saya mendapatkan kesalahan
Itu adalah layanan saya, itu hanya membaca AsyncStorage dari reaksi asli, jadi setelah login saya menetapkan nilai di sana dan sebelum setiap permintaan kode ambil saja info dan atur di header, Anda bisa melakukan hal yang sama, atau menggunakan localStorage jika Anda ada di web.
Di mana Anda menyimpan informasi yang ingin Anda gunakan?
Anda hanya dapat menggunakan ini
//save the token after login or when it refreshes
localStorage.setItem('token', yourToken);
dan gunakan itu
const asyncAuthLink = setContext(() => {
// grab token from localStorage
const token = localStorage.getItem('token');
return {
headers: {
authorization: token
},
};
},
);
@adrianolsk terima kasih atas penjelasannya tetapi saya menggunakan angular Saya tidak dapat mengimpor layanan dalam file grapqh.module.ts Saya mendapatkan kesalahan ketika saya menggunakan layanan ini
adakah yang bisa tahu cara menggunakan layanan dalam file module.ts tanpa menggunakan kelas dan konstruktor?
Terima kasih
Saya mencoba menggunakan fromPromise
untuk async menyegarkan token.
Pada dasarnya mengikuti kotak ketiga dari posting ini
Saya berhasil mendapatkan dan menyimpan token, tetapi catch
atau filter
atau flatMap
dipanggil. Saya tidak yakin bagaimana men-debug ini, jadi beberapa saran akan sangat membantu.
if (token && refreshToken) {
return fromPromise(
getNewToken(client)
.then(({ data: { refreshToken } }) => {
console.log("Promise data: ", refreshToken);
localStorage.setItem("token", refreshToken.token);
localStorage.setItem("refreshToken", refreshToken.refreshToken);
return refreshToken.token;
})
.catch((error) => {
// Handle token refresh errors e.g clear stored tokens, redirect to login, ...
console.log("Error after setting token: ", error);
return;
})
)
.filter((value) => {
console.log("In filter: ", value);
return Boolean(value);
})
.flatMap(() => {
console.log("In flat map");
// retry the request, returning the new observable
return forward(operation);
});
}
@adrianolsk : pendekatan itu tampaknya selalu menyegarkan token, bahkan sebelum kedaluwarsa, yang dalam kasus beberapa layanan otentikasi (mis. Auth0's checkSession ) akan membuat perjalanan bolak-balik server Auth0 yang tidak perlu untuk setiap permintaan GraphQL.
Saya mencoba menggunakan
fromPromise
untuk async menyegarkan token.
Pada dasarnya mengikuti kotak ketiga dari posting iniSaya berhasil mendapatkan dan menyimpan token, tetapi
catch
ataufilter
atauflatMap
dipanggil. Saya tidak yakin bagaimana men-debug ini, jadi beberapa saran akan sangat membantu.if (token && refreshToken) { return fromPromise( getNewToken(client) .then(({ data: { refreshToken } }) => { console.log("Promise data: ", refreshToken); localStorage.setItem("token", refreshToken.token); localStorage.setItem("refreshToken", refreshToken.refreshToken); return refreshToken.token; }) .catch((error) => { // Handle token refresh errors e.g clear stored tokens, redirect to login, ... console.log("Error after setting token: ", error); return; }) ) .filter((value) => { console.log("In filter: ", value); return Boolean(value); }) .flatMap(() => { console.log("In flat map"); // retry the request, returning the new observable return forward(operation); }); }
Saya telah menemukan apa penyebab kesalahan tersebut. Tidak terlihat pada kode di atas, tetapi saya menggunakan fungsi map
untuk memetakan setiap kesalahan yang dihasilkan. Ini menyebabkan onError
tidak mengembalikan apa pun dan observable tidak berlangganan operasi untuk pembaruan token.
Cukup membingungkan dan butuh waktu lama bagi saya untuk memahaminya. Terima kasih kepada penulis posting blog karena telah membantu saya.
ERROR Error: Network error: Cannot read property 'length' of null
@WilsonLau0755 , saya memiliki masalah yang sama. Memecahkannya dengan mengatur semua null
header ke string kosong ''
.
Mengapa onError tidak hanya tersedia untuk digunakan dengan async menunggu?
Komentar yang paling membantu
Sepertinya
onError
callback tidak menerima fungsiaync
atauPromise
kembali. Lihat kode https://github.com/apollographql/apollo-link/blob/59abe7064004b600c848ee7c7e4a97acf5d230c2/packages/apollo-link-error/src/index.ts#L60 -L74Masalah ini telah dilaporkan sebelumnya: #190
Saya pikir itu akan bekerja lebih baik jika
apollo-link-error
dapat menanganiPromise
, mirip dengan apa yang dilakukanapollo-link-retry
sini: #436