feat: 添加鉴权相关逻辑。
This commit is contained in:
@ -3,20 +3,23 @@ import { FC } from "react";
|
||||
import { Login } from "./login";
|
||||
|
||||
export interface AuthContext {
|
||||
accessToken: string | undefined;
|
||||
accessToken: string | null;
|
||||
setAccessToken: (token: string) => void;
|
||||
setRefreshToken: (token: string) => void;
|
||||
refreshToken: string | undefined;
|
||||
login: (dto: any) => void;
|
||||
account?: any;
|
||||
setAccount: (dto: any) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
const Context = createContext({} as AuthContext);
|
||||
|
||||
export const useAuth = () => useContext(Context);
|
||||
|
||||
export const AuthProvider: FC = ({ children }) => {
|
||||
const [accessToken, setAccessToken] = useState<string>();
|
||||
const [accessToken, setAccessToken] = useState<string | null>(
|
||||
localStorage.getItem("accessToken")
|
||||
);
|
||||
const [refreshToken, setRefreshToken] = useState<string>();
|
||||
const [account, setAccount] = useState<any>();
|
||||
|
||||
@ -24,6 +27,12 @@ export const AuthProvider: FC = ({ children }) => {
|
||||
setAccessToken(dto.accessToken);
|
||||
setRefreshToken(dto.refreshToken);
|
||||
setAccount(dto.account);
|
||||
localStorage.setItem("accessToken", dto.accessToken);
|
||||
};
|
||||
const logout = () => {
|
||||
setAccessToken(null);
|
||||
setRefreshToken(undefined);
|
||||
setAccount(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -36,6 +45,7 @@ export const AuthProvider: FC = ({ children }) => {
|
||||
login,
|
||||
account,
|
||||
setAccount,
|
||||
logout,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { makeStyles } from "@material-ui/core";
|
||||
import { FC, useEffect, useRef } from "react";
|
||||
import { FC, Fragment, useEffect, useRef } from "react";
|
||||
import { useAuth } from "./auth.provider";
|
||||
const useStyles = makeStyles({
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
iframe: {
|
||||
height: "300px",
|
||||
width: "500px",
|
||||
@ -9,8 +9,20 @@ const useStyles = makeStyles({
|
||||
top: "100px",
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
zIndex: theme.zIndex.modal,
|
||||
border: "none",
|
||||
boxShadow: theme.shadows[4],
|
||||
},
|
||||
});
|
||||
mask: {
|
||||
top: "0",
|
||||
left: "0",
|
||||
bottom: "0",
|
||||
right: "0",
|
||||
position: "absolute",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.3)",
|
||||
zIndex: theme.zIndex.modal,
|
||||
},
|
||||
}));
|
||||
|
||||
export const Login: FC = () => {
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
@ -41,11 +53,14 @@ export const Login: FC = () => {
|
||||
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
className={classes.iframe}
|
||||
title="Auth"
|
||||
src="https://user.rpi.ivanli.cc/auth/login"
|
||||
></iframe>
|
||||
<Fragment>
|
||||
<div className={classes.mask} />
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
className={classes.iframe}
|
||||
title="Auth"
|
||||
src="https://user.rpi.ivanli.cc/auth/login"
|
||||
></iframe>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
@ -5,6 +5,8 @@ import {
|
||||
InMemoryCache,
|
||||
split,
|
||||
ApolloProvider,
|
||||
fromPromise,
|
||||
FetchResult,
|
||||
} from "@apollo/client";
|
||||
import { withScalars } from "apollo-link-scalars";
|
||||
import { buildClientSchema, IntrospectionQuery } from "graphql";
|
||||
@ -13,9 +15,16 @@ import { FC } from "react";
|
||||
import introspectionResult from "../../generated/graphql.schema.json";
|
||||
import { onError } from "@apollo/client/link/error";
|
||||
import { WebSocketLink } from "@apollo/client/link/ws";
|
||||
import { getMainDefinition } from "@apollo/client/utilities";
|
||||
import { getMainDefinition, Observable } from "@apollo/client/utilities";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { deepOmit } from "../../utils/deep-omit";
|
||||
import { useMemo } from "react";
|
||||
import { EventEmitter } from "events";
|
||||
import { useState } from "react";
|
||||
import { useAuth } from "../auth/auth.provider";
|
||||
import { useEffect } from "react";
|
||||
import { useRef } from "react";
|
||||
import { setContext } from "@apollo/client/link/context";
|
||||
|
||||
const schema = buildClientSchema(
|
||||
introspectionResult as unknown as IntrospectionQuery
|
||||
@ -37,77 +46,138 @@ const cleanTypeName = new ApolloLink((operation, forward) => {
|
||||
);
|
||||
});
|
||||
|
||||
export const FennecApolloClientProvider: FC = ({ children }) => {
|
||||
export const AppApolloClientProvider: FC = ({ children }) => {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const errorLink = onError(({ graphQLErrors, networkError }) => {
|
||||
if (graphQLErrors) {
|
||||
graphQLErrors.forEach((error) => {
|
||||
enqueueSnackbar(error.message, {
|
||||
const { accessToken, logout } = useAuth();
|
||||
|
||||
const [loggedEventTarget] = useState(() => new EventEmitter());
|
||||
const accessTokenRef = useRef(accessToken);
|
||||
const logoutRef = useRef(logout);
|
||||
|
||||
useEffect(() => {
|
||||
accessTokenRef.current = accessToken;
|
||||
if (accessToken) {
|
||||
loggedEventTarget.emit("logged", accessToken);
|
||||
}
|
||||
}, [loggedEventTarget, accessToken]);
|
||||
useEffect(() => {
|
||||
logoutRef.current = logout;
|
||||
}, [logout]);
|
||||
const client = useMemo(() => {
|
||||
const authLink = onError(
|
||||
({ graphQLErrors, networkError, operation, forward }) => {
|
||||
if (graphQLErrors) {
|
||||
for (const error of graphQLErrors) {
|
||||
if (error.extensions?.code === "401") {
|
||||
return fromPromise(
|
||||
new Promise<Observable<FetchResult>>((resolve) => {
|
||||
loggedEventTarget.once("logged", (accessToken: string) => {
|
||||
const oldHeaders = operation.getContext().headers;
|
||||
operation.setContext({
|
||||
headers: {
|
||||
...oldHeaders,
|
||||
authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
resolve(forward(operation));
|
||||
});
|
||||
logoutRef.current();
|
||||
})
|
||||
).flatMap((v) => v);
|
||||
}
|
||||
}
|
||||
}
|
||||
const httpResult = (networkError as any)?.result;
|
||||
if (httpResult?.statusCode === 401) {
|
||||
return fromPromise(
|
||||
new Promise<Observable<FetchResult>>((resolve) => {
|
||||
loggedEventTarget.once("logged", (accessToken: string) => {
|
||||
const oldHeaders = operation.getContext().headers;
|
||||
operation.setContext({
|
||||
headers: {
|
||||
...oldHeaders,
|
||||
authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
resolve(forward(operation));
|
||||
});
|
||||
logoutRef.current();
|
||||
})
|
||||
).flatMap((v) => v);
|
||||
}
|
||||
}
|
||||
).concat(
|
||||
setContext(() => ({
|
||||
headers: {
|
||||
authorization: `Bearer ${accessTokenRef.current}`,
|
||||
},
|
||||
}))
|
||||
);
|
||||
const errorLink = onError(({ graphQLErrors, networkError }) => {
|
||||
if (graphQLErrors) {
|
||||
graphQLErrors.forEach((error) => {
|
||||
enqueueSnackbar(error.message, {
|
||||
variant: "error",
|
||||
});
|
||||
});
|
||||
graphQLErrors.forEach(({ message, locations, path }) => {
|
||||
console.error(
|
||||
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
|
||||
);
|
||||
});
|
||||
}
|
||||
if (networkError) {
|
||||
console.log(`[Network error]: ${networkError}`);
|
||||
enqueueSnackbar(networkError.message, {
|
||||
variant: "error",
|
||||
});
|
||||
});
|
||||
graphQLErrors.forEach(({ message, locations, path }) => {
|
||||
console.error(
|
||||
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
|
||||
);
|
||||
});
|
||||
}
|
||||
if (networkError) {
|
||||
console.log(`[Network error]: ${networkError}`);
|
||||
enqueueSnackbar(networkError.message, {
|
||||
variant: "error",
|
||||
});
|
||||
}
|
||||
});
|
||||
const wsLink = new WebSocketLink({
|
||||
uri: `${window.location.protocol.replace("http", "ws")}//${
|
||||
window.location.hostname
|
||||
}:${window.location.port}/api/graphql`,
|
||||
options: {
|
||||
reconnect: true,
|
||||
},
|
||||
});
|
||||
const httpLink = new HttpLink({
|
||||
uri: "/api/graphql",
|
||||
});
|
||||
const splitLink = split(
|
||||
({ query }) => {
|
||||
const definition = getMainDefinition(query);
|
||||
return (
|
||||
definition.kind === "OperationDefinition" &&
|
||||
definition.operation === "subscription"
|
||||
);
|
||||
},
|
||||
wsLink,
|
||||
httpLink
|
||||
);
|
||||
const link = ApolloLink.from([
|
||||
errorLink,
|
||||
withScalars({ schema, typesMap }) as unknown as ApolloLink,
|
||||
cleanTypeName,
|
||||
splitLink,
|
||||
]);
|
||||
const client = new ApolloClient({
|
||||
connectToDevTools: true,
|
||||
ssrMode: typeof window === "undefined",
|
||||
link,
|
||||
cache: new InMemoryCache({
|
||||
typePolicies: {
|
||||
// PipelineTaskLogs: {
|
||||
// keyFields: ["unit"],
|
||||
// },
|
||||
// PipelineTask: {
|
||||
// fields: {
|
||||
// logs: {
|
||||
// merge(existing = [], incoming: any[]) {
|
||||
// return [...existing, ...incoming];
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
}
|
||||
});
|
||||
const wsLink = new WebSocketLink({
|
||||
uri: `${window.location.protocol.replace("http", "ws")}//${
|
||||
window.location.hostname
|
||||
}:${window.location.port}/api/graphql`,
|
||||
options: {
|
||||
reconnect: true,
|
||||
connectionParams: {
|
||||
headers: {
|
||||
authorization: `Bearer ${accessTokenRef.current} `,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
const httpLink = new HttpLink({
|
||||
uri: "/api/graphql",
|
||||
});
|
||||
const splitLink = split(
|
||||
({ query }) => {
|
||||
const definition = getMainDefinition(query);
|
||||
return (
|
||||
definition.kind === "OperationDefinition" &&
|
||||
definition.operation === "subscription"
|
||||
);
|
||||
},
|
||||
wsLink,
|
||||
httpLink
|
||||
);
|
||||
const link = ApolloLink.from([
|
||||
errorLink,
|
||||
authLink,
|
||||
withScalars({ schema, typesMap }) as unknown as ApolloLink,
|
||||
cleanTypeName,
|
||||
splitLink,
|
||||
]);
|
||||
const client = new ApolloClient({
|
||||
connectToDevTools: true,
|
||||
ssrMode: typeof window === "undefined",
|
||||
link,
|
||||
cache: new InMemoryCache({
|
||||
typePolicies: {},
|
||||
}),
|
||||
});
|
||||
|
||||
return client;
|
||||
}, [enqueueSnackbar, loggedEventTarget]);
|
||||
|
||||
return <ApolloProvider client={client}>{children}</ApolloProvider>;
|
||||
};
|
||||
|
Reference in New Issue
Block a user