CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
handleRequest.ts236 linesDownload Raw Back to utils
1import type { Emitter } from 'strict-event-emitter'2import { DeferredPromise } from '@open-draft/deferred-promise'3import { until } from '@open-draft/until'4import type { HttpRequestEventMap } from '../glossary'5import { emitAsync } from './emitAsync'6import { kResponsePromise, RequestController } from '../RequestController'7import {8  createServerErrorResponse,9  isResponseError,10  isResponseLike,11  ResponseError,12} from './responseUtils'13import { InterceptorError } from '../InterceptorError'14import { isNodeLikeError } from './isNodeLikeError'15import { isObject } from './isObject'16 17interface HandleRequestOptions {18  requestId: string19  request: Request20  emitter: Emitter<HttpRequestEventMap>21  controller: RequestController22 23  /**24   * Called when the request has been handled25   * with the given `Response` instance.26   */27  onResponse: (response: Response) => void | Promise<void>28 29  /**30   * Called when the request has been handled31   * with the given `Response.error()` instance.32   */33  onRequestError: (response: ResponseError) => void34 35  /**36   * Called when an unhandled error happens during the37   * request handling. This is never a thrown error/response.38   */39  onError: (error: unknown) => void40}41 42/**43 * @returns {Promise<boolean>} Indicates whether the request has been handled.44 */45export async function handleRequest(46  options: HandleRequestOptions47): Promise<boolean> {48  const handleResponse = async (49    response: Response | Error | Record<string, any>50  ) => {51    if (response instanceof Error) {52      options.onError(response)53      return true54    }55 56    // Handle "Response.error()" instances.57    if (isResponseError(response)) {58      options.onRequestError(response)59      return true60    }61 62    /**63     * Handle normal responses or response-like objects.64     * @note This must come before the arbitrary object check65     * since Response instances are, in fact, objects.66     */67    if (isResponseLike(response)) {68      await options.onResponse(response)69      return true70    }71 72    // Handle arbitrary objects provided to `.errorWith(reason)`.73    if (isObject(response)) {74      options.onError(response)75      return true76    }77 78    return false79  }80 81  const handleResponseError = async (error: unknown): Promise<boolean> => {82    // Forward the special interceptor error instances83    // to the developer. These must not be handled in any way.84    if (error instanceof InterceptorError) {85      throw result.error86    }87 88    // Support mocking Node.js-like errors.89    if (isNodeLikeError(error)) {90      options.onError(error)91      return true92    }93 94    // Handle thrown responses.95    if (error instanceof Response) {96      return await handleResponse(error)97    }98 99    return false100  }101 102  // Add the last "request" listener to check if the request103  // has been handled in any way. If it hasn't, resolve the104  // response promise with undefined.105  options.emitter.once('request', ({ requestId: pendingRequestId }) => {106    if (pendingRequestId !== options.requestId) {107      return108    }109 110    if (options.controller[kResponsePromise].state === 'pending') {111      options.controller[kResponsePromise].resolve(undefined)112    }113  })114 115  const requestAbortPromise = new DeferredPromise<void, unknown>()116 117  /**118   * @note `signal` is not always defined in React Native.119   */120  if (options.request.signal) {121    if (options.request.signal.aborted) {122      requestAbortPromise.reject(options.request.signal.reason)123    } else {124      options.request.signal.addEventListener(125        'abort',126        () => {127          requestAbortPromise.reject(options.request.signal.reason)128        },129        { once: true }130      )131    }132  }133 134  const result = await until(async () => {135    // Emit the "request" event and wait until all the listeners136    // for that event are finished (e.g. async listeners awaited).137    // By the end of this promise, the developer cannot affect the138    // request anymore.139    const requestListenersPromise = emitAsync(options.emitter, 'request', {140      requestId: options.requestId,141      request: options.request,142      controller: options.controller,143    })144 145    await Promise.race([146      // Short-circuit the request handling promise if the request gets aborted.147      requestAbortPromise,148      requestListenersPromise,149      options.controller[kResponsePromise],150    ])151 152    // The response promise will settle immediately once153    // the developer calls either "respondWith" or "errorWith".154    return await options.controller[kResponsePromise]155  })156 157  // Handle the request being aborted while waiting for the request listeners.158  if (requestAbortPromise.state === 'rejected') {159    options.onError(requestAbortPromise.rejectionReason)160    return true161  }162 163  if (result.error) {164    // Handle the error during the request listener execution.165    // These can be thrown responses or request errors.166    if (await handleResponseError(result.error)) {167      return true168    }169 170    // If the developer has added "unhandledException" listeners,171    // allow them to handle the error. They can translate it to a172    // mocked response, network error, or forward it as-is.173    if (options.emitter.listenerCount('unhandledException') > 0) {174      // Create a new request controller just for the unhandled exception case.175      // This is needed because the original controller might have been already176      // interacted with (e.g. "respondWith" or "errorWith" called on it).177      const unhandledExceptionController = new RequestController(178        options.request179      )180 181      await emitAsync(options.emitter, 'unhandledException', {182        error: result.error,183        request: options.request,184        requestId: options.requestId,185        controller: unhandledExceptionController,186      }).then(() => {187        // If all the "unhandledException" listeners have finished188        // but have not handled the response in any way, preemptively189        // resolve the pending response promise from the new controller.190        // This prevents it from hanging forever.191        if (192          unhandledExceptionController[kResponsePromise].state === 'pending'193        ) {194          unhandledExceptionController[kResponsePromise].resolve(undefined)195        }196      })197 198      const nextResult = await until(199        () => unhandledExceptionController[kResponsePromise]200      )201 202      /**203       * @note Handle the result of the unhandled controller204       * in the same way as the original request controller.205       * The exception here is that thrown errors within the206       * "unhandledException" event do NOT result in another207       * emit of the same event. They are forwarded as-is.208       */209      if (nextResult.error) {210        return handleResponseError(nextResult.error)211      }212 213      if (nextResult.data) {214        return handleResponse(nextResult.data)215      }216    }217 218    // Otherwise, coerce unhandled exceptions to a 500 Internal Server Error response.219    options.onResponse(createServerErrorResponse(result.error))220    return true221  }222 223  /**224   * Handle a mocked Response instance.225   * @note That this can also be an Error in case226   * the developer called "errorWith". This differentiates227   * unhandled exceptions from intended errors.228   */229  if (result.data) {230    return handleResponse(result.data)231  }232 233  // In all other cases, consider the request unhandled.234  return false235}236 
basant307/AI_Governance_Project · CoolFace