البرمجة

إدارة تحديث رموز التوكن باستخدام RxJS في Angular

منذ بدايتي مع Angular 2، قمت بإعداد خدماتي لتعيد Observable of T. في الخدمة، كنت أقوم بإجراء استدعاء map()، وكانت المكونات التي تستخدم هذه الخدمات تكتفي بالاشتراك في subscribe() للانتظار حتى يتم الرد. في هذه الحالات البسيطة، لم أحتاج حقًا للتعمق في فهم rxjs، وكان كل شيء على ما يُرام.

الآن أود تحقيق الآتي: أستخدم المصادقة بواسطة OAuth2 مع رموز التحديث. أرغب في بناء خدمة API ستستخدمها جميع الخدمات الأخرى، وستتعامل بشكل شفاف مع رمز التحديث عندما يتم إرجاع خطأ 401. لذا، في حالة 401، أقوم أولاً بجلب رمز جديد من نقطة نهاية OAuth2، ثم أقوم بإعادة محاولة الطلب باستخدام الرمز الجديد. فيما يلي الشيفرة التي تعمل بشكل جيد، باستخدام Promises:

typescript
// الشيفرة باستخدام Promises request(url: string, request: RequestOptionsArgs): Promise<Response> { var me = this; request.headers = request.headers || new Headers(); var isSecureCall: boolean = true; if (isSecureCall === true) { me.authService.setAuthorizationHeader(request.headers); } request.headers.append('Content-Type', 'application/json'); request.headers.append('Accept', 'application/json'); return this.http.request(url, request).toPromise() .catch(initialError => { if (initialError && initialError.status === 401 && isSecureCall === true) { return me.authService.refreshAuthentication().then((authenticationResult: AuthenticationResult) => { if (authenticationResult.IsAuthenticated == true) { me.authService.setAuthorizationHeader(request.headers); return this.http.request(url, request).toPromise(); } return <any>Promise.reject(initialError); }); } else { return <any>Promise.reject(initialError); } }); }

في هذه الشيفرة، authService.refreshAuthentication() ستجلب الرمز الجديد وتخزنه في localStorage. authService.setAuthorizationHeader ستقوم بتعيين رأس ‘Authorization’ إلى الرمز المحدث سابقًا. إذا نظرت إلى الطريقة catch، سترى أنها تعيد promise (لرمز التحديث) الذي في نهاية المطاف سيعيد promise آخر (للمحاولة الثانية الفعلية للطلب).

حاولت القيام بذلك دون اللجوء إلى Promises:

typescript
// الشيفرة باستخدام Observables request(url: string, request: RequestOptionsArgs): Observable<Response> { var me = this; request.headers = request.headers || new Headers(); var isSecureCall: boolean = true; if (isSecureCall === true) { me.authService.setAuthorizationHeader(request.headers); } request.headers.append('Content-Type', 'application/json'); request.headers.append('Accept', 'application/json'); return this.http.request(url, request) .catch(initialError => { if (initialError && initialError.status === 401 && isSecureCall === true) { return me.authService.refreshAuthenticationObservable().map((authenticationResult: AuthenticationResult) => { if (authenticationResult.IsAuthenticated == true) { me.authService.setAuthorizationHeader(request.headers); return this.http.request(url, request); } return Observable.throw(initialError); }); } else { return Observable.throw(initialError); } }); }

الشيفرة السابقة لا تعمل كما أتوقع: في حالة استجابة 200، ستعيد بشكل صحيح الاستجابة. ومع ذلك، إذا اكتشفت خطأ 401، ستجلب الرمز الجديد بنجاح، ولكن الاشتراك في النهاية سيسترجع Observable بدلاً من الاستجابة. أفترض أن هذا هو الObservable الغير المنفذ الذي يجب أن يقوم بالمحاولة مرة أخرى.

أدرك أن ترجمة الطريقة التقليدية للعمل إلى مكتبة rxjs ربما ليست الطريقة الأمثل، ولكني لم أتمكن حقًا من فهم “كل شيء هو تيار” بشكل جيد. حاولت بعض الحلول الأخرى التي تتضمن flatmap، retryWhen وغيرها… ولكني لم أحقق تقدمًا كبيرًا. أرجو المساعدة.

المزيد من المعلومات

بالتأكيد، دعوني أقدم لك مزيدًا من المعلومات حول كيفية التعامل مع rxjs وكيف يمكن تحسين الشيفرة لتحقيق الهدف المرجو.

في هذا السياق، يتم استخدام مكتبة rxjs لتمثيل التدفقات (streams) في JavaScript، حيث يُعتبر Observable مفهومًا أساسيًا. يعتبر Observable تمثيلاً للبيانات المتغيرة مع مرور الوقت. ومن خلال تحويل الشيفرة للاستفادة الكاملة من rxjs، يمكن تحقيق فعالية أكبر في التعامل مع التحقق من صحة الرمز وتحديثه.

في مثال الشيفرة الحالي، يمكن استخدام ميزات rxjs مثل switchMap و catchError لتبسيط عملية التحكم في الأخطاء وإدارة التحديث. اليك كيف يمكن تحسين الشيفرة:

typescript
// الشيفرة المحسنة باستخدام Observables و rxjs import { of, throwError } from 'rxjs'; import { switchMap, catchError } from 'rxjs/operators'; request(url: string, request: RequestOptionsArgs): Observable<Response> { var me = this; request.headers = request.headers || new Headers(); var isSecureCall: boolean = true; if (isSecureCall === true) { me.authService.setAuthorizationHeader(request.headers); } request.headers.append('Content-Type', 'application/json'); request.headers.append('Accept', 'application/json'); return this.http.request(url, request) .pipe( catchError(initialError => { if (initialError && initialError.status === 401 && isSecureCall === true) { return me.authService.refreshAuthenticationObservable() .pipe( switchMap((authenticationResult: AuthenticationResult) => { if (authenticationResult.IsAuthenticated == true) { me.authService.setAuthorizationHeader(request.headers); return this.http.request(url, request); } return throwError(initialError); }) ); } else { return throwError(initialError); } }) ); }

تم استخدام pipe لتجميع سلسلة من العمليات (operators) في rxjs. switchMap تستخدم للانتقال إلى Observable جديد عندما يتم إصدار قيمة، بينما catchError تستخدم للتعامل مع الأخطاء. يتم استخدام throwError لرمي خطأ في حالة فشل التحديث.

هذه الشيفرة تحسن قابلية الصيانة والقراءة، وتستفيد من قوة rxjs لتسهيل إدارة التدفقات والأخطاء.

زر الذهاب إلى الأعلى