feat: refresh token when access token has expired
This commit is contained in:
parent
52f2c969cc
commit
08a41e5e2b
13
.env.example
13
.env.example
@ -1,7 +1,12 @@
|
|||||||
NEXT_PUBLIC_API_URL=http://localhost:3000
|
NEXT_PUBLIC_API_URL=
|
||||||
|
|
||||||
NEXTAUTH_URL=http://localhost:3000
|
NEXTAUTH_URL=
|
||||||
NEXTAUTH_SECRET=
|
NEXTAUTH_SECRET=
|
||||||
|
|
||||||
DISCORD_CLIENT_ID=
|
OAUTH_CLIENT_ID=
|
||||||
DISCORD_CLIENT_SECRET=
|
OAUTH_CLIENT_SECRET=
|
||||||
|
OAUTH_ISSUER=
|
||||||
|
OAUTH_AUTHORIZATION_URL=
|
||||||
|
OAUTH_TOKEN_URL=
|
||||||
|
OAUTH_USERINFO_URL=
|
||||||
|
OAUTH_JWKS_ENDPOINT=
|
@ -9,11 +9,14 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.7.9",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"moment": "^2.30.1",
|
||||||
"next": "15.0.3",
|
"next": "15.0.3",
|
||||||
"next-auth": "^4.24.10",
|
"next-auth": "^4.24.10",
|
||||||
"react": "19.0.0-rc-66855b96-20241106",
|
"react": "19.0.0-rc-66855b96-20241106",
|
||||||
"react-dom": "19.0.0-rc-66855b96-20241106"
|
"react-dom": "19.0.0-rc-66855b96-20241106",
|
||||||
|
"zustand": "^5.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jsonwebtoken": "^9.0.7",
|
"@types/jsonwebtoken": "^9.0.7",
|
||||||
|
@ -33,7 +33,7 @@ const Auth = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<p>Welcome, {session.user.name || "User"}!</p>
|
<p>Welcome, {session.user?.name || "User"}!</p>
|
||||||
<button
|
<button
|
||||||
style={{
|
style={{
|
||||||
padding: "10px 20px",
|
padding: "10px 20px",
|
||||||
|
@ -1,59 +1,33 @@
|
|||||||
import { useSession } from "next-auth/react";
|
import { axiosInstance } from "@/lib/axios";
|
||||||
|
import axios, { AxiosError } from "axios";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
const FetchWithSession = () => {
|
const FetchWithSession = () => {
|
||||||
const { data: session, status } = useSession();
|
const [data, setData] = useState<{ message: string } | null>(null);
|
||||||
const [data, setData] = useState(null);
|
const [error, setError] = useState<Error | AxiosError | null>(null);
|
||||||
const [error, setError] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
if (status === "authenticated" && session) {
|
await axiosInstance
|
||||||
try {
|
.get<{
|
||||||
const response = await fetch(
|
message: string;
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/ping`,
|
}>(`/ping`)
|
||||||
{
|
.then((response) => setData(response.data))
|
||||||
method: "GET",
|
.catch((err: Error | AxiosError) => {
|
||||||
headers: {
|
if (axios.isAxiosError(err)) setError(err.response?.data);
|
||||||
"Content-Type": "application/json",
|
else setError(err);
|
||||||
Authorization: `Bearer ${session.accessToken}`,
|
});
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Failed to fetch data");
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
setData(result);
|
|
||||||
} catch (err) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-expect-error
|
|
||||||
setError(err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchData();
|
setInterval(() => {
|
||||||
}, [status, session]);
|
fetchData();
|
||||||
|
}, 1000);
|
||||||
if (status === "loading") {
|
}, []);
|
||||||
return <div>Loading...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === "unauthenticated") {
|
|
||||||
return <div>You must log in to access this data.</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{error && <p>Error: {error}</p>}
|
{error && <p>Error: {error.message}</p>}
|
||||||
{data ? (
|
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
|
||||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
||||||
) : (
|
|
||||||
<p>Fetching data...</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import moment from "moment";
|
||||||
import { AuthOptions, Session } from "next-auth";
|
import { AuthOptions, Session } from "next-auth";
|
||||||
|
import { JWT } from "next-auth/jwt";
|
||||||
|
|
||||||
export const authOptions: AuthOptions = {
|
export const authOptions: AuthOptions = {
|
||||||
providers: [
|
providers: [
|
||||||
@ -9,11 +11,10 @@ export const authOptions: AuthOptions = {
|
|||||||
type: "oauth",
|
type: "oauth",
|
||||||
clientId: process.env.OAUTH_CLIENT_ID,
|
clientId: process.env.OAUTH_CLIENT_ID,
|
||||||
clientSecret: process.env.OAUTH_CLIENT_SECRET,
|
clientSecret: process.env.OAUTH_CLIENT_SECRET,
|
||||||
wellKnown: process.env.OAUTH_WELL_KNOWN,
|
|
||||||
authorization: {
|
authorization: {
|
||||||
url: process.env.OAUTH_AUTHORIZATION_URL,
|
url: process.env.OAUTH_AUTHORIZATION_URL,
|
||||||
params: {
|
params: {
|
||||||
scope: "openid email profile",
|
scope: "openid profile offline_access",
|
||||||
response_type: "code",
|
response_type: "code",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -27,35 +28,73 @@ export const authOptions: AuthOptions = {
|
|||||||
return {
|
return {
|
||||||
id: profile.sub || profile.id,
|
id: profile.sub || profile.id,
|
||||||
name:
|
name:
|
||||||
profile.name || profile.preferred_username ||
|
profile.name ||
|
||||||
`${profile.given_name} ${profile.family_name}`
|
profile.preferred_username ||
|
||||||
|
`${profile.given_name} ${profile.family_name}`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
callbacks: {
|
callbacks: {
|
||||||
async jwt({ token, account, user }) {
|
async jwt({ token, account, user }) {
|
||||||
if (account) {
|
if (account && user) {
|
||||||
token.accessToken = account.access_token;
|
token.accessToken = account.access_token;
|
||||||
|
token.accessTokenExpires = moment(account.expires_at).subtract(5, "s");
|
||||||
token.refreshToken = account.refresh_token;
|
token.refreshToken = account.refresh_token;
|
||||||
token.expiresAt = Date.now() + account.expires_in * 1000000;
|
token.user = user;
|
||||||
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user) {
|
if (moment().isBefore(moment(token.accessTokenExpires))) {
|
||||||
token.userId = user.id;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
return token;
|
return refreshAccessToken(token);
|
||||||
},
|
},
|
||||||
async session({ session, token }) {
|
async session({ session, token }) {
|
||||||
if (token) {
|
if (token) {
|
||||||
session.user.id = token.userId;
|
session.user = token.user;
|
||||||
session.accessToken = token.accessToken;
|
session.accessToken = token.accessToken;
|
||||||
session.refreshToken = token.refreshToken;
|
session.accessTokenExpires = token.accessTokenExpires;
|
||||||
session.expiresAt = token.expiresAt;
|
session.error = token.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const refreshAccessToken = async (token: JWT): Promise<JWT> => {
|
||||||
|
const response = await axios.post<{
|
||||||
|
access_token: string;
|
||||||
|
expires_in: number;
|
||||||
|
refresh_token: string;
|
||||||
|
error_description?: string;
|
||||||
|
}>(
|
||||||
|
process.env.OAUTH_TOKEN_URL,
|
||||||
|
{
|
||||||
|
grant_type: "refresh_token",
|
||||||
|
refresh_token: token.refreshToken,
|
||||||
|
client_id: process.env.OAUTH_CLIENT_ID,
|
||||||
|
client_secret: process.env.OAUTH_CLIENT_SECRET,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
throw new Error(
|
||||||
|
response.data.error_description || "Failed to refresh access token"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...token,
|
||||||
|
accessToken: response.data.access_token,
|
||||||
|
accessTokenExpires: moment().add(response.data.expires_in, "seconds").subtract(5, "s"),
|
||||||
|
refreshToken: response.data.refresh_token,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
28
src/lib/axios.ts
Normal file
28
src/lib/axios.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import moment, { Moment } from "moment";
|
||||||
|
import { getSession } from "next-auth/react";
|
||||||
|
|
||||||
|
let cachedAccessToken: string | null = null;
|
||||||
|
let tokenExpirationAt: Moment | null = null;
|
||||||
|
|
||||||
|
export const axiosInstance = axios.create({
|
||||||
|
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
||||||
|
});
|
||||||
|
|
||||||
|
axiosInstance.interceptors.request.use(async (config) => {
|
||||||
|
if (tokenExpirationAt && moment().isBefore(tokenExpirationAt)) {
|
||||||
|
config.headers.Authorization = `Bearer ${cachedAccessToken}`;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = await getSession();
|
||||||
|
if (!session) {
|
||||||
|
throw new Error("User is not authenticated");
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedAccessToken = session.accessToken;
|
||||||
|
tokenExpirationAt = moment(session.accessTokenExpires);
|
||||||
|
|
||||||
|
config.headers.Authorization = `Bearer ${cachedAccessToken}`;
|
||||||
|
return config;
|
||||||
|
});
|
32
src/types/next-auth.d.ts
vendored
32
src/types/next-auth.d.ts
vendored
@ -1,24 +1,28 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
import { Moment } from "moment";
|
||||||
import NextAuth, { DefaultSession } from "next-auth";
|
import NextAuth, { DefaultSession } from "next-auth";
|
||||||
import { JWT } from "next-auth/jwt";
|
import { JWT } from "next-auth/jwt";
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
id: string;
|
||||||
|
sub: string;
|
||||||
|
name: string;
|
||||||
|
preferred_username: string;
|
||||||
|
given_name: string;
|
||||||
|
family_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
declare module "next-auth" {
|
declare module "next-auth" {
|
||||||
interface Session extends DefaultSession {
|
interface Session extends DefaultSession {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
expiresAt: number;
|
accessTokenExpires: Moment;
|
||||||
user: {
|
error?: Error;
|
||||||
id: string;
|
user: User;
|
||||||
sub: string;
|
|
||||||
name: string;
|
|
||||||
preferred_username: string;
|
|
||||||
given_name: string;
|
|
||||||
family_name: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Account {
|
interface Account {
|
||||||
expires_in: number;
|
expires_at: number;
|
||||||
access_token: string;
|
access_token: string;
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
}
|
}
|
||||||
@ -27,8 +31,12 @@ declare module "next-auth" {
|
|||||||
declare module "next-auth/jwt" {
|
declare module "next-auth/jwt" {
|
||||||
interface JWT {
|
interface JWT {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
accessTokenExpires: Moment;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
expiresAt: number;
|
error?: Error;
|
||||||
userId: string;
|
user: User | AdapterUser;
|
||||||
|
iat: number;
|
||||||
|
exp: number;
|
||||||
|
jti: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
88
yarn.lock
88
yarn.lock
@ -635,6 +635,11 @@ ast-types-flow@^0.0.8:
|
|||||||
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6"
|
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6"
|
||||||
integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==
|
integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==
|
||||||
|
|
||||||
|
asynckit@^0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
|
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
||||||
|
|
||||||
available-typed-arrays@^1.0.7:
|
available-typed-arrays@^1.0.7:
|
||||||
version "1.0.7"
|
version "1.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846"
|
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846"
|
||||||
@ -647,6 +652,15 @@ axe-core@^4.10.0:
|
|||||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.2.tgz#85228e3e1d8b8532a27659b332e39b7fa0e022df"
|
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.2.tgz#85228e3e1d8b8532a27659b332e39b7fa0e022df"
|
||||||
integrity sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==
|
integrity sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==
|
||||||
|
|
||||||
|
axios@^1.7.9:
|
||||||
|
version "1.7.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a"
|
||||||
|
integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.15.6"
|
||||||
|
form-data "^4.0.0"
|
||||||
|
proxy-from-env "^1.1.0"
|
||||||
|
|
||||||
axobject-query@^4.1.0:
|
axobject-query@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee"
|
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee"
|
||||||
@ -778,6 +792,13 @@ color@^4.2.3:
|
|||||||
color-convert "^2.0.1"
|
color-convert "^2.0.1"
|
||||||
color-string "^1.9.0"
|
color-string "^1.9.0"
|
||||||
|
|
||||||
|
combined-stream@^1.0.8:
|
||||||
|
version "1.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||||
|
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||||
|
dependencies:
|
||||||
|
delayed-stream "~1.0.0"
|
||||||
|
|
||||||
commander@^4.0.0:
|
commander@^4.0.0:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
||||||
@ -881,6 +902,11 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1:
|
|||||||
has-property-descriptors "^1.0.0"
|
has-property-descriptors "^1.0.0"
|
||||||
object-keys "^1.1.1"
|
object-keys "^1.1.1"
|
||||||
|
|
||||||
|
delayed-stream@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
|
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
||||||
|
|
||||||
detect-libc@^2.0.3:
|
detect-libc@^2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700"
|
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700"
|
||||||
@ -1358,6 +1384,11 @@ flatted@^3.2.9:
|
|||||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27"
|
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27"
|
||||||
integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==
|
integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==
|
||||||
|
|
||||||
|
follow-redirects@^1.15.6:
|
||||||
|
version "1.15.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
|
||||||
|
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
|
||||||
|
|
||||||
for-each@^0.3.3:
|
for-each@^0.3.3:
|
||||||
version "0.3.3"
|
version "0.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
|
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
|
||||||
@ -1373,6 +1404,15 @@ foreground-child@^3.1.0:
|
|||||||
cross-spawn "^7.0.0"
|
cross-spawn "^7.0.0"
|
||||||
signal-exit "^4.0.1"
|
signal-exit "^4.0.1"
|
||||||
|
|
||||||
|
form-data@^4.0.0:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48"
|
||||||
|
integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==
|
||||||
|
dependencies:
|
||||||
|
asynckit "^0.4.0"
|
||||||
|
combined-stream "^1.0.8"
|
||||||
|
mime-types "^2.1.12"
|
||||||
|
|
||||||
fs.realpath@^1.0.0:
|
fs.realpath@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
@ -2013,6 +2053,18 @@ micromatch@^4.0.4, micromatch@^4.0.8:
|
|||||||
braces "^3.0.3"
|
braces "^3.0.3"
|
||||||
picomatch "^2.3.1"
|
picomatch "^2.3.1"
|
||||||
|
|
||||||
|
mime-db@1.52.0:
|
||||||
|
version "1.52.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||||
|
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||||
|
|
||||||
|
mime-types@^2.1.12:
|
||||||
|
version "2.1.35"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||||
|
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||||
|
dependencies:
|
||||||
|
mime-db "1.52.0"
|
||||||
|
|
||||||
minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||||
@ -2037,6 +2089,11 @@ minimist@^1.2.0, minimist@^1.2.6:
|
|||||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
|
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
|
||||||
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
|
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
|
||||||
|
|
||||||
|
moment@^2.30.1:
|
||||||
|
version "2.30.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
|
||||||
|
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
|
||||||
|
|
||||||
ms@^2.1.1, ms@^2.1.3:
|
ms@^2.1.1, ms@^2.1.3:
|
||||||
version "2.1.3"
|
version "2.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||||
@ -2387,6 +2444,11 @@ prop-types@^15.8.1:
|
|||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
react-is "^16.13.1"
|
react-is "^16.13.1"
|
||||||
|
|
||||||
|
proxy-from-env@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||||
|
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||||
|
|
||||||
punycode@^2.1.0:
|
punycode@^2.1.0:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
|
||||||
@ -2637,16 +2699,8 @@ streamsearch@^1.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
|
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
|
||||||
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
|
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
|
||||||
version "4.2.3"
|
name string-width-cjs
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
|
||||||
dependencies:
|
|
||||||
emoji-regex "^8.0.0"
|
|
||||||
is-fullwidth-code-point "^3.0.0"
|
|
||||||
strip-ansi "^6.0.1"
|
|
||||||
|
|
||||||
string-width@^4.1.0:
|
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@ -2727,14 +2781,7 @@ string.prototype.trimstart@^1.0.8:
|
|||||||
define-properties "^1.2.1"
|
define-properties "^1.2.1"
|
||||||
es-object-atoms "^1.0.0"
|
es-object-atoms "^1.0.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
|
||||||
dependencies:
|
|
||||||
ansi-regex "^5.0.1"
|
|
||||||
|
|
||||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@ -3068,3 +3115,8 @@ yocto-queue@^0.1.0:
|
|||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||||
|
|
||||||
|
zustand@^5.0.2:
|
||||||
|
version "5.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.2.tgz#f7595ada55a565f1fd6464f002a91e701ee0cfca"
|
||||||
|
integrity sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==
|
||||||
|
Loading…
Reference in New Issue
Block a user