Issue: Catching errors in proxy function calls

I am currently using Vue 3 along with the latest Quasar Framework.

To simplify my API calls, I created an Api class as a wrapper for Axios with various methods such as get, post, etc.

Now, I need to intercept these method calls.

In order to achieve this, I created a Proxy for the Api class instance. The goal is to redirect the user to the login page if they are Unauthenticated and also retrieve CSRF cookies if required before repeating the request.

However, when trying to use the Api instance with the Proxy, I encountered an error:

async function signIn() {
      loading.value = true;
      const payload: object = {
        email: login.value,
        password: password.value,
        remember: remember.value
      }
      try {
        const response = await api.post('/login', payload);
        console.log(response)
      } catch (e) {
        console.error(e);
      }
      loading.value = false;
    }

The error thrown was:

LoginForm.vue?0a10:29 TypeError: boot_api__WEBPACK_IMPORTED_MODULE_1__.api.post is not a function
       at eval (LoginForm.vue?0a10:26:1)
       at Generator.next (<anonymous>)
       at eval (VM2781 LoginForm.vue:13:71)
       at new Promise (<anonymous>)
       at __awaiter (VM2781 LoginForm.vue:9:12)
       at signIn (LoginForm.vue?0a10:17:1)
       at callWithErrorHandling (runtime-core.esm-bundler.js?f781:155:1)
       at callWithAsyncErrorHandling (runtime-core.esm-bundler.js?f781:164:1)
       at emit$1 (runtime-core.esm-bundler.js?f781:720:1)
       at eval (runtime-core.esm-bundler.js?f781:7292:1)

I'm puzzled by this issue. My IDE shows no errors, and everything works fine without using the Proxy...

Here's the complete code for the LoginForm vue component:

<template>
  <q-form
    @submit="signIn"
    class="column q-gutter-y-md login-form-width"
  >
    <q-input v-model="login"
             label="Login"
             class="col"
             filled
             :disable="loading"
    />
    <q-input v-model="password"
             label="Password"
             type="password"
             class="col"
             filled
             :disable="loading"
    />
    <q-checkbox v-model="remember"
                label="Remember"
                class="col"
                filled
                :disable="loading"
    />
    <q-btn label="Login"
           type="submit"
           unelevated
           color="primary"
           :loading="loading"
    />
  </q-form>
</template>

<script lang="ts">
import {defineComponent, Ref, ref} from 'vue';
import {api} from 'boot/api';

export default defineComponent({
  name: 'LoginForm',
  setup() {
    const login: Ref<string> = ref('');
    const password: Ref<string> = ref('');
    const remember: Ref<boolean> = ref(true);
    const loading: Ref<boolean> = ref(false);
    const errors: Ref<object> = ref({
      login: [],
      password: [],
    });

    async function signIn() {
      loading.value = true;
      const payload: object = {
        email: login.value,
        password: password.value,
        remember: remember.value
      }
      try {
        const response = await api.post('/login', payload);
        console.log(response)
      } catch (e) {
        console.error(e);
      }
      loading.value = false;
    }

    return {login, password, remember, loading, errors, signIn}
  }
})
</script>

<style scoped>
.login-form-width {
  width: 100%;
  max-width: 350px;
}
</style>

And here's the full code for the api (from the Quasar boot file):

import {boot} from 'quasar/wrappers'
import {AxiosError, AxiosInstance, Method} from 'axios';
import {axiosInstance} from 'boot/axios';

type ApiMethod = 'get' | 'post';

class ApiResponse {
  public data: object
  public code: number | null
  public message: string | null

  constructor(data: object = {}, code: number | null = 0, message: string | null = '') {
    this.data = data;
    this.code = code;
    this.message = message;
  }
}

class ApiError {
  public data: object
  public code: number | null
  public message: string | null

  constructor(data: object = {}, code: number | null = 0, message: string | null = '') {
    this.data = data;
    this.code = code;
    this.message = message;
  }
}

interface ApiRequestConfig {
  url: string,
  method: Method,
  headers?: object,
  params?: object,
  data?: object | string,
}

class Api {
  axios: AxiosInstance

  constructor(axios: AxiosInstance) {
    this.axios = axios;
    this.axios.defaults.withCredentials = true;
  }

  public async get(url: string, params: object = {}): Promise<ApiResponse> {
    return this.request({method: 'GET', url, params});
  }

  public async post(url: string, payload: object = {}): Promise<ApiResponse> {
    return this.request({method: 'POST', url, data: payload});
  }

  private async request(config: ApiRequestConfig): Promise<ApiResponse> {
    try {
      const response = await this.axios.request(config);
      return new ApiResponse(response.data, response.status, response.statusText)
    } catch (e) {
      const error = e as AxiosError;
      if (error.response) {
        throw new ApiError(error.response.data, error.response.status, error.response.statusText);
      } else {
        throw new ApiError({}, 0, '')
      }
    }
  }
}


declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $api: Api;
  }
}

let api: Api = new Api(axiosInstance);

// "async" is optional;
// more info on params: https://v2.quasar.dev/quasar-cli/boot-files
export default boot(async ({router}) => {
  api = new Proxy(api, {
    async get(target: Api, prop: ApiMethod) {
      if (typeof target[prop] === 'function') {
        return async function func(args: unknown[]): Promise<unknown> {
          try {
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            return await target[prop](...args);
          } catch (e) {
            const error = e as ApiError;
            if (error.code === 401 || error.code === 403) {
              await router.push('login');
            }
            if (error.code === 419) {
              await target.get('/sanctum/csrf-cookie');
              return func(args);
            }

          }
        }
      }
    }
  })
})

export {api};


Answer №1

Adjusting the request method for simplicity...

  private async modifyRequest(config: ApiRequestConfig): Promise<ApiResponse> {
    try {
      const response = await this.axios.request(config);
      return new ApiResponse(response.data, response.status, response.statusText)
    } catch (e) {
      const error = e as AxiosError;
      if (error.response) {
        if (error.response.status === 401 || error.response.status === 403) {
          await this.router.push('login');
        } else if (error.response.status === 419) {
          await this.get('/sanctum/csrf-cookie');
          return await this.modifyRequest(config);
        } else {
          throw new ApiError(error.response.data, error.response.status, error.response.data?.message ?? error.response.statusText);
        }
      } else {
        throw new ApiError();
      }
    }
    return new ApiResponse();
  }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Executing a function that includes showModalDialog outside of an iframe might display the incorrect page

This website structure looks like: `--main.html `--dialog.html `--folder `--iframe.html The code for each page is as follows: main.html: <html> <head> <script type="text/javascript"> function t ...

Encountering issues with multiple arguments in ajax within a C# MVC application - Further details included

There seems to be a missing piece in my code. Here's my controller method public void SubmitSweep(int personID, int DD, int MM, int YYYY, int hh, int mm, int dealId) Here's how I define my button <button id="submit@(person.Id)" clas ...

Only users who are logged in to Node.js can access the message

Whenever users are online and do not close our clients like a browser tab or android application, I have the ability to send a message to each specific user by utilizing the following code: socket.broadcast.to(socketId) .emit('new message', ...

The test does not pass when attempting to use a shorthand operator to ascertain the truthfulness of

I've encountered an interesting issue with my unit test. It seems to work perfectly fine when I directly return true or false, but fails when I try to use a shorthand method to determine the result. Let's say I have a function called isMatched w ...

Automatically updating a database value in CodeIgniter after a countdown has expired

I am looking to automatically update a value in my MySQL database using CodeIgniter once a countdown timer reaches zero. However, I am struggling to figure out how to implement this. Here is an example: I have a database structured like this: [image lin ...

Having trouble locating an external Javascript file in a Node.JS/Express app with Jade template?

In my Node.JS/Express app, I am using the Jade template engine. The issue arises when trying to reference a server-side Javascript file named common_routines. Despite placing the Javascript file in the directory above my views directory and referencing it ...

Send a webhook post request without causing a redirection

Is there a way to send a post request to a webhook without redirecting the user directly to the webhook URL, after validating their input? ...

Converting the jQuery $.xajax loadmore feature into a custom XMLHttpRequest JavaScript function

I'm facing challenges while trying to create a XMLHttpRequest loadmore function as compared to using $.ajax. I am uncertain about what I might be missing in my code. Below is the function that is based on a previously working $.ajax version that I ha ...

Utilizing jQuery to pinpoint the exact position within a Flexbox container

I have a unique setup with multiple boxes arranged using Flexbox as the container and list tags as individual boxes inside. These boxes are responsive and change position as the width is resized. My goal is to use jQuery to detect which boxes are touching ...

The Implementation of Comet Programming

I'm interested in creating a web application similar to Google Docs where multiple users can edit and view changes in real-time. Would Comet Programming be the best approach for this? As a newcomer to Web Development, I'm currently learning Java ...

The application experiences a sudden failure when Mongoose is unable to locate the specified item

I am facing an issue where my app crashes when mongoose is unable to find the item. I want to display a 404 error page instead. Here is the code snippet: try { let theBeverage = Product.findOne({ _id: beverageId }); await theBeverage.then((data) ...

"Apple TV now features an iOS app that allows users to watch YouTube videos

I am interested in developing an iOS application that can play YouTube videos in a UIWebView window. Users should have the option to play the videos directly on their iOS device or stream them via AirPlay to an AppleTV. The content displayed in the UIWebV ...

The AJAX POST request encountered an error

I apologize for the lackluster title. Basically, when the script is executed, it triggers an 'error' alert as shown in the jQuery code below. I suspect that the issue lies in the structure of my JSON data, but I'm uncertain about the necess ...

React useEffect only retrieves the last element of an array object

I am facing an issue where React seems to only save the last element in my array. Even though I have two elements, when mapping over them, only the last element is being placed in the hook each time. React.useEffect(() => { if (bearbeiten) { handleCli ...

Encountering obstacles when trying to implement mongoose virtuals in a TypeScript environment

I have been exploring how to utilize virtuals in mongoose with typescript. Let's say I have an interface for defining a user. interface User { id: mongoose.ObjectId; name: string; likes: string; } Next, I define a schema for mongoose. // ...

Preserve the values of checkboxes throughout the entire website by utilizing localStorage

Example A functionality in the example allows users to add images to a container by clicking on checkboxes. However, there is an issue where when a checkbox is checked on one page to store an image, and then another checkbox is checked on a different page ...

"How can I exclude empty fields from the form.serialize function in jQuery when using php?" is not functioning as expected

I am facing an issue with using How do I use jQuery's form.serialize but exclude empty fields. Despite following a minimal example that directs to a php script: <html><head> <script src="jquery.min.js"></script> <script typ ...

Sending postMessage during the beforeunload event does not work as expected

When postMessage() is triggered within the beforeunload window event in an Ionic 2 browser, I've noticed that the message doesn't make it to the parent. However, if the same message is sent during the unload or load event, it is received successf ...

An issue occurred with building deployments on Vercel due to a typing error

When attempting to deploy my build on Vercel, I encountered an error. The deployment works fine locally, but when trying to build it on vercel, the following error occurs: [![Type error: Type '(ref: HTMLDivElement | null) => HTMLDivElement | null&a ...

Creating a multi-level mobile navigation menu in WordPress can greatly enhance user experience and make

Hey there, I am currently in the process of developing a custom WordPress theme and working on creating a mobile navigation system. Although I have managed to come up with a solution that I am quite pleased with after multiple attempts, I have encountered ...