为什么我的 Auth0 + Vue 3 设置会抛出 TypeScript 错误?

Why does my Auth0 + Vue 3 setup throw TypeScript errors?

我发现这个 this repo 为 Vue 2 创建了一个 Auth0-spa-js TypeScript 插件,我在我的 Vue 3 项目中使用它。

但问题是,当使用像 auth.loginWithRedirect();auth.logout(); 这样的函数时,它会抛出一个 TypeScript 错误,提示 Object is of type 'unknown'. ts(2571).

我是 TypeScipt 的新手,不确定我做错了什么。

这是抛出错误的 link to my Vue 3 repo。并且抛出错误的代码的重要部分也复制在下面。

这是 Auth0 插件的样子:

import createAuth0Client, {
  Auth0Client,
  GetIdTokenClaimsOptions,
  GetTokenSilentlyOptions,
  GetTokenWithPopupOptions,
  LogoutOptions,
  RedirectLoginOptions,
  User,
} from "@auth0/auth0-spa-js";
import { App, Plugin, computed, reactive, watchEffect } from "vue";
import { NavigationGuardWithThis } from "vue-router";

let client: Auth0Client;

interface Auth0PluginState {
  loading: boolean;
  isAuthenticated: boolean;
  user: User | undefined;
  popupOpen: boolean;
  error: any;
}

const state = reactive<Auth0PluginState>({
  loading: true,
  isAuthenticated: false,
  user: {},
  popupOpen: false,
  error: null,
});

async function handleRedirectCallback() {
  state.loading = true;

  try {
    await client.handleRedirectCallback();
    state.user = await client.getUser();
    state.isAuthenticated = true;
  } catch (e) {
    state.error = e;
  } finally {
    state.loading = false;
  }
}

function loginWithRedirect(o: RedirectLoginOptions) {
  return client.loginWithRedirect(o);
}

function getIdTokenClaims(o: GetIdTokenClaimsOptions) {
  return client.getIdTokenClaims(o);
}

function getTokenSilently(o: GetTokenSilentlyOptions) {
  return client.getTokenSilently(o);
}

function getTokenWithPopup(o: GetTokenWithPopupOptions) {
  return client.getTokenWithPopup(o);
}

function logout(o: LogoutOptions) {
  return client.logout(o);
}

const authPlugin = {
  isAuthenticated: computed(() => state.isAuthenticated),
  loading: computed(() => state.loading),
  user: computed(() => state.user),
  getIdTokenClaims,
  getTokenSilently,
  getTokenWithPopup,
  handleRedirectCallback,
  loginWithRedirect,
  logout,
};

const routeGuard: NavigationGuardWithThis<undefined> = (
  to: any,
  from: any,
  next: any
) => {
  const { isAuthenticated, loading, loginWithRedirect } = authPlugin;

  const verify = async () => {
    // If the user is authenticated, continue with the route
    if (isAuthenticated.value) {
      return next();
    }

    // Otherwise, log in
    await loginWithRedirect({ appState: { targetUrl: to.fullPath } });
  };

  // If loading has already finished, check our auth state using `fn()`
  if (!loading.value) {
    return verify();
  }

  // Watch for the loading property to change before we check isAuthenticated
  watchEffect(() => {
    if (!loading.value) {
      return verify();
    }
  });
};

interface Auth0PluginOptions {
  domain: string;
  clientId: string;
  audience: string;
  redirectUri: string;

  onRedirectCallback(appState: any): void;
}

async function init(options: Auth0PluginOptions): Promise<Plugin> {
  client = await createAuth0Client({
    // domain: process.env.VUE_APP_AUTH0_DOMAIN,
    // client_id: process.env.VUE_APP_AUTH0_CLIENT_KEY,
    domain: options.domain,
    client_id: options.clientId,
    audience: options.audience,
    redirect_uri: options.redirectUri,
  });

  try {
    // If the user is returning to the app after authentication
    if (
      window.location.search.includes("code=") &&
      window.location.search.includes("state=")
    ) {
      // handle the redirect and retrieve tokens
      const { appState } = await client.handleRedirectCallback();

      // Notify subscribers that the redirect callback has happened, passing the appState
      // (useful for retrieving any pre-authentication state)
      options.onRedirectCallback(appState);
    }
  } catch (e) {
    state.error = e;
  } finally {
    // Initialize our internal authentication state
    state.isAuthenticated = await client.isAuthenticated();
    state.user = await client.getUser();
    state.loading = false;
  }

  return {
    install: (app: App) => {
      app.provide("Auth", authPlugin);
    },
  };
}

interface Auth0Plugin {
  init(options: Auth0PluginOptions): Promise<Plugin>;
  routeGuard: NavigationGuardWithThis<undefined>;
}

export const Auth0: Auth0Plugin = {
  init,
  routeGuard,
};

这就是我的 App.vue 文件的样子(错误显示在这个文件上):

<template>
  <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/profile">Profile</router-link> |
    <router-link to="/faunaapi">API</router-link> |
    <button @click.prevent="login">LOGIN</button> |
    <button @click.prevent="logout">LOGOUT</button>
  </div>
  <router-view />
</template>

<script lang="ts">
import { defineComponent, inject } from "vue";
export default defineComponent({
  name: "App",
  setup() {
    const auth = inject("Auth");
    const login = (): void => {
      auth.loginWithRedirect(); //<-- ERROR: Object is of type 'unknown'.
    };
    const logout = (): void => {
      auth.logout({
        returnTo: window.location.origin,
      }); //<-- ERROR: Object is of type 'unknown'.
    };
    return {
      logout,
      login,
      ...auth,
    };
  },
});
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;
}

#nav a {
  font-weight: bold;
  color: #2c3e50;
}

#nav a.router-link-exact-active {
  color: #42b983;
}
</style>

你知道我做错了什么吗?

因此,如果您将鼠标悬停在“注入”上,您会看到如下内容:

(alias) inject<unknown>(key: string | InjectionKey<unknown>): unknown

特别是这里 inject<unknown>< > 里面是什么 Generic Type

简单地说,您需要告诉 Inject 您要注入的是什么类型,所以它会推断它。

试试这个:

解决方案 1 - 传递类型参数(推荐):

import { Auth0Client } from "@auth0/auth0-spa-js";
import { defineComponent, inject } from "vue";
export default defineComponent({
  name: "App",
  setup() {
    /* Here you pass Auth0Client types, the error should go away. 
    You should get type validation and autocompletion for `auth` now */
    const auth = inject<Auth0Client>("Auth")!; 

解决方案 2 - 铸造注入类型

// Here you are basically saying "Hey whatever inject returns, is an Auth0Client."
const auth = inject("Auth") as Auth0Client;

希望有用:)!